Merge branch 'master' into fishing

Conflicts:
	stardew-access/CustomSoundEffects.cs
master
Mohammad Shoaib Khan 2023-03-10 15:44:12 +05:30
commit 5fa9fd5eca
No known key found for this signature in database
GPG Key ID: D8040D966320B620
68 changed files with 5966 additions and 5689 deletions

504
LICENSE-NVDA 100644
View File

@ -0,0 +1,504 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
[This is the first released version of the Lesser GPL. It also counts
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
Licenses are intended to guarantee your freedom to share and change
free software--to make sure the software is free for all its users.
This license, the Lesser General Public License, applies to some
specially designated software packages--typically libraries--of the
Free Software Foundation and other authors who decide to use it. You
can use it too, but we suggest you first think carefully about whether
this license or the ordinary General Public License is the better
strategy to use in any particular case, based on the explanations below.
When we speak of free software, we are referring to freedom of use,
not price. Our General Public Licenses are designed to make sure that
you have the freedom to distribute copies of free software (and charge
for this service if you wish); that you receive source code or can get
it if you want it; that you can change the software and use pieces of
it in new free programs; and that you are informed that you can do
these things.
To protect your rights, we need to make restrictions that forbid
distributors to deny you these rights or to ask you to surrender these
rights. These restrictions translate to certain responsibilities for
you if you distribute copies of the library or if you modify it.
For example, if you distribute copies of the library, whether gratis
or for a fee, you must give the recipients all the rights that we gave
you. You must make sure that they, too, receive or can get the source
code. If you link other code with the library, you must provide
complete object files to the recipients, so that they can relink them
with the library after making changes to the library and recompiling
it. And you must show them these terms so they know their rights.
We protect your rights with a two-step method: (1) we copyright the
library, and (2) we offer you this license, which gives you legal
permission to copy, distribute and/or modify the library.
To protect each distributor, we want to make it very clear that
there is no warranty for the free library. Also, if the library is
modified by someone else and passed on, the recipients should know
that what they have is not the original version, so that the original
author's reputation will not be affected by problems that might be
introduced by others.
Finally, software patents pose a constant threat to the existence of
any free program. We wish to make sure that a company cannot
effectively restrict the users of a free program by obtaining a
restrictive license from a patent holder. Therefore, we insist that
any patent license obtained for a version of the library must be
consistent with the full freedom of use specified in this license.
Most GNU software, including some libraries, is covered by the
ordinary GNU General Public License. This license, the GNU Lesser
General Public License, applies to certain designated libraries, and
is quite different from the ordinary General Public License. We use
this license for certain libraries in order to permit linking those
libraries into non-free programs.
When a program is linked with a library, whether statically or using
a shared library, the combination of the two is legally speaking a
combined work, a derivative of the original library. The ordinary
General Public License therefore permits such linking only if the
entire combination fits its criteria of freedom. The Lesser General
Public License permits more lax criteria for linking other code with
the library.
We call this license the "Lesser" General Public License because it
does Less to protect the user's freedom than the ordinary General
Public License. It also provides other free software developers Less
of an advantage over competing non-free programs. These disadvantages
are the reason we use the ordinary General Public License for many
libraries. However, the Lesser license provides advantages in certain
special circumstances.
For example, on rare occasions, there may be a special need to
encourage the widest possible use of a certain library, so that it becomes
a de-facto standard. To achieve this, non-free programs must be
allowed to use the library. A more frequent case is that a free
library does the same job as widely used non-free libraries. In this
case, there is little to gain by limiting the free library to free
software only, so we use the Lesser General Public License.
In other cases, permission to use a particular library in non-free
programs enables a greater number of people to use a large body of
free software. For example, permission to use the GNU C Library in
non-free programs enables many more people to use the whole GNU
operating system, as well as its variant, the GNU/Linux operating
system.
Although the Lesser General Public License is Less protective of the
users' freedom, it does ensure that the user of a program that is
linked with the Library has the freedom and the wherewithal to run
that program using a modified version of the Library.
The precise terms and conditions for copying, distribution and
modification follow. Pay close attention to the difference between a
"work based on the library" and a "work that uses the library". The
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
program which contains a notice placed by the copyright holder or
other authorized party saying it may be distributed under the terms of
this Lesser General Public License (also called "this License").
Each licensee is addressed as "you".
A "library" means a collection of software functions and/or data
prepared so as to be conveniently linked with application programs
(which use some of those functions and data) to form executables.
The "Library", below, refers to any such software library or work
which has been distributed under these terms. A "work based on the
Library" means either the Library or any derivative work under
copyright law: that is to say, a work containing the Library or a
portion of it, either verbatim or with modifications and/or translated
straightforwardly into another language. (Hereinafter, translation is
included without limitation in the term "modification".)
"Source code" for a work means the preferred form of the work for
making modifications to it. For a library, complete source code means
all the source code for all modules it contains, plus any associated
interface definition files, plus the scripts used to control compilation
and installation of the library.
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running a program using the Library is not restricted, and output from
such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
appropriate copyright notice and disclaimer of warranty; keep intact
all the notices that refer to this License and to the absence of any
warranty; and distribute a copy of this License along with the
Library.
You may charge a fee for the physical act of transferring a copy,
and you may at your option offer warranty protection in exchange for a
fee.
2. You may modify your copy or copies of the Library or any portion
of it, thus forming a work based on the Library, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) The modified work must itself be a software library.
b) You must cause the files modified to carry prominent notices
stating that you changed the files and the date of any change.
c) You must cause the whole of the work to be licensed at no
charge to all third parties under the terms of this License.
d) If a facility in the modified Library refers to a function or a
table of data to be supplied by an application program that uses
the facility, other than as an argument passed when the facility
is invoked, then you must make a good faith effort to ensure that,
in the event an application does not supply such function or
table, the facility still operates, and performs whatever part of
its purpose remains meaningful.
(For example, a function in a library to compute square roots has
a purpose that is entirely well-defined independent of the
application. Therefore, Subsection 2d requires that any
application-supplied function or table used by this function must
be optional: if the application does not supply it, the square
root function must still compute square roots.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Library,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Library, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote
it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Library.
In addition, mere aggregation of another work not based on the Library
with the Library (or with a work based on the Library) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may opt to apply the terms of the ordinary GNU General Public
License instead of this License to a given copy of the Library. To do
this, you must alter all the notices that refer to this License, so
that they refer to the ordinary GNU General Public License, version 2,
instead of to this License. (If a newer version than version 2 of the
ordinary GNU General Public License has appeared, then you can specify
that version instead if you wish.) Do not make any other change in
these notices.
Once this change is made in a given copy, it is irreversible for
that copy, so the ordinary GNU General Public License applies to all
subsequent copies and derivative works made from that copy.
This option is useful when you wish to copy part of the code of
the Library into a program that is not a library.
4. You may copy and distribute the Library (or a portion or
derivative of it, under Section 2) in object code or executable form
under the terms of Sections 1 and 2 above provided that you accompany
it with the complete corresponding machine-readable source code, which
must be distributed under the terms of Sections 1 and 2 above on a
medium customarily used for software interchange.
If distribution of object code is made by offering access to copy
from a designated place, then offering equivalent access to copy the
source code from the same place satisfies the requirement to
distribute the source code, even though third parties are not
compelled to copy the source along with the object code.
5. A program that contains no derivative of any portion of the
Library, but is designed to work with the Library by being compiled or
linked with it, is called a "work that uses the Library". Such a
work, in isolation, is not a derivative work of the Library, and
therefore falls outside the scope of this License.
However, linking a "work that uses the Library" with the Library
creates an executable that is a derivative of the Library (because it
contains portions of the Library), rather than a "work that uses the
library". The executable is therefore covered by this License.
Section 6 states terms for distribution of such executables.
When a "work that uses the Library" uses material from a header file
that is part of the Library, the object code for the work may be a
derivative work of the Library even though the source code is not.
Whether this is true is especially significant if the work can be
linked without the Library, or if the work is itself a library. The
threshold for this to be true is not precisely defined by law.
If such an object file uses only numerical parameters, data
structure layouts and accessors, and small macros and small inline
functions (ten lines or less in length), then the use of the object
file is unrestricted, regardless of whether it is legally a derivative
work. (Executables containing this object code plus portions of the
Library will still fall under Section 6.)
Otherwise, if the work is a derivative of the Library, you may
distribute the object code for the work under the terms of Section 6.
Any executables containing that work also fall under Section 6,
whether or not they are linked directly with the Library itself.
6. As an exception to the Sections above, you may also combine or
link a "work that uses the Library" with the Library to produce a
work containing portions of the Library, and distribute that work
under terms of your choice, provided that the terms permit
modification of the work for the customer's own use and reverse
engineering for debugging such modifications.
You must give prominent notice with each copy of the work that the
Library is used in it and that the Library and its use are covered by
this License. You must supply a copy of this License. If the work
during execution displays copyright notices, you must include the
copyright notice for the Library among them, as well as a reference
directing the user to the copy of this License. Also, you must do one
of these things:
a) Accompany the work with the complete corresponding
machine-readable source code for the Library including whatever
changes were used in the work (which must be distributed under
Sections 1 and 2 above); and, if the work is an executable linked
with the Library, with the complete machine-readable "work that
uses the Library", as object code and/or source code, so that the
user can modify the Library and then relink to produce a modified
executable containing the modified Library. (It is understood
that the user who changes the contents of definitions files in the
Library will not necessarily be able to recompile the application
to use the modified definitions.)
b) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (1) uses at run time a
copy of the library already present on the user's computer system,
rather than copying library functions into the executable, and (2)
will operate properly with a modified version of the library, if
the user installs one, as long as the modified version is
interface-compatible with the version that the work was made with.
c) Accompany the work with a written offer, valid for at
least three years, to give the same user the materials
specified in Subsection 6a, above, for a charge no more
than the cost of performing this distribution.
d) If distribution of the work is made by offering access to copy
from a designated place, offer equivalent access to copy the above
specified materials from the same place.
e) Verify that the user has already received a copy of these
materials or that you have already sent this user a copy.
For an executable, the required form of the "work that uses the
Library" must include any data and utility programs needed for
reproducing the executable from it. However, as a special exception,
the materials to be distributed need not include anything that is
normally distributed (in either source or binary form) with the major
components (compiler, kernel, and so on) of the operating system on
which the executable runs, unless that component itself accompanies
the executable.
It may happen that this requirement contradicts the license
restrictions of other proprietary libraries that do not normally
accompany the operating system. Such a contradiction means you cannot
use both them and the Library together in an executable that you
distribute.
7. You may place library facilities that are a work based on the
Library side-by-side in a single library together with other library
facilities not covered by this License, and distribute such a combined
library, provided that the separate distribution of the work based on
the Library and of the other library facilities is otherwise
permitted, and provided that you do these two things:
a) Accompany the combined library with a copy of the same work
based on the Library, uncombined with any other library
facilities. This must be distributed under the terms of the
Sections above.
b) Give prominent notice with the combined library of the fact
that part of it is a work based on the Library, and explaining
where to find the accompanying uncombined form of the same work.
8. You may not copy, modify, sublicense, link with, or distribute
the Library except as expressly provided under this License. Any
attempt otherwise to copy, modify, sublicense, link with, or
distribute the Library is void, and will automatically terminate your
rights under this License. However, parties who have received copies,
or rights, from you under this License will not have their licenses
terminated so long as such parties remain in full compliance.
9. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Library or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Library (or any work based on the
Library), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Library or works based on it.
10. Each time you redistribute the Library (or any work based on the
Library), the recipient automatically receives a license from the
original licensor to copy, distribute, link with or modify the Library
subject to these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties with
this License.
11. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Library at all. For example, if a patent
license would not permit royalty-free redistribution of the Library by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Library.
If any portion of this section is held invalid or unenforceable under any
particular circumstance, the balance of the section is intended to apply,
and the section as a whole is intended to apply in other circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
12. If the distribution and/or use of the Library is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Library under this License may add
an explicit geographical distribution limitation excluding those countries,
so that distribution is permitted only in or among countries not thus
excluded. In such case, this License incorporates the limitation as if
written in the body of this License.
13. The Free Software Foundation may publish revised and/or new
versions of the Lesser General Public License from time to time.
Such new versions will be similar in spirit to the present version,
but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Library
specifies a version number of this License which applies to it and
"any later version", you have the option of following the terms and
conditions either of that version or of any later version published by
the Free Software Foundation. If the Library does not specify a
license version number, you may choose any version ever published by
the Free Software Foundation.
14. If you wish to incorporate parts of the Library into other free
programs whose distribution conditions are incompatible with these,
write to the author to ask for permission. For software which is
copyrighted by the Free Software Foundation, write to the Free
Software Foundation; we sometimes make exceptions for this. Our
decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
If you develop a new library, and you want it to be of the greatest
possible use to the public, we recommend making it free software that
everyone can redistribute and change. You can do so by permitting
redistribution under these terms (or, alternatively, under the terms of the
ordinary General Public License).
To apply these terms, attach the following notices to the library. It is
safest to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
<one line to give the library's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Also add information on how to contact you by electronic and paper mail.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the library, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
<signature of Ty Coon>, 1 April 1990
Ty Coon, President of Vice
That's all there is to it!

165
LICENSE-TOLK 100644
View File

@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. [http://fsf.org/]
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

View File

@ -0,0 +1,32 @@
PIXABAY LICENSE CERTIFICATE
==============================================
This document confirms the download of an audio file pursuant to the Pixabay License as defined in the Pixabay Terms of Service available at https://https://pixabay.com/service/terms/
Licensor's Username:
https://pixabay.com/users/pixabay-1/
Licensee:
TheOneWhoKnocks
Audio File Title:
Invalid Selection
Audio File URL:
https://pixabay.com/sound-effects/invalid-selection-39351/
Audio File ID:
39351
Date of download:
2023-02-20 05:52:26 UTC
Pixabay GmbH c/o Lacore Rechtsanwälte LLP
Berliner Freiheit 2, 10785 Berlin, Germany
Pixabay is a user-contributed stock content website. The above-named Licensor is responsible for this audio file. Pixabay monitors uploaded audio files only to a reasonable extent. Pixabay cannot be held responsible for the acts or omissions of its users and does not represent or warrant that any required third-party consents or licenses have been obtained.
For any queries related to this document please contact Pixabay via info@pixabay.com.
==== THIS IS NOT A TAX RECEIPT OR INVOICE ====

View File

@ -1,4 +1,5 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using stardew_access.Features;
using stardew_access.Patches; using stardew_access.Patches;
using StardewModdingAPI; using StardewModdingAPI;
using StardewValley; using StardewValley;
@ -327,18 +328,18 @@ namespace stardew_access
return; return;
} }
BuildingNAnimalMenuPatches.marked[index] = new Vector2((int)Game1.player.getTileX(), (int)Game1.player.getTileY()); BuildingOperations.marked[index] = new Vector2((int)Game1.player.getTileX(), (int)Game1.player.getTileY());
MainClass.InfoLog($"Location {(int)Game1.player.getTileX()}x {(int)Game1.player.getTileY()}y added at {index} index."); MainClass.InfoLog($"Location {(int)Game1.player.getTileX()}x {(int)Game1.player.getTileY()}y added at {index} index.");
}); });
helper.ConsoleCommands.Add("marklist", "List all marked positions.", (string commmand, string[] args) => helper.ConsoleCommands.Add("marklist", "List all marked positions.", (string commmand, string[] args) =>
{ {
string toPrint = ""; string toPrint = "";
for (int i = 0; i < BuildingNAnimalMenuPatches.marked.Length; i++) for (int i = 0; i < BuildingOperations.marked.Length; i++)
{ {
if (BuildingNAnimalMenuPatches.marked[i] != Vector2.Zero) if (BuildingOperations.marked[i] != Vector2.Zero)
{ {
toPrint = $"{toPrint}\n Index {i}: {BuildingNAnimalMenuPatches.marked[i].X}x {BuildingNAnimalMenuPatches.marked[i].Y}y"; toPrint = $"{toPrint}\n Index {i}: {BuildingOperations.marked[i].X}x {BuildingOperations.marked[i].Y}y";
} }
} }
@ -355,7 +356,7 @@ namespace stardew_access
helper.ConsoleCommands.Add("buildsel", "Select the building index which you want to upgrade/demolish/paint", (string commmand, string[] args) => helper.ConsoleCommands.Add("buildsel", "Select the building index which you want to upgrade/demolish/paint", (string commmand, string[] args) =>
{ {
if ((Game1.activeClickableMenu is not CarpenterMenu && Game1.activeClickableMenu is not PurchaseAnimalsMenu && Game1.activeClickableMenu is not AnimalQueryMenu) || !BuildingNAnimalMenuPatches.isOnFarm) if ((Game1.activeClickableMenu is not CarpenterMenu && Game1.activeClickableMenu is not PurchaseAnimalsMenu && Game1.activeClickableMenu is not AnimalQueryMenu) || !CarpenterMenuPatch.isOnFarm)
{ {
MainClass.InfoLog($"Cannot select building."); MainClass.InfoLog($"Cannot select building.");
return; return;
@ -380,12 +381,12 @@ namespace stardew_access
string? positionIndexInString = args.ElementAtOrDefault(1); string? positionIndexInString = args.ElementAtOrDefault(1);
int positionIndex = 0; int positionIndex = 0;
if (BuildingNAnimalMenuPatches.isMoving) if (CarpenterMenuPatch.isMoving)
{ {
if (BuildingNAnimalMenuPatches.isConstructing || BuildingNAnimalMenuPatches.isMoving) if (CarpenterMenuPatch.isConstructing || CarpenterMenuPatch.isMoving)
{ {
if (BuildingNAnimalMenuPatches.availableBuildings[index] == null) if (BuildingOperations.availableBuildings[index] == null)
{ {
MainClass.InfoLog($"No building found with index {index}. Use buildlist."); MainClass.InfoLog($"No building found with index {index}. Use buildlist.");
return; return;
@ -406,9 +407,9 @@ namespace stardew_access
} }
} }
} }
else if (BuildingNAnimalMenuPatches.isConstructing && !BuildingNAnimalMenuPatches.isUpgrading) else if (CarpenterMenuPatch.isConstructing && !CarpenterMenuPatch.isUpgrading)
{ {
if (BuildingNAnimalMenuPatches.marked[index] == Vector2.Zero) if (BuildingOperations.marked[index] == Vector2.Zero)
{ {
MainClass.InfoLog($"No marked position found at {index} index."); MainClass.InfoLog($"No marked position found at {index} index.");
return; return;
@ -416,7 +417,7 @@ namespace stardew_access
} }
else else
{ {
if (BuildingNAnimalMenuPatches.availableBuildings[index] == null) if (BuildingOperations.availableBuildings[index] == null)
{ {
MainClass.InfoLog($"No building found with index {index}. Use buildlist."); MainClass.InfoLog($"No building found with index {index}. Use buildlist.");
return; return;
@ -427,19 +428,19 @@ namespace stardew_access
if (Game1.activeClickableMenu is PurchaseAnimalsMenu) if (Game1.activeClickableMenu is PurchaseAnimalsMenu)
{ {
BuildingNAnimalMenuPatches.PurchaseAnimal(BuildingNAnimalMenuPatches.availableBuildings[index]); BuildingOperations.PurchaseAnimal(BuildingOperations.availableBuildings[index]);
} }
else if (Game1.activeClickableMenu is AnimalQueryMenu) else if (Game1.activeClickableMenu is AnimalQueryMenu)
{ {
BuildingNAnimalMenuPatches.MoveAnimal(BuildingNAnimalMenuPatches.availableBuildings[index]); BuildingOperations.MoveAnimal(BuildingOperations.availableBuildings[index]);
} }
else else
{ {
if (BuildingNAnimalMenuPatches.isConstructing && !BuildingNAnimalMenuPatches.isUpgrading) { response = BuildingNAnimalMenuPatches.Contstruct(BuildingNAnimalMenuPatches.marked[index]); } if (CarpenterMenuPatch.isConstructing && !CarpenterMenuPatch.isUpgrading) { response = BuildingOperations.Contstruct(BuildingOperations.marked[index]); }
else if (BuildingNAnimalMenuPatches.isMoving) { response = BuildingNAnimalMenuPatches.Move(BuildingNAnimalMenuPatches.availableBuildings[index], BuildingNAnimalMenuPatches.marked[positionIndex]); } else if (CarpenterMenuPatch.isMoving) { response = BuildingOperations.Move(BuildingOperations.availableBuildings[index], BuildingOperations.marked[positionIndex]); }
else if (BuildingNAnimalMenuPatches.isDemolishing) { response = BuildingNAnimalMenuPatches.Demolish(BuildingNAnimalMenuPatches.availableBuildings[index]); } else if (CarpenterMenuPatch.isDemolishing) { response = BuildingOperations.Demolish(BuildingOperations.availableBuildings[index]); }
else if (BuildingNAnimalMenuPatches.isUpgrading) { response = BuildingNAnimalMenuPatches.Upgrade(BuildingNAnimalMenuPatches.availableBuildings[index]); } else if (CarpenterMenuPatch.isUpgrading) { response = BuildingOperations.Upgrade(BuildingOperations.availableBuildings[index]); }
else if (BuildingNAnimalMenuPatches.isPainting) { response = BuildingNAnimalMenuPatches.Paint(BuildingNAnimalMenuPatches.availableBuildings[index]); } else if (CarpenterMenuPatch.isPainting) { response = BuildingOperations.Paint(BuildingOperations.availableBuildings[index]); }
} }
if (response != null) if (response != null)
@ -517,7 +518,7 @@ namespace stardew_access
string? name = buildings[i].nameOfIndoorsWithoutUnique; string? name = buildings[i].nameOfIndoorsWithoutUnique;
name = (name == "null") ? buildings[i].buildingType.Value : name; name = (name == "null") ? buildings[i].buildingType.Value : name;
BuildingNAnimalMenuPatches.availableBuildings[buildingIndex] = buildings[i]; BuildingOperations.availableBuildings[buildingIndex] = buildings[i];
toPrint = $"{toPrint}\nIndex {buildingIndex}: {name}: At {buildings[i].tileX}x and {buildings[i].tileY}y"; toPrint = $"{toPrint}\nIndex {buildingIndex}: {name}: At {buildings[i].tileX}x and {buildings[i].tileY}y";
++buildingIndex; ++buildingIndex;
} }

View File

@ -22,6 +22,7 @@ namespace stardew_access
soundEffects.Add("drop_item", TYPE.Sound); soundEffects.Add("drop_item", TYPE.Sound);
soundEffects.Add("colliding", TYPE.Sound); soundEffects.Add("colliding", TYPE.Sound);
soundEffects.Add("invalid-selection", TYPE.Sound);
soundEffects.Add("bobber_target_up", TYPE.Sound); soundEffects.Add("bobber_target_up", TYPE.Sound);
soundEffects.Add("bobber_target_down", TYPE.Sound); soundEffects.Add("bobber_target_down", TYPE.Sound);

View File

@ -0,0 +1,366 @@
using Microsoft.Xna.Framework;
using stardew_access.Patches;
using StardewValley;
using StardewValley.Buildings;
using StardewValley.Locations;
using StardewValley.Objects;
namespace stardew_access.Features
{
internal class BuildingOperations
{
internal static Building?[] availableBuildings = new Building[100];
internal static Vector2[] marked = new Vector2[10];
public static string? Demolish(Building? toDemolish)
{
if (toDemolish == null)
return null;
string? response = null;
// This code is taken from the game's code (CarpenterMenu.cs::654)
Farm farm = (Farm)Game1.getLocationFromName("Farm");
Action buildingLockFailed = delegate
{
if (CarpenterMenuPatch.isDemolishing)
{
response = Game1.content.LoadString("Strings\\UI:Carpenter_CantDemolish_LockFailed");
}
};
Action continueDemolish = delegate
{
if (CarpenterMenuPatch.isDemolishing && toDemolish != null && farm.buildings.Contains(toDemolish))
{
if ((int)toDemolish.daysOfConstructionLeft.Value > 0 || (int)toDemolish.daysUntilUpgrade.Value > 0)
{
response = Game1.content.LoadString("Strings\\UI:Carpenter_CantDemolish_DuringConstruction");
}
else if (toDemolish.indoors.Value != null && toDemolish.indoors.Value is AnimalHouse && ((AnimalHouse)toDemolish.indoors.Value).animalsThatLiveHere.Count > 0)
{
response = Game1.content.LoadString("Strings\\UI:Carpenter_CantDemolish_AnimalsHere");
}
else if (toDemolish.indoors.Value != null && toDemolish.indoors.Value.farmers.Any())
{
response = Game1.content.LoadString("Strings\\UI:Carpenter_CantDemolish_PlayerHere");
}
else
{
if (toDemolish.indoors.Value != null && toDemolish.indoors.Value is Cabin)
{
foreach (Farmer current in Game1.getAllFarmers())
{
if (current.currentLocation != null && current.currentLocation.Name == ((Cabin)toDemolish.indoors.Value).GetCellarName())
{
response = Game1.content.LoadString("Strings\\UI:Carpenter_CantDemolish_PlayerHere");
return;
}
}
}
if (toDemolish.indoors.Value is Cabin && ((Cabin)toDemolish.indoors.Value).farmhand.Value.isActive())
{
response = Game1.content.LoadString("Strings\\UI:Carpenter_CantDemolish_FarmhandOnline");
}
else
{
toDemolish.BeforeDemolish();
Chest? chest = null;
if (toDemolish.indoors.Value is Cabin)
{
List<Item> list = ((Cabin)toDemolish.indoors.Value).demolish();
if (list.Count > 0)
{
chest = new Chest(playerChest: true);
chest.fixLidFrame();
chest.items.Set(list);
}
}
if (farm.destroyStructure(toDemolish))
{
_ = (int)toDemolish.tileY.Value;
_ = (int)toDemolish.tilesHigh.Value;
Game1.flashAlpha = 1f;
toDemolish.showDestroyedAnimation(Game1.getFarm());
Game1.playSound("explosion");
Utility.spreadAnimalsAround(toDemolish, farm);
if (CarpenterMenuPatch.carpenterMenu != null)
DelayedAction.functionAfterDelay(CarpenterMenuPatch.carpenterMenu.returnToCarpentryMenu, 1500);
// freeze = true;
if (chest != null)
{
farm.objects[new Vector2((int)toDemolish.tileX.Value + (int)toDemolish.tilesWide.Value / 2, (int)toDemolish.tileY.Value + (int)toDemolish.tilesHigh.Value / 2)] = chest;
}
}
}
}
}
};
if (toDemolish != null)
{
if (toDemolish.indoors.Value != null && toDemolish.indoors.Value is Cabin && !Game1.IsMasterGame)
{
response = Game1.content.LoadString("Strings\\UI:Carpenter_CantDemolish_LockFailed");
toDemolish = null;
return response;
}
if (CarpenterMenuPatch.carpenterMenu != null && !CarpenterMenuPatch.carpenterMenu.CanDemolishThis(toDemolish))
{
toDemolish = null;
return response;
}
if (CarpenterMenuPatch.carpenterMenu != null && !Game1.IsMasterGame && !CarpenterMenuPatch.carpenterMenu.hasPermissionsToDemolish(toDemolish))
{
toDemolish = null;
return response;
}
}
if (toDemolish != null && toDemolish.indoors.Value is Cabin)
{
Cabin cabin = (Cabin)toDemolish.indoors.Value;
if (cabin.farmhand.Value != null && (bool)cabin.farmhand.Value.isCustomized.Value)
{
Game1.currentLocation.createQuestionDialogue(Game1.content.LoadString("Strings\\UI:Carpenter_DemolishCabinConfirm", cabin.farmhand.Value.Name), Game1.currentLocation.createYesNoResponses(), delegate (Farmer f, string answer)
{
if (answer == "Yes")
{
Game1.activeClickableMenu = CarpenterMenuPatch.carpenterMenu;
Game1.player.team.demolishLock.RequestLock(continueDemolish, buildingLockFailed);
}
else
{
if (CarpenterMenuPatch.carpenterMenu != null)
DelayedAction.functionAfterDelay(CarpenterMenuPatch.carpenterMenu.returnToCarpentryMenu, 1000);
}
});
return response;
}
}
if (toDemolish != null)
{
Game1.player.team.demolishLock.RequestLock(continueDemolish, buildingLockFailed);
}
return response;
}
public static string? Contstruct(Vector2 position)
{
string? response = null;
// This code is taken from the game's code (CarpenterMenu.cs::874)
Game1.player.team.buildLock.RequestLock(delegate
{
if (CarpenterMenuPatch.isOnFarm && Game1.locationRequest == null)
{
if (tryToBuild(position))
{
if (CarpenterMenuPatch.carpenterMenu != null)
{
CarpenterMenuPatch.carpenterMenu.CurrentBlueprint.consumeResources();
DelayedAction.functionAfterDelay(CarpenterMenuPatch.carpenterMenu.returnToCarpentryMenuAfterSuccessfulBuild, 2000);
}
// freeze = true;
}
else
{
response = Game1.content.LoadString("Strings\\UI:Carpenter_CantBuild");
}
}
Game1.player.team.buildLock.ReleaseLock();
});
return response;
}
public static bool tryToBuild(Vector2 position)
{
if (CarpenterMenuPatch.carpenterMenu == null)
return false;
return ((Farm)Game1.getLocationFromName("Farm")).buildStructure(CarpenterMenuPatch.carpenterMenu.CurrentBlueprint, position, Game1.player, CarpenterMenuPatch.isMagicalConstruction);
}
public static string? Upgrade(Building? toUpgrade)
{
string? response = null;
// This code is taken from the game's code (CarpenterMenu.cs::775)
if (CarpenterMenuPatch.carpenterMenu != null && toUpgrade != null && CarpenterMenuPatch.carpenterMenu.CurrentBlueprint.name != null && toUpgrade.buildingType.Equals(CarpenterMenuPatch.carpenterMenu.CurrentBlueprint.nameOfBuildingToUpgrade))
{
CarpenterMenuPatch.carpenterMenu.CurrentBlueprint.consumeResources();
toUpgrade.daysUntilUpgrade.Value = 2;
toUpgrade.showUpgradeAnimation(Game1.getFarm());
Game1.playSound("axe");
DelayedAction.functionAfterDelay(CarpenterMenuPatch.carpenterMenu.returnToCarpentryMenuAfterSuccessfulBuild, 1500);
// freeze = true;
// Game1.multiplayer.globalChatInfoMessage("BuildingBuild", Game1.player.Name, Utility.AOrAn(carpenterMenu.CurrentBlueprint.displayName), carpenterMenu.CurrentBlueprint.displayName, Game1.player.farmName.Value);
}
else if (toUpgrade != null)
{
response = Game1.content.LoadString("Strings\\UI:Carpenter_CantUpgrade_BuildingType");
}
return response;
}
public static string? Paint(Building? toPaint)
{
string? response = null;
// This code is taken from the game's code (CarpenterMenu.cs::793)
Farm farm_location = Game1.getFarm();
if (toPaint != null)
{
if (!toPaint.CanBePainted())
{
response = Game1.content.LoadString("Strings\\UI:Carpenter_CannotPaint");
return response;
}
if (CarpenterMenuPatch.carpenterMenu != null && !CarpenterMenuPatch.carpenterMenu.HasPermissionsToPaint(toPaint))
{
response = Game1.content.LoadString("Strings\\UI:Carpenter_CannotPaint_Permission");
return response;
}
toPaint.color.Value = Color.White;
if (CarpenterMenuPatch.carpenterMenu != null)
CarpenterMenuPatch.carpenterMenu.SetChildMenu(new StardewValley.Menus.BuildingPaintMenu(toPaint));
}
/* TODO Add painting of farm house
else if (farm_location.GetHouseRect().Contains(Utility.Vector2ToPoint(new Vector2(toPaint.tileX, toPaint.tileY))))
{
if (!carpenterMenu.CanPaintHouse())
{
response = Game1.content.LoadString("Strings\\UI:Carpenter_CannotPaint");
}
else if (!carpenterMenu.HasPermissionsToPaint(null))
{
response = Game1.content.LoadString("Strings\\UI:Carpenter_CannotPaint_Permission");
}
else
{
carpenterMenu.SetChildMenu(new BuildingPaintMenu("House", () => (farm_location.paintedHouseTexture != null) ? farm_location.paintedHouseTexture : Farm.houseTextures, farm_location.houseSource.Value, farm_location.housePaintColor.Value));
}
}*/
return response;
}
public static string? Move(Building? buildingToMove, Vector2 position)
{
string? response = null;
// This code is taken from the game's code (CarpenterMenu.cs::829)
if (buildingToMove != null)
{
string? name = buildingToMove.nameOfIndoorsWithoutUnique;
name = (name == "null") ? buildingToMove.buildingType.Value : name;
if ((int)buildingToMove.daysOfConstructionLeft.Value > 0)
{
buildingToMove = null;
return "Building under construction, cannot move";
}
if (CarpenterMenuPatch.carpenterMenu != null && !CarpenterMenuPatch.carpenterMenu.hasPermissionsToMove(buildingToMove))
{
buildingToMove = null;
return "You don't have permission to move this building";
}
Game1.playSound("axchop");
if (((Farm)Game1.getLocationFromName("Farm")).buildStructure(buildingToMove, position, Game1.player))
{
if (buildingToMove is ShippingBin)
{
((ShippingBin)buildingToMove).initLid();
}
if (buildingToMove is GreenhouseBuilding)
{
Game1.getFarm().greenhouseMoved.Value = true;
}
buildingToMove.performActionOnBuildingPlacement();
buildingToMove = null;
Game1.playSound("axchop");
DelayedAction.playSoundAfterDelay("dirtyHit", 50);
DelayedAction.playSoundAfterDelay("dirtyHit", 150);
response = $"{buildingToMove} moved to {position.X}x {position.Y}y";
}
else
{
Game1.playSound("cancel");
response = $"Cannot move building to {position.X}x {position.Y}y";
}
}
return response;
}
public static void PurchaseAnimal(Building? selection)
{
if (selection == null)
return;
if (PurchaseAnimalsMenuPatch.purchaseAnimalsMenu == null)
return;
int x = (selection.tileX.Value * Game1.tileSize) - Game1.viewport.X;
int y = (selection.tileY.Value * Game1.tileSize) - Game1.viewport.Y;
if (PurchaseAnimalsMenuPatch.animalBeingPurchased != null && !selection.buildingType.Value.Contains(PurchaseAnimalsMenuPatch.animalBeingPurchased.buildingTypeILiveIn.Value))
{
string warn = Game1.content.LoadString("Strings\\StringsFromCSFiles:PurchaseAnimalsMenu.cs.11326", PurchaseAnimalsMenuPatch.animalBeingPurchased.displayType);
MainClass.ScreenReader.Say(warn, true);
return;
}
if (((AnimalHouse)selection.indoors.Value).isFull())
{
string warn = Game1.content.LoadString("Strings\\StringsFromCSFiles:PurchaseAnimalsMenu.cs.11321");
MainClass.ScreenReader.Say(warn, true);
return;
}
PurchaseAnimalsMenuPatch.purchaseAnimalsMenu.receiveLeftClick(x, y);
}
public static void MoveAnimal(Building? selection)
{
if (selection == null)
return;
if (AnimalQueryMenuPatch.animalQueryMenu == null)
return;
if (AnimalQueryMenuPatch.animalBeingMoved == null)
return;
// The following code is taken from the game's source code [AnimalQueryMenu.cs::receiveLeftClick]
if (selection.buildingType.Value.Contains(AnimalQueryMenuPatch.animalBeingMoved.buildingTypeILiveIn.Value))
{
if (((AnimalHouse)selection.indoors.Value).isFull())
{
string warn = Game1.content.LoadString("Strings\\UI:AnimalQuery_Moving_BuildingFull");
MainClass.ScreenReader.Say(warn, true);
return;
}
if (selection.Equals(AnimalQueryMenuPatch.animalBeingMoved.home))
{
string warn = Game1.content.LoadString("Strings\\UI:AnimalQuery_Moving_AlreadyHome");
MainClass.ScreenReader.Say(warn, true);
return;
}
((AnimalHouse)AnimalQueryMenuPatch.animalBeingMoved.home.indoors.Value).animalsThatLiveHere.Remove(AnimalQueryMenuPatch.animalBeingMoved.myID.Value);
if (((AnimalHouse)AnimalQueryMenuPatch.animalBeingMoved.home.indoors.Value).animals.ContainsKey(AnimalQueryMenuPatch.animalBeingMoved.myID.Value))
{
((AnimalHouse)selection.indoors.Value).animals.Add(AnimalQueryMenuPatch.animalBeingMoved.myID.Value, AnimalQueryMenuPatch.animalBeingMoved);
((AnimalHouse)AnimalQueryMenuPatch.animalBeingMoved.home.indoors.Value).animals.Remove(AnimalQueryMenuPatch.animalBeingMoved.myID.Value);
}
AnimalQueryMenuPatch.animalBeingMoved.home = selection;
AnimalQueryMenuPatch.animalBeingMoved.homeLocation.Value = new Vector2((int)selection.tileX.Value, (int)selection.tileY.Value);
((AnimalHouse)selection.indoors.Value).animalsThatLiveHere.Add(AnimalQueryMenuPatch.animalBeingMoved.myID.Value);
AnimalQueryMenuPatch.animalBeingMoved.makeSound();
Game1.globalFadeToBlack(AnimalQueryMenuPatch.animalQueryMenu.finishedPlacingAnimal);
}
else
{
string warn = Game1.content.LoadString("Strings\\UI:AnimalQuery_Moving_CantLiveThere");
MainClass.ScreenReader.Say(warn, true);
}
return;
}
}
}

View File

@ -0,0 +1,212 @@
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Features
{
internal class InventoryUtils
{
internal static string hoveredItemQueryKey = "";
internal static int prevSlotIndex = -999;
internal static bool narrateHoveredSlot(InventoryMenu inventoryMenu, List<ClickableComponent> inventory, IList<Item> actualInventory, int x, int y,
bool giveExtraDetails = false, int hoverPrice = -1, int extraItemToShowIndex = -1, int extraItemToShowAmount = -1,
bool handleHighlightedItem = false, String highlightedItemPrefix = "", String highlightedItemSuffix = "")
{
if (narrateHoveredSlotAndReturnIndex(inventoryMenu, inventory, actualInventory, x, y,
giveExtraDetails = false, hoverPrice = -1, extraItemToShowIndex = -1, extraItemToShowAmount = -1,
handleHighlightedItem = false, highlightedItemPrefix = "", highlightedItemSuffix = "") == -999)
return false;
return true;
}
internal static int narrateHoveredSlotAndReturnIndex(InventoryMenu inventoryMenu, List<ClickableComponent> inventory, IList<Item> actualInventory, int x, int y,
bool giveExtraDetails = false, int hoverPrice = -1, int extraItemToShowIndex = -1, int extraItemToShowAmount = -1,
bool handleHighlightedItem = false, String highlightedItemPrefix = "", String highlightedItemSuffix = "")
{
for (int i = 0; i < inventory.Count; i++)
{
if (!inventory[i].containsPoint(x, y)) continue;
if ((i + 1) > actualInventory.Count || actualInventory[i] == null)
{
// For empty slot
checkAndSpeak("Empty Slot", i);
prevSlotIndex = i;
return i;
}
string toSpeak = "";
bool isHighlighted = inventoryMenu.highlightMethod(actualInventory[i]);
string namePrefix = handleHighlightedItemPrefix(isHighlighted, highlightedItemPrefix);
string nameSuffix = $"{handleHighlightedItemSuffix(isHighlighted, highlightedItemSuffix)}{handleUnHighlightedItem(isHighlighted, i)}";
string name = $"{namePrefix}{actualInventory[i].DisplayName}{nameSuffix}";
int stack = actualInventory[i].Stack;
string quality = getQualityFromItem(actualInventory[i]);
string healthNStamine = getHealthNStaminaFromItem(actualInventory[i]);
string buffs = getBuffsFromItem(actualInventory[i]);
string description = actualInventory[i].getDescription();
string price = getPrice(hoverPrice);
string requirements = getExtraItemInfo(extraItemToShowIndex, extraItemToShowAmount);
if (giveExtraDetails)
{
if (stack > 1)
toSpeak = $"{stack} {name} {quality}, \n{requirements}, \n{price}, \n{description}, \n{healthNStamine}, \n{buffs}";
else
toSpeak = $"{name} {quality}, \n{requirements}, \n{price}, \n{description}, \n{healthNStamine}, \n{buffs}";
}
else
{
if (stack > 1)
toSpeak = $"{stack} {name} {quality}, \n{requirements}, \n{price}";
else
toSpeak = $"{name} {quality}, \n{requirements}, \n{price}";
}
checkAndSpeak(toSpeak, i);
prevSlotIndex = i;
return i;
}
// If no slot is hovered
return -999;
}
private static void checkAndSpeak(String toSpeak, int hoveredInventoryIndex)
{
if (hoveredItemQueryKey == $"{toSpeak}:{hoveredInventoryIndex}") return;
hoveredItemQueryKey = $"{toSpeak}:{hoveredInventoryIndex}";
MainClass.ScreenReader.Say(toSpeak, true);
}
private static String getQualityFromItem(Item item)
{
if (item is not StardewValley.Object || ((StardewValley.Object)item).Quality <= 0)
return "";
int qualityIndex = ((StardewValley.Object)item).Quality;
if (qualityIndex == 1)
{
return "Silver quality";
}
else if (qualityIndex == 2 || qualityIndex == 3)
{
return "Gold quality";
}
else if (qualityIndex >= 4)
{
return "Iridium quality";
}
return "";
}
private static String getHealthNStaminaFromItem(Item item)
{
if (item is not StardewValley.Object || ((StardewValley.Object)item).Edibility == -300)
return "";
String toReturn = "";
int stamina_recovery = ((StardewValley.Object)item).staminaRecoveredOnConsumption();
toReturn += $"{stamina_recovery} Energy";
if (stamina_recovery < 0) return toReturn;
int health_recovery = ((StardewValley.Object)item).healthRecoveredOnConsumption();
toReturn += $"\n\t{health_recovery} Health";
return toReturn;
}
private static String getBuffsFromItem(Item item)
{
if (item == null) return "";
if (item is not StardewValley.Object) return "";
if (((StardewValley.Object)item) == null) return "";
// These variables are taken from the game's code itself (IClickableMenu.cs -> 1016 line)
bool edibleItem = (int)((StardewValley.Object)item).Edibility != -300;
string[]? buffIconsToDisplay = (edibleItem && Game1.objectInformation[((StardewValley.Object)item).ParentSheetIndex].Split('/').Length > 7)
? item.ModifyItemBuffs(Game1.objectInformation[((StardewValley.Object)item).ParentSheetIndex].Split('/')[7].Split(' '))
: null;
if (buffIconsToDisplay == null)
return "";
String toReturn = "";
for (int j = 0; j < buffIconsToDisplay.Length; j++)
{
string buffName = ((Convert.ToInt32(buffIconsToDisplay[j]) > 0) ? "+" : "") + buffIconsToDisplay[j] + " ";
if (j <= 11)
{
buffName = Game1.content.LoadString("Strings\\UI:ItemHover_Buff" + j, buffName);
}
try
{
int count = int.Parse(buffName.Substring(0, buffName.IndexOf(' ')));
if (count != 0)
toReturn += $"{buffName}\n";
}
catch (Exception) { }
}
return toReturn;
}
private static String getExtraItemInfo(int itemIndex, int itemAmount)
{
if (itemIndex == -1) return "";
string itemName = Game1.objectInformation[itemIndex].Split('/')[0];
if (itemAmount != -1)
return $"Required: {itemAmount} {itemName}";
else
return $"Required: {itemName}";
}
private static String getPrice(int price)
{
if (price == -1) return "";
return $"Sell Price: {price} g";
}
private static String handleHighlightedItemPrefix(bool isHighlighted, String prefix)
{
if (MainClass.Config.DisableInventoryVerbosity) return "";
if (!isHighlighted) return "";
return prefix;
}
private static String handleHighlightedItemSuffix(bool isHighlighted, String suffix)
{
if (MainClass.Config.DisableInventoryVerbosity) return "";
if (!isHighlighted) return "";
return suffix;
}
private static String handleUnHighlightedItem(bool isHighlighted, int hoveredInventoryIndex)
{
if (isHighlighted) return "";
if (prevSlotIndex != hoveredInventoryIndex)
Game1.playSound("invalid-selection");
if (MainClass.Config.DisableInventoryVerbosity) return "";
return " not usable here";
}
internal static void Cleanup()
{
hoveredItemQueryKey = "";
prevSlotIndex = -999;
}
}
}

View File

@ -77,18 +77,17 @@ namespace stardew_access.Features
return getStaticTileInfoAtWithCategory(x, y).name; return getStaticTileInfoAtWithCategory(x, y).name;
} }
public (string? name, CATEGORY category) getStaticTileInfoAtWithCategory(int x, int y) public (string? name, CATEGORY category) getStaticTileInfoAtWithCategory(int x, int y) {
{
List<JObject> allData = new List<JObject>(); List<JObject> allData = new List<JObject>();
if (customTilesData != null) allData.Add(customTilesData); if (customTilesData != null) allData.Add(customTilesData);
if (staticTilesData != null) allData.Add(staticTilesData); if (staticTilesData != null) allData.Add(staticTilesData);
foreach (JObject data in allData) foreach (JObject data in allData) {
{
foreach (KeyValuePair<string, JToken?> location in data) foreach (KeyValuePair<string, JToken?> location in data)
{ {
if (location.Key.Contains("||") && MainClass.ModHelper != null) string locationName = location.Key;
if (locationName.Contains("||") && MainClass.ModHelper != null)
{ {
// Mod Specific Tiles // Mod Specific Tiles
// We can add tiles that only get detected when the specified mod is loaded. // We can add tiles that only get detected when the specified mod is loaded.
@ -109,69 +108,99 @@ namespace stardew_access.Features
// . // .
// . // .
// } // }
string uniqueModID = location.Key.Substring(location.Key.LastIndexOf("||") + 2); string uniqueModID = locationName.Substring(locationName.LastIndexOf("||") + 2);
string locationName = location.Key.Remove(location.Key.LastIndexOf("||")); locationName = locationName.Remove(locationName.LastIndexOf("||"));
bool isLoaded = MainClass.ModHelper.ModRegistry.IsLoaded(uniqueModID); bool isLoaded = MainClass.ModHelper.ModRegistry.IsLoaded(uniqueModID);
if (!isLoaded) continue; // Skip if the specified mod is not loaded if (!isLoaded) continue; // Skip if the specified mod is not loaded
if (!Game1.currentLocation.Name.ToLower().Equals(locationName.ToLower())) continue;
} }
else if (!Game1.currentLocation.Name.ToLower().Equals(location.Key.ToLower())) continue;
if (location.Value != null) if (locationName.Contains("_") && locationName.ToLower().StartsWith("farm_"))
foreach (var tile in ((JObject)location.Value)) {
string farmType = locationName.Substring(locationName.LastIndexOf("_") + 1);
int farmTypeIndex = getFarmTypeIndex(farmType);
locationName = locationName.Remove(locationName.LastIndexOf("_"));
if (farmTypeIndex != Game1.whichFarm) continue; // Skip if current farm type does not matches
// if (Game1.whichModFarm != null) MainClass.DebugLog($"{farmType} {Game1.whichModFarm.MapName}");
if (farmTypeIndex != 7 || Game1.whichModFarm == null || !farmType.ToLower().Equals(Game1.whichModFarm.MapName.ToLower())) continue; // Not tested but should work
}
if (locationName.ToLower().Equals("town_joja") && Game1.MasterPlayer.mailReceived.Contains("JojaMember"))
{
locationName = "town";
}
if (!Game1.currentLocation.Name.ToLower().Equals(locationName.ToLower())) continue;
if (location.Value == null) continue;
foreach (var tile in ((JObject)location.Value))
{
if (tile.Value == null) continue;
JToken? tileXArray = tile.Value["x"];
JToken? tileYArray = tile.Value["y"];
JToken? tileType = tile.Value["type"];
if (tileXArray == null || tileYArray == null || tileType == null)
continue;
bool isXPresent = false;
bool isYPresent = false;
foreach (var item in tileXArray)
{ {
if (tile.Value == null) if (short.Parse(item.ToString()) != x)
continue; continue;
JToken? tileXArray = tile.Value["x"]; isXPresent = true;
JToken? tileYArray = tile.Value["y"]; break;
JToken? tileType = tile.Value["type"];
if (tileXArray == null || tileYArray == null || tileType == null)
continue;
bool isXPresent = false;
bool isYPresent = false;
foreach (var item in tileXArray)
{
if (short.Parse(item.ToString()) == x)
{
isXPresent = true;
break;
}
}
foreach (var item in tileYArray)
{
if (short.Parse(item.ToString()) == y)
{
isYPresent = true;
break;
}
}
if (isXPresent && isYPresent)
{
string key = tile.Key;
if (key.Contains('[') && key.Contains(']'))
{
int i1 = key.IndexOf('[');
int i2 = key.LastIndexOf(']');
if (i1 < i2)
{
key = key.Remove(i1, ++i2 - i1);
}
}
return (key.Trim(), CATEGORY.FromString(tileType.ToString().ToLower()));
}
} }
foreach (var item in tileYArray)
{
if (short.Parse(item.ToString()) != y)
continue;
isYPresent = true;
break;
}
if (isXPresent && isYPresent)
{
string key = tile.Key;
if (key.Contains('[') && key.Contains(']'))
{
int i1 = key.IndexOf('[');
int i2 = key.LastIndexOf(']');
if (i1 < i2)
{
key = key.Remove(i1, ++i2 - i1);
}
}
return (key.Trim(), CATEGORY.FromString(tileType.ToString().ToLower()));
}
}
} }
} }
return (null, CATEGORY.Others); return (null, CATEGORY.Others);
} }
private int getFarmTypeIndex(string farmType)
{
return farmType.ToLower() switch
{
"default" => 0,
"riverlands" => 1,
"forest" => 2,
"mountains" => 3,
"combat" => 4,
"fourcorners" => 5,
"beach" => 6,
_ => 7,
};
}
} }
} }

View File

@ -455,15 +455,19 @@ namespace stardew_access.Features
else if (Game1.currentLocation is FarmHouse farmHouse) else if (Game1.currentLocation is FarmHouse farmHouse)
{ {
if (farmHouse.upgradeLevel >= 1) if (farmHouse.upgradeLevel >= 1)
if (farmHouse.getKitchenStandingSpot().X == x && (farmHouse.getKitchenStandingSpot().Y - 1) == y) // Standing spot is where the player will stand if (farmHouse.getKitchenStandingSpot().X == x && (farmHouse.getKitchenStandingSpot().Y - 1) == y)
return (CATEGORY.Interactables, "Kitchen"); return (CATEGORY.Interactables, "Stove");
else if ((farmHouse.getKitchenStandingSpot().X + 1) == x && (farmHouse.getKitchenStandingSpot().Y - 1) == y)
return (CATEGORY.Others, "Sink");
else if (farmHouse.fridgePosition.X == x && farmHouse.fridgePosition.Y == y) else if (farmHouse.fridgePosition.X == x && farmHouse.fridgePosition.Y == y)
return (CATEGORY.Interactables, "Fridge"); return (CATEGORY.Interactables, "Fridge");
} }
else if (Game1.currentLocation is IslandFarmHouse islandFarmHouse) else if (Game1.currentLocation is IslandFarmHouse islandFarmHouse)
{ {
if ((islandFarmHouse.fridgePosition.X - 1) == x && islandFarmHouse.fridgePosition.Y == y) if ((islandFarmHouse.fridgePosition.X - 2) == x && islandFarmHouse.fridgePosition.Y == y)
return (CATEGORY.Interactables, "Kitchen"); return (CATEGORY.Interactables, "Stove");
else if ((islandFarmHouse.fridgePosition.X - 1) == x && islandFarmHouse.fridgePosition.Y == y)
return (CATEGORY.Others, "Sink");
else if (islandFarmHouse.fridgePosition.X == x && islandFarmHouse.fridgePosition.Y == y) else if (islandFarmHouse.fridgePosition.X == x && islandFarmHouse.fridgePosition.Y == y)
return (CATEGORY.Interactables, "Fridge"); return (CATEGORY.Interactables, "Fridge");
} }
@ -520,7 +524,7 @@ namespace stardew_access.Features
} }
else if (Game1.currentLocation is IslandWest islandWest) else if (Game1.currentLocation is IslandWest islandWest)
{ {
if (islandWest.shippingBinPosition.X == x && islandWest.shippingBinPosition.Y == y) if ((islandWest.shippingBinPosition.X == x || (islandWest.shippingBinPosition.X + 1) == x) && islandWest.shippingBinPosition.Y == y)
return (CATEGORY.Interactables, "Shipping Bin"); return (CATEGORY.Interactables, "Shipping Bin");
} }
else if (Game1.currentLocation is IslandNorth islandNorth) else if (Game1.currentLocation is IslandNorth islandNorth)

View File

@ -16,220 +16,216 @@ namespace stardew_access
#region Dialogue Patches #region Dialogue Patches
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(DialogueBox), nameof(DialogueBox.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(DialogueBox), nameof(DialogueBox.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(DialoguePatches), nameof(DialoguePatches.DialoguePatch)) postfix: new HarmonyMethod(typeof(DialogueBoxPatch), nameof(DialogueBoxPatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(DialogueBox), nameof(DialogueBox.receiveLeftClick)), original: AccessTools.Method(typeof(DialogueBox), nameof(DialogueBox.receiveLeftClick)),
postfix: new HarmonyMethod(typeof(DialoguePatches), nameof(DialoguePatches.ClearDialogueString)) postfix: new HarmonyMethod(typeof(DialogueBoxPatch), nameof(DialogueBoxPatch.RecieveLeftClickPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(IClickableMenu), nameof(IClickableMenu.drawHoverText), new Type[] { typeof(SpriteBatch), typeof(string), typeof(SpriteFont), typeof(int), typeof(int), typeof(int), typeof(string), typeof(int), typeof(string[]), typeof(Item), typeof(int), typeof(int), typeof(int), typeof(int), typeof(int), typeof(float), typeof(CraftingRecipe), typeof(IList<Item>) }), original: AccessTools.Method(typeof(IClickableMenu), nameof(IClickableMenu.drawHoverText), new Type[] { typeof(SpriteBatch), typeof(string), typeof(SpriteFont), typeof(int), typeof(int), typeof(int), typeof(string), typeof(int), typeof(string[]), typeof(Item), typeof(int), typeof(int), typeof(int), typeof(int), typeof(int), typeof(float), typeof(CraftingRecipe), typeof(IList<Item>) }),
postfix: new HarmonyMethod(typeof(DialoguePatches), nameof(DialoguePatches.HoverTextPatch)) postfix: new HarmonyMethod(typeof(IClickableMenuPatch), nameof(IClickableMenuPatch.DrawHoverTextPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(NPC), nameof(NPC.drawAboveAlwaysFrontLayer)), original: AccessTools.Method(typeof(NPC), nameof(NPC.drawAboveAlwaysFrontLayer)),
postfix: new HarmonyMethod(typeof(DialoguePatches), nameof(DialoguePatches.drawAboveAlwaysFrontLayerPatch)) postfix: new HarmonyMethod(typeof(NPCPatch), nameof(NPCPatch.DrawAboveAlwaysFrontLayerPatch))
); );
#endregion #endregion
#region Title Menu Patches #region Title Menu Patches
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(TitleMenu), nameof(TitleMenu.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(TitleMenu), nameof(TitleMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(TitleMenuPatches), nameof(TitleMenuPatches.TitleMenuPatch)) postfix: new HarmonyMethod(typeof(TitleMenuPatch), nameof(TitleMenuPatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(LoadGameMenu.SaveFileSlot), nameof(LoadGameMenu.SaveFileSlot.Draw), new Type[] { typeof(SpriteBatch), typeof(int) }), original: AccessTools.Method(typeof(LoadGameMenu.SaveFileSlot), nameof(LoadGameMenu.SaveFileSlot.Draw), new Type[] { typeof(SpriteBatch), typeof(int) }),
postfix: new HarmonyMethod(typeof(TitleMenuPatches), nameof(TitleMenuPatches.LoadGameMenuPatch)) postfix: new HarmonyMethod(typeof(LoadGameMenuPatch), nameof(LoadGameMenuPatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(CharacterCustomization), nameof(CharacterCustomization.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(CharacterCustomization), nameof(CharacterCustomization.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(TitleMenuPatches), nameof(CharacterCustomizationPatches.CharacterCustomizationMenuPatch)) postfix: new HarmonyMethod(typeof(CharacterCustomizationMenuPatch), nameof(CharacterCustomizationMenuPatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(CoopMenu), nameof(CoopMenu.update), new Type[] { typeof(GameTime) }), original: AccessTools.Method(typeof(CoopMenu), nameof(CoopMenu.update), new Type[] { typeof(GameTime) }),
postfix: new HarmonyMethod(typeof(TitleMenuPatches), nameof(TitleMenuPatches.CoopMenuPatch)) postfix: new HarmonyMethod(typeof(CoopMenuPatch), nameof(CoopMenuPatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(AdvancedGameOptions), nameof(AdvancedGameOptions.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(AdvancedGameOptions), nameof(AdvancedGameOptions.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(TitleMenuPatches), nameof(TitleMenuPatches.AdvancedGameOptionsPatch)) postfix: new HarmonyMethod(typeof(AdvancedGameOptionsPatch), nameof(AdvancedGameOptionsPatch.DrawPatch))
); );
#endregion #endregion
#region Game Menu Patches #region Game Menu Patches
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(GameMenu), nameof(GameMenu.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(GameMenu), nameof(GameMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(GameMenuPatches), nameof(GameMenuPatches.GameMenuPatch)) postfix: new HarmonyMethod(typeof(GameMenuPatch), nameof(GameMenuPatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(OptionsPage), nameof(OptionsPage.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(OptionsPage), nameof(OptionsPage.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(GameMenuPatches), nameof(GameMenuPatches.OptionsPagePatch)) postfix: new HarmonyMethod(typeof(OptionsPagePatch), nameof(OptionsPagePatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(ExitPage), nameof(ExitPage.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(ExitPage), nameof(ExitPage.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(GameMenuPatches), nameof(GameMenuPatches.ExitPagePatch)) postfix: new HarmonyMethod(typeof(ExitPagePatch), nameof(ExitPagePatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(CraftingPage), nameof(CraftingPage.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(CraftingPage), nameof(CraftingPage.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(GameMenuPatches), nameof(GameMenuPatches.CraftingPagePatch)) postfix: new HarmonyMethod(typeof(CraftingPagePatch), nameof(CraftingPagePatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(InventoryPage), nameof(InventoryPage.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(InventoryPage), nameof(InventoryPage.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(GameMenuPatches), nameof(GameMenuPatches.InventoryPagePatch)) postfix: new HarmonyMethod(typeof(InventoryPagePatch), nameof(InventoryPagePatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(ItemGrabMenu), nameof(ItemGrabMenu.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(ItemGrabMenu), nameof(ItemGrabMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(GameMenuPatches), nameof(GameMenuPatches.ItemGrabMenuPatch)) postfix: new HarmonyMethod(typeof(ItemGrabMenuPatch), nameof(ItemGrabMenuPatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(GeodeMenu), nameof(GeodeMenu.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(GeodeMenu), nameof(GeodeMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(GameMenuPatches), nameof(GameMenuPatches.GeodeMenuPatch)) postfix: new HarmonyMethod(typeof(GeodeMenuPatch), nameof(GeodeMenuPatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(ShopMenu), nameof(ShopMenu.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(ShopMenu), nameof(ShopMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(GameMenuPatches), nameof(GameMenuPatches.ShopMenuPatch)) postfix: new HarmonyMethod(typeof(ShopMenuPatch), nameof(ShopMenuPatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(SocialPage), nameof(SocialPage.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(SocialPage), nameof(SocialPage.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(GameMenuPatches), nameof(GameMenuPatches.SocialPagePatch)) postfix: new HarmonyMethod(typeof(SocialPagePatch), nameof(SocialPagePatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(CollectionsPage), nameof(CollectionsPage.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(CollectionsPage), nameof(CollectionsPage.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(GameMenuPatches), nameof(GameMenuPatches.CollectionsPagePatch)) postfix: new HarmonyMethod(typeof(CollectionsPagePatch), nameof(CollectionsPagePatch.DrawPatch))
); );
#endregion #endregion
#region Bundle Menu Patches #region Bundle Menu Patches
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(JunimoNoteMenu), nameof(JunimoNoteMenu.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(JunimoNoteMenu), nameof(JunimoNoteMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(BundleMenuPatches), nameof(BundleMenuPatches.JunimoNoteMenuPatch)) postfix: new HarmonyMethod(typeof(JunimoNoteMenuPatch), nameof(JunimoNoteMenuPatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(JojaCDMenu), nameof(JojaCDMenu.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(JojaCDMenu), nameof(JojaCDMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(BundleMenuPatches), nameof(BundleMenuPatches.JojaCDMenuPatch)) postfix: new HarmonyMethod(typeof(JojaCDMenuPatch), nameof(JojaCDMenuPatch.DrawPatch))
); );
#endregion #endregion
#region Menu Patches #region Menu Patches
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(LetterViewerMenu), nameof(LetterViewerMenu.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(LetterViewerMenu), nameof(LetterViewerMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(DialoguePatches), nameof(DialoguePatches.LetterViewerMenuPatch)) postfix: new HarmonyMethod(typeof(LetterViwerMenuPatch), nameof(LetterViwerMenuPatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(ShippingMenu), nameof(ShippingMenu.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(ShippingMenu), nameof(ShippingMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.ShippingMenuPatch)) postfix: new HarmonyMethod(typeof(ShippingMenuPatch), nameof(ShippingMenuPatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(LevelUpMenu), nameof(LevelUpMenu.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(LevelUpMenu), nameof(LevelUpMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.LevelUpMenuPatch)) postfix: new HarmonyMethod(typeof(LevelUpMenuPatch), nameof(LevelUpMenuPatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(ConfirmationDialog), nameof(ConfirmationDialog.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(ConfirmationDialog), nameof(ConfirmationDialog.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.ConfirmationDialogPatch)) postfix: new HarmonyMethod(typeof(ConfirmationDialogMenuPatch), nameof(ConfirmationDialogMenuPatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(TitleTextInputMenu), nameof(TitleTextInputMenu.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(TitleTextInputMenu), nameof(TitleTextInputMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.TitleTextInputMenuPatch)) postfix: new HarmonyMethod(typeof(TitleTextInputMenuPatch), nameof(TitleTextInputMenuPatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(NamingMenu), nameof(NamingMenu.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(NamingMenu), nameof(NamingMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.NamingMenuPatch)) postfix: new HarmonyMethod(typeof(NamingMenuPatch), nameof(NamingMenuPatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(MineElevatorMenu), nameof(MineElevatorMenu.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(MineElevatorMenu), nameof(MineElevatorMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.MineElevatorMenuPatch)) postfix: new HarmonyMethod(typeof(MineElevatorMenuPatch), nameof(MineElevatorMenuPatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(LanguageSelectionMenu), nameof(LanguageSelectionMenu.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(LanguageSelectionMenu), nameof(LanguageSelectionMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.LanguageSelectionMenuPatch)) postfix: new HarmonyMethod(typeof(LanguageSelectionMenuPatch), nameof(LanguageSelectionMenuPatch.DrawPatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(MuseumMenu), nameof(MuseumMenu.receiveKeyPress), new Type[] { typeof(Keys) }),
prefix: new HarmonyMethod(typeof(DonationMenuPatches), nameof(DonationMenuPatches.MuseumMenuKeyPressPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(ChooseFromListMenu), nameof(ChooseFromListMenu.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(ChooseFromListMenu), nameof(ChooseFromListMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.ChooseFromListMenuPatch)) postfix: new HarmonyMethod(typeof(ChooseFromListMenuPatch), nameof(ChooseFromListMenuPatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(TailoringMenu), nameof(TailoringMenu.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(TailoringMenu), nameof(TailoringMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.TailoringMenuPatch)) postfix: new HarmonyMethod(typeof(TailoringMenuPatch), nameof(TailoringMenuPatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(PondQueryMenu), nameof(PondQueryMenu.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(PondQueryMenu), nameof(PondQueryMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.PondQueryMenuPatch)) postfix: new HarmonyMethod(typeof(PondQueryMenuPatch), nameof(PondQueryMenuPatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(ForgeMenu), nameof(ForgeMenu.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(ForgeMenu), nameof(ForgeMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.ForgeMenuPatch)) postfix: new HarmonyMethod(typeof(ForgeMenuPatch), nameof(ForgeMenuPatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(ItemListMenu), nameof(ItemListMenu.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(ItemListMenu), nameof(ItemListMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.ItemListMenuPatch)) postfix: new HarmonyMethod(typeof(ItemListMenuPatch), nameof(ItemListMenuPatch.DrawPatch))
); );
#endregion #endregion
#region Quest Patches #region Quest Patches
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(SpecialOrdersBoard), nameof(SpecialOrdersBoard.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(SpecialOrdersBoard), nameof(SpecialOrdersBoard.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(QuestPatches), nameof(QuestPatches.SpecialOrdersBoardPatch)) postfix: new HarmonyMethod(typeof(SpecialOrdersBoardPatch), nameof(SpecialOrdersBoardPatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(QuestLog), nameof(QuestLog.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(QuestLog), nameof(QuestLog.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(QuestPatches), nameof(QuestPatches.QuestLogPatch)) postfix: new HarmonyMethod(typeof(QuestLogPatch), nameof(QuestLogPatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(Billboard), nameof(Billboard.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(Billboard), nameof(Billboard.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(QuestPatches), nameof(QuestPatches.BillboardPatch)) postfix: new HarmonyMethod(typeof(BillboardPatch), nameof(BillboardPatch.DrawPatch))
); );
#endregion #endregion
#region Chat Menu Patches #region Chat Menu Patches
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(ChatBox), nameof(ChatBox.update), new Type[] { typeof(GameTime) }), original: AccessTools.Method(typeof(ChatBox), nameof(ChatBox.update), new Type[] { typeof(GameTime) }),
postfix: new HarmonyMethod(typeof(ChatMenuPatches), nameof(ChatMenuPatches.ChatBoxPatch)) postfix: new HarmonyMethod(typeof(ChatBoxPatch), nameof(ChatBoxPatch.UpdatePatch))
); );
#endregion #endregion
#region On Menu CLose Patch #region On Menu CLose Patch
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(IClickableMenu), nameof(IClickableMenu.exitThisMenu)), original: AccessTools.Method(typeof(IClickableMenu), nameof(IClickableMenu.exitThisMenu)),
postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.IClickableMenuOnExitPatch)) postfix: new HarmonyMethod(typeof(IClickableMenuPatch), nameof(IClickableMenuPatch.ExitThisMenuPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(Game1), nameof(Game1.exitActiveMenu)), original: AccessTools.Method(typeof(Game1), nameof(Game1.exitActiveMenu)),
prefix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.Game1ExitActiveMenuPatch)) prefix: new HarmonyMethod(typeof(Game1Patch), nameof(Game1Patch.ExitActiveMenuPatch))
); );
#endregion #endregion
@ -237,17 +233,17 @@ namespace stardew_access
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(CarpenterMenu), nameof(CarpenterMenu.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(CarpenterMenu), nameof(CarpenterMenu.draw), new Type[] { typeof(SpriteBatch) }),
prefix: new HarmonyMethod(typeof(BuildingNAnimalMenuPatches), nameof(BuildingNAnimalMenuPatches.CarpenterMenuPatch)) prefix: new HarmonyMethod(typeof(CarpenterMenuPatch), nameof(CarpenterMenuPatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(PurchaseAnimalsMenu), nameof(PurchaseAnimalsMenu.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(PurchaseAnimalsMenu), nameof(PurchaseAnimalsMenu.draw), new Type[] { typeof(SpriteBatch) }),
prefix: new HarmonyMethod(typeof(BuildingNAnimalMenuPatches), nameof(BuildingNAnimalMenuPatches.PurchaseAnimalsMenuPatch)) prefix: new HarmonyMethod(typeof(PurchaseAnimalsMenuPatch), nameof(PurchaseAnimalsMenuPatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(AnimalQueryMenu), nameof(AnimalQueryMenu.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(AnimalQueryMenu), nameof(AnimalQueryMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(BuildingNAnimalMenuPatches), nameof(BuildingNAnimalMenuPatches.AnimalQueryMenuPatch)) postfix: new HarmonyMethod(typeof(AnimalQueryMenuPatch), nameof(AnimalQueryMenuPatch.DrawPatch))
); );
#endregion #endregion
@ -255,24 +251,29 @@ namespace stardew_access
#region Donation Menus #region Donation Menus
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(MuseumMenu), nameof(MuseumMenu.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(MuseumMenu), nameof(MuseumMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(DonationMenuPatches), nameof(DonationMenuPatches.MuseumMenuPatch)) postfix: new HarmonyMethod(typeof(MuseumMenuPatch), nameof(MuseumMenuPatch.DrawPatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(MuseumMenu), nameof(MuseumMenu.receiveKeyPress), new Type[] { typeof(Keys) }),
prefix: new HarmonyMethod(typeof(MuseumMenuPatch), nameof(MuseumMenuPatch.RecieveKeyPressPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(FieldOfficeMenu), nameof(FieldOfficeMenu.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(FieldOfficeMenu), nameof(FieldOfficeMenu.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(DonationMenuPatches), nameof(DonationMenuPatches.FieldOfficeMenuPatch)) postfix: new HarmonyMethod(typeof(FieldOfficeMenuPatch), nameof(FieldOfficeMenuPatch.DrawPatch))
); );
#endregion #endregion
#region Mini Games #region Mini Games
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(Intro), nameof(Intro.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(Intro), nameof(Intro.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MiniGamesPatches), nameof(MiniGamesPatches.IntroPatch)) postfix: new HarmonyMethod(typeof(IntroPatch), nameof(IntroPatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(GrandpaStory), nameof(GrandpaStory.draw), new Type[] { typeof(SpriteBatch) }), original: AccessTools.Method(typeof(GrandpaStory), nameof(GrandpaStory.draw), new Type[] { typeof(SpriteBatch) }),
postfix: new HarmonyMethod(typeof(MiniGamesPatches), nameof(MiniGamesPatches.GrandpaStoryPatch)) postfix: new HarmonyMethod(typeof(GrandpaStoryPatch), nameof(GrandpaStoryPatch.DrawPatch))
); );
harmony.Patch( harmony.Patch(
@ -284,12 +285,17 @@ namespace stardew_access
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(Game1), nameof(Game1.playSound)), original: AccessTools.Method(typeof(Game1), nameof(Game1.playSound)),
prefix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.PlaySoundPatch)) prefix: new HarmonyMethod(typeof(Game1Patch), nameof(Game1Patch.PlaySoundPatch))
); );
harmony.Patch( harmony.Patch(
original: AccessTools.Method(typeof(InstanceGame), nameof(InstanceGame.Exit)), original: AccessTools.Method(typeof(InstanceGame), nameof(InstanceGame.Exit)),
prefix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.ExitEventPatch)) prefix: new HarmonyMethod(typeof(InstanceGamePatch), nameof(InstanceGamePatch.ExitPatch))
);
harmony.Patch(
original: AccessTools.Method(typeof(TextBox), nameof(TextBox.Draw)),
prefix: new HarmonyMethod(typeof(TextBoxPatch), nameof(TextBoxPatch.DrawPatch))
); );
} }
} }

View File

@ -85,6 +85,7 @@ namespace stardew_access
public Boolean Warning { get; set; } = true; // Toggles the warnings feature public Boolean Warning { get; set; } = true; // Toggles the warnings feature
public Boolean TTS { get; set; } = true; // Toggles the screen reader/tts. public Boolean TTS { get; set; } = true; // Toggles the screen reader/tts.
public Boolean TrackDroppedItems {get; set;} = true; // Toggles detecting the dropped items. public Boolean TrackDroppedItems {get; set;} = true; // Toggles detecting the dropped items.
public Boolean DisableInventoryVerbosity {get; set;} = false; // If enabled, does not speaks 'not usable here' and 'donatable' in inventories
#endregion #endregion
public int MaximumFishingDifficulty { get; set; } = 999; // TODO Add doc public int MaximumFishingDifficulty { get; set; } = 999; // TODO Add doc

View File

@ -13,7 +13,8 @@ namespace stardew_access
public class MainClass : Mod public class MainClass : Mod
{ {
#region Global Vars & Properties #region Global Vars & Properties
#pragma warning disable CS8603
#pragma warning disable CS8603
private static int prevDate = -99; private static int prevDate = -99;
private static ModConfig? config; private static ModConfig? config;
private Harmony? harmony; private Harmony? harmony;
@ -99,7 +100,6 @@ namespace stardew_access
return warnings; return warnings;
} }
} }
#pragma warning restore CS8603
#endregion #endregion
/********* /*********
@ -128,14 +128,14 @@ namespace stardew_access
HarmonyPatches.Initialize(harmony); HarmonyPatches.Initialize(harmony);
//Initialize marked locations //Initialize marked locations
for (int i = 0; i < BuildingNAnimalMenuPatches.marked.Length; i++) for (int i = 0; i < BuildingOperations.marked.Length; i++)
{ {
BuildingNAnimalMenuPatches.marked[i] = Vector2.Zero; BuildingOperations.marked[i] = Vector2.Zero;
} }
for (int i = 0; i < BuildingNAnimalMenuPatches.availableBuildings.Length; i++) for (int i = 0; i < BuildingOperations.availableBuildings.Length; i++)
{ {
BuildingNAnimalMenuPatches.availableBuildings[i] = null; BuildingOperations.availableBuildings[i] = null;
} }
#endregion #endregion
@ -210,54 +210,32 @@ namespace stardew_access
return; return;
#region Simulate left and right clicks #region Simulate left and right clicks
if (Game1.activeClickableMenu != null) if (Game1.activeClickableMenu != null && !TextBoxPatch.isAnyTextBoxActive)
{ {
bool isCustomizingCharacter = Game1.activeClickableMenu is CharacterCustomization || (TitleMenu.subMenu != null && TitleMenu.subMenu is CharacterCustomization); bool isCustomizingCharacter = Game1.activeClickableMenu is CharacterCustomization || (TitleMenu.subMenu != null && TitleMenu.subMenu is CharacterCustomization);
#region Mouse Click Simulation #region Mouse Click Simulation
// Main Keybinds if (Config.LeftClickMainKey.JustPressed() || Config.LeftClickAlternateKey.JustPressed())
if (Config.LeftClickMainKey.JustPressed())
{ {
Game1.activeClickableMenu.receiveLeftClick(Game1.getMouseX(true), Game1.getMouseY(true)); Game1.activeClickableMenu.receiveLeftClick(Game1.getMouseX(true), Game1.getMouseY(true));
} }
if (Config.RightClickMainKey.JustPressed())
{
Game1.activeClickableMenu.receiveRightClick(Game1.getMouseX(true), Game1.getMouseY(true));
}
// Alternate Keybinds if (Config.RightClickMainKey.JustPressed() || Config.RightClickAlternateKey.JustPressed())
if (!isCustomizingCharacter && Game1.activeClickableMenu is not AnimalQueryMenu && Config.LeftClickAlternateKey.JustPressed()) // Excluding the character creation menu
{
Game1.activeClickableMenu.receiveLeftClick(Game1.getMouseX(true), Game1.getMouseY(true));
}
if (!isCustomizingCharacter && Game1.activeClickableMenu is not AnimalQueryMenu && Config.RightClickAlternateKey.JustPressed()) // Excluding the character creation menu
{ {
Game1.activeClickableMenu.receiveRightClick(Game1.getMouseX(true), Game1.getMouseY(true)); Game1.activeClickableMenu.receiveRightClick(Game1.getMouseX(true), Game1.getMouseY(true));
} }
#endregion #endregion
} }
if (Game1.currentMinigame != null) if (Game1.currentMinigame != null && !TextBoxPatch.isAnyTextBoxActive)
{ {
bool isCustomizingCharacter = Game1.activeClickableMenu is CharacterCustomization || (TitleMenu.subMenu != null && TitleMenu.subMenu is CharacterCustomization);
#region Mouse Click Simulation #region Mouse Click Simulation
// Main Keybinds if (Config.LeftClickMainKey.JustPressed() || Config.LeftClickAlternateKey.JustPressed())
if (Config.LeftClickMainKey.JustPressed())
{ {
Game1.currentMinigame.receiveLeftClick(Game1.getMouseX(true), Game1.getMouseY(true)); Game1.currentMinigame.receiveLeftClick(Game1.getMouseX(true), Game1.getMouseY(true));
} }
if (Config.RightClickMainKey.JustPressed())
{
Game1.currentMinigame.receiveRightClick(Game1.getMouseX(true), Game1.getMouseY(true));
}
// Alternate Keybinds if (Config.RightClickMainKey.JustPressed() || Config.RightClickAlternateKey.JustPressed())
if (Config.LeftClickAlternateKey.JustPressed())
{
Game1.currentMinigame.receiveLeftClick(Game1.getMouseX(true), Game1.getMouseY(true));
}
if (Config.RightClickAlternateKey.JustPressed())
{ {
Game1.currentMinigame.receiveRightClick(Game1.getMouseX(true), Game1.getMouseY(true)); Game1.currentMinigame.receiveRightClick(Game1.getMouseX(true), Game1.getMouseY(true));
} }
@ -377,4 +355,4 @@ namespace stardew_access
monitor.Log(message, LogLevel.Debug); monitor.Log(message, LogLevel.Debug);
} }
} }
} }

View File

@ -1,714 +0,0 @@
using Microsoft.Xna.Framework;
using StardewValley;
using StardewValley.Buildings;
using StardewValley.Locations;
using StardewValley.Menus;
using StardewValley.Objects;
namespace stardew_access.Patches
{
internal class BuildingNAnimalMenuPatches
{
internal static Vector2[] marked = new Vector2[10];
internal static Building?[] availableBuildings = new Building[100];
internal static CarpenterMenu? carpenterMenu = null;
internal static bool isNarratingAnimalInfo = false;
internal static string animalQueryMenuQuery = " ";
internal static string carpenterMenuQuery = "", purchaseAnimalMenuQuery = "";
internal static bool isSayingBlueprintInfo = false;
internal static string prevBlueprintInfo = "";
internal static bool isOnFarm = false, isUpgrading = false, isDemolishing = false, isPainting = false, isConstructing = false, isMoving = false, isMagicalConstruction = false;
internal static bool firstTimeInNamingMenu = true;
internal static PurchaseAnimalsMenu? purchaseAnimalsMenu;
internal static AnimalQueryMenu? animalQueryMenu;
private static FarmAnimal? animalBeingPurchasedOrMoved = null;
internal static void AnimalQueryMenuPatch(AnimalQueryMenu __instance, bool ___confirmingSell, FarmAnimal ___animal, TextBox ___textBox, string ___parentName, bool ___movingAnimal)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
bool isPrimaryInfoKeyPressed = MainClass.Config.PrimaryInfoKey.JustPressed(); // For narrating animal details
bool isEscPressed = Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Escape); // For escaping/unselecting from the animal name text box
string toSpeak = " ", details = " ";
isOnFarm = ___movingAnimal;
animalQueryMenu = __instance;
animalBeingPurchasedOrMoved = ___animal;
if (___textBox.Selected)
{
toSpeak = ___textBox.Text;
if (isEscPressed)
{
___textBox.Selected = false;
}
}
else
{
if (isPrimaryInfoKeyPressed & !isNarratingAnimalInfo)
{
string name = ___animal.displayName;
string type = ___animal.displayType;
int age = (___animal.GetDaysOwned() + 1) / 28 + 1;
string ageText = (age <= 1) ? Game1.content.LoadString("Strings\\UI:AnimalQuery_Age1") : Game1.content.LoadString("Strings\\UI:AnimalQuery_AgeN", age);
string parent = "";
if ((int)___animal.age.Value < (byte)___animal.ageWhenMature.Value)
{
ageText += Game1.content.LoadString("Strings\\UI:AnimalQuery_AgeBaby");
}
if (___parentName != null)
{
parent = Game1.content.LoadString("Strings\\UI:AnimalQuery_Parent", ___parentName);
}
details = $"Name: {name} Type: {type} \n\t Age: {ageText} {parent}";
animalQueryMenuQuery = " ";
isNarratingAnimalInfo = true;
Task.Delay(200).ContinueWith(_ => { isNarratingAnimalInfo = false; });
}
if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
toSpeak = "OK button";
else if (__instance.sellButton != null && __instance.sellButton.containsPoint(x, y))
toSpeak = $"Sell for {___animal.getSellPrice()}g button";
else if (___confirmingSell && __instance.yesButton != null && __instance.yesButton.containsPoint(x, y))
toSpeak = "Confirm selling animal";
else if (___confirmingSell && __instance.noButton != null && __instance.noButton.containsPoint(x, y))
toSpeak = "Cancel selling animal";
else if (__instance.moveHomeButton != null && __instance.moveHomeButton.containsPoint(x, y))
toSpeak = "Change home building button";
else if (__instance.allowReproductionButton != null && __instance.allowReproductionButton.containsPoint(x, y))
toSpeak = ((___animal.allowReproduction.Value) ? "Enabled" : "Disabled") + " allow reproduction button";
else if (__instance.textBoxCC != null && __instance.textBoxCC.containsPoint(x, y))
toSpeak = "Animal name text box";
}
if (animalQueryMenuQuery != toSpeak)
{
animalQueryMenuQuery = toSpeak;
MainClass.ScreenReader.Say($"{details} {toSpeak}", true);
}
}
catch (System.Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void PurchaseAnimalsMenuPatch(PurchaseAnimalsMenu __instance, bool ___onFarm, bool ___namingAnimal, TextBox ___textBox, FarmAnimal ___animalBeingPurchased)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
purchaseAnimalsMenu = __instance;
isOnFarm = ___onFarm;
animalBeingPurchasedOrMoved = ___animalBeingPurchased;
if (___onFarm && ___namingAnimal)
{
string toSpeak = "";
if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
{
toSpeak = "Cancel Button";
}
else if (__instance.doneNamingButton != null && __instance.doneNamingButton.containsPoint(x, y))
{
toSpeak = "OK Button";
}
else if (__instance.randomButton != null && __instance.randomButton.containsPoint(x, y))
{
toSpeak = "Random Name Button";
}
else if (__instance.textBoxCC != null && __instance.textBoxCC.containsPoint(x, y))
{
toSpeak = "Name Text Box";
string? value = ___textBox.Text;
if (value != "" && value != null && value != "null")
toSpeak = $"{toSpeak}, Value: {value}";
}
if (purchaseAnimalMenuQuery != toSpeak)
{
purchaseAnimalMenuQuery = toSpeak;
if (firstTimeInNamingMenu)
{
toSpeak = $"Enter the name of animal in the name text box. {toSpeak}";
firstTimeInNamingMenu = false;
}
MainClass.ScreenReader.Say(toSpeak, true);
}
}
else if (___onFarm && !___namingAnimal)
{
firstTimeInNamingMenu = true;
}
else if (!___onFarm && !___namingAnimal)
{
firstTimeInNamingMenu = true;
if (__instance.hovered != null)
{
string toSpeak = "";
if (((StardewValley.Object)__instance.hovered.item).Type != null)
{
toSpeak = ((StardewValley.Object)__instance.hovered.item).Type;
}
else
{
string displayName = PurchaseAnimalsMenu.getAnimalTitle(__instance.hovered.hoverText);
int price = __instance.hovered.item.salePrice();
string description = PurchaseAnimalsMenu.getAnimalDescription(__instance.hovered.hoverText);
toSpeak = $"{displayName}, Price: {price}g, Description: {description}";
}
if (purchaseAnimalMenuQuery != toSpeak)
{
purchaseAnimalMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void CarpenterMenuPatch(
CarpenterMenu __instance, bool ___onFarm, List<Item> ___ingredients, int ___price,
List<BluePrint> ___blueprints, int ___currentBlueprintIndex, bool ___upgrading, bool ___demolishing, bool ___moving,
bool ___painting, bool ___magicalConstruction)
{
try
{
isOnFarm = ___onFarm;
carpenterMenu = __instance;
isMagicalConstruction = ___magicalConstruction;
if (!___onFarm)
{
isUpgrading = false;
isDemolishing = false;
isPainting = false;
isMoving = false;
isConstructing = false;
#region The blueprint menu
BluePrint currentBluprint = __instance.CurrentBlueprint;
if (currentBluprint == null)
return;
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
bool isPrimaryInfoKeyPressed = MainClass.Config.PrimaryInfoKey.JustPressed();
string ingredients = "";
string name = currentBluprint.displayName;
string upgradeName = currentBluprint.nameOfBuildingToUpgrade;
string description = currentBluprint.description;
string price = $"{___price}g";
string blueprintInfo;
int width = currentBluprint.tilesWidth;
int height = currentBluprint.tilesHeight;
#region Get ingredients
for (int i = 0; i < ___ingredients.Count; i++)
{
string itemName = ___ingredients[i].DisplayName;
int itemStack = ___ingredients[i].Stack;
string itemQuality = "";
int qualityValue = ((StardewValley.Object)___ingredients[i]).Quality;
if (qualityValue == 1)
{
itemQuality = "Silver quality";
}
else if (qualityValue == 2 || qualityValue == 3)
{
itemQuality = "Gold quality";
}
else if (qualityValue >= 4)
{
itemQuality = "Iridium quality";
}
ingredients = $"{ingredients}, {itemStack} {itemName} {itemQuality}";
}
#endregion
blueprintInfo = $"{name}, Price: {price}, Ingredients: {ingredients}, Dimensions: {width} width and {height} height, Description: {description}";
if (isPrimaryInfoKeyPressed && !isSayingBlueprintInfo)
{
SayBlueprintInfo(blueprintInfo);
}
else if (prevBlueprintInfo != blueprintInfo)
{
prevBlueprintInfo = blueprintInfo;
SayBlueprintInfo(blueprintInfo);
}
else
{
if (__instance.backButton != null && __instance.backButton.containsPoint(x, y))
{
string toSpeak = "Previous Blueprint";
if (carpenterMenuQuery != toSpeak)
{
carpenterMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
if (__instance.forwardButton != null && __instance.forwardButton.containsPoint(x, y))
{
string toSpeak = "Next Blueprint";
if (carpenterMenuQuery != toSpeak)
{
carpenterMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
if (__instance.demolishButton != null && __instance.demolishButton.containsPoint(x, y))
{
string toSpeak = $"Demolish Building" + (__instance.CanDemolishThis(___blueprints[___currentBlueprintIndex]) ? "" : ", cannot demolish building");
if (carpenterMenuQuery != toSpeak)
{
carpenterMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
{
string toSpeak = "Construct Building" + (___blueprints[___currentBlueprintIndex].doesFarmerHaveEnoughResourcesToBuild() ? "" : ", cannot cunstrut building, not enough resources to build.");
if (carpenterMenuQuery != toSpeak)
{
carpenterMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
if (__instance.moveButton != null && __instance.moveButton.containsPoint(x, y))
{
string toSpeak = "Move Building";
if (carpenterMenuQuery != toSpeak)
{
carpenterMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
if (__instance.paintButton != null && __instance.paintButton.containsPoint(x, y))
{
string toSpeak = "Paint Building";
if (carpenterMenuQuery != toSpeak)
{
carpenterMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
if (__instance.cancelButton != null && __instance.cancelButton.containsPoint(x, y))
{
string toSpeak = "Cancel Button";
if (carpenterMenuQuery != toSpeak)
{
carpenterMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
}
#endregion
}
else
{
if (___demolishing)
isDemolishing = true;
else if (___upgrading)
isUpgrading = true;
else if (___painting)
isPainting = true;
else if (___moving)
isMoving = true;
else
isConstructing = true;
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
private static async void SayBlueprintInfo(string info)
{
isSayingBlueprintInfo = true;
MainClass.ScreenReader.Say(info, true);
await Task.Delay(300);
isSayingBlueprintInfo = false;
}
public static string? Demolish(Building? toDemolish)
{
if (toDemolish == null)
return null;
string? response = null;
// This code is taken from the game's code (CarpenterMenu.cs::654)
Farm farm = (Farm)Game1.getLocationFromName("Farm");
Action buildingLockFailed = delegate
{
if (isDemolishing)
{
response = Game1.content.LoadString("Strings\\UI:Carpenter_CantDemolish_LockFailed");
}
};
Action continueDemolish = delegate
{
if (isDemolishing && toDemolish != null && farm.buildings.Contains(toDemolish))
{
if ((int)toDemolish.daysOfConstructionLeft.Value > 0 || (int)toDemolish.daysUntilUpgrade.Value > 0)
{
response = Game1.content.LoadString("Strings\\UI:Carpenter_CantDemolish_DuringConstruction");
}
else if (toDemolish.indoors.Value != null && toDemolish.indoors.Value is AnimalHouse && ((AnimalHouse)toDemolish.indoors.Value).animalsThatLiveHere.Count > 0)
{
response = Game1.content.LoadString("Strings\\UI:Carpenter_CantDemolish_AnimalsHere");
}
else if (toDemolish.indoors.Value != null && toDemolish.indoors.Value.farmers.Any())
{
response = Game1.content.LoadString("Strings\\UI:Carpenter_CantDemolish_PlayerHere");
}
else
{
if (toDemolish.indoors.Value != null && toDemolish.indoors.Value is Cabin)
{
foreach (Farmer current in Game1.getAllFarmers())
{
if (current.currentLocation != null && current.currentLocation.Name == ((Cabin)toDemolish.indoors.Value).GetCellarName())
{
response = Game1.content.LoadString("Strings\\UI:Carpenter_CantDemolish_PlayerHere");
return;
}
}
}
if (toDemolish.indoors.Value is Cabin && ((Cabin)toDemolish.indoors.Value).farmhand.Value.isActive())
{
response = Game1.content.LoadString("Strings\\UI:Carpenter_CantDemolish_FarmhandOnline");
}
else
{
toDemolish.BeforeDemolish();
Chest? chest = null;
if (toDemolish.indoors.Value is Cabin)
{
List<Item> list = ((Cabin)toDemolish.indoors.Value).demolish();
if (list.Count > 0)
{
chest = new Chest(playerChest: true);
chest.fixLidFrame();
chest.items.Set(list);
}
}
if (farm.destroyStructure(toDemolish))
{
_ = (int)toDemolish.tileY.Value;
_ = (int)toDemolish.tilesHigh.Value;
Game1.flashAlpha = 1f;
toDemolish.showDestroyedAnimation(Game1.getFarm());
Game1.playSound("explosion");
Utility.spreadAnimalsAround(toDemolish, farm);
if (carpenterMenu != null)
DelayedAction.functionAfterDelay(carpenterMenu.returnToCarpentryMenu, 1500);
// freeze = true;
if (chest != null)
{
farm.objects[new Vector2((int)toDemolish.tileX.Value + (int)toDemolish.tilesWide.Value / 2, (int)toDemolish.tileY.Value + (int)toDemolish.tilesHigh.Value / 2)] = chest;
}
}
}
}
}
};
if (toDemolish != null)
{
if (toDemolish.indoors.Value != null && toDemolish.indoors.Value is Cabin && !Game1.IsMasterGame)
{
response = Game1.content.LoadString("Strings\\UI:Carpenter_CantDemolish_LockFailed");
toDemolish = null;
return response;
}
if (carpenterMenu != null && !carpenterMenu.CanDemolishThis(toDemolish))
{
toDemolish = null;
return response;
}
if (carpenterMenu != null && !Game1.IsMasterGame && !carpenterMenu.hasPermissionsToDemolish(toDemolish))
{
toDemolish = null;
return response;
}
}
if (toDemolish != null && toDemolish.indoors.Value is Cabin)
{
Cabin cabin = (Cabin)toDemolish.indoors.Value;
if (cabin.farmhand.Value != null && (bool)cabin.farmhand.Value.isCustomized.Value)
{
Game1.currentLocation.createQuestionDialogue(Game1.content.LoadString("Strings\\UI:Carpenter_DemolishCabinConfirm", cabin.farmhand.Value.Name), Game1.currentLocation.createYesNoResponses(), delegate (Farmer f, string answer)
{
if (answer == "Yes")
{
Game1.activeClickableMenu = carpenterMenu;
Game1.player.team.demolishLock.RequestLock(continueDemolish, buildingLockFailed);
}
else
{
if (carpenterMenu != null)
DelayedAction.functionAfterDelay(carpenterMenu.returnToCarpentryMenu, 1000);
}
});
return response;
}
}
if (toDemolish != null)
{
Game1.player.team.demolishLock.RequestLock(continueDemolish, buildingLockFailed);
}
return response;
}
public static string? Contstruct(Vector2 position)
{
string? response = null;
// This code is taken from the game's code (CarpenterMenu.cs::874)
Game1.player.team.buildLock.RequestLock(delegate
{
if (isOnFarm && Game1.locationRequest == null)
{
if (tryToBuild(position))
{
if (carpenterMenu != null)
{
carpenterMenu.CurrentBlueprint.consumeResources();
DelayedAction.functionAfterDelay(carpenterMenu.returnToCarpentryMenuAfterSuccessfulBuild, 2000);
}
// freeze = true;
}
else
{
response = Game1.content.LoadString("Strings\\UI:Carpenter_CantBuild");
}
}
Game1.player.team.buildLock.ReleaseLock();
});
return response;
}
public static bool tryToBuild(Vector2 position)
{
if (carpenterMenu == null)
return false;
return ((Farm)Game1.getLocationFromName("Farm")).buildStructure(carpenterMenu.CurrentBlueprint, position, Game1.player, isMagicalConstruction);
}
public static string? Upgrade(Building? toUpgrade)
{
string? response = null;
// This code is taken from the game's code (CarpenterMenu.cs::775)
if (carpenterMenu != null && toUpgrade != null && carpenterMenu.CurrentBlueprint.name != null && toUpgrade.buildingType.Equals(carpenterMenu.CurrentBlueprint.nameOfBuildingToUpgrade))
{
carpenterMenu.CurrentBlueprint.consumeResources();
toUpgrade.daysUntilUpgrade.Value = 2;
toUpgrade.showUpgradeAnimation(Game1.getFarm());
Game1.playSound("axe");
DelayedAction.functionAfterDelay(carpenterMenu.returnToCarpentryMenuAfterSuccessfulBuild, 1500);
// freeze = true;
// Game1.multiplayer.globalChatInfoMessage("BuildingBuild", Game1.player.Name, Utility.AOrAn(carpenterMenu.CurrentBlueprint.displayName), carpenterMenu.CurrentBlueprint.displayName, Game1.player.farmName.Value);
}
else if (toUpgrade != null)
{
response = Game1.content.LoadString("Strings\\UI:Carpenter_CantUpgrade_BuildingType");
}
return response;
}
public static string? Paint(Building? toPaint)
{
string? response = null;
// This code is taken from the game's code (CarpenterMenu.cs::793)
Farm farm_location = Game1.getFarm();
if (toPaint != null)
{
if (!toPaint.CanBePainted())
{
response = Game1.content.LoadString("Strings\\UI:Carpenter_CannotPaint");
return response;
}
if (carpenterMenu != null && !carpenterMenu.HasPermissionsToPaint(toPaint))
{
response = Game1.content.LoadString("Strings\\UI:Carpenter_CannotPaint_Permission");
return response;
}
toPaint.color.Value = Color.White;
if (carpenterMenu != null)
carpenterMenu.SetChildMenu(new BuildingPaintMenu(toPaint));
}
/* TODO Add painting of farm house
else if (farm_location.GetHouseRect().Contains(Utility.Vector2ToPoint(new Vector2(toPaint.tileX, toPaint.tileY))))
{
if (!carpenterMenu.CanPaintHouse())
{
response = Game1.content.LoadString("Strings\\UI:Carpenter_CannotPaint");
}
else if (!carpenterMenu.HasPermissionsToPaint(null))
{
response = Game1.content.LoadString("Strings\\UI:Carpenter_CannotPaint_Permission");
}
else
{
carpenterMenu.SetChildMenu(new BuildingPaintMenu("House", () => (farm_location.paintedHouseTexture != null) ? farm_location.paintedHouseTexture : Farm.houseTextures, farm_location.houseSource.Value, farm_location.housePaintColor.Value));
}
}*/
return response;
}
public static string? Move(Building? buildingToMove, Vector2 position)
{
string? response = null;
// This code is taken from the game's code (CarpenterMenu.cs::829)
if (buildingToMove != null)
{
string? name = buildingToMove.nameOfIndoorsWithoutUnique;
name = (name == "null") ? buildingToMove.buildingType.Value : name;
if ((int)buildingToMove.daysOfConstructionLeft.Value > 0)
{
buildingToMove = null;
return "Building under construction, cannot move";
}
if (carpenterMenu != null && !carpenterMenu.hasPermissionsToMove(buildingToMove))
{
buildingToMove = null;
return "You don't have permission to move this building";
}
Game1.playSound("axchop");
if (((Farm)Game1.getLocationFromName("Farm")).buildStructure(buildingToMove, position, Game1.player))
{
if (buildingToMove is ShippingBin)
{
((ShippingBin)buildingToMove).initLid();
}
if (buildingToMove is GreenhouseBuilding)
{
Game1.getFarm().greenhouseMoved.Value = true;
}
buildingToMove.performActionOnBuildingPlacement();
buildingToMove = null;
Game1.playSound("axchop");
DelayedAction.playSoundAfterDelay("dirtyHit", 50);
DelayedAction.playSoundAfterDelay("dirtyHit", 150);
response = $"{buildingToMove} moved to {position.X}x {position.Y}y";
}
else
{
Game1.playSound("cancel");
response = $"Cannot move building to {position.X}x {position.Y}y";
}
}
return response;
}
public static void PurchaseAnimal(Building? selection)
{
if (selection == null)
return;
if (purchaseAnimalsMenu == null)
return;
int x = (selection.tileX.Value * Game1.tileSize) - Game1.viewport.X;
int y = (selection.tileY.Value * Game1.tileSize) - Game1.viewport.Y;
if (animalBeingPurchasedOrMoved != null && !selection.buildingType.Value.Contains(animalBeingPurchasedOrMoved.buildingTypeILiveIn.Value))
{
string warn = Game1.content.LoadString("Strings\\StringsFromCSFiles:PurchaseAnimalsMenu.cs.11326", animalBeingPurchasedOrMoved.displayType);
MainClass.ScreenReader.Say(warn, true);
return;
}
if (((AnimalHouse)selection.indoors.Value).isFull())
{
string warn = Game1.content.LoadString("Strings\\StringsFromCSFiles:PurchaseAnimalsMenu.cs.11321");
MainClass.ScreenReader.Say(warn, true);
return;
}
purchaseAnimalsMenu.receiveLeftClick(x, y);
}
public static void MoveAnimal(Building? selection)
{
if (selection == null)
return;
if (animalQueryMenu == null)
return;
if (animalBeingPurchasedOrMoved == null)
return;
// The following code is taken from the game's source code [AnimalQueryMenu.cs::receiveLeftClick]
if (selection.buildingType.Value.Contains(animalBeingPurchasedOrMoved.buildingTypeILiveIn.Value))
{
if (((AnimalHouse)selection.indoors.Value).isFull())
{
string warn = Game1.content.LoadString("Strings\\UI:AnimalQuery_Moving_BuildingFull");
MainClass.ScreenReader.Say(warn, true);
return;
}
if (selection.Equals(animalBeingPurchasedOrMoved.home))
{
string warn = Game1.content.LoadString("Strings\\UI:AnimalQuery_Moving_AlreadyHome");
MainClass.ScreenReader.Say(warn, true);
return;
}
((AnimalHouse)animalBeingPurchasedOrMoved.home.indoors.Value).animalsThatLiveHere.Remove(animalBeingPurchasedOrMoved.myID.Value);
if (((AnimalHouse)animalBeingPurchasedOrMoved.home.indoors.Value).animals.ContainsKey(animalBeingPurchasedOrMoved.myID.Value))
{
((AnimalHouse)selection.indoors.Value).animals.Add(animalBeingPurchasedOrMoved.myID.Value, animalBeingPurchasedOrMoved);
((AnimalHouse)animalBeingPurchasedOrMoved.home.indoors.Value).animals.Remove(animalBeingPurchasedOrMoved.myID.Value);
}
animalBeingPurchasedOrMoved.home = selection;
animalBeingPurchasedOrMoved.homeLocation.Value = new Vector2((int)selection.tileX.Value, (int)selection.tileY.Value);
((AnimalHouse)selection.indoors.Value).animalsThatLiveHere.Add(animalBeingPurchasedOrMoved.myID.Value);
animalBeingPurchasedOrMoved.makeSound();
Game1.globalFadeToBlack(animalQueryMenu.finishedPlacingAnimal);
}
else
{
string warn = Game1.content.LoadString("Strings\\UI:AnimalQuery_Moving_CantLiveThere");
MainClass.ScreenReader.Say(warn, true);
}
return;
}
}
}

View File

@ -1,360 +0,0 @@
using StardewValley;
using StardewValley.Locations;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class BundleMenuPatches
{
internal static string junimoNoteMenuQuery = "";
internal static string currentJunimoArea = "";
internal static string jojaCDMenuQuery = "";
internal static bool isUsingCustomButtons = false;
internal static int currentIngredientListItem = -1, currentIngredientInputSlot = -1, currentInventorySlot = -1;
#region Joja Mart Bundle/Quests
internal static void JojaCDMenuPatch(JojaCDMenu __instance)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
string toSpeak = "";
for (int i = 0; i < __instance.checkboxes.Count; i++)
{
ClickableComponent c = __instance.checkboxes[i];
if (!c.containsPoint(x, y))
continue;
if (c.name.Equals("complete"))
{
toSpeak = $"Completed {getNameFromIndex(i)}";
}
else
{
toSpeak = $"{getNameFromIndex(i)} Cost: {__instance.getPriceFromButtonNumber(i)}g Description: {__instance.getDescriptionFromButtonNumber(i)}";
}
break;
}
if (jojaCDMenuQuery != toSpeak)
{
jojaCDMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
}
catch (System.Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
private static string getNameFromIndex(int i)
{
string name = i switch
{
0 => "Bus",
1 => "Minecarts",
2 => "Bridge",
3 => "Greenhouse",
4 => "Panning",
_ => "",
};
if (name != "")
return $"{name} Project";
else
return "unkown";
}
#endregion
#region Community Center Bundles
internal static void JunimoNoteMenuPatch(JunimoNoteMenu __instance, bool ___specificBundlePage, int ___whichArea, Bundle ___currentPageBundle)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
if (!___specificBundlePage)
{
currentIngredientListItem = -1;
isUsingCustomButtons = false;
string areaName = __instance.scrambledText ? CommunityCenter.getAreaEnglishDisplayNameFromNumber(___whichArea) : CommunityCenter.getAreaDisplayNameFromNumber(___whichArea);
string reward = __instance.getRewardNameForArea(___whichArea);
if (__instance.scrambledText)
{
string toSpeak = "Scrambled Text";
if (junimoNoteMenuQuery != toSpeak)
{
junimoNoteMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
if (currentJunimoArea != areaName)
{
currentJunimoArea = areaName;
MainClass.ScreenReader.Say($"Area {areaName}, {reward}", true);
return;
}
for (int i = 0; i < __instance.bundles.Count; i++)
{
if (__instance.bundles[i].containsPoint(x, y))
{
string toSpeak = $"{__instance.bundles[i].name} bundle";
if (junimoNoteMenuQuery != toSpeak)
{
junimoNoteMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
}
if (__instance.presentButton != null && __instance.presentButton.containsPoint(x, y))
{
string toSpeak = "Present Button";
if (junimoNoteMenuQuery != toSpeak)
{
junimoNoteMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
if (__instance.fromGameMenu)
{
if (__instance.areaNextButton.visible && __instance.areaNextButton.containsPoint(x, y))
{
string toSpeak = "Next Area Button";
if (junimoNoteMenuQuery != toSpeak)
{
junimoNoteMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
if (__instance.areaBackButton.visible && __instance.areaBackButton.containsPoint(x, y))
{
string toSpeak = "Previous Area Button";
if (junimoNoteMenuQuery != toSpeak)
{
junimoNoteMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
}
}
else
{
bool isIPressed = MainClass.Config.BundleMenuIngredientsKey.JustPressed(); // For the ingredients
bool isCPressed = MainClass.Config.BundleMenuInventoryItemsKey.JustPressed(); // For the items in inventory
bool isPPressed = MainClass.Config.BundleMenuPurchaseButtonKey.JustPressed(); // For the Purchase Button
bool isVPressed = MainClass.Config.BundleMenuIngredientsInputSlotKey.JustPressed(); // For the ingredient input slots
bool isBackPressed = MainClass.Config.BundleMenuBackButtonKey.JustPressed(); // For the back button
bool isLeftShiftPressed = Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.LeftShift);
if (isIPressed && !isUsingCustomButtons)
{
isUsingCustomButtons = true;
JunimoNoteCustomButtons(__instance, ___currentPageBundle, 0, isLeftShiftPressed);
Task.Delay(200).ContinueWith(_ => { isUsingCustomButtons = false; });
}
else if (isVPressed && !isUsingCustomButtons)
{
isUsingCustomButtons = true;
JunimoNoteCustomButtons(__instance, ___currentPageBundle, 1, isLeftShiftPressed);
Task.Delay(200).ContinueWith(_ => { isUsingCustomButtons = false; });
}
else if (isCPressed && !isUsingCustomButtons)
{
isUsingCustomButtons = true;
JunimoNoteCustomButtons(__instance, ___currentPageBundle, 2, isLeftShiftPressed);
Task.Delay(200).ContinueWith(_ => { isUsingCustomButtons = false; });
}
else if (isBackPressed && __instance.backButton != null && !__instance.backButton.containsPoint(x, y))
{
__instance.backButton.snapMouseCursorToCenter();
MainClass.ScreenReader.Say("Back Button", true);
}
else if (isPPressed && __instance.purchaseButton != null && !__instance.purchaseButton.containsPoint(x, y))
{
__instance.purchaseButton.snapMouseCursorToCenter();
MainClass.ScreenReader.Say("Purchase Button", true);
}
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
private static void JunimoNoteCustomButtons(JunimoNoteMenu __instance, Bundle ___currentPageBundle, int signal, bool isLeftShiftPressed = false)
{
try
{
switch (signal)
{
case 0: // For ingredient list
{
if (___currentPageBundle.ingredients.Count >= 0)
{
currentIngredientListItem = currentIngredientListItem + (isLeftShiftPressed ? -1 : 1);
if (currentIngredientListItem >= ___currentPageBundle.ingredients.Count)
if (isLeftShiftPressed)
currentIngredientListItem = ___currentPageBundle.ingredients.Count - 1;
else
currentIngredientListItem = 0;
if (currentIngredientListItem < 0)
if (isLeftShiftPressed)
currentIngredientListItem = ___currentPageBundle.ingredients.Count - 1;
else
currentIngredientListItem = 0;
ClickableTextureComponent c = __instance.ingredientList[currentIngredientListItem];
BundleIngredientDescription ingredient = ___currentPageBundle.ingredients[currentIngredientListItem];
Item item = new StardewValley.Object(ingredient.index, ingredient.stack, isRecipe: false, -1, ingredient.quality);
bool completed = false;
if (___currentPageBundle != null && ___currentPageBundle.ingredients != null && currentIngredientListItem < ___currentPageBundle.ingredients.Count && ___currentPageBundle.ingredients[currentIngredientListItem].completed)
{
completed = true;
}
string toSpeak = item.DisplayName;
if (!completed)
{
int quality = ingredient.quality;
if (quality == 1)
{
toSpeak = $"Silver quality {toSpeak}";
}
else if (quality == 2 || quality == 3)
{
toSpeak = $"Gold quality {toSpeak}";
}
else if (quality >= 4)
{
toSpeak = $"Iridium quality {toSpeak}";
}
toSpeak = $"{ingredient.stack} {toSpeak}";
}
if (completed)
toSpeak = $"Completed {toSpeak}";
c.snapMouseCursorToCenter();
MainClass.ScreenReader.Say(toSpeak, true);
}
}
break;
case 1: // For input slot list
{
if (__instance.ingredientSlots.Count >= 0)
{
currentIngredientInputSlot = currentIngredientInputSlot + (isLeftShiftPressed ? -1 : 1);
if (currentIngredientInputSlot >= __instance.ingredientSlots.Count)
if (isLeftShiftPressed)
currentIngredientInputSlot = __instance.ingredientSlots.Count - 1;
else
currentIngredientInputSlot = 0;
if (currentIngredientInputSlot < 0)
if (isLeftShiftPressed)
currentIngredientInputSlot = __instance.ingredientSlots.Count - 1;
else
currentIngredientInputSlot = 0;
ClickableTextureComponent c = __instance.ingredientSlots[currentIngredientInputSlot];
Item item = c.item;
string toSpeak;
if (item == null)
{
toSpeak = $"Input Slot {currentIngredientInputSlot + 1}";
}
else
{
toSpeak = item.DisplayName;
}
c.snapMouseCursorToCenter();
MainClass.ScreenReader.Say(toSpeak, true);
}
}
break;
case 2: // For inventory slots
{
if (__instance.inventory != null && __instance.inventory.actualInventory.Count >= 0)
{
int prevSlotIndex = currentInventorySlot;
currentInventorySlot = currentInventorySlot + (isLeftShiftPressed ? -1 : 1);
if (currentInventorySlot >= __instance.inventory.actualInventory.Count)
if (isLeftShiftPressed)
currentInventorySlot = __instance.inventory.actualInventory.Count - 1;
else
currentInventorySlot = 0;
if (currentInventorySlot < 0)
if (isLeftShiftPressed)
currentInventorySlot = __instance.inventory.actualInventory.Count - 1;
else
currentInventorySlot = 0;
Item item = __instance.inventory.actualInventory[currentInventorySlot];
ClickableComponent c = __instance.inventory.inventory[currentInventorySlot];
string toSpeak;
if (item != null)
{
toSpeak = item.DisplayName;
if ((item as StardewValley.Object) != null)
{
int quality = ((StardewValley.Object)item).Quality;
if (quality == 1)
{
toSpeak = $"Silver quality {toSpeak}";
}
else if (quality == 2 || quality == 3)
{
toSpeak = $"Gold quality {toSpeak}";
}
else if (quality >= 4)
{
toSpeak = $"Iridium quality {toSpeak}";
}
}
toSpeak = $"{item.Stack} {toSpeak}";
if (!__instance.inventory.highlightMethod(__instance.inventory.actualInventory[currentInventorySlot]))
{
toSpeak = $"{toSpeak} not usable here";
}
}
else
{
toSpeak = "Empty Slot";
}
c.snapMouseCursorToCenter();
MainClass.ScreenReader.Say(toSpeak, true);
}
}
break;
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
#endregion
}
}

View File

@ -0,0 +1,70 @@
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class JojaCDMenuPatch
{
internal static string jojaCDMenuQuery = "";
internal static void DrawPatch(JojaCDMenu __instance)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
string toSpeak = "";
for (int i = 0; i < __instance.checkboxes.Count; i++)
{
ClickableComponent c = __instance.checkboxes[i];
if (!c.containsPoint(x, y))
continue;
if (c.name.Equals("complete"))
{
toSpeak = $"Completed {getNameFromIndex(i)}";
}
else
{
toSpeak = $"{getNameFromIndex(i)} Cost: {__instance.getPriceFromButtonNumber(i)}g Description: {__instance.getDescriptionFromButtonNumber(i)}";
}
break;
}
if (jojaCDMenuQuery != toSpeak)
{
jojaCDMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
}
catch (System.Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
private static string getNameFromIndex(int i)
{
string name = i switch
{
0 => "Bus",
1 => "Minecarts",
2 => "Bridge",
3 => "Greenhouse",
4 => "Panning",
_ => "",
};
if (name != "")
return $"{name} Project";
else
return "unkown";
}
internal static void Cleanup()
{
jojaCDMenuQuery = "";
}
}
}

View File

@ -0,0 +1,297 @@
using StardewValley;
using StardewValley.Locations;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class JunimoNoteMenuPatch
{
internal static string junimoNoteMenuQuery = "";
internal static string currentJunimoArea = "";
internal static bool isUsingCustomKeyBinds = false;
internal static int currentIngredientListItem = -1, currentIngredientInputSlot = -1, currentInventorySlot = -1;
internal static void DrawPatch(JunimoNoteMenu __instance, bool ___specificBundlePage, int ___whichArea, Bundle ___currentPageBundle)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
if (narrateJunimoArea(__instance, ___specificBundlePage, ___whichArea, x, y))
{
return;
}
narrateBundlePage(__instance, ___specificBundlePage, ___currentPageBundle, x, y);
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
private static bool narrateJunimoArea(JunimoNoteMenu __instance, bool ___specificBundlePage, int ___whichArea, int x, int y)
{
if (___specificBundlePage)
return false;
currentIngredientListItem = -1;
isUsingCustomKeyBinds = false;
string areaName = __instance.scrambledText ? CommunityCenter.getAreaEnglishDisplayNameFromNumber(___whichArea) : CommunityCenter.getAreaDisplayNameFromNumber(___whichArea);
string reward = __instance.getRewardNameForArea(___whichArea);
if (__instance.scrambledText)
{
string scrambledText = "Scrambled Text";
if (junimoNoteMenuQuery != scrambledText)
{
junimoNoteMenuQuery = scrambledText;
MainClass.ScreenReader.Say(scrambledText, true);
}
return true;
}
if (currentJunimoArea != areaName)
{
currentJunimoArea = areaName;
MainClass.ScreenReader.Say($"Area {areaName}, {reward}", true);
return true;
}
string toSpeak = "";
if (__instance.presentButton != null && __instance.presentButton.containsPoint(x, y))
{
toSpeak = "Present Button";
}
else if (__instance.fromGameMenu && __instance.areaNextButton.visible && __instance.areaNextButton.containsPoint(x, y))
{
toSpeak = "Next Area Button";
}
else if (__instance.fromGameMenu && __instance.areaBackButton.visible && __instance.areaBackButton.containsPoint(x, y))
{
toSpeak = "Previous Area Button";
}
else
{
for (int i = 0; i < __instance.bundles.Count; i++)
{
if (!__instance.bundles[i].containsPoint(x, y))
continue;
toSpeak = $"{__instance.bundles[i].name} bundle";
break;
}
}
if (junimoNoteMenuQuery != toSpeak)
{
junimoNoteMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
return true;
}
return false;
}
private static void narrateBundlePage(JunimoNoteMenu __instance, bool ___specificBundlePage, Bundle ___currentPageBundle, int x, int y)
{
if (!___specificBundlePage)
return;
bool isIPressed = MainClass.Config.BundleMenuIngredientsKey.JustPressed(); // For the ingredients
bool isCPressed = MainClass.Config.BundleMenuInventoryItemsKey.JustPressed(); // For the items in inventory
bool isPPressed = MainClass.Config.BundleMenuPurchaseButtonKey.JustPressed(); // For the Purchase Button
bool isVPressed = MainClass.Config.BundleMenuIngredientsInputSlotKey.JustPressed(); // For the ingredient input slots
bool isBackPressed = MainClass.Config.BundleMenuBackButtonKey.JustPressed(); // For the back button
bool isLeftShiftPressed = Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.LeftShift);
if (isIPressed && !isUsingCustomKeyBinds)
{
isUsingCustomKeyBinds = true;
cycleThroughIngredientList(__instance, ___currentPageBundle, isLeftShiftPressed);
Task.Delay(200).ContinueWith(_ => { isUsingCustomKeyBinds = false; });
}
else if (isVPressed && !isUsingCustomKeyBinds)
{
isUsingCustomKeyBinds = true;
cycleThroughInputSlots(__instance, ___currentPageBundle, isLeftShiftPressed);
Task.Delay(200).ContinueWith(_ => { isUsingCustomKeyBinds = false; });
}
else if (isCPressed && !isUsingCustomKeyBinds)
{
isUsingCustomKeyBinds = true;
cycleThroughInventorySlots(__instance, ___currentPageBundle, isLeftShiftPressed);
Task.Delay(200).ContinueWith(_ => { isUsingCustomKeyBinds = false; });
}
else if (isBackPressed && __instance.backButton != null && !__instance.backButton.containsPoint(x, y))
{
__instance.backButton.snapMouseCursorToCenter();
MainClass.ScreenReader.Say("Back Button", true);
}
else if (isPPressed && __instance.purchaseButton != null && !__instance.purchaseButton.containsPoint(x, y))
{
__instance.purchaseButton.snapMouseCursorToCenter();
MainClass.ScreenReader.Say("Purchase Button", true);
}
return;
}
private static void cycleThroughIngredientList(JunimoNoteMenu __instance, Bundle ___currentPageBundle, bool isLeftShiftPressed = false)
{
if (___currentPageBundle.ingredients.Count < 0)
return;
currentIngredientListItem = currentIngredientListItem + (isLeftShiftPressed ? -1 : 1);
if (currentIngredientListItem >= ___currentPageBundle.ingredients.Count)
if (isLeftShiftPressed)
currentIngredientListItem = ___currentPageBundle.ingredients.Count - 1;
else
currentIngredientListItem = 0;
if (currentIngredientListItem < 0)
if (isLeftShiftPressed)
currentIngredientListItem = ___currentPageBundle.ingredients.Count - 1;
else
currentIngredientListItem = 0;
ClickableTextureComponent c = __instance.ingredientList[currentIngredientListItem];
BundleIngredientDescription ingredient = ___currentPageBundle.ingredients[currentIngredientListItem];
Item item = new StardewValley.Object(ingredient.index, ingredient.stack, isRecipe: false, -1, ingredient.quality);
bool completed = false;
if (___currentPageBundle != null && ___currentPageBundle.ingredients != null && currentIngredientListItem < ___currentPageBundle.ingredients.Count && ___currentPageBundle.ingredients[currentIngredientListItem].completed)
{
completed = true;
}
string toSpeak = item.DisplayName;
if (completed)
{
toSpeak = $"Completed {toSpeak}";
}
else
{
int quality = ingredient.quality;
if (quality == 1)
{
toSpeak = $"Silver quality {toSpeak}";
}
else if (quality == 2 || quality == 3)
{
toSpeak = $"Gold quality {toSpeak}";
}
else if (quality >= 4)
{
toSpeak = $"Iridium quality {toSpeak}";
}
toSpeak = $"{ingredient.stack} {toSpeak}";
}
c.snapMouseCursorToCenter();
MainClass.ScreenReader.Say(toSpeak, true);
}
private static void cycleThroughInputSlots(JunimoNoteMenu __instance, Bundle ___currentPageBundle, bool isLeftShiftPressed = false)
{
if (__instance.ingredientSlots.Count < 0)
return;
currentIngredientInputSlot = currentIngredientInputSlot + (isLeftShiftPressed ? -1 : 1);
if (currentIngredientInputSlot >= __instance.ingredientSlots.Count)
if (isLeftShiftPressed)
currentIngredientInputSlot = __instance.ingredientSlots.Count - 1;
else
currentIngredientInputSlot = 0;
if (currentIngredientInputSlot < 0)
if (isLeftShiftPressed)
currentIngredientInputSlot = __instance.ingredientSlots.Count - 1;
else
currentIngredientInputSlot = 0;
ClickableTextureComponent c = __instance.ingredientSlots[currentIngredientInputSlot];
Item item = c.item;
string toSpeak;
if (item == null)
{
toSpeak = $"Input Slot {currentIngredientInputSlot + 1}";
}
else
{
toSpeak = item.DisplayName;
}
c.snapMouseCursorToCenter();
MainClass.ScreenReader.Say(toSpeak, true);
}
private static void cycleThroughInventorySlots(JunimoNoteMenu __instance, Bundle ___currentPageBundle, bool isLeftShiftPressed = false)
{
if (__instance.inventory == null || __instance.inventory.actualInventory.Count < 0)
return;
int prevSlotIndex = currentInventorySlot;
currentInventorySlot = currentInventorySlot + (isLeftShiftPressed ? -1 : 1);
if (currentInventorySlot >= __instance.inventory.actualInventory.Count)
if (isLeftShiftPressed)
currentInventorySlot = __instance.inventory.actualInventory.Count - 1;
else
currentInventorySlot = 0;
if (currentInventorySlot < 0)
if (isLeftShiftPressed)
currentInventorySlot = __instance.inventory.actualInventory.Count - 1;
else
currentInventorySlot = 0;
Item item = __instance.inventory.actualInventory[currentInventorySlot];
ClickableComponent c = __instance.inventory.inventory[currentInventorySlot];
string toSpeak;
if (item != null)
{
toSpeak = item.DisplayName;
if ((item as StardewValley.Object) != null)
{
int quality = ((StardewValley.Object)item).Quality;
if (quality == 1)
{
toSpeak = $"Silver quality {toSpeak}";
}
else if (quality == 2 || quality == 3)
{
toSpeak = $"Gold quality {toSpeak}";
}
else if (quality >= 4)
{
toSpeak = $"Iridium quality {toSpeak}";
}
}
toSpeak = $"{item.Stack} {toSpeak}";
if (!__instance.inventory.highlightMethod(__instance.inventory.actualInventory[currentInventorySlot]))
{
toSpeak = $"{toSpeak} not usable here";
}
}
else
{
toSpeak = "Empty Slot";
}
c.snapMouseCursorToCenter();
MainClass.ScreenReader.Say(toSpeak, true);
}
internal static void Cleanup()
{
JunimoNoteMenuPatch.currentIngredientListItem = -1;
JunimoNoteMenuPatch.currentIngredientInputSlot = -1;
JunimoNoteMenuPatch.currentInventorySlot = -1;
JunimoNoteMenuPatch.junimoNoteMenuQuery = "";
}
}
}

View File

@ -1,427 +0,0 @@
using Microsoft.Xna.Framework.Graphics;
using StardewModdingAPI;
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class DialoguePatches
{
internal static string currentLetterText = " ";
internal static string currentDialogue = " ";
internal static bool isDialogueAppearingFirstTime = true;
internal static void DialoguePatch(DialogueBox __instance, SpriteBatch b)
{
try
{
if (__instance.transitioning)
return;
if (__instance.characterDialogue != null)
{
// For Normal Character dialogues
Dialogue dialogue = __instance.characterDialogue;
string speakerName = dialogue.speaker.displayName;
List<Response> responses = __instance.responses;
string toSpeak = " ";
string dialogueText = "";
string response = "";
bool hasResponses = dialogue.isCurrentDialogueAQuestion();
dialogueText = $"{speakerName} said {__instance.getCurrentString()}";
if (hasResponses)
{
if (__instance.selectedResponse >= 0 && __instance.selectedResponse < responses.Count)
response = $"{__instance.selectedResponse + 1}: {responses[__instance.selectedResponse].responseText}";
else
// When the dialogue is not finished writing then the selectedResponse is <0 and this results
// in the first response not being detcted, so this sets the first response option to be the default
// if the current dialogue is a question or has responses
response = $"1: {responses[0].responseText}";
}
if (hasResponses)
{
if (currentDialogue != response)
{
currentDialogue = response;
if (isDialogueAppearingFirstTime)
{
toSpeak = $"{dialogueText} \n\t {response}";
isDialogueAppearingFirstTime = false;
}
else
toSpeak = response;
MainClass.ScreenReader.Say(toSpeak, true);
}
}
else
{
if (currentDialogue != dialogueText)
{
currentDialogue = dialogueText;
MainClass.ScreenReader.Say(dialogueText, true);
}
}
}
else if (__instance.isQuestion)
{
// For Dialogues with responses/answers like the dialogue when we click on tv
string toSpeak = "";
string dialogueText = "";
string response = "";
bool hasResponses = false;
if (__instance.responses.Count > 0)
hasResponses = true;
dialogueText = __instance.getCurrentString();
if (hasResponses)
if (__instance.selectedResponse >= 0 && __instance.selectedResponse < __instance.responses.Count)
response = $"{__instance.selectedResponse + 1}: {__instance.responses[__instance.selectedResponse].responseText}";
else
// When the dialogue is not finished writing then the selectedResponse is <0 and this results
// in the first response not being detcted, so this sets the first response option to be the default
// if the current dialogue is a question or has responses
response = $"1: {__instance.responses[0].responseText}";
if (hasResponses)
{
if (currentDialogue != response)
{
currentDialogue = response;
if (isDialogueAppearingFirstTime)
{
toSpeak = $"{dialogueText} \n\t {response}";
isDialogueAppearingFirstTime = false;
}
else
toSpeak = response;
MainClass.ScreenReader.Say(toSpeak, true);
}
}
else
{
if (currentDialogue != dialogueText)
{
currentDialogue = dialogueText;
MainClass.ScreenReader.Say(dialogueText, true);
}
}
}
else if (Game1.activeClickableMenu is DialogueBox)
{
// Basic dialogues like `No mails in the mail box`
if (currentDialogue != __instance.getCurrentString())
{
currentDialogue = __instance.getCurrentString();
MainClass.ScreenReader.Say(__instance.getCurrentString(), true);
}
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate dialog:\n{e.StackTrace}\n{e.Message}");
}
}
internal static void ClearDialogueString()
{
// CLears the currentDialogue string on closing dialog
currentDialogue = " ";
isDialogueAppearingFirstTime = true;
}
internal static void HoverTextPatch(string? text, int moneyAmountToDisplayAtBottom = -1, string? boldTitleText = null, int extraItemToShowIndex = -1, int extraItemToShowAmount = -1, string[]? buffIconsToDisplay = null, Item? hoveredItem = null, CraftingRecipe? craftingIngredients = null)
{
try
{
#region Skip narrating hover text for certain menus
if (Game1.activeClickableMenu is TitleMenu && !(((TitleMenu)Game1.activeClickableMenu).GetChildMenu() is CharacterCustomization))
return;
else if (Game1.activeClickableMenu is LetterViewerMenu || Game1.activeClickableMenu is QuestLog)
return;
else if (Game1.activeClickableMenu is Billboard)
return;
else if (Game1.activeClickableMenu is GeodeMenu)
return;
else if (Game1.activeClickableMenu is GameMenu && ((GameMenu)Game1.activeClickableMenu).GetCurrentPage() is InventoryPage)
return;
else if (Game1.activeClickableMenu is GameMenu && ((GameMenu)Game1.activeClickableMenu).GetCurrentPage() is CraftingPage)
return;
else if (Game1.activeClickableMenu is GameMenu && ((GameMenu)Game1.activeClickableMenu).GetCurrentPage() is OptionsPage)
return;
else if (Game1.activeClickableMenu is GameMenu && ((GameMenu)Game1.activeClickableMenu).GetCurrentPage() is ExitPage)
return;
else if (Game1.activeClickableMenu is GameMenu && ((GameMenu)Game1.activeClickableMenu).GetCurrentPage() is SocialPage)
return;
else if (Game1.activeClickableMenu is ItemGrabMenu)
return;
else if (Game1.activeClickableMenu is ShopMenu)
return;
else if (Game1.activeClickableMenu is ConfirmationDialog)
return;
else if (Game1.activeClickableMenu is JunimoNoteMenu)
return;
else if (Game1.activeClickableMenu is CarpenterMenu)
return;
else if (Game1.activeClickableMenu is PurchaseAnimalsMenu)
return;
else if (Game1.activeClickableMenu is CraftingPage)
return;
else if (Game1.activeClickableMenu is AnimalQueryMenu)
return;
else if (Game1.activeClickableMenu is ConfirmationDialog)
return;
else if (Game1.activeClickableMenu is ReadyCheckDialog)
return;
else if (Game1.activeClickableMenu is JojaCDMenu)
return;
else if (Game1.activeClickableMenu is TailoringMenu)
return;
else if (Game1.activeClickableMenu is PondQueryMenu)
return;
else if (Game1.activeClickableMenu is ForgeMenu)
return;
else if (Game1.activeClickableMenu is ItemListMenu)
return;
else if (Game1.activeClickableMenu is FieldOfficeMenu)
return;
else if (Game1.activeClickableMenu is MuseumMenu)
return;
#endregion
string toSpeak = " ";
#region Add item count before title
if (hoveredItem != null && hoveredItem.HasBeenInInventory)
{
int count = hoveredItem.Stack;
if (count > 1)
toSpeak = $"{toSpeak} {count} ";
}
#endregion
#region Add title if any
if (boldTitleText != null)
toSpeak = $"{toSpeak} {boldTitleText}\n";
#endregion
#region Add quality of item
if (hoveredItem is StardewValley.Object && ((StardewValley.Object)hoveredItem).Quality > 0)
{
int quality = ((StardewValley.Object)hoveredItem).Quality;
if (quality == 1)
{
toSpeak = $"{toSpeak} Silver quality";
}
else if (quality == 2 || quality == 3)
{
toSpeak = $"{toSpeak} Gold quality";
}
else if (quality >= 4)
{
toSpeak = $"{toSpeak} Iridium quality";
}
}
#endregion
#region Narrate hovered required ingredients
if (extraItemToShowIndex != -1)
{
string itemName = Game1.objectInformation[extraItemToShowIndex].Split('/')[0];
if (extraItemToShowAmount != -1)
toSpeak = $"{toSpeak} Required: {extraItemToShowAmount} {itemName}";
else
toSpeak = $"{toSpeak} Required: {itemName}";
}
#endregion
#region Add money
if (moneyAmountToDisplayAtBottom != -1)
toSpeak = $"{toSpeak} \nCost: {moneyAmountToDisplayAtBottom}g\n";
#endregion
#region Add the base text
if (text == "???")
toSpeak = "unknown";
else
toSpeak = $"{toSpeak} {text}";
#endregion
#region Add crafting ingredients
if (craftingIngredients != null)
{
toSpeak = $"{toSpeak} \n{craftingIngredients.description}";
toSpeak = $"{toSpeak} \nIngredients\n";
craftingIngredients.recipeList.ToList().ForEach(recipe =>
{
int count = recipe.Value;
int item = recipe.Key;
string name = craftingIngredients.getNameFromIndex(item);
toSpeak = $"{toSpeak} ,{count} {name}";
});
}
#endregion
#region Add health & stamina
if (hoveredItem is StardewValley.Object && ((StardewValley.Object)hoveredItem).Edibility != -300)
{
int stamina_recovery = ((StardewValley.Object)hoveredItem).staminaRecoveredOnConsumption();
toSpeak = $"{toSpeak} {stamina_recovery} Energy\n";
if (stamina_recovery >= 0)
{
int health_recovery = ((StardewValley.Object)hoveredItem).healthRecoveredOnConsumption();
toSpeak = $"{toSpeak} {health_recovery} Health";
}
}
#endregion
#region Add buff items (effects like +1 walking speed)
if (buffIconsToDisplay != null)
{
for (int i = 0; i < buffIconsToDisplay.Length; i++)
{
string buffName = ((Convert.ToInt32(buffIconsToDisplay[i]) > 0) ? "+" : "") + buffIconsToDisplay[i] + " ";
if (i <= 11)
{
buffName = Game1.content.LoadString("Strings\\UI:ItemHover_Buff" + i, buffName);
}
try
{
int count = int.Parse(buffName.Substring(0, buffName.IndexOf(' ')));
if (count != 0)
toSpeak = $"{toSpeak} {buffName}\n";
}
catch (Exception) { }
}
}
#endregion
#region Narrate toSpeak
// To prevent it from getting conflicted by two hover texts at the same time, two seperate methods are used.
// For example, sometimes `Welcome to Pierre's` and the items in seeds shop get conflicted causing it to speak infinitely.
if (toSpeak.ToString() != " ")
{
if (Context.IsPlayerFree)
MainClass.ScreenReader.SayWithChecker(toSpeak.ToString(), true); // Normal Checker
else
MainClass.ScreenReader.SayWithMenuChecker(toSpeak.ToString(), true); // Menu Checker
}
#endregion
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate dialog:\n{e.StackTrace}\n{e.Message}");
}
}
internal static void LetterViewerMenuPatch(LetterViewerMenu __instance)
{
try
{
if (!__instance.IsActive())
return;
NarrateLetterContent(__instance);
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void NarrateLetterContent(LetterViewerMenu __instance)
{
int x = Game1.getMousePosition().X, y = Game1.getMousePosition().Y;
#region Texts in the letter
string message = __instance.mailMessage[__instance.page];
string toSpeak = $"{message}";
if (__instance.ShouldShowInteractable())
{
if (__instance.moneyIncluded > 0)
{
string moneyText = Game1.content.LoadString("Strings\\UI:LetterViewer_MoneyIncluded", __instance.moneyIncluded);
toSpeak += $"\t\n\t ,Included money: {moneyText}";
}
else if (__instance.learnedRecipe != null && __instance.learnedRecipe.Length > 0)
{
string recipeText = Game1.content.LoadString("Strings\\UI:LetterViewer_LearnedRecipe", __instance.cookingOrCrafting);
toSpeak += $"\t\n\t ,Learned Recipe: {recipeText}";
}
}
if (currentLetterText != toSpeak)
{
currentLetterText = toSpeak;
// snap mouse to accept quest button
if (__instance.acceptQuestButton != null && __instance.questID != -1)
{
toSpeak += "\t\n Left click to accept quest.";
__instance.acceptQuestButton.snapMouseCursorToCenter();
}
if (__instance.mailMessage.Count > 1)
toSpeak = $"Page {__instance.page + 1} of {__instance.mailMessage.Count}:\n\t{toSpeak}";
MainClass.ScreenReader.Say(toSpeak, true);
}
#endregion
#region Narrate items given in the mail
if (__instance.ShouldShowInteractable())
{
foreach (ClickableComponent c in __instance.itemsToGrab)
{
if (c.item == null)
continue;
string name = c.item.DisplayName;
if (c.containsPoint(x, y))
MainClass.ScreenReader.SayWithChecker($"Left click to collect {name}", false);
}
}
#endregion
#region Narrate buttons
if (__instance.backButton != null && __instance.backButton.visible && __instance.backButton.containsPoint(x, y))
MainClass.ScreenReader.SayWithChecker($"Previous page button", false);
if (__instance.forwardButton != null && __instance.forwardButton.visible && __instance.forwardButton.containsPoint(x, y))
MainClass.ScreenReader.SayWithChecker($"Next page button", false);
#endregion
}
internal static void drawAboveAlwaysFrontLayerPatch(NPC __instance, string ___textAboveHead, int ___textAboveHeadTimer)
{
try
{
if (___textAboveHeadTimer > 2900 && ___textAboveHead != null)
{
MainClass.ScreenReader.SayWithChecker($"{__instance.displayName} says {___textAboveHead}", true);
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Error in patch:NPCShowTextAboveHeadPatch \n{e.Message}\n{e.StackTrace}");
}
}
}
}

View File

@ -1,417 +0,0 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using StardewValley;
using StardewValley.Locations;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class DonationMenuPatches
{
internal static string museumQueryKey = " ";
internal static string fieldOfficeMenuQuery = " ";
private static bool isMoving = false;
private static (int x, int y)[] donationTiles =
{
(26,5),(26,6),(26,7),(26,8),(26,9),(26,10),(26,11),
(29,5),(30,5),(31,5),(32,5),(33,5),(34,5),(35,5),(36,5),
(28,6),(29,6),(30,6),(31,6),(32,6),(33,6),(34,6),(35,6),(36,6),(37,6),
(28,9),(29,9),(30,9),(31,9),(32,9),(33,9),(34,9),(35,9),(36,9),
(28,10),(29,10),(30,10),(31,10),(32,10),(33,10),(34,10),(35,10),(36,10),
(30,13),(31,13),(32,13),(33,13),(34,13),
(30,14),(31,14),(32,14),(33,14),(34,14),
(28,15),(29,15),(30,15),(31,15),(32,15),(33,15),(34,15),(35,15),(36,15),
(28,16),(29,16),(30,16),(31,16),(32,16),(33,16),(34,16),(35,16),(36,16),
(39,6),(40,6),(41,6),(42,6),(43,6),(44,6),(45,6),(46,6),
(39,7),(40,7),(41,7),(42,7),(43,7),(44,7),(45,7),(46,7),
(48,5),(48,6),(48,7),
(42,15),(43,15),(44,15),(45,15),(46,15),(47,15),
(42,16),(43,16),(44,16),(45,16),(46,16),(47,16),
};
#region Museum
internal static bool MuseumMenuKeyPressPatch()
{
try
{
if (isMoving)
return false;
if (!isMoving)
{
isMoving = true;
Task.Delay(200).ContinueWith(_ => { isMoving = false; });
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
return true;
}
internal static void MuseumMenuPatch(MuseumMenu __instance, bool ___holdingMuseumPiece)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
if (__instance.heldItem != null)
{
// Museum Inventory
string toSpeak = " ";
int tileX = (int)(Utility.ModifyCoordinateFromUIScale(x) + (float)Game1.viewport.X) / 64;
int tileY = (int)(Utility.ModifyCoordinateFromUIScale(y) + (float)Game1.viewport.Y) / 64;
LibraryMuseum libraryMuseum = (LibraryMuseum)Game1.currentLocation;
if (libraryMuseum.isTileSuitableForMuseumPiece(tileX, tileY))
toSpeak = $"slot {tileX}x {tileY}y";
if (museumQueryKey != toSpeak)
{
museumQueryKey = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
}
else
{
// Player Inventory
int i = narrateHoveredItemInInventory(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y);
if (i != -9999)
{
bool isPrimaryInfoKeyPressed = MainClass.Config.PrimaryInfoKey.JustPressed(); // For donating hovered item
if (isPrimaryInfoKeyPressed && __instance.inventory.actualInventory[i] != null)
{
foreach (var tile in donationTiles)
{
#region Manually donates the hovered item (https://github.com/veywrn/StardewValley/blob/3ff171b6e9e6839555d7881a391b624ccd820a83/StardewValley/Menus/MuseumMenu.cs#L206-L247)
int tileX = tile.x;
int tileY = tile.y;
if (((LibraryMuseum)Game1.currentLocation).isTileSuitableForMuseumPiece(tileX, tileY) && ((LibraryMuseum)Game1.currentLocation).isItemSuitableForDonation(__instance.inventory.actualInventory[i]))
{
int objectID = __instance.inventory.actualInventory[i].ParentSheetIndex;
int rewardsCount = ((LibraryMuseum)Game1.currentLocation).getRewardsForPlayer(Game1.player).Count;
((LibraryMuseum)Game1.currentLocation).museumPieces.Add(new Vector2(tileX, tileY), ((StardewValley.Object)__instance.inventory.actualInventory[i]).ParentSheetIndex);
Game1.playSound("stoneStep");
if (((LibraryMuseum)Game1.currentLocation).getRewardsForPlayer(Game1.player).Count > rewardsCount)
{
Game1.playSound("reward");
}
else
{
Game1.playSound("newArtifact");
}
Game1.player.completeQuest(24);
__instance.inventory.actualInventory[i].Stack--;
if (__instance.inventory.actualInventory[i].Stack <= 0)
{
__instance.inventory.actualInventory[i] = null;
}
int pieces = ((LibraryMuseum)Game1.currentLocation).museumPieces.Count();
Game1.stats.checkForArchaeologyAchievements();
switch (pieces)
{
case 95:
globalChatInfoMessage("MuseumComplete", Game1.player.farmName.Value);
break;
case 40:
globalChatInfoMessage("Museum40", Game1.player.farmName.Value);
break;
default:
globalChatInfoMessage("donation", Game1.player.Name, "object:" + objectID);
break;
}
break;
}
#endregion
}
}
}
else if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
{
if (museumQueryKey != $"ok button")
{
museumQueryKey = $"ok button";
MainClass.ScreenReader.Say("ok button", true);
}
}
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
// Returns the index of the hovered item or -9999
internal static int narrateHoveredItemInInventory(InventoryMenu inventoryMenu, List<ClickableComponent> inventory, IList<Item> actualInventory, int x, int y)
{
#region Narrate hovered item
for (int i = 0; i < inventory.Count; i++)
{
if (inventory[i].containsPoint(x, y))
{
string toSpeak = "";
if ((i + 1) <= actualInventory.Count)
{
if (actualInventory[i] != null)
{
string name = actualInventory[i].DisplayName;
int stack = actualInventory[i].Stack;
string quality = "";
#region Add quality of item
if (actualInventory[i] is StardewValley.Object && ((StardewValley.Object)actualInventory[i]).Quality > 0)
{
int qualityIndex = ((StardewValley.Object)actualInventory[i]).Quality;
if (qualityIndex == 1)
{
quality = "Silver quality";
}
else if (qualityIndex == 2 || qualityIndex == 3)
{
quality = "Gold quality";
}
else if (qualityIndex >= 4)
{
quality = "Iridium quality";
}
}
#endregion
if (inventoryMenu.highlightMethod(inventoryMenu.actualInventory[i]))
name = $"Donatable {name}";
if (stack > 1)
toSpeak = $"{stack} {name} {quality}";
else
toSpeak = $"{name} {quality}";
}
else
{
// For empty slot
toSpeak = "Empty Slot";
}
}
else
{
// For empty slot
toSpeak = "Empty Slot";
}
if (museumQueryKey != $"{toSpeak}:{i}")
{
museumQueryKey = $"{toSpeak}:{i}";
MainClass.ScreenReader.Say(toSpeak, true);
}
return i;
}
}
#endregion
return -9999;
}
#region These methods are taken from the game's source code, https://github.com/veywrn/StardewValley/blob/3ff171b6e9e6839555d7881a391b624ccd820a83/StardewValley/Multiplayer.cs#L1331-L1395
internal static void globalChatInfoMessage(string messageKey, params string[] args)
{
if (Game1.IsMultiplayer || Game1.multiplayerMode != 0)
{
receiveChatInfoMessage(Game1.player, messageKey, args);
sendChatInfoMessage(messageKey, args);
}
}
internal static void sendChatInfoMessage(string messageKey, params string[] args)
{
if (Game1.IsClient)
{
Game1.client.sendMessage(15, messageKey, args);
}
else if (Game1.IsServer)
{
foreach (long id in Game1.otherFarmers.Keys)
{
Game1.server.sendMessage(id, 15, Game1.player, messageKey, args);
}
}
}
internal static void receiveChatInfoMessage(Farmer sourceFarmer, string messageKey, string[] args)
{
if (Game1.chatBox != null)
{
try
{
string[] processedArgs = args.Select(delegate (string arg)
{
if (arg.StartsWith("achievement:"))
{
int key = Convert.ToInt32(arg.Substring("achievement:".Length));
return Game1.content.Load<Dictionary<int, string>>("Data\\Achievements")[key].Split('^')[0];
}
return arg.StartsWith("object:") ? new StardewValley.Object(Convert.ToInt32(arg.Substring("object:".Length)), 1).DisplayName : arg;
}).ToArray();
ChatBox chatBox = Game1.chatBox;
LocalizedContentManager content = Game1.content;
string path = "Strings\\UI:Chat_" + messageKey;
object[] substitutions = processedArgs;
chatBox.addInfoMessage(content.LoadString(path, substitutions));
}
catch (ContentLoadException)
{
}
catch (FormatException)
{
}
catch (OverflowException)
{
}
catch (KeyNotFoundException)
{
}
}
}
#endregion
#endregion
#region Field Office
internal static void FieldOfficeMenuPatch(FieldOfficeMenu __instance)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
string toSpeak = " ";
if (__instance.trashCan != null && __instance.trashCan.containsPoint(x, y))
{
toSpeak = "Trashcan";
}
else if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
{
toSpeak = "ok button";
}
else if (__instance.dropItemInvisibleButton != null && __instance.dropItemInvisibleButton.containsPoint(x, y))
{
toSpeak = "drop item";
}
else
{
for (int i = 0; i < __instance.inventory.inventory.Count; i++)
{
if (!__instance.inventory.inventory[i].containsPoint(x, y))
continue;
if (__instance.inventory.actualInventory[i] == null)
toSpeak = "Empty slot";
else
{
toSpeak = $"{__instance.inventory.actualInventory[i].Stack} {__instance.inventory.actualInventory[i].DisplayName}";
if (!__instance.inventory.highlightMethod(__instance.inventory.actualInventory[i]))
{
toSpeak = $"{toSpeak} not usable here";
}
}
if (fieldOfficeMenuQuery != $"{toSpeak}:{i}")
{
fieldOfficeMenuQuery = $"{toSpeak}:{i}";
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
for (int i = 0; i < __instance.pieceHolders.Count; i++)
{
if (!__instance.pieceHolders[i].containsPoint(x, y))
continue;
if (__instance.pieceHolders[i].item == null)
toSpeak = i switch
{
0 => "Center skeleton slot",
1 => "Center skeleton slot",
2 => "Center skeleton slot",
3 => "Center skeleton slot",
4 => "Center skeleton slot",
5 => "Center skeleton slot",
6 => "Snake slot",
7 => "Snake slot",
8 => "Snake slot",
9 => "Bat slot",
10 => "Frog slot",
_ => "Donation slot"
};
else
toSpeak = $"Slot {i + 1} finished: {__instance.pieceHolders[i].item.DisplayName}";
if (__instance.heldItem != null && __instance.pieceHolders[i].item == null)
{
int highlight = getPieceIndexForDonationItem(__instance.heldItem.ParentSheetIndex);
if (highlight != -1 && highlight == i)
toSpeak += "Donatable ";
}
if (fieldOfficeMenuQuery != $"{toSpeak}:{i}")
{
fieldOfficeMenuQuery = $"{toSpeak}:{i}";
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
}
if (fieldOfficeMenuQuery != toSpeak)
{
fieldOfficeMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
if (__instance.dropItemInvisibleButton != null && __instance.dropItemInvisibleButton.containsPoint(x, y))
Game1.playSound("drop_item");
}
}
catch (System.Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static int getPieceIndexForDonationItem(int itemIndex)
{
switch (itemIndex)
{
case 820:
return 5;
case 821:
return 4;
case 822:
return 3;
case 823:
return 0;
case 824:
return 1;
case 825:
return 8;
case 826:
return 7;
case 827:
return 9;
case 828:
return 10;
default:
return -1;
}
}
#endregion
}
}

View File

@ -0,0 +1,123 @@
using StardewValley;
using stardew_access.Features;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class FieldOfficeMenuPatch
{
private static string fieldOfficeMenuQuery = "";
internal static void DrawPatch(FieldOfficeMenu __instance)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
string toSpeak = " ";
if (__instance.trashCan != null && __instance.trashCan.containsPoint(x, y))
{
toSpeak = "Trashcan";
}
else if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
{
toSpeak = "ok button";
}
else if (__instance.dropItemInvisibleButton != null && __instance.dropItemInvisibleButton.containsPoint(x, y))
{
toSpeak = "drop item";
}
else
{
if (InventoryUtils.narrateHoveredSlot(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y))
return;
for (int i = 0; i < __instance.pieceHolders.Count; i++)
{
if (!__instance.pieceHolders[i].containsPoint(x, y))
continue;
if (__instance.pieceHolders[i].item == null)
toSpeak = i switch
{
0 => "Center skeleton slot",
1 => "Center skeleton slot",
2 => "Center skeleton slot",
3 => "Center skeleton slot",
4 => "Center skeleton slot",
5 => "Center skeleton slot",
6 => "Snake slot",
7 => "Snake slot",
8 => "Snake slot",
9 => "Bat slot",
10 => "Frog slot",
_ => "Donation slot"
};
else
toSpeak = $"Slot {i + 1} finished: {__instance.pieceHolders[i].item.DisplayName}";
if (!MainClass.Config.DisableInventoryVerbosity && __instance.heldItem != null && __instance.pieceHolders[i].item == null)
{
int highlight = getPieceIndexForDonationItem(__instance.heldItem.ParentSheetIndex);
if (highlight != -1 && highlight == i)
toSpeak += "Donatable ";
}
if (fieldOfficeMenuQuery != $"{toSpeak}:{i}")
{
fieldOfficeMenuQuery = $"{toSpeak}:{i}";
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
}
if (fieldOfficeMenuQuery != toSpeak)
{
fieldOfficeMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
if (__instance.dropItemInvisibleButton != null && __instance.dropItemInvisibleButton.containsPoint(x, y))
Game1.playSound("drop_item");
}
}
catch (System.Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static int getPieceIndexForDonationItem(int itemIndex)
{
switch (itemIndex)
{
case 820:
return 5;
case 821:
return 4;
case 822:
return 3;
case 823:
return 0;
case 824:
return 1;
case 825:
return 8;
case 826:
return 7;
case 827:
return 9;
case 828:
return 10;
default:
return -1;
}
}
internal static void Cleanup()
{
fieldOfficeMenuQuery = "";
}
}
}

View File

@ -0,0 +1,251 @@
using Microsoft.Xna.Framework;
using StardewValley;
using stardew_access.Features;
using StardewValley.Locations;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class MuseumMenuPatch
{
private static string museumQueryKey = "";
private static bool isMoving = false;
private static (int x, int y)[] donationTiles =
{
(26,5),(26,6),(26,7),(26,8),(26,9),(26,10),(26,11),
(29,5),(30,5),(31,5),(32,5),(33,5),(34,5),(35,5),(36,5),
(28,6),(29,6),(30,6),(31,6),(32,6),(33,6),(34,6),(35,6),(36,6),(37,6),
(28,9),(29,9),(30,9),(31,9),(32,9),(33,9),(34,9),(35,9),(36,9),
(28,10),(29,10),(30,10),(31,10),(32,10),(33,10),(34,10),(35,10),(36,10),
(30,13),(31,13),(32,13),(33,13),(34,13),
(30,14),(31,14),(32,14),(33,14),(34,14),
(28,15),(29,15),(30,15),(31,15),(32,15),(33,15),(34,15),(35,15),(36,15),
(28,16),(29,16),(30,16),(31,16),(32,16),(33,16),(34,16),(35,16),(36,16),
(39,6),(40,6),(41,6),(42,6),(43,6),(44,6),(45,6),(46,6),
(39,7),(40,7),(41,7),(42,7),(43,7),(44,7),(45,7),(46,7),
(48,5),(48,6),(48,7),
(42,15),(43,15),(44,15),(45,15),(46,15),(47,15),
(42,16),(43,16),(44,16),(45,16),(46,16),(47,16),
};
internal static bool RecieveKeyPressPatch()
{
try
{
if (isMoving)
return false;
if (!isMoving)
{
isMoving = true;
Task.Delay(200).ContinueWith(_ => { isMoving = false; });
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
return true;
}
internal static void DrawPatch(MuseumMenu __instance, bool ___holdingMuseumPiece)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
narrateMuseumInventory(__instance, x, y);
narratePlayerInventory(__instance, x, y);
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
private static void narrateMuseumInventory(MuseumMenu __instance, int x, int y)
{
if (__instance.heldItem == null) return;
string toSpeak = "";
int tileX = (int)(Utility.ModifyCoordinateFromUIScale(x) + (float)Game1.viewport.X) / 64;
int tileY = (int)(Utility.ModifyCoordinateFromUIScale(y) + (float)Game1.viewport.Y) / 64;
LibraryMuseum libraryMuseum = (LibraryMuseum)Game1.currentLocation;
if (libraryMuseum.isTileSuitableForMuseumPiece(tileX, tileY))
toSpeak = $"slot {tileX}x {tileY}y";
if (museumQueryKey != toSpeak)
{
museumQueryKey = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
}
private static void narratePlayerInventory(MuseumMenu __instance, int x, int y)
{
if (__instance.heldItem != null) return;
if (narrateHoveredButtons(__instance, x, y)) return;
int hoveredItemIndex = InventoryUtils.narrateHoveredSlotAndReturnIndex(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y,
handleHighlightedItem: true, highlightedItemPrefix: "Donatable ");
if (hoveredItemIndex != -9999)
{
bool isPrimaryInfoKeyPressed = MainClass.Config.PrimaryInfoKey.JustPressed(); // For donating hovered item
if (isPrimaryInfoKeyPressed && hoveredItemIndex >= 0 && hoveredItemIndex < __instance.inventory.actualInventory.Count && __instance.inventory.actualInventory[hoveredItemIndex] != null)
{
manuallyDonateItem(__instance, hoveredItemIndex);
}
}
}
private static bool narrateHoveredButtons(MuseumMenu __instance, int x, int y)
{
string toSpeak = "";
bool isDropItemButton = false;
if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
{
toSpeak = "Ok button";
}
else if (__instance.dropItemInvisibleButton != null && __instance.dropItemInvisibleButton.containsPoint(x, y))
{
toSpeak = "Drop Item";
isDropItemButton = true;
}
else
{
return false;
}
if (museumQueryKey != toSpeak)
{
museumQueryKey = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
if (isDropItemButton) Game1.playSound("drop_item");
}
return true;
}
private static void manuallyDonateItem(MuseumMenu __instance, int i)
{
foreach (var tile in donationTiles)
{
#region Manually donates the hovered item (https://github.com/veywrn/StardewValley/blob/3ff171b6e9e6839555d7881a391b624ccd820a83/StardewValley/Menus/MuseumMenu.cs#L206-L247)
int tileX = tile.x;
int tileY = tile.y;
if (((LibraryMuseum)Game1.currentLocation).isTileSuitableForMuseumPiece(tileX, tileY) && ((LibraryMuseum)Game1.currentLocation).isItemSuitableForDonation(__instance.inventory.actualInventory[i]))
{
int objectID = __instance.inventory.actualInventory[i].ParentSheetIndex;
int rewardsCount = ((LibraryMuseum)Game1.currentLocation).getRewardsForPlayer(Game1.player).Count;
((LibraryMuseum)Game1.currentLocation).museumPieces.Add(new Vector2(tileX, tileY), ((StardewValley.Object)__instance.inventory.actualInventory[i]).ParentSheetIndex);
Game1.playSound("stoneStep");
if (((LibraryMuseum)Game1.currentLocation).getRewardsForPlayer(Game1.player).Count > rewardsCount)
{
Game1.playSound("reward");
}
else
{
Game1.playSound("newArtifact");
}
Game1.player.completeQuest(24);
__instance.inventory.actualInventory[i].Stack--;
if (__instance.inventory.actualInventory[i].Stack <= 0)
{
__instance.inventory.actualInventory[i] = null;
}
int pieces = ((LibraryMuseum)Game1.currentLocation).museumPieces.Count();
Game1.stats.checkForArchaeologyAchievements();
switch (pieces)
{
case 95:
globalChatInfoMessage("MuseumComplete", Game1.player.farmName.Value);
break;
case 40:
globalChatInfoMessage("Museum40", Game1.player.farmName.Value);
break;
default:
globalChatInfoMessage("donation", Game1.player.Name, "object:" + objectID);
break;
}
break;
}
#endregion
}
}
#region These methods are taken from the game's source code, https://github.com/veywrn/StardewValley/blob/3ff171b6e9e6839555d7881a391b624ccd820a83/StardewValley/Multiplayer.cs#L1331-L1395
internal static void globalChatInfoMessage(string messageKey, params string[] args)
{
if (Game1.IsMultiplayer || Game1.multiplayerMode != 0)
{
receiveChatInfoMessage(Game1.player, messageKey, args);
sendChatInfoMessage(messageKey, args);
}
}
internal static void sendChatInfoMessage(string messageKey, params string[] args)
{
if (Game1.IsClient)
{
Game1.client.sendMessage(15, messageKey, args);
}
else if (Game1.IsServer)
{
foreach (long id in Game1.otherFarmers.Keys)
{
Game1.server.sendMessage(id, 15, Game1.player, messageKey, args);
}
}
}
internal static void receiveChatInfoMessage(Farmer sourceFarmer, string messageKey, string[] args)
{
if (Game1.chatBox != null)
{
try
{
string[] processedArgs = args.Select(delegate (string arg)
{
if (arg.StartsWith("achievement:"))
{
int key = Convert.ToInt32(arg.Substring("achievement:".Length));
return Game1.content.Load<Dictionary<int, string>>("Data\\Achievements")[key].Split('^')[0];
}
return arg.StartsWith("object:") ? new StardewValley.Object(Convert.ToInt32(arg.Substring("object:".Length)), 1).DisplayName : arg;
}).ToArray();
ChatBox chatBox = Game1.chatBox;
LocalizedContentManager content = Game1.content;
string path = "Strings\\UI:Chat_" + messageKey;
object[] substitutions = processedArgs;
chatBox.addInfoMessage(content.LoadString(path, substitutions));
}
catch (Microsoft.Xna.Framework.Content.ContentLoadException)
{
}
catch (FormatException)
{
}
catch (OverflowException)
{
}
catch (KeyNotFoundException)
{
}
}
}
#endregion
internal static void Cleanup()
{
museumQueryKey = "";
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,21 @@
namespace stardew_access.Patches
{
internal class CollectionsPagePatch
{
internal static void DrawPatch(StardewValley.Menus.CollectionsPage __instance)
{
try
{
int x = StardewValley.Game1.getMousePosition().X, y = StardewValley.Game1.getMousePosition().Y;
if (__instance.letterviewerSubMenu != null)
{
LetterViwerMenuPatch.narrateLetterContent(__instance.letterviewerSubMenu);
}
}
catch (System.Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
}
}

View File

@ -0,0 +1,251 @@
using StardewValley;
using stardew_access.Features;
using StardewValley.Menus;
using StardewValley.Objects;
namespace stardew_access.Patches
{
internal class CraftingPagePatch
{
internal static string hoveredItemQueryKey = "";
internal static string craftingPageQueryKey = "";
internal static int currentSelectedCraftingRecipe = -1;
internal static bool isSelectingRecipe = false;
internal static void DrawPatch(CraftingPage __instance, CraftingRecipe ___hoverRecipe, int ___currentCraftingPage)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
handleKeyBinds(__instance, ___currentCraftingPage);
if (narrateMenuButtons(__instance, x, y))
{
return;
}
if (narrateHoveredRecipe(__instance, ___currentCraftingPage, ___hoverRecipe, x, y))
{
return;
}
if (InventoryUtils.narrateHoveredSlot(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y))
{
craftingPageQueryKey = "";
return;
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
private static void handleKeyBinds(CraftingPage __instance, int ___currentCraftingPage)
{
if (MainClass.Config.SnapToFirstSecondaryInventorySlotKey.JustPressed() && __instance.pagesOfCraftingRecipes[___currentCraftingPage].Count > 0)
{
// snap to first crafting recipe
__instance.setCurrentlySnappedComponentTo(__instance.pagesOfCraftingRecipes[___currentCraftingPage].ElementAt(0).Key.myID);
__instance.pagesOfCraftingRecipes[___currentCraftingPage].ElementAt(0).Key.snapMouseCursorToCenter();
currentSelectedCraftingRecipe = 0;
}
else if (MainClass.Config.SnapToFirstInventorySlotKey.JustPressed() && __instance.inventory.inventory.Count > 0)
{
// snap to first inventory slot
__instance.setCurrentlySnappedComponentTo(__instance.inventory.inventory[0].myID);
__instance.inventory.inventory[0].snapMouseCursorToCenter();
currentSelectedCraftingRecipe = -1;
}
else if (MainClass.Config.CraftingMenuCycleThroughRecipiesKey.JustPressed() && !isSelectingRecipe)
{
isSelectingRecipe = true;
CycleThroughRecipies(__instance.pagesOfCraftingRecipes, ___currentCraftingPage, __instance);
Task.Delay(200).ContinueWith(_ => { isSelectingRecipe = false; });
}
}
private static bool narrateMenuButtons(CraftingPage __instance, int x, int y)
{
string? toSpeak = null;
bool isDropItemButton = false;
if (__instance.upButton != null && __instance.upButton.containsPoint(x, y))
{
toSpeak = "Previous Recipe List";
}
else if (__instance.downButton != null && __instance.downButton.containsPoint(x, y))
{
toSpeak = "Next Recipe List";
}
else if (__instance.trashCan != null && __instance.trashCan.containsPoint(x, y))
{
toSpeak = "Trash Can";
}
else if (__instance.dropItemInvisibleButton != null && __instance.dropItemInvisibleButton.containsPoint(x, y))
{
toSpeak = "Drop Item";
isDropItemButton = true;
}
else
{
return false;
}
if (toSpeak != null && craftingPageQueryKey != toSpeak)
{
craftingPageQueryKey = toSpeak;
hoveredItemQueryKey = "";
MainClass.ScreenReader.Say(toSpeak, true);
if (isDropItemButton) Game1.playSound("drop_item");
}
return true;
}
private static bool narrateHoveredRecipe(CraftingPage __instance, int ___currentCraftingPage, CraftingRecipe ___hoverRecipe, int x, int y)
{
if (___hoverRecipe == null)
{
var isRecipeInFocus = false;
foreach (var item in __instance.pagesOfCraftingRecipes[___currentCraftingPage])
{
if (!item.Key.containsPoint(x, y))
continue;
isRecipeInFocus = true;
break;
}
if (!isRecipeInFocus)
return false;
string query = $"unknown recipe:{__instance.getCurrentlySnappedComponent().myID}";
if (craftingPageQueryKey != query)
{
craftingPageQueryKey = query;
hoveredItemQueryKey = "";
MainClass.ScreenReader.Say("unknown recipe", true);
}
return true;
}
string name = ___hoverRecipe.DisplayName;
int numberOfProduce = ___hoverRecipe.numberProducedPerCraft;
string description = "";
string ingredients = "";
string buffs = "";
string craftable = "";
description = $"Description:\n{___hoverRecipe.description}";
craftable = ___hoverRecipe.doesFarmerHaveIngredientsInInventory(getContainerContents(__instance._materialContainers)) ? "Craftable" : "Not Craftable";
#region Crafting ingredients
ingredients = "Ingredients:\n";
for (int i = 0; i < ___hoverRecipe.recipeList.Count; i++)
{
int recipeCount = ___hoverRecipe.recipeList.ElementAt(i).Value;
int recipeItem = ___hoverRecipe.recipeList.ElementAt(i).Key;
string recipeName = ___hoverRecipe.getNameFromIndex(recipeItem);
ingredients += $" ,{recipeCount} {recipeName}";
}
#endregion
#region Health & stamina and buff items (effects like +1 walking speed)
Item producesItem = ___hoverRecipe.createItem();
if (producesItem is StardewValley.Object producesItemObject)
{
if (producesItemObject.Edibility != -300)
{
int stamina_recovery = producesItemObject.staminaRecoveredOnConsumption();
buffs += $"{stamina_recovery} Energy";
if (stamina_recovery >= 0)
{
int health_recovery = producesItemObject.healthRecoveredOnConsumption();
buffs += $"\n{health_recovery} Health";
}
}
// These variables are taken from the game's code itself (IClickableMenu.cs -> 1016 line)
bool edibleItem = producesItem != null && (int)producesItemObject.Edibility != -300;
string[]? buffIconsToDisplay = (producesItem != null && edibleItem && Game1.objectInformation[producesItemObject.ParentSheetIndex].Split('/').Length > 7)
? producesItem.ModifyItemBuffs(Game1.objectInformation[producesItemObject.ParentSheetIndex].Split('/')[7].Split(' '))
: null;
if (buffIconsToDisplay != null)
{
for (int j = 0; j < buffIconsToDisplay.Length; j++)
{
string buffName = ((Convert.ToInt32(buffIconsToDisplay[j]) > 0) ? "+" : "") + buffIconsToDisplay[j] + " ";
if (j <= 11)
{
buffName = Game1.content.LoadString("Strings\\UI:ItemHover_Buff" + j, buffName);
}
try
{
int count = int.Parse(buffName.Substring(0, buffName.IndexOf(' ')));
if (count != 0)
buffs += $"{buffName}\n";
}
catch (Exception) { }
}
buffs = $"Buffs and boosts:\n {buffs}";
}
}
#endregion
string toSpeak = $"{numberOfProduce} {name}, {craftable}, \n\t{ingredients}, \n\t{description} \n\t{buffs}";
if (craftingPageQueryKey != toSpeak)
{
craftingPageQueryKey = toSpeak;
hoveredItemQueryKey = "";
MainClass.ScreenReader.Say(toSpeak, true);
}
return true;
}
private static void CycleThroughRecipies(List<Dictionary<ClickableTextureComponent, CraftingRecipe>> pagesOfCraftingRecipes, int ___currentCraftingPage, CraftingPage __instance)
{
currentSelectedCraftingRecipe++;
if (currentSelectedCraftingRecipe < 0 || currentSelectedCraftingRecipe >= pagesOfCraftingRecipes[0].Count)
currentSelectedCraftingRecipe = 0;
__instance.setCurrentlySnappedComponentTo(pagesOfCraftingRecipes[___currentCraftingPage].ElementAt(currentSelectedCraftingRecipe).Key.myID);
pagesOfCraftingRecipes[___currentCraftingPage].ElementAt(currentSelectedCraftingRecipe).Key.snapMouseCursorToCenter();
// Skip if recipe is not unlocked/unknown
if (pagesOfCraftingRecipes[___currentCraftingPage].ElementAt(currentSelectedCraftingRecipe).Key.hoverText.Equals("ghosted"))
CycleThroughRecipies(pagesOfCraftingRecipes, ___currentCraftingPage, __instance);
}
// This method is used to get the inventory items to check if the player has enough ingredients for a recipe
// Taken from CraftingPage.cs -> 169 line
internal static IList<Item>? getContainerContents(List<Chest> materialContainers)
{
if (materialContainers == null)
{
return null;
}
List<Item> items = new List<Item>();
for (int i = 0; i < materialContainers.Count; i++)
{
items.AddRange(materialContainers[i].items);
}
return items;
}
internal static void Cleanup()
{
hoveredItemQueryKey = "";
craftingPageQueryKey = "";
currentSelectedCraftingRecipe = -1;
isSelectingRecipe = false;
}
}
}

View File

@ -0,0 +1,48 @@
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class ExitPagePatch
{
internal static string exitPageQueryKey = "";
internal static void DrawPatch(ExitPage __instance)
{
try
{
if (__instance.exitToTitle.visible &&
__instance.exitToTitle.containsPoint(Game1.getMouseX(true), Game1.getMouseY(true)))
{
string toSpeak = "Exit to Title Button";
if (exitPageQueryKey != toSpeak)
{
exitPageQueryKey = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
if (__instance.exitToDesktop.visible &&
__instance.exitToDesktop.containsPoint(Game1.getMouseX(true), Game1.getMouseY(true)))
{
string toSpeak = "Exit to Desktop Button";
if (exitPageQueryKey != toSpeak)
{
exitPageQueryKey = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void Cleanup()
{
exitPageQueryKey = "";
}
}
}

View File

@ -0,0 +1,48 @@
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class GameMenuPatch
{
internal static string gameMenuQueryKey = "";
internal static void DrawPatch(GameMenu __instance)
{
try
{
// Skip if in map page
if (__instance.currentTab == 3)
return;
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
for (int i = 0; i < __instance.tabs.Count; i++)
{
if (!__instance.tabs[i].containsPoint(x, y))
continue;
string toSpeak = $"{GameMenu.getLabelOfTabFromIndex(i)} Tab" + ((i == __instance.currentTab) ? " Active" : "");
if (gameMenuQueryKey != toSpeak)
{
gameMenuQueryKey = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
// If not hovering on any tab button
Cleanup();
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void Cleanup()
{
gameMenuQueryKey = "";
}
}
}

View File

@ -0,0 +1,157 @@
using stardew_access.Features;
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class InventoryPagePatch
{
internal static string inventoryPageQueryKey = "";
internal static string hoveredItemQueryKey = "";
internal static void DrawPatch(InventoryPage __instance)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
handleKeyBinds();
if (narrateHoveredButton(__instance, x, y))
{
return;
}
if (narrateHoveredEquipmentSlot(__instance, x, y))
{
return;
}
if (InventoryUtils.narrateHoveredSlot(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y, true))
{
inventoryPageQueryKey = "";
return;
}
// If no slot or button is hovered
Cleanup();
}
catch (Exception e)
{
MainClass.ErrorLog($"An error occured in InventoryPagePatch()->DrawPatch():\n{e.Message}\n{e.StackTrace}");
}
}
private static void handleKeyBinds()
{
if (!MainClass.Config.MoneyKey.JustPressed())
return;
string farmName = Game1.content.LoadString("Strings\\UI:Inventory_FarmName", Game1.player.farmName.Value);
string currentFunds = Game1.content.LoadString("Strings\\UI:Inventory_CurrentFunds" + (Game1.player.useSeparateWallets ? "_Separate" : ""), Utility.getNumberWithCommas(Game1.player.Money));
string totalEarnings = Game1.content.LoadString("Strings\\UI:Inventory_TotalEarnings" + (Game1.player.useSeparateWallets ? "_Separate" : ""), Utility.getNumberWithCommas((int)Game1.player.totalMoneyEarned));
int festivalScore = Game1.player.festivalScore;
int walnut = Game1.netWorldState.Value.GoldenWalnuts.Value;
int qiGems = Game1.player.QiGems;
int qiCoins = Game1.player.clubCoins;
string toSpeak = $"{farmName}\n{currentFunds}\n{totalEarnings}";
if (festivalScore > 0)
toSpeak = $"{toSpeak}\nFestival Score: {festivalScore}";
if (walnut > 0)
toSpeak = $"{toSpeak}\nGolden Walnut: {walnut}";
if (qiGems > 0)
toSpeak = $"{toSpeak}\nQi Gems: {qiGems}";
if (qiCoins > 0)
toSpeak = $"{toSpeak}\nQi Club Coins: {qiCoins}";
MainClass.ScreenReader.Say(toSpeak, true);
}
private static bool narrateHoveredButton(InventoryPage __instance, int x, int y)
{
string? toSpeak = null;
bool isDropItemButton = false;
if (__instance.inventory.dropItemInvisibleButton != null && __instance.inventory.dropItemInvisibleButton.containsPoint(x, y))
{
toSpeak = "Drop Item";
isDropItemButton = true;
}
else if (__instance.organizeButton != null && __instance.organizeButton.containsPoint(x, y))
{
toSpeak = "Organize Inventory Button";
}
else if (__instance.trashCan != null && __instance.trashCan.containsPoint(x, y))
{
toSpeak = "Trash Can";
}
else if (__instance.organizeButton != null && __instance.organizeButton.containsPoint(x, y))
{
toSpeak = "Organize Button";
}
else if (__instance.junimoNoteIcon != null && __instance.junimoNoteIcon.containsPoint(x, y))
{
toSpeak = "Community Center Button";
}
else
{
return false;
}
if (toSpeak != null && inventoryPageQueryKey != toSpeak)
{
inventoryPageQueryKey = toSpeak;
hoveredItemQueryKey = "";
MainClass.ScreenReader.Say(toSpeak, true);
if (isDropItemButton) Game1.playSound("drop_item");
}
return true;
}
private static bool narrateHoveredEquipmentSlot(InventoryPage __instance, int mouseX, int mouseY)
{
for (int i = 0; i < __instance.equipmentIcons.Count; i++)
{
if (!__instance.equipmentIcons[i].containsPoint(mouseX, mouseY))
continue;
string toSpeak = getNameAndDescriptionOfItem(__instance.equipmentIcons[i].name);
if (inventoryPageQueryKey != toSpeak)
{
inventoryPageQueryKey = toSpeak;
hoveredItemQueryKey = "";
MainClass.ScreenReader.Say(toSpeak, true);
}
return true;
}
return false;
}
private static string getNameAndDescriptionOfItem(string slotName) => slotName switch
{
"Hat" => (Game1.player.hat.Value != null) ? $"{Game1.player.hat.Value.DisplayName}, {Game1.player.hat.Value.getDescription()}" : "Hat slot",
"Left Ring" => (Game1.player.leftRing.Value != null) ? $"{Game1.player.leftRing.Value.DisplayName}, {Game1.player.leftRing.Value.getDescription()}" : "Left Ring slot",
"Right Ring" => (Game1.player.rightRing.Value != null) ? $"{Game1.player.rightRing.Value.DisplayName}, {Game1.player.rightRing.Value.getDescription()}" : "Right ring slot",
"Boots" => (Game1.player.boots.Value != null) ? $"{Game1.player.boots.Value.DisplayName}, {Game1.player.boots.Value.getDescription()}" : "Boots slot",
"Shirt" => (Game1.player.shirtItem.Value != null) ? $"{Game1.player.shirtItem.Value.DisplayName}, {Game1.player.shirtItem.Value.getDescription()}" : "Shirt slot",
"Pants" => (Game1.player.pantsItem.Value != null) ? $"{Game1.player.pantsItem.Value.DisplayName}, {Game1.player.pantsItem.Value.getDescription()}" : "Pants slot",
_ => "unkown slot"
};
internal static void Cleanup()
{
InventoryUtils.Cleanup();
inventoryPageQueryKey = "";
hoveredItemQueryKey = "";
}
}
}

View File

@ -0,0 +1,67 @@
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class OptionsPagePatch
{
internal static string optionsPageQueryKey = "";
internal static void DrawPatch(OptionsPage __instance)
{
try
{
int currentItemIndex = Math.Max(0, Math.Min(__instance.options.Count - 7, __instance.currentItemIndex));
int x = Game1.getMouseX(true), y = Game1.getMouseY(true);
for (int i = 0; i < __instance.optionSlots.Count; i++)
{
if (!__instance.optionSlots[i].bounds.Contains(x, y) || currentItemIndex + i >= __instance.options.Count || !__instance.options[currentItemIndex + i].bounds.Contains(x - __instance.optionSlots[i].bounds.X, y - __instance.optionSlots[i].bounds.Y))
continue;
OptionsElement optionsElement = __instance.options[currentItemIndex + i];
string toSpeak = optionsElement.label;
if (optionsElement is OptionsButton)
toSpeak = $" {toSpeak} Button";
else if (optionsElement is OptionsCheckbox)
toSpeak = (((OptionsCheckbox)optionsElement).isChecked ? "Enabled" : "Disabled") + $" {toSpeak} Checkbox";
else if (optionsElement is OptionsDropDown)
toSpeak = $"{toSpeak} Dropdown, option {((OptionsDropDown)optionsElement).dropDownDisplayOptions[((OptionsDropDown)optionsElement).selectedOption]} selected";
else if (optionsElement is OptionsSlider)
toSpeak = $"{((OptionsSlider)optionsElement).value}% {toSpeak} Slider";
else if (optionsElement is OptionsPlusMinus)
toSpeak = $"{((OptionsPlusMinus)optionsElement).displayOptions[((OptionsPlusMinus)optionsElement).selected]} selected of {toSpeak}";
else if (optionsElement is OptionsInputListener)
{
string buttons = "";
((OptionsInputListener)optionsElement).buttonNames.ForEach(name => { buttons += $", {name}"; });
toSpeak = $"{toSpeak} is bound to {buttons}. Left click to change.";
}
else
{
if (toSpeak.Contains(":"))
toSpeak = toSpeak.Replace(":", "");
toSpeak = $"{toSpeak} Options:";
}
if (optionsPageQueryKey != toSpeak)
{
optionsPageQueryKey = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void Cleanup()
{
optionsPageQueryKey = "";
}
}
}

View File

@ -0,0 +1,166 @@
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class SocialPagePatch
{
internal static string socialPageQuery = "";
internal static void DrawPatch(SocialPage __instance, List<ClickableTextureComponent> ___sprites, int ___slotPosition, List<string> ___kidsNames)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
for (int i = ___slotPosition; i < ___slotPosition + 5; i++)
{
if (i >= ___sprites.Count)
continue;
if (__instance.names[i] is string && narrateNPCDetails(__instance, i, ___kidsNames, x, y))
{
return;
}
else if (__instance.names[i] is long && narrateFarmerDetails(__instance, i, ___sprites, x, y))
{
return;
}
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
private static bool narrateNPCDetails(SocialPage __instance, int i, List<string> ___kidsNames, int x, int y)
{
if (!__instance.characterSlots[i].bounds.Contains(x, y))
return false;
string name = $"{__instance.names[i] as string}";
int heartLevel = Game1.player.getFriendshipHeartLevelForNPC(name);
bool datable = SocialPage.isDatable(name);
Friendship friendship = __instance.getFriendship(name);
int giftsThisWeek = friendship.GiftsThisWeek;
bool hasTalked = Game1.player.hasPlayerTalkedToNPC(name);
bool spouse = friendship.IsMarried();
bool housemate = spouse && SocialPage.isRoommateOfAnyone(name);
___kidsNames.Add("Robin");
___kidsNames.Add("Pierre");
___kidsNames.Add("Caroline");
___kidsNames.Add("Jodi");
___kidsNames.Add("Kent");
___kidsNames.Add("George");
___kidsNames.Add("Evelyn");
___kidsNames.Add("Demetrius");
string toSpeak = $"{name}";
if (!hasTalked)
{
toSpeak = $"{toSpeak}, not talked yet";
}
if (datable | housemate)
{
string text2 = (LocalizedContentManager.CurrentLanguageCode != LocalizedContentManager.LanguageCode.pt) ? Game1.content.LoadString("Strings\\StringsFromCSFiles:SocialPage.cs.11635") : ((__instance.getGender(name) == 0) ? Game1.content.LoadString("Strings\\StringsFromCSFiles:SocialPage.cs.11635").Split('/').First() : Game1.content.LoadString("Strings\\StringsFromCSFiles:SocialPage.cs.11635").Split('/').Last());
if (housemate)
{
text2 = Game1.content.LoadString("Strings\\StringsFromCSFiles:Housemate");
}
else if (spouse)
{
text2 = ((__instance.getGender(name) == 0) ? Game1.content.LoadString("Strings\\StringsFromCSFiles:SocialPage.cs.11636") : Game1.content.LoadString("Strings\\StringsFromCSFiles:SocialPage.cs.11637"));
}
else if (__instance.isMarriedToAnyone(name))
{
text2 = ((__instance.getGender(name) == 0) ? Game1.content.LoadString("Strings\\UI:SocialPage_MarriedToOtherPlayer_MaleNPC") : Game1.content.LoadString("Strings\\UI:SocialPage_MarriedToOtherPlayer_FemaleNPC"));
}
else if (!Game1.player.isMarried() && friendship.IsDating())
{
text2 = ((__instance.getGender(name) == 0) ? Game1.content.LoadString("Strings\\StringsFromCSFiles:SocialPage.cs.11639") : Game1.content.LoadString("Strings\\StringsFromCSFiles:SocialPage.cs.11640"));
}
else if (__instance.getFriendship(name).IsDivorced())
{
text2 = ((__instance.getGender(name) == 0) ? Game1.content.LoadString("Strings\\StringsFromCSFiles:SocialPage.cs.11642") : Game1.content.LoadString("Strings\\StringsFromCSFiles:SocialPage.cs.11643"));
}
toSpeak = $"{toSpeak}, {text2}";
}
if (!__instance.getFriendship(name).IsMarried() && ___kidsNames.Contains(name))
{
toSpeak = $"{toSpeak}, married";
}
if (spouse)
{
toSpeak = $"{toSpeak}, spouse";
}
else if (friendship.IsDating())
{
toSpeak = $"{toSpeak}, dating";
}
toSpeak = $"{toSpeak}, {heartLevel} hearts, {giftsThisWeek} gifts given this week.";
if (socialPageQuery != toSpeak)
{
socialPageQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return true;
}
private static bool narrateFarmerDetails(SocialPage __instance, int i, List<ClickableTextureComponent> ___sprites, int x, int y)
{
long farmerID = (long)__instance.names[i];
Farmer farmer = Game1.getFarmerMaybeOffline(farmerID);
if (farmer == null)
return false;
int gender = (!farmer.IsMale) ? 1 : 0;
ClickableTextureComponent clickableTextureComponent = ___sprites[i];
if (!clickableTextureComponent.containsPoint(x, y))
return false;
Friendship friendship = Game1.player.team.GetFriendship(Game1.player.UniqueMultiplayerID, farmerID);
bool spouse = friendship.IsMarried();
string toSpeak = "";
string text2 = (LocalizedContentManager.CurrentLanguageCode != LocalizedContentManager.LanguageCode.pt) ? Game1.content.LoadString("Strings\\StringsFromCSFiles:SocialPage.cs.11635") : ((gender == 0) ? Game1.content.LoadString("Strings\\StringsFromCSFiles:SocialPage.cs.11635").Split('/').First() : Game1.content.LoadString("Strings\\StringsFromCSFiles:SocialPage.cs.11635").Split('/').Last());
if (spouse)
{
text2 = ((gender == 0) ? Game1.content.LoadString("Strings\\StringsFromCSFiles:SocialPage.cs.11636") : Game1.content.LoadString("Strings\\StringsFromCSFiles:SocialPage.cs.11637"));
}
else if (farmer.isMarried() && !farmer.hasRoommate())
{
text2 = ((gender == 0) ? Game1.content.LoadString("Strings\\UI:SocialPage_MarriedToOtherPlayer_MaleNPC") : Game1.content.LoadString("Strings\\UI:SocialPage_MarriedToOtherPlayer_FemaleNPC"));
}
else if (!Game1.player.isMarried() && friendship.IsDating())
{
text2 = ((gender == 0) ? Game1.content.LoadString("Strings\\StringsFromCSFiles:SocialPage.cs.11639") : Game1.content.LoadString("Strings\\StringsFromCSFiles:SocialPage.cs.11640"));
}
else if (friendship.IsDivorced())
{
text2 = ((gender == 0) ? Game1.content.LoadString("Strings\\StringsFromCSFiles:SocialPage.cs.11642") : Game1.content.LoadString("Strings\\StringsFromCSFiles:SocialPage.cs.11643"));
}
toSpeak = $"{farmer.displayName}, {text2}";
if (socialPageQuery != toSpeak)
{
socialPageQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return true;
}
internal static void Cleanup()
{
socialPageQuery = "";
}
}
}

View File

@ -1,801 +0,0 @@
using Microsoft.Xna.Framework;
using stardew_access.Features;
using StardewModdingAPI;
using StardewValley;
using StardewValley.Buildings;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class MenuPatches
{
internal static string currentLevelUpTitle = " ";
internal static bool firstTimeInNamingMenu = true;
internal static bool isNarratingPondInfo = false;
internal static string tailoringMenuQuery = " ";
internal static string pondQueryMenuQuery = " ";
internal static string forgeMenuQuery = " ";
internal static string itemListMenuQuery = " ";
public static Vector2? prevTile = null;
internal static void ItemListMenuPatch(ItemListMenu __instance, string ___title, int ___currentTab, int ___totalValueOfItems, List<Item> ___itemsToList)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
string toSpeak = " ", currentList = " ";
for (int i = ___currentTab * __instance.itemsPerCategoryPage; i < ___currentTab * __instance.itemsPerCategoryPage + __instance.itemsPerCategoryPage; i++)
{
if (i == 0)
currentList = ___title;
if (___itemsToList.Count > i)
{
if (___itemsToList[i] == null)
{
currentList = $"{currentList}, \n" + Game1.content.LoadString("Strings\\UI:ItemList_ItemsLostValue", ___totalValueOfItems);
continue;
}
currentList = $"{currentList}, \n {___itemsToList[i].Stack} {___itemsToList[i].DisplayName}";
}
}
if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
toSpeak = $"Page {___currentTab + 1} of {((int)___itemsToList.Count / __instance.itemsPerCategoryPage) + 1} \n {currentList} \n ok button";
else if (__instance.forwardButton != null && __instance.forwardButton.containsPoint(x, y))
toSpeak = "Next page button";
else if (__instance.backButton != null && __instance.backButton.containsPoint(x, y))
toSpeak = "Previous page button";
if (itemListMenuQuery != toSpeak)
{
itemListMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
}
catch (System.Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void ForgeMenuPatch(ForgeMenu __instance)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
string toSpeak = " ";
if (__instance.leftIngredientSpot != null && __instance.leftIngredientSpot.containsPoint(x, y))
{
if (__instance.leftIngredientSpot.item == null)
{
toSpeak = "Input weapon or tool here";
}
else
{
Item item = __instance.leftIngredientSpot.item;
toSpeak = $"Weapon slot: {item.Stack} {item.DisplayName}";
}
}
else if (__instance.rightIngredientSpot != null && __instance.rightIngredientSpot.containsPoint(x, y))
{
if (__instance.rightIngredientSpot.item == null)
{
toSpeak = "Input gemstone here";
}
else
{
Item item = __instance.rightIngredientSpot.item;
toSpeak = $"Gemstone slot: {item.Stack} {item.DisplayName}";
}
}
else if (__instance.startTailoringButton != null && __instance.startTailoringButton.containsPoint(x, y))
{
toSpeak = "Star forging button";
}
else if (__instance.unforgeButton != null && __instance.unforgeButton.containsPoint(x, y))
{
toSpeak = "Unforge button";
}
else if (__instance.trashCan != null && __instance.trashCan.containsPoint(x, y))
{
toSpeak = "Trashcan";
}
else if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
{
toSpeak = "ok button";
}
else if (__instance.dropItemInvisibleButton != null && __instance.dropItemInvisibleButton.containsPoint(x, y))
{
toSpeak = "drop item";
}
else if (__instance.equipmentIcons.Count > 0 && __instance.equipmentIcons[0].containsPoint(x, y))
{
toSpeak = "Left ring Slot";
if (Game1.player.leftRing.Value != null)
toSpeak = $"{toSpeak}: {Game1.player.leftRing.Value.DisplayName}";
}
else if (__instance.equipmentIcons.Count > 0 && __instance.equipmentIcons[1].containsPoint(x, y))
{
toSpeak = "Right ring Slot";
if (Game1.player.rightRing.Value != null)
toSpeak = $"{toSpeak}: {Game1.player.rightRing.Value.DisplayName}";
}
else
{
for (int i = 0; i < __instance.inventory.inventory.Count; i++)
{
if (!__instance.inventory.inventory[i].containsPoint(x, y))
continue;
if (__instance.inventory.actualInventory[i] == null)
toSpeak = "Empty slot";
else
{
toSpeak = $"{__instance.inventory.actualInventory[i].Stack} {__instance.inventory.actualInventory[i].DisplayName}";
if (!__instance.inventory.highlightMethod(__instance.inventory.actualInventory[i]))
{
toSpeak = $"{toSpeak} not usable here";
}
}
if (forgeMenuQuery != $"{toSpeak}:{i}")
{
forgeMenuQuery = $"{toSpeak}:{i}";
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
}
if (forgeMenuQuery != toSpeak)
{
forgeMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
if (__instance.dropItemInvisibleButton != null && __instance.dropItemInvisibleButton.containsPoint(x, y))
Game1.playSound("drop_item");
}
}
catch (System.Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void PondQueryMenuPatch(PondQueryMenu __instance, StardewValley.Object ____fishItem, FishPond ____pond, string ____statusText, bool ___confirmingEmpty)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
bool isPrimaryInfoKeyPressed = MainClass.Config.PrimaryInfoKey.JustPressed();
string toSpeak = " ", extra = "";
if (___confirmingEmpty)
{
if (__instance.yesButton != null && __instance.yesButton.containsPoint(x, y))
toSpeak = "Confirm button";
else if (__instance.noButton != null && __instance.noButton.containsPoint(x, y))
toSpeak = "Cancel button";
}
else
{
if (isPrimaryInfoKeyPressed && !isNarratingPondInfo)
{
string pond_name_text = Game1.content.LoadString("Strings\\UI:PondQuery_Name", ____fishItem.DisplayName);
string population_text = Game1.content.LoadString("Strings\\UI:PondQuery_Population", string.Concat(____pond.FishCount), ____pond.maxOccupants.Value);
bool has_unresolved_needs = ____pond.neededItem.Value != null && ____pond.HasUnresolvedNeeds() && !____pond.hasCompletedRequest.Value;
string bring_text = "";
if (has_unresolved_needs && ____pond.neededItem.Value != null)
bring_text = Game1.content.LoadString("Strings\\UI:PondQuery_StatusRequest_Bring") + $": {____pond.neededItemCount} {____pond.neededItem.Value.DisplayName}";
extra = $"{pond_name_text} {population_text} {bring_text} Status: {____statusText}";
pondQueryMenuQuery = " ";
isNarratingPondInfo = true;
Task.Delay(200).ContinueWith(_ => { isNarratingPondInfo = false; });
}
if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
toSpeak = "Ok button";
else if (__instance.changeNettingButton != null && __instance.changeNettingButton.containsPoint(x, y))
toSpeak = "Change netting button";
else if (__instance.emptyButton != null && __instance.emptyButton.containsPoint(x, y))
toSpeak = "Empty pond button";
}
if (pondQueryMenuQuery != toSpeak)
{
pondQueryMenuQuery = toSpeak;
MainClass.ScreenReader.Say(extra + " \n\t" + toSpeak, true);
}
}
catch (System.Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void TailoringMenuPatch(TailoringMenu __instance)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
string toSpeak = " ";
if (__instance.leftIngredientSpot != null && __instance.leftIngredientSpot.containsPoint(x, y))
{
if (__instance.leftIngredientSpot.item == null)
{
toSpeak = "Input cloth here";
}
else
{
Item item = __instance.leftIngredientSpot.item;
toSpeak = $"Cloth slot: {item.Stack} {item.DisplayName}";
}
}
else if (__instance.rightIngredientSpot != null && __instance.rightIngredientSpot.containsPoint(x, y))
{
if (__instance.rightIngredientSpot.item == null)
{
toSpeak = "Input ingredient here";
}
else
{
Item item = __instance.rightIngredientSpot.item;
toSpeak = $"Ingredient slot: {item.Stack} {item.DisplayName}";
}
}
else if (__instance.startTailoringButton != null && __instance.startTailoringButton.containsPoint(x, y))
{
toSpeak = "Star tailoring button";
}
else if (__instance.trashCan != null && __instance.trashCan.containsPoint(x, y))
{
toSpeak = "Trashcan";
}
else if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
{
toSpeak = "ok button";
}
else if (__instance.dropItemInvisibleButton != null && __instance.dropItemInvisibleButton.containsPoint(x, y))
{
toSpeak = "drop item";
}
else if (__instance.equipmentIcons.Count > 0 && __instance.equipmentIcons[0].containsPoint(x, y))
{
toSpeak = "Hat Slot";
if (Game1.player.hat.Value != null)
toSpeak = $"{toSpeak}: {Game1.player.hat.Value.DisplayName}";
}
else if (__instance.equipmentIcons.Count > 0 && __instance.equipmentIcons[1].containsPoint(x, y))
{
toSpeak = "Shirt Slot";
if (Game1.player.shirtItem.Value != null)
toSpeak = $"{toSpeak}: {Game1.player.shirtItem.Value.DisplayName}";
}
else if (__instance.equipmentIcons.Count > 0 && __instance.equipmentIcons[2].containsPoint(x, y))
{
toSpeak = "Pants Slot";
if (Game1.player.pantsItem.Value != null)
toSpeak = $"{toSpeak}: {Game1.player.pantsItem.Value.DisplayName}";
}
else
{
for (int i = 0; i < __instance.inventory.inventory.Count; i++)
{
if (!__instance.inventory.inventory[i].containsPoint(x, y))
continue;
if (__instance.inventory.actualInventory[i] == null)
toSpeak = "Empty slot";
else
{
toSpeak = $"{__instance.inventory.actualInventory[i].Stack} {__instance.inventory.actualInventory[i].DisplayName}";
if (!__instance.inventory.highlightMethod(__instance.inventory.actualInventory[i]))
{
toSpeak = $"{toSpeak} not usable here";
}
}
if (tailoringMenuQuery != $"{toSpeak}:{i}")
{
tailoringMenuQuery = $"{toSpeak}:{i}";
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
}
if (tailoringMenuQuery != toSpeak)
{
tailoringMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
if (__instance.dropItemInvisibleButton != null && __instance.dropItemInvisibleButton.containsPoint(x, y))
Game1.playSound("drop_item");
}
}
catch (System.Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void ChooseFromListMenuPatch(ChooseFromListMenu __instance, List<string> ___options, int ___index, bool ___isJukebox)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
string toSpeak = "";
if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
toSpeak = "Select " + (___isJukebox ? Utility.getSongTitleFromCueName(___options[___index]) : ___options[___index]) + " button";
else if (__instance.cancelButton != null && __instance.cancelButton.containsPoint(x, y))
toSpeak = "Cancel button";
else if (__instance.backButton != null && __instance.backButton.containsPoint(x, y))
toSpeak = "Previous option: " + (___isJukebox ? Utility.getSongTitleFromCueName(___options[Math.Max(0, ___index - 1)]) : ___options[Math.Max(0, ___index - 1)]) + " button";
else if (__instance.forwardButton != null && __instance.forwardButton.containsPoint(x, y))
toSpeak = "Next option: " + (___isJukebox ? Utility.getSongTitleFromCueName(___options[Math.Min(___options.Count, ___index + 1)]) : ___options[Math.Min(___options.Count, ___index + 1)]) + " button";
MainClass.ScreenReader.SayWithMenuChecker(toSpeak, true);
}
catch (System.Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static bool PlaySoundPatch(string cueName)
{
try
{
if (!Context.IsPlayerFree)
return true;
if (!Game1.player.isMoving())
return true;
if (cueName == "grassyStep" || cueName == "sandyStep" || cueName == "snowyStep" || cueName == "stoneStep" || cueName == "thudStep" || cueName == "woodyStep")
{
Vector2 nextTile = CurrentPlayer.FacingTile;
if (TileInfo.isCollidingAtTile((int)nextTile.X, (int)nextTile.Y))
{
if (prevTile != nextTile)
{
prevTile = nextTile;
//Game1.playSound("colliding");
}
return false;
}
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
return true;
}
internal static void LanguageSelectionMenuPatch(LanguageSelectionMenu __instance)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
if (__instance.nextPageButton != null && __instance.nextPageButton.containsPoint(x, y))
{
MainClass.ScreenReader.SayWithMenuChecker($"Next Page Button", true);
return;
}
if (__instance.previousPageButton != null && __instance.previousPageButton.containsPoint(x, y))
{
MainClass.ScreenReader.SayWithMenuChecker($"Previous Page Button", true);
return;
}
for (int i = 0; i < __instance.languages.Count; i++)
{
if (__instance.languages[i].containsPoint(x, y))
{
MainClass.ScreenReader.SayWithMenuChecker($"{__instance.languageList[i]} Button", true);
break;
}
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void MineElevatorMenuPatch(List<ClickableComponent> ___elevators)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
for (int i = 0; i < ___elevators.Count; i++)
{
if (___elevators[i].containsPoint(x, y))
{
MainClass.ScreenReader.SayWithMenuChecker($"{___elevators[i].name} level", true);
break;
}
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void TitleTextInputMenuPatch(TitleTextInputMenu __instance)
{
try
{
string toSpeak = "";
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
if (__instance.pasteButton != null && __instance.pasteButton.containsPoint(x, y))
toSpeak = $"Paste button";
if (toSpeak != "")
MainClass.ScreenReader.SayWithChecker(toSpeak, true);
}
catch (System.Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void NamingMenuPatch(NamingMenu __instance, TextBox ___textBox, string ___title)
{
try
{
string toSpeak = "";
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
bool isEscPressed = Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Escape); // For escaping/unselecting from the animal name text box
if (firstTimeInNamingMenu)
{
firstTimeInNamingMenu = false;
___textBox.Selected = false;
}
if (___textBox.Selected)
{
___textBox.Update();
toSpeak = ___textBox.Text;
if (isEscPressed)
{
___textBox.Selected = false;
}
}
else
{
if (__instance.textBoxCC != null && __instance.textBoxCC.containsPoint(x, y))
toSpeak = $"{___title} text box";
else if (__instance.doneNamingButton != null && __instance.doneNamingButton.containsPoint(x, y))
toSpeak = $"Done naming button";
else if (__instance.randomButton != null && __instance.randomButton.containsPoint(x, y))
toSpeak = $"Random button";
}
if (toSpeak != "")
MainClass.ScreenReader.SayWithChecker(toSpeak, true);
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void ConfirmationDialogPatch(ConfirmationDialog __instance, string ___message)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true);
string toSpeak = ___message;
if (__instance.okButton.containsPoint(x, y))
{
toSpeak += "\n\tOk Button";
}
else if (__instance.cancelButton.containsPoint(x, y))
{
toSpeak += "\n\tCancel Button";
}
MainClass.ScreenReader.SayWithMenuChecker(toSpeak, true);
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void LevelUpMenuPatch(LevelUpMenu __instance, List<int> ___professionsToChoose, List<string> ___leftProfessionDescription, List<string> ___rightProfessionDescription, List<string> ___extraInfoForLevel, List<CraftingRecipe> ___newCraftingRecipes, string ___title, bool ___isActive, bool ___isProfessionChooser)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true);
string leftProfession = " ", rightProfession = " ", extraInfo = " ", newCraftingRecipe = " ", toSpeak = " ";
if (!__instance.informationUp)
{
return;
}
if (__instance.isProfessionChooser)
{
if (___professionsToChoose.Count() == 0)
{
return;
}
for (int j = 0; j < ___leftProfessionDescription.Count; j++)
{
leftProfession += ___leftProfessionDescription[j] + ", ";
}
for (int i = 0; i < ___rightProfessionDescription.Count; i++)
{
rightProfession += ___rightProfessionDescription[i] + ", ";
}
if (__instance.leftProfession.containsPoint(x, y))
{
if ((MainClass.Config.LeftClickMainKey.JustPressed() || MainClass.Config.LeftClickAlternateKey.JustPressed()) && __instance.readyToClose())
{
Game1.player.professions.Add(___professionsToChoose[0]);
__instance.getImmediateProfessionPerk(___professionsToChoose[0]);
___isActive = false;
__instance.informationUp = false;
___isProfessionChooser = false;
__instance.RemoveLevelFromLevelList();
__instance.exitThisMenu();
return;
}
toSpeak = $"Selected: {leftProfession} Left click to choose.";
}
if (__instance.rightProfession.containsPoint(x, y))
{
if ((MainClass.Config.LeftClickMainKey.JustPressed() || MainClass.Config.LeftClickAlternateKey.JustPressed()) && __instance.readyToClose())
{
Game1.player.professions.Add(___professionsToChoose[1]);
__instance.getImmediateProfessionPerk(___professionsToChoose[1]);
___isActive = false;
__instance.informationUp = false;
___isProfessionChooser = false;
__instance.RemoveLevelFromLevelList();
__instance.exitThisMenu();
return;
}
toSpeak = $"Selected: {rightProfession} Left click to choose.";
}
}
else
{
foreach (string s2 in ___extraInfoForLevel)
{
extraInfo += s2 + ", ";
}
foreach (CraftingRecipe s in ___newCraftingRecipes)
{
string cookingOrCrafting = Game1.content.LoadString("Strings\\UI:LearnedRecipe_" + (s.isCookingRecipe ? "cooking" : "crafting"));
string message = Game1.content.LoadString("Strings\\UI:LevelUp_NewRecipe", cookingOrCrafting, s.DisplayName);
newCraftingRecipe += $"{message}, ";
}
}
if (__instance.okButton.containsPoint(x, y))
{
if (MainClass.Config.LeftClickMainKey.JustPressed() || MainClass.Config.LeftClickAlternateKey.JustPressed())
__instance.okButtonClicked();
toSpeak = $"{___title} {extraInfo} {newCraftingRecipe}. Left click to close.";
}
if (toSpeak != " ")
MainClass.ScreenReader.SayWithMenuChecker(toSpeak, true);
else if (__instance.isProfessionChooser && currentLevelUpTitle != $"{___title}. Select a new profession.")
{
MainClass.ScreenReader.SayWithMenuChecker($"{___title}. Select a new profession.", true);
currentLevelUpTitle = $"{___title}. Select a new profession.";
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void ShippingMenuPatch(ShippingMenu __instance, List<int> ___categoryTotals)
{
try
{
if (__instance.currentPage == -1)
{
int total = ___categoryTotals[5];
string toSpeak;
if (__instance.okButton.containsPoint(Game1.getMouseX(true), Game1.getMouseY(true)))
{
// Perform Left Click
if (MainClass.Config.LeftClickMainKey.JustPressed() || MainClass.Config.LeftClickAlternateKey.JustPressed())
{
Game1.activeClickableMenu.receiveLeftClick(Game1.getMouseX(true), Game1.getMouseY(true));
}
toSpeak = $"{total}g in total. Press left mouse button to save.";
MainClass.ScreenReader.SayWithChecker(toSpeak, true);
}
for (int i = 0; i < __instance.categories.Count; i++)
{
if (__instance.categories[i].containsPoint(Game1.getMouseX(true), Game1.getMouseY(true)))
{
toSpeak = $"Money recieved from {__instance.getCategoryName(i)}: {___categoryTotals[i]}g.";
MainClass.ScreenReader.SayWithChecker(toSpeak, true);
}
}
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
#region Cleanup on exitting a menu
internal static void Game1ExitActiveMenuPatch()
{
try
{
Cleanup(Game1.activeClickableMenu);
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void IClickableMenuOnExitPatch(IClickableMenu __instance)
{
try
{
Cleanup(__instance);
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
private static void Cleanup(IClickableMenu menu)
{
if (menu is LetterViewerMenu)
{
DialoguePatches.currentLetterText = " ";
}
else if (menu is LevelUpMenu)
{
currentLevelUpTitle = " ";
}
else if (menu is Billboard)
{
QuestPatches.currentDailyQuestText = " ";
}
else if (menu is GameMenu)
{
GameMenuPatches.gameMenuQueryKey = "";
GameMenuPatches.craftingPageQueryKey = "";
GameMenuPatches.inventoryPageQueryKey = "";
GameMenuPatches.exitPageQueryKey = "";
GameMenuPatches.optionsPageQueryKey = "";
GameMenuPatches.socialPageQuery = "";
GameMenuPatches.currentSelectedCraftingRecipe = -1;
GameMenuPatches.isSelectingRecipe = false;
}
else if (menu is JunimoNoteMenu)
{
BundleMenuPatches.currentIngredientListItem = -1;
BundleMenuPatches.currentIngredientInputSlot = -1;
BundleMenuPatches.currentInventorySlot = -1;
BundleMenuPatches.junimoNoteMenuQuery = "";
}
else if (menu is ShopMenu)
{
GameMenuPatches.shopMenuQueryKey = "";
}
else if (menu is ItemGrabMenu)
{
GameMenuPatches.itemGrabMenuQueryKey = "";
}
else if (menu is GeodeMenu)
{
GameMenuPatches.geodeMenuQueryKey = "";
}
else if (menu is CarpenterMenu)
{
BuildingNAnimalMenuPatches.carpenterMenuQuery = "";
BuildingNAnimalMenuPatches.isUpgrading = false;
BuildingNAnimalMenuPatches.isDemolishing = false;
BuildingNAnimalMenuPatches.isPainting = false;
BuildingNAnimalMenuPatches.isMoving = false;
BuildingNAnimalMenuPatches.isConstructing = false;
BuildingNAnimalMenuPatches.carpenterMenu = null;
}
else if (menu is PurchaseAnimalsMenu)
{
BuildingNAnimalMenuPatches.purchaseAnimalMenuQuery = "";
BuildingNAnimalMenuPatches.firstTimeInNamingMenu = true;
BuildingNAnimalMenuPatches.purchaseAnimalsMenu = null;
}
else if (menu is DialogueBox)
{
DialoguePatches.isDialogueAppearingFirstTime = true;
DialoguePatches.currentDialogue = " ";
}
else if (menu is JojaCDMenu)
{
BundleMenuPatches.jojaCDMenuQuery = "";
}
else if (menu is QuestLog)
{
QuestPatches.questLogQuery = " ";
}
else if (menu is TailoringMenu)
{
tailoringMenuQuery = " ";
}
else if (menu is ForgeMenu)
{
forgeMenuQuery = " ";
}
else if (menu is ItemListMenu)
{
itemListMenuQuery = " ";
}
else if (menu is FieldOfficeMenu)
{
DonationMenuPatches.fieldOfficeMenuQuery = " ";
}
else if (menu is MuseumMenu)
{
DonationMenuPatches.museumQueryKey = " ";
}
else if (menu is PondQueryMenu)
{
pondQueryMenuQuery = " ";
}
GameMenuPatches.hoveredItemQueryKey = "";
}
#endregion
internal static void ExitEventPatch()
{
if (MainClass.ScreenReader != null)
MainClass.ScreenReader.CloseScreenReader();
}
}
}

View File

@ -0,0 +1,116 @@
using stardew_access.Features;
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class ForgeMenuPatch
{
private static string forgeMenuQuery = "";
internal static void DrawPatch(ForgeMenu __instance)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
if (narrateHoveredButton(__instance, x, y)) return;
if (InventoryUtils.narrateHoveredSlot(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y))
{
Cleanup();
}
}
catch (System.Exception e)
{
MainClass.ErrorLog($"An error occured in forge menu patch:\n{e.Message}\n{e.StackTrace}");
}
}
private static bool narrateHoveredButton(ForgeMenu __instance, int x, int y)
{
string toSpeak = "";
bool isDropItemButton = false;
if (__instance.leftIngredientSpot != null && __instance.leftIngredientSpot.containsPoint(x, y))
{
if (__instance.leftIngredientSpot.item == null)
{
toSpeak = "Input weapon or tool here";
}
else
{
Item item = __instance.leftIngredientSpot.item;
toSpeak = $"Weapon slot: {item.Stack} {item.DisplayName}";
}
}
else if (__instance.rightIngredientSpot != null && __instance.rightIngredientSpot.containsPoint(x, y))
{
if (__instance.rightIngredientSpot.item == null)
{
toSpeak = "Input gemstone here";
}
else
{
Item item = __instance.rightIngredientSpot.item;
toSpeak = $"Gemstone slot: {item.Stack} {item.DisplayName}";
}
}
else if (__instance.startTailoringButton != null && __instance.startTailoringButton.containsPoint(x, y))
{
toSpeak = "Star forging button";
}
else if (__instance.unforgeButton != null && __instance.unforgeButton.containsPoint(x, y))
{
toSpeak = "Unforge button";
}
else if (__instance.trashCan != null && __instance.trashCan.containsPoint(x, y))
{
toSpeak = "Trashcan";
}
else if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
{
toSpeak = "ok button";
}
else if (__instance.dropItemInvisibleButton != null && __instance.dropItemInvisibleButton.containsPoint(x, y))
{
toSpeak = "drop item";
isDropItemButton = true;
}
else if (__instance.equipmentIcons.Count > 0 && __instance.equipmentIcons[0].containsPoint(x, y))
{
toSpeak = "Left ring Slot";
if (Game1.player.leftRing.Value != null)
toSpeak = $"{toSpeak}: {Game1.player.leftRing.Value.DisplayName}";
}
else if (__instance.equipmentIcons.Count > 0 && __instance.equipmentIcons[1].containsPoint(x, y))
{
toSpeak = "Right ring Slot";
if (Game1.player.rightRing.Value != null)
toSpeak = $"{toSpeak}: {Game1.player.rightRing.Value.DisplayName}";
}
else
{
return false;
}
if (forgeMenuQuery != toSpeak)
{
forgeMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
if (isDropItemButton) Game1.playSound("drop_item");
}
return true;
}
internal static void Cleanup()
{
forgeMenuQuery = "";
}
}
}

View File

@ -0,0 +1,88 @@
using stardew_access.Features;
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class GeodeMenuPatch
{
private static string geodeMenuQueryKey = "";
internal static void DrawPatch(GeodeMenu __instance)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
if (narrateRecievedTreasure(__instance)) return;
if (narrateHoveredButton(__instance, x, y)) return;
if (InventoryUtils.narrateHoveredSlot(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y))
geodeMenuQueryKey = "";
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
private static bool narrateRecievedTreasure(GeodeMenu __instance)
{
// Narrates the treasure recieved on breaking the geode
if (__instance.geodeTreasure == null) return false;
string name = __instance.geodeTreasure.DisplayName;
int stack = __instance.geodeTreasure.Stack;
string toSpeak = $"Recieved {stack} {name}";
if (geodeMenuQueryKey != toSpeak)
{
geodeMenuQueryKey = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return true;
}
private static bool narrateHoveredButton(GeodeMenu __instance, int x, int y)
{
string toSpeak = "";
bool isDropItemButton = false;
if (__instance.geodeSpot != null && __instance.geodeSpot.containsPoint(x, y))
{
toSpeak = "Place geode here";
}
else if (__instance.dropItemInvisibleButton != null && __instance.dropItemInvisibleButton.containsPoint(x, y))
{
toSpeak = "Drop item here";
isDropItemButton = true;
}
else if (__instance.trashCan != null && __instance.trashCan.containsPoint(x, y))
{
toSpeak = "Trash can";
}
else if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
{
toSpeak = "Ok button";
}
else
{
return false;
}
if (geodeMenuQueryKey == toSpeak) return true;
geodeMenuQueryKey = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
if (isDropItemButton) Game1.playSound("drop_item");
return true;
}
internal static void Cleanup()
{
geodeMenuQueryKey = "";
}
}
}

View File

@ -0,0 +1,230 @@
using stardew_access.Features;
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class ItemGrabMenuPatch
{
internal static string itemGrabMenuQueryKey = "";
internal static string hoveredItemQueryKey = "";
internal static void DrawPatch(ItemGrabMenu __instance)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
if (MainClass.Config.SnapToFirstSecondaryInventorySlotKey.JustPressed() && __instance.ItemsToGrabMenu.inventory.Count > 0 && !__instance.shippingBin)
{
__instance.setCurrentlySnappedComponentTo(__instance.ItemsToGrabMenu.inventory[0].myID);
__instance.ItemsToGrabMenu.inventory[0].snapMouseCursorToCenter();
}
else if (MainClass.Config.SnapToFirstInventorySlotKey.JustPressed() && __instance.inventory.inventory.Count > 0)
{
__instance.setCurrentlySnappedComponentTo(__instance.inventory.inventory[0].myID);
__instance.inventory.inventory[0].snapMouseCursorToCenter();
}
if (narrateHoveredButton(__instance, x, y))
{
InventoryUtils.Cleanup();
return;
}
if (narrateLastShippedItem(__instance, x, y))
{
InventoryUtils.Cleanup();
return;
}
if (InventoryUtils.narrateHoveredSlot(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y, true))
{
itemGrabMenuQueryKey = "";
return;
}
if (InventoryUtils.narrateHoveredSlot(__instance.ItemsToGrabMenu, __instance.ItemsToGrabMenu.inventory, __instance.ItemsToGrabMenu.actualInventory, x, y, true))
{
itemGrabMenuQueryKey = "";
return;
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
private static bool narrateHoveredButton(ItemGrabMenu __instance, int x, int y)
{
string toSpeak = "";
bool isDropItemButton = false;
if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
{
toSpeak = "Ok Button";
}
else if (__instance.trashCan != null && __instance.trashCan.containsPoint(x, y))
{
toSpeak = "Trash Can";
}
else if (__instance.organizeButton != null && __instance.organizeButton.containsPoint(x, y))
{
toSpeak = "Organize Button";
}
else if (__instance.fillStacksButton != null && __instance.fillStacksButton.containsPoint(x, y))
{
toSpeak = "Add to existing stacks button";
}
else if (__instance.specialButton != null && __instance.specialButton.containsPoint(x, y))
{
toSpeak = "Special Button";
}
else if (__instance.colorPickerToggleButton != null && __instance.colorPickerToggleButton.containsPoint(x, y))
{
toSpeak = "Color Picker: " + (__instance.chestColorPicker.visible ? "Enabled" : "Disabled");
}
else if (__instance.junimoNoteIcon != null && __instance.junimoNoteIcon.containsPoint(x, y))
{
toSpeak = "Community Center Button";
}
else if (__instance.dropItemInvisibleButton != null && __instance.dropItemInvisibleButton.containsPoint(x, y))
{
toSpeak = "Drop Item";
isDropItemButton = true;
}
else
{
return false;
}
// FIXME
/*if (__instance.discreteColorPickerCC.Count > 0) {
for (int i = 0; i < __instance.discreteColorPickerCC.Count; i++)
{
if (__instance.discreteColorPickerCC[i].containsPoint(x, y))
{
MainClass.monitor.Log(i.ToString(), LogLevel.Debug);
string toSpeak = getChestColorName(i);
if (itemGrabMenuQueryKey != toSpeak)
{
itemGrabMenuQueryKey = toSpeak;
hoveredItemQueryKey = "";
ScreenReader.say(toSpeak, true);
Game1.playSound("sa_drop_item");
}
return;
}
}
}*/
if (itemGrabMenuQueryKey == toSpeak) return true;
itemGrabMenuQueryKey = toSpeak;
hoveredItemQueryKey = "";
MainClass.ScreenReader.Say(toSpeak, true);
if (isDropItemButton) Game1.playSound("drop_item");
return true;
}
private static bool narrateLastShippedItem(ItemGrabMenu __instance, int x, int y)
{
if (!__instance.shippingBin || Game1.getFarm().lastItemShipped == null || !__instance.lastShippedHolder.containsPoint(x, y))
return false;
Item lastShippedItem = Game1.getFarm().lastItemShipped;
string name = lastShippedItem.DisplayName;
int count = lastShippedItem.Stack;
string toSpeak = $"Last Shipped: {count} {name}";
if (itemGrabMenuQueryKey != toSpeak)
{
itemGrabMenuQueryKey = toSpeak;
hoveredItemQueryKey = "";
MainClass.ScreenReader.Say(toSpeak, true);
}
return true;
}
// TODO Add color names
private static string getChestColorName(int i)
{
string toReturn = "";
switch (i)
{
case 0:
toReturn = "Default chest color";
break;
case 1:
toReturn = "Default chest color";
break;
case 2:
toReturn = "Default chest color";
break;
case 3:
toReturn = "Default chest color";
break;
case 4:
toReturn = "Default chest color";
break;
case 5:
toReturn = "Default chest color";
break;
case 6:
toReturn = "Default chest color";
break;
case 7:
toReturn = "Default chest color";
break;
case 8:
toReturn = "Default chest color";
break;
case 9:
toReturn = "Default chest color";
break;
case 10:
toReturn = "Default chest color";
break;
case 11:
toReturn = "Default chest color";
break;
case 12:
toReturn = "Default chest color";
break;
case 13:
toReturn = "Default chest color";
break;
case 14:
toReturn = "Default chest color";
break;
case 15:
toReturn = "Default chest color";
break;
case 16:
toReturn = "Default chest color";
break;
case 17:
toReturn = "Default chest color";
break;
case 18:
toReturn = "Default chest color";
break;
case 19:
toReturn = "Default chest color";
break;
case 20:
toReturn = "Default chest color";
break;
}
return toReturn;
}
internal static void Cleanup()
{
hoveredItemQueryKey = "";
itemGrabMenuQueryKey = "";
}
}
}

View File

@ -0,0 +1,121 @@
using stardew_access.Features;
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class ShopMenuPatch
{
internal static string shopMenuQueryKey = "";
internal static string hoveredItemQueryKey = "";
internal static void DrawPatch(ShopMenu __instance)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
if (MainClass.Config.SnapToFirstSecondaryInventorySlotKey.JustPressed() && __instance.forSaleButtons.Count > 0)
{
__instance.forSaleButtons[0].snapMouseCursorToCenter();
__instance.setCurrentlySnappedComponentTo(__instance.forSaleButtons[0].myID);
}
else if (MainClass.Config.SnapToFirstInventorySlotKey.JustPressed() && __instance.inventory.inventory.Count > 0)
{
__instance.inventory.inventory[0].snapMouseCursorToCenter();
__instance.setCurrentlySnappedComponentTo(__instance.inventory.inventory[0].myID);
}
if (narrateHoveredButton(__instance, x, y)) return;
if (InventoryUtils.narrateHoveredSlot(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y, hoverPrice: __instance.hoverPrice))
{
shopMenuQueryKey = "";
return;
}
narrateHoveredSellingItem(__instance);
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
private static bool narrateHoveredButton(ShopMenu __instance, int x, int y)
{
string toSpeak = "";
bool isDropItemButton = false;
if (__instance.inventory.dropItemInvisibleButton != null && __instance.inventory.dropItemInvisibleButton.containsPoint(x, y))
{
toSpeak = "Drop Item";
isDropItemButton = true;
}
else if (__instance.upArrow != null && __instance.upArrow.containsPoint(x, y))
{
toSpeak = "Up Arrow Button";
}
else if (__instance.downArrow != null && __instance.downArrow.containsPoint(x, y))
{
toSpeak = "Down Arrow Button";
}
else
{
return false;
}
if (shopMenuQueryKey == toSpeak) return true;
shopMenuQueryKey = toSpeak;
hoveredItemQueryKey = "";
MainClass.ScreenReader.Say(toSpeak, true);
if (isDropItemButton) Game1.playSound("drop_item");
return true;
}
private static void narrateHoveredSellingItem(ShopMenu __instance)
{
if (__instance.hoveredItem == null) return;
string name = __instance.hoveredItem.DisplayName;
string price = $"Buy Price: {__instance.hoverPrice} g";
string description = __instance.hoveredItem.getDescription();
string requirements = "";
#region get required items for item
int itemIndex = -1, itemAmount = 5;
if (__instance.itemPriceAndStock[__instance.hoveredItem].Length > 2)
itemIndex = __instance.itemPriceAndStock[__instance.hoveredItem][2];
if (__instance.itemPriceAndStock[__instance.hoveredItem].Length > 3)
itemAmount = __instance.itemPriceAndStock[__instance.hoveredItem][3];
if (itemIndex != -1)
{
string itemName = Game1.objectInformation[itemIndex].Split('/')[0];
if (itemAmount != -1)
requirements = $"Required: {itemAmount} {itemName}";
else
requirements = $"Required: {itemName}";
}
#endregion
string toSpeak = $"{name}, {requirements}, {price}, \n\t{description}";
if (shopMenuQueryKey == toSpeak) return;
shopMenuQueryKey = toSpeak;
hoveredItemQueryKey = "";
MainClass.ScreenReader.Say(toSpeak, true);
}
internal static void Cleanup()
{
shopMenuQueryKey = "";
hoveredItemQueryKey = "";
}
}
}

View File

@ -0,0 +1,115 @@
using stardew_access.Features;
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class TailoringMenuPatch
{
internal static string tailoringMenuQuery = "";
internal static void DrawPatch(TailoringMenu __instance)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
if (InventoryUtils.narrateHoveredSlot(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y))
return;
}
catch (System.Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
private static bool narrateHoveredButton(TailoringMenu __instance, int x, int y)
{
string toSpeak = "";
bool isDropItemButton = false;
if (__instance.leftIngredientSpot != null && __instance.leftIngredientSpot.containsPoint(x, y))
{
if (__instance.leftIngredientSpot.item == null)
{
toSpeak = "Input cloth here";
}
else
{
Item item = __instance.leftIngredientSpot.item;
toSpeak = $"Cloth slot: {item.Stack} {item.DisplayName}";
}
}
else if (__instance.rightIngredientSpot != null && __instance.rightIngredientSpot.containsPoint(x, y))
{
if (__instance.rightIngredientSpot.item == null)
{
toSpeak = "Input ingredient here";
}
else
{
Item item = __instance.rightIngredientSpot.item;
toSpeak = $"Ingredient slot: {item.Stack} {item.DisplayName}";
}
}
else if (__instance.startTailoringButton != null && __instance.startTailoringButton.containsPoint(x, y))
{
toSpeak = "Star tailoring button";
}
else if (__instance.trashCan != null && __instance.trashCan.containsPoint(x, y))
{
toSpeak = "Trashcan";
}
else if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
{
toSpeak = "ok button";
}
else if (__instance.dropItemInvisibleButton != null && __instance.dropItemInvisibleButton.containsPoint(x, y))
{
toSpeak = "drop item";
isDropItemButton = true;
}
else if (__instance.equipmentIcons.Count > 0 && __instance.equipmentIcons[0].containsPoint(x, y))
{
toSpeak = "Hat Slot";
if (Game1.player.hat.Value != null)
toSpeak = $"{toSpeak}: {Game1.player.hat.Value.DisplayName}";
}
else if (__instance.equipmentIcons.Count > 0 && __instance.equipmentIcons[1].containsPoint(x, y))
{
toSpeak = "Shirt Slot";
if (Game1.player.shirtItem.Value != null)
toSpeak = $"{toSpeak}: {Game1.player.shirtItem.Value.DisplayName}";
}
else if (__instance.equipmentIcons.Count > 0 && __instance.equipmentIcons[2].containsPoint(x, y))
{
toSpeak = "Pants Slot";
if (Game1.player.pantsItem.Value != null)
toSpeak = $"{toSpeak}: {Game1.player.pantsItem.Value.DisplayName}";
}
else {
return false;
}
if (tailoringMenuQuery != toSpeak)
{
tailoringMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
if (isDropItemButton) Game1.playSound("drop_item");
}
return true;
}
internal static void Cleanup()
{
tailoringMenuQuery = "";
}
}
}

View File

@ -4,43 +4,11 @@ using StardewValley.Minigames;
namespace stardew_access.Patches namespace stardew_access.Patches
{ {
public class MiniGamesPatches public class GrandpaStoryPatch
{ {
public static string grandpaStoryQuery = " "; public static string grandpaStoryQuery = " ";
public static string introQuery = " ";
internal static void IntroPatch(Intro __instance, int ___currentState) internal static void DrawPatch(GrandpaStory __instance, StardewValley.Menus.LetterViewerMenu ___letterView, bool ___drawGrandpa, bool ___letterReceived, bool ___mouseActive, Queue<string> ___grandpaSpeech, int ___grandpaSpeechTimer, int ___totalMilliseconds, int ___scene, int ___parallaxPan)
{
try
{
if (MainClass.ModHelper == null)
return;
string toSpeak = " ";
if (___currentState == 3)
{
toSpeak = MainClass.ModHelper.Translation.Get("intro.scene3");
}
else if (___currentState == 4)
{
toSpeak = MainClass.ModHelper.Translation.Get("intro.scene4");
}
if (toSpeak != " " && introQuery != toSpeak)
{
introQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, false);
return;
}
}
catch (System.Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void GrandpaStoryPatch(GrandpaStory __instance, StardewValley.Menus.LetterViewerMenu ___letterView, bool ___drawGrandpa, bool ___letterReceived, bool ___mouseActive, Queue<string> ___grandpaSpeech, int ___grandpaSpeechTimer, int ___totalMilliseconds, int ___scene, int ___parallaxPan)
{ {
try try
{ {
@ -49,7 +17,7 @@ namespace stardew_access.Patches
if (___letterView != null) if (___letterView != null)
{ {
DialoguePatches.NarrateLetterContent(___letterView); LetterViwerMenuPatch.narrateLetterContent(___letterView);
} }
if (MainClass.ModHelper == null) if (MainClass.ModHelper == null)
@ -117,4 +85,4 @@ namespace stardew_access.Patches
return new Rectangle((int)Utility.getTopLeftPositionForCenteringOnScreen(Game1.viewport, 1294, 730).X + (286 - ___parallaxPan) * 4, (int)Utility.getTopLeftPositionForCenteringOnScreen(Game1.viewport, 1294, 730).Y + 218 + Math.Max(0, Math.Min(60, (___grandpaSpeechTimer - 5000) / 8)), 524, 344); return new Rectangle((int)Utility.getTopLeftPositionForCenteringOnScreen(Game1.viewport, 1294, 730).X + (286 - ___parallaxPan) * 4, (int)Utility.getTopLeftPositionForCenteringOnScreen(Game1.viewport, 1294, 730).Y + 218 + Math.Max(0, Math.Min(60, (___grandpaSpeechTimer - 5000) / 8)), 524, 344);
} }
} }
} }

View File

@ -0,0 +1,40 @@
using StardewValley.Minigames;
namespace stardew_access.Patches
{
public class IntroPatch
{
public static string introQuery = " ";
internal static void DrawPatch(Intro __instance, int ___currentState)
{
try
{
if (MainClass.ModHelper == null)
return;
string toSpeak = " ";
if (___currentState == 3)
{
toSpeak = MainClass.ModHelper.Translation.Get("intro.scene3");
}
else if (___currentState == 4)
{
toSpeak = MainClass.ModHelper.Translation.Get("intro.scene4");
}
if (toSpeak != " " && introQuery != toSpeak)
{
introQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, false);
return;
}
}
catch (System.Exception e)
{
MainClass.ErrorLog($"An error occured in intro minigame patch:\n{e.Message}\n{e.StackTrace}");
}
}
}
}

View File

@ -1,14 +1,13 @@
using StardewValley; using StardewValley.Menus;
using StardewValley.Menus;
namespace stardew_access.Patches namespace stardew_access.Patches
{ {
internal class ChatMenuPatches internal class ChatBoxPatch
{ {
private static int currentChatMessageIndex = 0; private static int currentChatMessageIndex = 0;
private static bool isChatRunning = false; private static bool isChatRunning = false;
internal static void ChatBoxPatch(ChatBox __instance, List<ChatMessage> ___messages) internal static void UpdatePatch(ChatBox __instance, List<ChatMessage> ___messages)
{ {
try try
{ {
@ -19,23 +18,22 @@ namespace stardew_access.Patches
bool isPrevButtonPressed = MainClass.Config.ChatMenuNextKey.JustPressed(); bool isPrevButtonPressed = MainClass.Config.ChatMenuNextKey.JustPressed();
bool isNextButtonPressed = MainClass.Config.ChatMenuPreviousKey.JustPressed(); bool isNextButtonPressed = MainClass.Config.ChatMenuPreviousKey.JustPressed();
if (___messages.Count > 0) if (___messages.Count <= 0) return;
#region To narrate previous and next chat messages
if (isNextButtonPressed && !isChatRunning)
{ {
#region To narrate previous and next chat messages isChatRunning = true;
if (isNextButtonPressed && !isChatRunning) CycleThroughChatMessages(true, ___messages);
{ Task.Delay(200).ContinueWith(_ => { isChatRunning = false; });
isChatRunning = true;
CycleThroughChatMessages(true, ___messages);
Task.Delay(200).ContinueWith(_ => { isChatRunning = false; });
}
else if (isPrevButtonPressed && !isChatRunning)
{
isChatRunning = true;
CycleThroughChatMessages(false, ___messages);
Task.Delay(200).ContinueWith(_ => { isChatRunning = false; });
}
#endregion
} }
else if (isPrevButtonPressed && !isChatRunning)
{
isChatRunning = true;
CycleThroughChatMessages(false, ___messages);
Task.Delay(200).ContinueWith(_ => { isChatRunning = false; });
}
#endregion
} }
else if (___messages.Count > 0) else if (___messages.Count > 0)
{ {

View File

@ -0,0 +1,128 @@
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class DialogueBoxPatch
{
private static string currentDialogue = "";
private static bool isDialogueAppearingFirstTime = true;
internal static void DrawPatch(DialogueBox __instance)
{
try
{
if (__instance.transitioning) return;
if (narrateCharacterDialogue(__instance)) return;
if (narrateQuestionDialogue(__instance)) return;
narrateBasicDialogue(__instance.getCurrentString());
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate dialog:\n{e.StackTrace}\n{e.Message}");
}
}
internal static void RecieveLeftClickPatch()
{
// CLears the currentDialogue string on closing dialog
Cleanup();
}
private static bool narrateCharacterDialogue(DialogueBox __instance)
{
if (__instance.characterDialogue == null) return false;
// For Normal Character dialogues
Dialogue dialogue = __instance.characterDialogue;
string speakerName = dialogue.speaker.displayName;
string dialogueText = "";
string responseText = "";
bool hasResponses = dialogue.isCurrentDialogueAQuestion();
dialogueText = $"{speakerName} said {__instance.getCurrentString()}";
if (hasResponses)
{
responseText = getCurrentResponseText(__instance);
CheckAndSpeak(isDialogueAppearingFirstTime ? $"{dialogueText} \n\t {responseText}" : responseText, responseText);
if (isDialogueAppearingFirstTime) isDialogueAppearingFirstTime = false;
}
else
{
CheckAndSpeak(dialogueText);
}
return true;
}
private static bool narrateQuestionDialogue(DialogueBox __instance)
{
if (!__instance.isQuestion) return false;
// For Dialogues with responses/answers like the dialogue when we click on tv
string questionText = "";
string responseText = "";
bool hasResponses = false;
if (__instance.responses.Count > 0) hasResponses = true;
if (!hasResponses) return false;
questionText = __instance.getCurrentString();
responseText = getCurrentResponseText(__instance);
CheckAndSpeak(isDialogueAppearingFirstTime ? $"{questionText} \n\t {responseText}" : responseText, responseText);
if (isDialogueAppearingFirstTime) isDialogueAppearingFirstTime = false;
return true;
}
private static void narrateBasicDialogue(string dialogue)
{
// Basic dialogues like `No mails in the mail box`
if (Game1.activeClickableMenu is not DialogueBox) return;
CheckAndSpeak(dialogue);
}
private static string getCurrentResponseText(DialogueBox __instance)
{
List<Response> responses = __instance.responses;
if (__instance.selectedResponse >= 0 && __instance.selectedResponse < responses.Count)
{
return $"{__instance.selectedResponse + 1}: {responses[__instance.selectedResponse].responseText}";
}
else
{
// When the dialogue is not finished writing then the selectedResponse is <0 and this results
// in the first response not being detcted, so this sets the first response option to be the default
// if the current dialogue is a question or has responses
return $"1: {responses[0].responseText}";
}
}
private static void CheckAndSpeak(string toSpeak)
{
if (currentDialogue == toSpeak) return;
currentDialogue = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
private static void CheckAndSpeak(string toSpeak, string checkQuery)
{
if (currentDialogue == checkQuery) return;
currentDialogue = checkQuery;
MainClass.ScreenReader.Say(toSpeak, true);
}
internal static void Cleanup()
{
currentDialogue = "";
isDialogueAppearingFirstTime = true;
}
}
}

View File

@ -0,0 +1,56 @@
using Microsoft.Xna.Framework;
using stardew_access.Features;
using StardewModdingAPI;
using StardewValley;
namespace stardew_access.Patches
{
internal class Game1Patch
{
private static Vector2? prevTile = null;
internal static void ExitActiveMenuPatch()
{
try
{
IClickableMenuPatch.Cleanup(Game1.activeClickableMenu);
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static bool PlaySoundPatch(string cueName)
{
try
{
if (!Context.IsPlayerFree)
return true;
if (!Game1.player.isMoving())
return true;
if (cueName == "grassyStep" || cueName == "sandyStep" || cueName == "snowyStep" || cueName == "stoneStep" || cueName == "thudStep" || cueName == "woodyStep")
{
Vector2 nextTile = CurrentPlayer.FacingTile;
if (TileInfo.isCollidingAtTile((int)nextTile.X, (int)nextTile.Y))
{
if (prevTile != nextTile)
{
prevTile = nextTile;
//Game1.playSound("colliding");
}
return false;
}
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
return true;
}
}
}

View File

@ -0,0 +1,326 @@
using stardew_access.Features;
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
// These patches are global, i.e. work on every menus
internal class IClickableMenuPatch
{
internal static void DrawHoverTextPatch(string? text, int moneyAmountToDisplayAtBottom = -1, string? boldTitleText = null, int extraItemToShowIndex = -1, int extraItemToShowAmount = -1, string[]? buffIconsToDisplay = null, Item? hoveredItem = null, CraftingRecipe? craftingIngredients = null)
{
try
{
#region Skip narrating hover text for certain menus
if (Game1.activeClickableMenu is TitleMenu && !(((TitleMenu)Game1.activeClickableMenu).GetChildMenu() is CharacterCustomization))
return;
else if (Game1.activeClickableMenu is LetterViewerMenu || Game1.activeClickableMenu is QuestLog)
return;
else if (Game1.activeClickableMenu is Billboard)
return;
else if (Game1.activeClickableMenu is GeodeMenu)
return;
else if (Game1.activeClickableMenu is GameMenu && ((GameMenu)Game1.activeClickableMenu).GetCurrentPage() is InventoryPage)
return;
else if (Game1.activeClickableMenu is GameMenu && ((GameMenu)Game1.activeClickableMenu).GetCurrentPage() is CraftingPage)
return;
else if (Game1.activeClickableMenu is GameMenu && ((GameMenu)Game1.activeClickableMenu).GetCurrentPage() is OptionsPage)
return;
else if (Game1.activeClickableMenu is GameMenu && ((GameMenu)Game1.activeClickableMenu).GetCurrentPage() is ExitPage)
return;
else if (Game1.activeClickableMenu is GameMenu && ((GameMenu)Game1.activeClickableMenu).GetCurrentPage() is SocialPage)
return;
else if (Game1.activeClickableMenu is ItemGrabMenu)
return;
else if (Game1.activeClickableMenu is ShopMenu)
return;
else if (Game1.activeClickableMenu is ConfirmationDialog)
return;
else if (Game1.activeClickableMenu is JunimoNoteMenu)
return;
else if (Game1.activeClickableMenu is CarpenterMenu)
return;
else if (Game1.activeClickableMenu is PurchaseAnimalsMenu)
return;
else if (Game1.activeClickableMenu is CraftingPage)
return;
else if (Game1.activeClickableMenu is AnimalQueryMenu)
return;
else if (Game1.activeClickableMenu is ConfirmationDialog)
return;
else if (Game1.activeClickableMenu is ReadyCheckDialog)
return;
else if (Game1.activeClickableMenu is JojaCDMenu)
return;
else if (Game1.activeClickableMenu is TailoringMenu)
return;
else if (Game1.activeClickableMenu is PondQueryMenu)
return;
else if (Game1.activeClickableMenu is ForgeMenu)
return;
else if (Game1.activeClickableMenu is ItemListMenu)
return;
else if (Game1.activeClickableMenu is FieldOfficeMenu)
return;
else if (Game1.activeClickableMenu is MuseumMenu)
return;
#endregion
string toSpeak = " ";
#region Add item count before title
if (hoveredItem != null && hoveredItem.HasBeenInInventory)
{
int count = hoveredItem.Stack;
if (count > 1)
toSpeak = $"{toSpeak} {count} ";
}
#endregion
#region Add title if any
if (boldTitleText != null)
toSpeak = $"{toSpeak} {boldTitleText}\n";
#endregion
#region Add quality of item
if (hoveredItem is StardewValley.Object && ((StardewValley.Object)hoveredItem).Quality > 0)
{
int quality = ((StardewValley.Object)hoveredItem).Quality;
if (quality == 1)
{
toSpeak = $"{toSpeak} Silver quality";
}
else if (quality == 2 || quality == 3)
{
toSpeak = $"{toSpeak} Gold quality";
}
else if (quality >= 4)
{
toSpeak = $"{toSpeak} Iridium quality";
}
}
#endregion
#region Narrate hovered required ingredients
if (extraItemToShowIndex != -1)
{
string itemName = Game1.objectInformation[extraItemToShowIndex].Split('/')[0];
if (extraItemToShowAmount != -1)
toSpeak = $"{toSpeak} Required: {extraItemToShowAmount} {itemName}";
else
toSpeak = $"{toSpeak} Required: {itemName}";
}
#endregion
#region Add money
if (moneyAmountToDisplayAtBottom != -1)
toSpeak = $"{toSpeak} \nCost: {moneyAmountToDisplayAtBottom}g\n";
#endregion
#region Add the base text
if (text == "???")
toSpeak = "unknown";
else
toSpeak = $"{toSpeak} {text}";
#endregion
#region Add crafting ingredients
if (craftingIngredients != null)
{
toSpeak = $"{toSpeak} \n{craftingIngredients.description}";
toSpeak = $"{toSpeak} \nIngredients\n";
craftingIngredients.recipeList.ToList().ForEach(recipe =>
{
int count = recipe.Value;
int item = recipe.Key;
string name = craftingIngredients.getNameFromIndex(item);
toSpeak = $"{toSpeak} ,{count} {name}";
});
}
#endregion
#region Add health & stamina
if (hoveredItem is StardewValley.Object && ((StardewValley.Object)hoveredItem).Edibility != -300)
{
int stamina_recovery = ((StardewValley.Object)hoveredItem).staminaRecoveredOnConsumption();
toSpeak = $"{toSpeak} {stamina_recovery} Energy\n";
if (stamina_recovery >= 0)
{
int health_recovery = ((StardewValley.Object)hoveredItem).healthRecoveredOnConsumption();
toSpeak = $"{toSpeak} {health_recovery} Health";
}
}
#endregion
#region Add buff items (effects like +1 walking speed)
if (buffIconsToDisplay != null)
{
for (int i = 0; i < buffIconsToDisplay.Length; i++)
{
string buffName = ((Convert.ToInt32(buffIconsToDisplay[i]) > 0) ? "+" : "") + buffIconsToDisplay[i] + " ";
if (i <= 11)
{
buffName = Game1.content.LoadString("Strings\\UI:ItemHover_Buff" + i, buffName);
}
try
{
int count = int.Parse(buffName.Substring(0, buffName.IndexOf(' ')));
if (count != 0)
toSpeak = $"{toSpeak} {buffName}\n";
}
catch (Exception) { }
}
}
#endregion
#region Narrate toSpeak
// To prevent it from getting conflicted by two hover texts at the same time, two seperate methods are used.
// For example, sometimes `Welcome to Pierre's` and the items in seeds shop get conflicted causing it to speak infinitely.
if (toSpeak.ToString() != " ")
{
if (StardewModdingAPI.Context.IsPlayerFree)
MainClass.ScreenReader.SayWithChecker(toSpeak.ToString(), true); // Normal Checker
else
MainClass.ScreenReader.SayWithMenuChecker(toSpeak.ToString(), true); // Menu Checker
}
#endregion
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate dialog:\n{e.StackTrace}\n{e.Message}");
}
}
internal static void ExitThisMenuPatch(IClickableMenu __instance)
{
try
{
Cleanup(__instance);
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void Cleanup(IClickableMenu menu)
{
if (menu is TitleMenu)
{
TitleMenuPatch.Cleanup();
}
else if (menu is CoopMenu)
{
CoopMenuPatch.Cleanup();
}
else if (menu is LoadGameMenu)
{
LoadGameMenuPatch.Cleanup();
}
else if (menu is AdvancedGameOptions)
{
AdvancedGameOptionsPatch.Cleanup();
}
else if (menu is LetterViewerMenu)
{
LetterViwerMenuPatch.Cleanup();
}
else if (menu is LevelUpMenu)
{
LevelUpMenuPatch.Cleanup();
}
else if (menu is Billboard)
{
BillboardPatch.Cleanup();
}
else if (menu is GameMenu)
{
GameMenuPatch.Cleanup();
ExitPagePatch.Cleanup();
OptionsPagePatch.Cleanup();
SocialPagePatch.Cleanup();
InventoryPagePatch.Cleanup();
CraftingPagePatch.Cleanup();
}
else if (menu is JunimoNoteMenu)
{
JunimoNoteMenuPatch.Cleanup();
}
else if (menu is ShopMenu)
{
ShopMenuPatch.Cleanup();
}
else if (menu is ItemGrabMenu)
{
ItemGrabMenuPatch.Cleanup();
}
else if (menu is GeodeMenu)
{
GeodeMenuPatch.Cleanup();
}
else if (menu is CarpenterMenu)
{
CarpenterMenuPatch.Cleanup();
}
else if (menu is PurchaseAnimalsMenu)
{
PurchaseAnimalsMenuPatch.Cleanup();
}
else if (menu is AnimalQueryMenu)
{
AnimalQueryMenuPatch.Cleanup();
}
else if (menu is DialogueBox)
{
DialogueBoxPatch.Cleanup();
}
else if (menu is JojaCDMenu)
{
JojaCDMenuPatch.Cleanup();
}
else if (menu is QuestLog)
{
QuestLogPatch.Cleaup();
}
else if (menu is TailoringMenu)
{
TailoringMenuPatch.Cleanup();
}
else if (menu is ForgeMenu)
{
ForgeMenuPatch.Cleanup();
}
else if (menu is ItemListMenu)
{
ItemListMenuPatch.Cleanup();
}
else if (menu is FieldOfficeMenu)
{
FieldOfficeMenuPatch.Cleanup();
}
else if (menu is MuseumMenu)
{
MuseumMenuPatch.Cleanup();
}
else if (menu is PondQueryMenu)
{
PondQueryMenuPatch.Cleanup();
}
else if (menu is GeodeMenu)
{
GeodeMenuPatch.Cleanup();
}
else if (menu is SpecialOrdersBoard)
{
SpecialOrdersBoardPatch.Cleanup();
}
InventoryUtils.Cleanup();
TextBoxPatch.activeTextBoxes = "";
}
}
}

View File

@ -0,0 +1,11 @@
namespace stardew_access.Patches
{
internal class InstanceGamePatch
{
internal static void ExitPatch()
{
if (MainClass.ScreenReader != null)
MainClass.ScreenReader.CloseScreenReader();
}
}
}

View File

@ -0,0 +1,22 @@
using StardewValley;
namespace stardew_access.Patches
{
internal class NPCPatch
{
internal static void DrawAboveAlwaysFrontLayerPatch(NPC __instance, string ___textAboveHead, int ___textAboveHeadTimer)
{
try
{
if (___textAboveHeadTimer > 2900 && ___textAboveHead != null)
{
MainClass.ScreenReader.SayWithChecker($"{__instance.displayName} says {___textAboveHead}", true);
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Error in patch:NPCShowTextAboveHeadPatch \n{e.Message}\n{e.StackTrace}");
}
}
}
}

View File

@ -0,0 +1,42 @@
namespace stardew_access.Patches
{
internal class TextBoxPatch
{
internal static string textBoxQuery = " ";
internal static string activeTextBoxes = "";
internal static bool isAnyTextBoxActive => activeTextBoxes != "";
internal static void DrawPatch(StardewValley.Menus.TextBox __instance)
{
try
{
string uniqueIdentifier = $"{__instance.X}:{__instance.Y}:{__instance.Height}:{__instance.Width}";
if (!__instance.Selected)
{
if (activeTextBoxes.Contains(uniqueIdentifier)) activeTextBoxes = activeTextBoxes.Replace(uniqueIdentifier, "");
return;
}
if (!activeTextBoxes.Contains(uniqueIdentifier)) activeTextBoxes += uniqueIdentifier;
bool isEscPressed = StardewValley.Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Escape);
string toSpeak = __instance.Text;
if (isEscPressed)
{
__instance.Selected = false;
}
if (textBoxQuery != toSpeak)
{
textBoxQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
}
catch (Exception e)
{
MainClass.ErrorLog($"An error occured in DrawPatch() in TextBoxPatch:\n{e.Message}\n{e.StackTrace}");
}
}
}
}

View File

@ -0,0 +1,93 @@
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class AnimalQueryMenuPatch
{
internal static bool isNarratingAnimalInfo = false;
internal static string animalQueryMenuQuery = "";
internal static AnimalQueryMenu? animalQueryMenu;
internal static FarmAnimal? animalBeingMoved = null;
internal static bool isOnFarm = false;
internal static void DrawPatch(AnimalQueryMenu __instance, bool ___confirmingSell, FarmAnimal ___animal, TextBox ___textBox, string ___parentName, bool ___movingAnimal)
{
try
{
if (TextBoxPatch.isAnyTextBoxActive) return;
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
isOnFarm = ___movingAnimal;
animalQueryMenu = __instance;
animalBeingMoved = ___animal;
narrateAnimalDetailsOnKeyPress(___animal, ___parentName);
narrateHoveredButton(__instance, ___animal, ___confirmingSell, x, y);
}
catch (System.Exception e)
{
MainClass.ErrorLog($"An error occured in AnimalQueryMenuPatch()->DrawPatch():\n{e.Message}\n{e.StackTrace}");
}
}
private static void narrateAnimalDetailsOnKeyPress(FarmAnimal ___animal, string ___parentName)
{
bool isPrimaryInfoKeyPressed = MainClass.Config.PrimaryInfoKey.JustPressed();
if (!isPrimaryInfoKeyPressed | isNarratingAnimalInfo)
return;
string name = ___animal.displayName;
string type = ___animal.displayType;
int age = (___animal.GetDaysOwned() + 1) / 28 + 1;
string ageText = (age <= 1) ? Game1.content.LoadString("Strings\\UI:AnimalQuery_Age1") : Game1.content.LoadString("Strings\\UI:AnimalQuery_AgeN", age);
string parent = "";
if ((int)___animal.age.Value < (byte)___animal.ageWhenMature.Value)
{
ageText += Game1.content.LoadString("Strings\\UI:AnimalQuery_AgeBaby");
}
if (___parentName != null)
{
parent = Game1.content.LoadString("Strings\\UI:AnimalQuery_Parent", ___parentName);
}
isNarratingAnimalInfo = true;
Task.Delay(200).ContinueWith(_ => { isNarratingAnimalInfo = false; }); // Adds delay
MainClass.ScreenReader.Say($"Name: {name} Type: {type} \n\t Age: {ageText} {parent}", true);
}
private static void narrateHoveredButton(AnimalQueryMenu __instance, FarmAnimal ___animal, bool ___confirmingSell, int x, int y)
{
string toSpeak = "";
if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
toSpeak = "OK button";
else if (__instance.sellButton != null && __instance.sellButton.containsPoint(x, y))
toSpeak = $"Sell for {___animal.getSellPrice()}g button";
else if (___confirmingSell && __instance.yesButton != null && __instance.yesButton.containsPoint(x, y))
toSpeak = "Confirm selling animal";
else if (___confirmingSell && __instance.noButton != null && __instance.noButton.containsPoint(x, y))
toSpeak = "Cancel selling animal";
else if (__instance.moveHomeButton != null && __instance.moveHomeButton.containsPoint(x, y))
toSpeak = "Change home building button";
else if (__instance.allowReproductionButton != null && __instance.allowReproductionButton.containsPoint(x, y))
toSpeak = ((___animal.allowReproduction.Value) ? "Enabled" : "Disabled") + " allow reproduction button";
else if (__instance.textBoxCC != null && __instance.textBoxCC.containsPoint(x, y))
toSpeak = "Animal name text box";
if (animalQueryMenuQuery != toSpeak)
{
animalQueryMenuQuery = toSpeak;
MainClass.ScreenReader.Say($"{toSpeak}", true);
}
}
internal static void Cleanup()
{
AnimalQueryMenuPatch.animalQueryMenuQuery = "";
AnimalQueryMenuPatch.animalQueryMenu = null;
}
}
}

View File

@ -0,0 +1,174 @@
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class CarpenterMenuPatch
{
internal static CarpenterMenu? carpenterMenu = null;
internal static string carpenterMenuQuery = "";
internal static bool isSayingBlueprintInfo = false;
internal static string prevBlueprintInfo = "";
internal static bool isOnFarm = false, isUpgrading = false, isDemolishing = false, isPainting = false, isConstructing = false, isMoving = false, isMagicalConstruction = false;
internal static void DrawPatch(
CarpenterMenu __instance, bool ___onFarm, List<Item> ___ingredients, int ___price,
List<BluePrint> ___blueprints, int ___currentBlueprintIndex, bool ___upgrading, bool ___demolishing, bool ___moving,
bool ___painting, bool ___magicalConstruction)
{
try
{
isOnFarm = ___onFarm;
carpenterMenu = __instance;
isMagicalConstruction = ___magicalConstruction;
if (!___onFarm)
{
isUpgrading = false;
isDemolishing = false;
isPainting = false;
isMoving = false;
isConstructing = false;
BluePrint currentBlueprint = __instance.CurrentBlueprint;
if (currentBlueprint == null)
return;
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
bool isPrimaryInfoKeyPressed = MainClass.Config.PrimaryInfoKey.JustPressed();
string blueprintInfo = getCurrentBlueprintInfo(currentBlueprint, ___price, ___ingredients);
if (isPrimaryInfoKeyPressed && !isSayingBlueprintInfo)
{
SpeakAndWait(blueprintInfo);
}
else if (prevBlueprintInfo != blueprintInfo)
{
prevBlueprintInfo = blueprintInfo;
SpeakAndWait(blueprintInfo);
}
else
{
narrateHoveredButton(__instance, ___blueprints, ___currentBlueprintIndex, x, y);
}
}
else
{
if (___demolishing)
isDemolishing = true;
else if (___upgrading)
isUpgrading = true;
else if (___painting)
isPainting = true;
else if (___moving)
isMoving = true;
else
isConstructing = true;
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
private static string getCurrentBlueprintInfo(BluePrint currentBlueprint, int ___price, List<Item> ___ingredients)
{
string ingredients = "";
string name = currentBlueprint.displayName;
string upgradeName = currentBlueprint.nameOfBuildingToUpgrade;
string description = currentBlueprint.description;
string price = $"{___price}g";
int width = currentBlueprint.tilesWidth;
int height = currentBlueprint.tilesHeight;
#region Get ingredients
for (int i = 0; i < ___ingredients.Count; i++)
{
string itemName = ___ingredients[i].DisplayName;
int itemStack = ___ingredients[i].Stack;
string itemQuality = "";
int qualityValue = ((StardewValley.Object)___ingredients[i]).Quality;
if (qualityValue == 1)
{
itemQuality = "Silver quality";
}
else if (qualityValue == 2 || qualityValue == 3)
{
itemQuality = "Gold quality";
}
else if (qualityValue >= 4)
{
itemQuality = "Iridium quality";
}
ingredients = $"{ingredients}, {itemStack} {itemName} {itemQuality}";
}
#endregion
return $"{name}, Price: {price}, Ingredients: {ingredients}, Dimensions: {width} width and {height} height, Description: {description}";
}
private static async void SpeakAndWait(string toSpeak)
{
isSayingBlueprintInfo = true;
MainClass.ScreenReader.Say(toSpeak, true);
await Task.Delay(300);
isSayingBlueprintInfo = false;
}
private static void narrateHoveredButton(CarpenterMenu __instance, List<BluePrint> ___blueprints, int ___currentBlueprintIndex, int x, int y)
{
string toSpeak = "";
if (__instance.backButton != null && __instance.backButton.containsPoint(x, y))
{
toSpeak = "Previous Blueprint";
}
else if (__instance.forwardButton != null && __instance.forwardButton.containsPoint(x, y))
{
toSpeak = "Next Blueprint";
}
else if (__instance.demolishButton != null && __instance.demolishButton.containsPoint(x, y))
{
toSpeak = $"Demolish Building" + (__instance.CanDemolishThis(___blueprints[___currentBlueprintIndex]) ? "" : ", cannot demolish building");
}
else if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
{
toSpeak = "Construct Building" + (___blueprints[___currentBlueprintIndex].doesFarmerHaveEnoughResourcesToBuild() ? "" : ", cannot cunstrut building, not enough resources to build.");
}
else if (__instance.moveButton != null && __instance.moveButton.containsPoint(x, y))
{
toSpeak = "Move Building";
}
else if (__instance.paintButton != null && __instance.paintButton.containsPoint(x, y))
{
toSpeak = "Paint Building";
}
else if (__instance.cancelButton != null && __instance.cancelButton.containsPoint(x, y))
{
toSpeak = "Cancel Button";
}
else
{
return;
}
if (carpenterMenuQuery != toSpeak)
{
carpenterMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
}
internal static void Cleanup()
{
CarpenterMenuPatch.carpenterMenuQuery = "";
CarpenterMenuPatch.isUpgrading = false;
CarpenterMenuPatch.isDemolishing = false;
CarpenterMenuPatch.isPainting = false;
CarpenterMenuPatch.isMoving = false;
CarpenterMenuPatch.isConstructing = false;
CarpenterMenuPatch.carpenterMenu = null;
}
}
}

View File

@ -0,0 +1,32 @@
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class ChooseFromListMenuPatch
{
internal static void DrawPatch(ChooseFromListMenu __instance, List<string> ___options, int ___index, bool ___isJukebox)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
string toSpeak = "";
if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
toSpeak = "Select " + (___isJukebox ? Utility.getSongTitleFromCueName(___options[___index]) : ___options[___index]) + " button";
else if (__instance.cancelButton != null && __instance.cancelButton.containsPoint(x, y))
toSpeak = "Cancel button";
else if (__instance.backButton != null && __instance.backButton.containsPoint(x, y))
toSpeak = "Previous option: " + (___isJukebox ? Utility.getSongTitleFromCueName(___options[Math.Max(0, ___index - 1)]) : ___options[Math.Max(0, ___index - 1)]) + " button";
else if (__instance.forwardButton != null && __instance.forwardButton.containsPoint(x, y))
toSpeak = "Next option: " + (___isJukebox ? Utility.getSongTitleFromCueName(___options[Math.Min(___options.Count, ___index + 1)]) : ___options[Math.Min(___options.Count, ___index + 1)]) + " button";
MainClass.ScreenReader.SayWithMenuChecker(toSpeak, true);
}
catch (System.Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
}
}

View File

@ -0,0 +1,32 @@
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class ConfirmationDialogMenuPatch
{
internal static void DrawPatch(ConfirmationDialog __instance, string ___message)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true);
string toSpeak = ___message;
if (__instance.okButton.containsPoint(x, y))
{
toSpeak += "\n\tOk Button";
}
else if (__instance.cancelButton.containsPoint(x, y))
{
toSpeak += "\n\tCancel Button";
}
MainClass.ScreenReader.SayWithMenuChecker(toSpeak, true);
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
}
}

View File

@ -0,0 +1,55 @@
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class ItemListMenuPatch
{
private static string itemListMenuQuery = "";
internal static void DrawPatch(ItemListMenu __instance, string ___title, int ___currentTab, int ___totalValueOfItems, List<Item> ___itemsToList)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
string toSpeak = "", currentList = "";
for (int i = ___currentTab * __instance.itemsPerCategoryPage; i < ___currentTab * __instance.itemsPerCategoryPage + __instance.itemsPerCategoryPage; i++)
{
if (i == 0) currentList = ___title;
if (___itemsToList.Count <= i) continue;
if (___itemsToList[i] == null)
{
currentList = $"{currentList}, \n" + Game1.content.LoadString("Strings\\UI:ItemList_ItemsLostValue", ___totalValueOfItems);
continue;
}
currentList = $"{currentList}, \n {___itemsToList[i].Stack} {___itemsToList[i].DisplayName}";
}
if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
toSpeak = $"Page {___currentTab + 1} of {((int)___itemsToList.Count / __instance.itemsPerCategoryPage) + 1} \n {currentList} \n ok button";
else if (__instance.forwardButton != null && __instance.forwardButton.containsPoint(x, y))
toSpeak = "Next page button";
else if (__instance.backButton != null && __instance.backButton.containsPoint(x, y))
toSpeak = "Previous page button";
if (itemListMenuQuery != toSpeak)
{
itemListMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
}
catch (System.Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void Cleanup()
{
itemListMenuQuery = "";
}
}
}

View File

@ -0,0 +1,41 @@
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class LanguageSelectionMenuPatch
{
internal static void DrawPatch(LanguageSelectionMenu __instance)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
if (__instance.nextPageButton != null && __instance.nextPageButton.containsPoint(x, y))
{
MainClass.ScreenReader.SayWithMenuChecker($"Next Page Button", true);
return;
}
if (__instance.previousPageButton != null && __instance.previousPageButton.containsPoint(x, y))
{
MainClass.ScreenReader.SayWithMenuChecker($"Previous Page Button", true);
return;
}
for (int i = 0; i < __instance.languages.Count; i++)
{
if (__instance.languages[i].containsPoint(x, y))
{
MainClass.ScreenReader.SayWithMenuChecker($"{__instance.languageList[i]} Button", true);
break;
}
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
}
}

View File

@ -0,0 +1,95 @@
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class LetterViwerMenuPatch
{
private static string currentLetterText = "";
internal static void DrawPatch(LetterViewerMenu __instance)
{
try
{
if (!__instance.IsActive())
return;
narrateLetterContent(__instance);
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void narrateLetterContent(LetterViewerMenu __instance)
{
int x = Game1.getMousePosition().X, y = Game1.getMousePosition().Y;
#region Texts in the letter
string message = __instance.mailMessage[__instance.page];
string toSpeak = $"{message}";
if (__instance.ShouldShowInteractable())
{
if (__instance.moneyIncluded > 0)
{
string moneyText = Game1.content.LoadString("Strings\\UI:LetterViewer_MoneyIncluded", __instance.moneyIncluded);
toSpeak += $"\t\n\t ,Included money: {moneyText}";
}
else if (__instance.learnedRecipe != null && __instance.learnedRecipe.Length > 0)
{
string recipeText = Game1.content.LoadString("Strings\\UI:LetterViewer_LearnedRecipe", __instance.cookingOrCrafting);
toSpeak += $"\t\n\t ,Learned Recipe: {recipeText}";
}
}
if (currentLetterText != toSpeak)
{
currentLetterText = toSpeak;
// snap mouse to accept quest button
if (__instance.acceptQuestButton != null && __instance.questID != -1)
{
toSpeak += "\t\n Left click to accept quest.";
__instance.acceptQuestButton.snapMouseCursorToCenter();
}
if (__instance.mailMessage.Count > 1)
toSpeak = $"Page {__instance.page + 1} of {__instance.mailMessage.Count}:\n\t{toSpeak}";
MainClass.ScreenReader.Say(toSpeak, true);
}
#endregion
#region Narrate items given in the mail
if (__instance.ShouldShowInteractable())
{
foreach (ClickableComponent c in __instance.itemsToGrab)
{
if (c.item == null)
continue;
string name = c.item.DisplayName;
if (c.containsPoint(x, y))
MainClass.ScreenReader.SayWithChecker($"Left click to collect {name}", false);
}
}
#endregion
#region Narrate buttons
if (__instance.backButton != null && __instance.backButton.visible && __instance.backButton.containsPoint(x, y))
MainClass.ScreenReader.SayWithChecker($"Previous page button", false);
if (__instance.forwardButton != null && __instance.forwardButton.visible && __instance.forwardButton.containsPoint(x, y))
MainClass.ScreenReader.SayWithChecker($"Next page button", false);
#endregion
}
internal static void Cleanup()
{
currentLetterText = "";
}
}
}

View File

@ -0,0 +1,109 @@
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class LevelUpMenuPatch
{
private static string currentLevelUpTitle = "";
internal static void DrawPatch(LevelUpMenu __instance, List<int> ___professionsToChoose, List<string> ___leftProfessionDescription, List<string> ___rightProfessionDescription, List<string> ___extraInfoForLevel, List<CraftingRecipe> ___newCraftingRecipes, string ___title, bool ___isActive, bool ___isProfessionChooser)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true);
string leftProfession = "", rightProfession = "", extraInfo = "", newCraftingRecipe = "", toSpeak = "";
if (!__instance.informationUp)
return;
if (__instance.isProfessionChooser)
{
if (___professionsToChoose.Count() == 0) return;
for (int j = 0; j < ___leftProfessionDescription.Count; j++)
{
leftProfession += ___leftProfessionDescription[j] + ", ";
}
for (int i = 0; i < ___rightProfessionDescription.Count; i++)
{
rightProfession += ___rightProfessionDescription[i] + ", ";
}
if (__instance.leftProfession.containsPoint(x, y))
{
if ((MainClass.Config.LeftClickMainKey.JustPressed() || MainClass.Config.LeftClickAlternateKey.JustPressed()) && __instance.readyToClose())
{
Game1.player.professions.Add(___professionsToChoose[0]);
__instance.getImmediateProfessionPerk(___professionsToChoose[0]);
___isActive = false;
__instance.informationUp = false;
___isProfessionChooser = false;
__instance.RemoveLevelFromLevelList();
__instance.exitThisMenu();
return;
}
toSpeak = $"Selected: {leftProfession} Left click to choose.";
}
if (__instance.rightProfession.containsPoint(x, y))
{
if ((MainClass.Config.LeftClickMainKey.JustPressed() || MainClass.Config.LeftClickAlternateKey.JustPressed()) && __instance.readyToClose())
{
Game1.player.professions.Add(___professionsToChoose[1]);
__instance.getImmediateProfessionPerk(___professionsToChoose[1]);
___isActive = false;
__instance.informationUp = false;
___isProfessionChooser = false;
__instance.RemoveLevelFromLevelList();
__instance.exitThisMenu();
return;
}
toSpeak = $"Selected: {rightProfession} Left click to choose.";
}
}
else
{
foreach (string s2 in ___extraInfoForLevel)
{
extraInfo += s2 + ", ";
}
foreach (CraftingRecipe s in ___newCraftingRecipes)
{
string cookingOrCrafting = Game1.content.LoadString("Strings\\UI:LearnedRecipe_" + (s.isCookingRecipe ? "cooking" : "crafting"));
string message = Game1.content.LoadString("Strings\\UI:LevelUp_NewRecipe", cookingOrCrafting, s.DisplayName);
newCraftingRecipe += $"{message}, ";
}
}
if (__instance.okButton.containsPoint(x, y))
{
if (MainClass.Config.LeftClickMainKey.JustPressed() || MainClass.Config.LeftClickAlternateKey.JustPressed())
__instance.okButtonClicked();
toSpeak = $"{___title} {extraInfo} {newCraftingRecipe}. Left click to close.";
}
if (toSpeak != "")
MainClass.ScreenReader.SayWithMenuChecker(toSpeak, true);
else if (__instance.isProfessionChooser && currentLevelUpTitle != $"{___title}. Select a new profession.")
{
MainClass.ScreenReader.SayWithMenuChecker($"{___title}. Select a new profession.", true);
currentLevelUpTitle = $"{___title}. Select a new profession.";
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void Cleanup()
{
currentLevelUpTitle = "";
}
}
}

View File

@ -0,0 +1,28 @@
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class MineElevatorMenuPatch
{
internal static void DrawPatch(List<ClickableComponent> ___elevators)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
for (int i = 0; i < ___elevators.Count; i++)
{
if (___elevators[i].containsPoint(x, y))
{
MainClass.ScreenReader.SayWithMenuChecker($"{___elevators[i].name} level", true);
break;
}
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
}
}

View File

@ -0,0 +1,42 @@
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class NamingMenuPatch
{
internal static bool firstTimeInNamingMenu = true;
internal static void DrawPatch(NamingMenu __instance, TextBox ___textBox, string ___title)
{
try
{
if (firstTimeInNamingMenu)
{
firstTimeInNamingMenu = false;
___textBox.Selected = false;
}
if (TextBoxPatch.isAnyTextBoxActive) return;
string toSpeak = "";
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
bool isEscPressed = Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Escape); // For escaping/unselecting from the animal name text box
if (__instance.textBoxCC != null && __instance.textBoxCC.containsPoint(x, y))
toSpeak = $"{___title} text box";
else if (__instance.doneNamingButton != null && __instance.doneNamingButton.containsPoint(x, y))
toSpeak = $"Done naming button";
else if (__instance.randomButton != null && __instance.randomButton.containsPoint(x, y))
toSpeak = $"Random button";
if (toSpeak != "")
MainClass.ScreenReader.SayWithChecker(toSpeak, true);
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
}
}

View File

@ -0,0 +1,72 @@
using StardewValley;
using StardewValley.Buildings;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class PondQueryMenuPatch
{
private static string pondQueryMenuQuery = "";
private static bool isNarratingPondInfo = false;
internal static void DrawPatch(PondQueryMenu __instance, StardewValley.Object ____fishItem, FishPond ____pond, string ____statusText, bool ___confirmingEmpty)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
bool isPrimaryInfoKeyPressed = MainClass.Config.PrimaryInfoKey.JustPressed();
string toSpeak = "", extra = "";
if (___confirmingEmpty)
{
if (__instance.yesButton != null && __instance.yesButton.containsPoint(x, y))
toSpeak = "Confirm button";
else if (__instance.noButton != null && __instance.noButton.containsPoint(x, y))
toSpeak = "Cancel button";
}
else
{
if (isPrimaryInfoKeyPressed && !isNarratingPondInfo)
{
string pond_name_text = Game1.content.LoadString("Strings\\UI:PondQuery_Name", ____fishItem.DisplayName);
string population_text = Game1.content.LoadString("Strings\\UI:PondQuery_Population", string.Concat(____pond.FishCount), ____pond.maxOccupants.Value);
bool has_unresolved_needs = ____pond.neededItem.Value != null && ____pond.HasUnresolvedNeeds() && !____pond.hasCompletedRequest.Value;
string bring_text = "";
if (has_unresolved_needs && ____pond.neededItem.Value != null)
bring_text = Game1.content.LoadString("Strings\\UI:PondQuery_StatusRequest_Bring") + $": {____pond.neededItemCount} {____pond.neededItem.Value.DisplayName}";
extra = $"{pond_name_text} {population_text} {bring_text} Status: {____statusText}";
pondQueryMenuQuery = "";
isNarratingPondInfo = true;
Task.Delay(200).ContinueWith(_ => { isNarratingPondInfo = false; });
}
if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
toSpeak = "Ok button";
else if (__instance.changeNettingButton != null && __instance.changeNettingButton.containsPoint(x, y))
toSpeak = "Change netting button";
else if (__instance.emptyButton != null && __instance.emptyButton.containsPoint(x, y))
toSpeak = "Empty pond button";
}
if (pondQueryMenuQuery != toSpeak)
{
pondQueryMenuQuery = toSpeak;
MainClass.ScreenReader.Say(extra + " \n\t" + toSpeak, true);
}
}
catch (System.Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void Cleanup()
{
pondQueryMenuQuery = "";
isNarratingPondInfo = false;
}
}
}

View File

@ -0,0 +1,113 @@
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class PurchaseAnimalsMenuPatch
{
internal static FarmAnimal? animalBeingPurchased = null;
internal static bool isOnFarm = false;
internal static string purchaseAnimalMenuQuery = "";
internal static PurchaseAnimalsMenu? purchaseAnimalsMenu;
internal static bool firstTimeInNamingMenu = true;
internal static void DrawPatch(PurchaseAnimalsMenu __instance, bool ___onFarm, bool ___namingAnimal, TextBox ___textBox, FarmAnimal ___animalBeingPurchased)
{
try
{
if (TextBoxPatch.isAnyTextBoxActive) return;
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
purchaseAnimalsMenu = __instance;
isOnFarm = ___onFarm;
animalBeingPurchased = ___animalBeingPurchased;
if (___onFarm && ___namingAnimal)
{
narrateNamingMenu(__instance, x, y);
}
else if (___onFarm && !___namingAnimal)
{
firstTimeInNamingMenu = true;
}
else if (!___onFarm && !___namingAnimal)
{
firstTimeInNamingMenu = true;
narratePurchasingMenu(__instance);
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
private static void narrateNamingMenu(PurchaseAnimalsMenu __instance, int x, int y)
{
string toSpeak = "";
if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
{
toSpeak = "Cancel Button";
}
else if (__instance.doneNamingButton != null && __instance.doneNamingButton.containsPoint(x, y))
{
toSpeak = "OK Button";
}
else if (__instance.randomButton != null && __instance.randomButton.containsPoint(x, y))
{
toSpeak = "Random Name Button";
}
else if (__instance.textBoxCC != null && __instance.textBoxCC.containsPoint(x, y))
{
toSpeak = "Name Text Box";
// string? value = ___textBox.Text;
// if (value != "" && value != null && value != "null")
// toSpeak = $"{toSpeak}, Value: {value}";
}
if (purchaseAnimalMenuQuery == toSpeak) return;
purchaseAnimalMenuQuery = toSpeak;
if (firstTimeInNamingMenu)
{
toSpeak = $"Enter the name of animal in the name text box. {toSpeak}";
firstTimeInNamingMenu = false;
}
MainClass.ScreenReader.Say(toSpeak, true);
}
private static void narratePurchasingMenu(PurchaseAnimalsMenu __instance)
{
if (__instance.hovered == null)
return;
string toSpeak = "";
if (((StardewValley.Object)__instance.hovered.item).Type != null)
{
toSpeak = ((StardewValley.Object)__instance.hovered.item).Type;
}
else
{
string displayName = PurchaseAnimalsMenu.getAnimalTitle(__instance.hovered.hoverText);
int price = __instance.hovered.item.salePrice();
string description = PurchaseAnimalsMenu.getAnimalDescription(__instance.hovered.hoverText);
toSpeak = $"{displayName}, Price: {price}g, Description: {description}";
}
if (purchaseAnimalMenuQuery == toSpeak) return;
purchaseAnimalMenuQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
internal static void Cleanup()
{
purchaseAnimalMenuQuery = "";
firstTimeInNamingMenu = true;
purchaseAnimalsMenu = null;
}
}
}

View File

@ -0,0 +1,46 @@
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class ShippingMenuPatch
{
internal static int prevSlotIndex = -999;
internal static void DrawPatch(ShippingMenu __instance, List<int> ___categoryTotals)
{
try
{
if (__instance.currentPage == -1)
{
int total = ___categoryTotals[5];
string toSpeak;
if (__instance.okButton.containsPoint(Game1.getMouseX(true), Game1.getMouseY(true)))
{
// Perform Left Click
if (MainClass.Config.LeftClickMainKey.JustPressed() || MainClass.Config.LeftClickAlternateKey.JustPressed())
{
Game1.activeClickableMenu.receiveLeftClick(Game1.getMouseX(true), Game1.getMouseY(true));
}
toSpeak = $"{total}g in total. Press left mouse button to save.";
MainClass.ScreenReader.SayWithChecker(toSpeak, true);
}
for (int i = 0; i < __instance.categories.Count; i++)
{
if (__instance.categories[i].containsPoint(Game1.getMouseX(true), Game1.getMouseY(true)))
{
toSpeak = $"Money recieved from {__instance.getCategoryName(i)}: {___categoryTotals[i]}g.";
MainClass.ScreenReader.SayWithChecker(toSpeak, true);
}
}
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
}
}

View File

@ -0,0 +1,27 @@
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class TitleTextInputMenuPatch
{
internal static void DrawPatch(TitleTextInputMenu __instance)
{
try
{
string toSpeak = "";
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
if (__instance.pasteButton != null && __instance.pasteButton.containsPoint(x, y))
toSpeak = $"Paste button";
if (toSpeak != "")
MainClass.ScreenReader.SayWithChecker(toSpeak, true);
}
catch (System.Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
}
}

View File

@ -1,287 +0,0 @@
using Microsoft.Xna.Framework.Graphics;
using StardewValley;
using StardewValley.Menus;
using StardewValley.Quests;
namespace stardew_access.Patches
{
internal class QuestPatches
{
internal static string currentDailyQuestText = " ";
internal static string questLogQuery = " ";
internal static bool isNarratingQuestInfo = false, firstTimeInIndividualQuest = true;
#region For Special Orders Board
internal static void SpecialOrdersBoardPatch(SpecialOrdersBoard __instance)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
if (__instance.acceptLeftQuestButton.visible && __instance.acceptLeftQuestButton.containsPoint(x, y))
{
string toSpeak = getSpecialOrderDetails(__instance.leftOrder);
toSpeak = $"Left Quest:\n\t{toSpeak}\n\tPress left click to accept this quest.";
MainClass.ScreenReader.SayWithMenuChecker(toSpeak, true);
return;
}
if (__instance.acceptRightQuestButton.visible && __instance.acceptRightQuestButton.containsPoint(x, y))
{
string toSpeak = getSpecialOrderDetails(__instance.rightOrder);
toSpeak = $"Right Quest:\n\t{toSpeak}\n\tPress left click to accept this quest.";
MainClass.ScreenReader.SayWithMenuChecker(toSpeak, true);
return;
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
private static string getSpecialOrderDetails(SpecialOrder order)
{
int daysLeft = order.GetDaysLeft();
string description = order.GetDescription();
string objectiveDescription = "";
string name = order.GetName();
int moneyReward = order.GetMoneyReward();
// Get each objectives
for (int i = 0; i < order.GetObjectiveDescriptions().Count; i++)
{
objectiveDescription += order.GetObjectiveDescriptions()[i] + ", \n";
}
string toReturn = $"{name}\n\tDescription:{description}\n\tObjectives: {objectiveDescription}";
if (order.IsTimedQuest())
{
toReturn = $"{toReturn}\n\tTime: {daysLeft} days";
}
if (order.HasMoneyReward())
{
toReturn = $"{toReturn}\n\tReward: {moneyReward}g";
}
return toReturn;
}
#endregion
#region For Normal Billboard in the town
internal static void BillboardPatch(Billboard __instance, bool ___dailyQuestBoard)
{
try
{
if (!___dailyQuestBoard)
{
#region Callender
for (int i = 0; i < __instance.calendarDays.Count; i++)
{
if (__instance.calendarDays[i].containsPoint(Game1.getMouseX(true), Game1.getMouseY(true)))
{
string toSpeak = $"Day {i + 1}";
if (__instance.calendarDays[i].name.Length > 0)
{
toSpeak += $", {__instance.calendarDays[i].name}";
}
if (__instance.calendarDays[i].hoverText.Length > 0)
{
toSpeak += $", {__instance.calendarDays[i].hoverText}";
}
if (Game1.dayOfMonth == i + 1)
toSpeak += $", Current";
MainClass.ScreenReader.SayWithChecker(toSpeak, true);
}
}
#endregion
}
else
{
#region Daily Quest Board
if (Game1.questOfTheDay == null || Game1.questOfTheDay.currentObjective == null || Game1.questOfTheDay.currentObjective.Length == 0)
{
// No quests
string toSpeak = "No quests for today!";
if (currentDailyQuestText != toSpeak)
{
currentDailyQuestText = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
}
else
{
SpriteFont font = ((LocalizedContentManager.CurrentLanguageCode == LocalizedContentManager.LanguageCode.ko) ? Game1.smallFont : Game1.dialogueFont);
string description = Game1.parseText(Game1.questOfTheDay.questDescription, font, 640);
string toSpeak = description;
if (currentDailyQuestText != toSpeak)
{
currentDailyQuestText = toSpeak;
// Snap to accept quest button
if (__instance.acceptQuestButton.visible)
{
toSpeak += "\t\n Left click to accept quest.";
__instance.acceptQuestButton.snapMouseCursorToCenter();
}
MainClass.ScreenReader.Say(toSpeak, true);
}
}
#endregion
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
#endregion
#region Journal Menu
internal static void QuestLogPatch(QuestLog __instance, int ___questPage, List<List<IQuest>> ___pages, int ___currentPage, IQuest ____shownQuest, List<string> ____objectiveText)
{
try
{
bool isPrimaryInfoKeyPressed = MainClass.Config.PrimaryInfoKey.JustPressed();
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
string toSpeak = " ", extra = "";
if (___questPage == -1)
{
#region Quest Lists
if (!firstTimeInIndividualQuest)
firstTimeInIndividualQuest = true;
for (int i = 0; i < __instance.questLogButtons.Count; i++)
{
if (___pages.Count() > 0 && ___pages[___currentPage].Count() > i)
{
if (!__instance.questLogButtons[i].containsPoint(x, y))
continue;
string name = ___pages[___currentPage][i].GetName();
int daysLeft = ___pages[___currentPage][i].GetDaysLeft();
toSpeak = $"{name}";
if (daysLeft > 0 && ___pages[___currentPage][i].ShouldDisplayAsComplete())
toSpeak += $"\t\n {daysLeft} days left";
toSpeak += ___pages[___currentPage][i].ShouldDisplayAsComplete() ? " completed!" : "";
break;
}
}
if (__instance.backButton != null && __instance.backButton.visible && __instance.backButton.containsPoint(x, y))
toSpeak = "Previous page button";
else if (__instance.forwardButton != null && __instance.forwardButton.visible && __instance.forwardButton.containsPoint(x, y))
toSpeak = "Next page button";
else if (__instance.upperRightCloseButton != null && __instance.upperRightCloseButton.visible && __instance.upperRightCloseButton.containsPoint(x, y))
toSpeak = "Close menu button";
if (questLogQuery != toSpeak)
{
questLogQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
#endregion
}
else
{
#region Individual quest
bool containsReward = __instance.HasReward() || __instance.HasMoneyReward();
string description = Game1.parseText(____shownQuest.GetDescription(), Game1.dialogueFont, __instance.width - 128);
string title = ____shownQuest.GetName();
if (firstTimeInIndividualQuest || (isPrimaryInfoKeyPressed && !isNarratingQuestInfo))
{
if (firstTimeInIndividualQuest)
toSpeak = "Back button";
if (____shownQuest.ShouldDisplayAsComplete())
{
#region Quest completed menu
extra = $"Quest: {title} Completed!";
if (__instance.HasMoneyReward())
extra += $"you recieved {____shownQuest.GetMoneyReward()}g";
#endregion
}
else
{
#region Quest in-complete menu
extra = $"Title: {title}. \t\n Description: {description}";
for (int j = 0; j < ____objectiveText.Count; j++)
{
string parsed_text = Game1.parseText(____objectiveText[j], width: __instance.width - 192, whichFont: Game1.dialogueFont);
if (____shownQuest != null && ____shownQuest is SpecialOrder)
{
OrderObjective order_objective = ((SpecialOrder)____shownQuest).objectives[j];
if (order_objective.GetMaxCount() > 1 && order_objective.ShouldShowProgress())
parsed_text += "\n\t" + order_objective.GetCount() + " of " + order_objective.GetMaxCount() + " completed";
}
extra += $"\t\nOrder {j + 1}: {parsed_text} \t\n";
}
if (____shownQuest != null)
{
int daysLeft = ____shownQuest.GetDaysLeft();
if (daysLeft > 0)
extra += $"\t\n{daysLeft} days left.";
}
#endregion
}
isNarratingQuestInfo = true;
Task.Delay(200).ContinueWith(_ => { isNarratingQuestInfo = false; });
questLogQuery = " ";
}
if (!firstTimeInIndividualQuest)
if (__instance.backButton != null && __instance.backButton.visible && __instance.backButton.containsPoint(x, y))
toSpeak = (___currentPage > 0) ? "Previous page button" : "Back button";
else if (__instance.forwardButton != null && __instance.forwardButton.visible && __instance.forwardButton.containsPoint(x, y))
toSpeak = "Next page button";
else if (__instance.cancelQuestButton != null && __instance.cancelQuestButton.visible && __instance.cancelQuestButton.containsPoint(x, y))
toSpeak = "Cancel quest button";
else if (__instance.upperRightCloseButton != null && __instance.upperRightCloseButton.visible && __instance.upperRightCloseButton.containsPoint(x, y))
toSpeak = "Close menu button";
else if (containsReward && __instance.rewardBox.containsPoint(x, y))
toSpeak = "Left click to collect reward";
if (firstTimeInIndividualQuest || (questLogQuery != toSpeak))
{
questLogQuery = toSpeak;
MainClass.ScreenReader.Say(extra + " \n\t" + toSpeak, true);
if (firstTimeInIndividualQuest)
firstTimeInIndividualQuest = false;
}
#endregion
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
#endregion
}
}

View File

@ -0,0 +1,98 @@
using Microsoft.Xna.Framework.Graphics;
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class BillboardPatch
{
private static string billboardQueryKey = "";
internal static void DrawPatch(Billboard __instance, bool ___dailyQuestBoard)
{
try
{
if (___dailyQuestBoard)
{
narrateDailyQuestBoard(__instance);
}
else
{
narrateCallendar(__instance);
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
private static void narrateCallendar(Billboard __instance)
{
for (int i = 0; i < __instance.calendarDays.Count; i++)
{
if (!__instance.calendarDays[i].containsPoint(Game1.getMouseX(true), Game1.getMouseY(true)))
continue;
string toSpeak = $"Day {i + 1}";
if (__instance.calendarDays[i].name.Length > 0)
{
toSpeak += $", {__instance.calendarDays[i].name}";
}
if (__instance.calendarDays[i].hoverText.Length > 0)
{
toSpeak += $", {__instance.calendarDays[i].hoverText}";
}
if (Game1.dayOfMonth == i + 1)
toSpeak += $", Current";
if (billboardQueryKey != toSpeak)
{
billboardQueryKey = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
}
}
private static void narrateDailyQuestBoard(Billboard __instance)
{
if (Game1.questOfTheDay == null || Game1.questOfTheDay.currentObjective == null || Game1.questOfTheDay.currentObjective.Length == 0)
{
// No quests
string toSpeak = "No quests for today!";
if (billboardQueryKey != toSpeak)
{
billboardQueryKey = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
}
else
{
SpriteFont font = ((LocalizedContentManager.CurrentLanguageCode == LocalizedContentManager.LanguageCode.ko) ? Game1.smallFont : Game1.dialogueFont);
string description = Game1.parseText(Game1.questOfTheDay.questDescription, font, 640);
string toSpeak = description;
if (billboardQueryKey != toSpeak)
{
billboardQueryKey = toSpeak;
// Snap to accept quest button
if (__instance.acceptQuestButton.visible)
{
toSpeak += "\t\n Left click to accept quest.";
__instance.acceptQuestButton.snapMouseCursorToCenter();
}
MainClass.ScreenReader.Say(toSpeak, true);
}
}
}
internal static void Cleanup()
{
billboardQueryKey = "";
}
}
}

View File

@ -0,0 +1,164 @@
using StardewValley;
using StardewValley.Menus;
using StardewValley.Quests;
namespace stardew_access.Patches
{
// a.k.a. Journal Menu
internal class QuestLogPatch
{
internal static string questLogQuery = "";
internal static bool isNarratingQuestInfo = false;
internal static bool firstTimeInIndividualQuest = true;
internal static void DrawPatch(QuestLog __instance, int ___questPage, List<List<IQuest>> ___pages, int ___currentPage, IQuest ____shownQuest, List<string> ____objectiveText)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
if (___questPage == -1)
{
narrateQuestList(__instance, ___pages, ___currentPage, x, y);
}
else
{
narrateIndividualQuest(__instance, ___currentPage, ____shownQuest, ____objectiveText, x, y);
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
private static void narrateQuestList(QuestLog __instance, List<List<IQuest>> ___pages, int ___currentPage, int x, int y)
{
string toSpeak = "";
if (!firstTimeInIndividualQuest) firstTimeInIndividualQuest = true;
if (__instance.backButton != null && __instance.backButton.visible && __instance.backButton.containsPoint(x, y))
toSpeak = "Previous page button";
else if (__instance.forwardButton != null && __instance.forwardButton.visible && __instance.forwardButton.containsPoint(x, y))
toSpeak = "Next page button";
else if (__instance.upperRightCloseButton != null && __instance.upperRightCloseButton.visible && __instance.upperRightCloseButton.containsPoint(x, y))
toSpeak = "Close menu button";
else
{
for (int i = 0; i < __instance.questLogButtons.Count; i++)
{
if (___pages.Count() <= 0 || ___pages[___currentPage].Count() <= i)
continue;
if (!__instance.questLogButtons[i].containsPoint(x, y))
continue;
string name = ___pages[___currentPage][i].GetName();
int daysLeft = ___pages[___currentPage][i].GetDaysLeft();
toSpeak = $"{name}";
if (daysLeft > 0 && ___pages[___currentPage][i].ShouldDisplayAsComplete())
toSpeak += $"\t\n {daysLeft} days left";
toSpeak += ___pages[___currentPage][i].ShouldDisplayAsComplete() ? " completed!" : "";
break;
}
}
if (questLogQuery != toSpeak)
{
questLogQuery = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
}
private static void narrateIndividualQuest(QuestLog __instance, int ___currentPage, IQuest ____shownQuest, List<string> ____objectiveText, int x, int y)
{
bool isPrimaryInfoKeyPressed = MainClass.Config.PrimaryInfoKey.JustPressed();
bool containsReward = __instance.HasReward() || __instance.HasMoneyReward();
string description = Game1.parseText(____shownQuest.GetDescription(), Game1.dialogueFont, __instance.width - 128);
string title = ____shownQuest.GetName();
string toSpeak = "";
string extra = "";
if (firstTimeInIndividualQuest || (isPrimaryInfoKeyPressed && !isNarratingQuestInfo))
{
if (firstTimeInIndividualQuest)
toSpeak = "Back button";
if (____shownQuest.ShouldDisplayAsComplete())
{
#region Quest completed menu
extra = $"Quest: {title} Completed!";
if (__instance.HasMoneyReward())
extra += $"you recieved {____shownQuest.GetMoneyReward()}g";
#endregion
}
else
{
#region Quest in-complete menu
extra = $"Title: {title}. \t\n Description: {description}";
for (int j = 0; j < ____objectiveText.Count; j++)
{
string parsed_text = Game1.parseText(____objectiveText[j], width: __instance.width - 192, whichFont: Game1.dialogueFont);
if (____shownQuest != null && ____shownQuest is SpecialOrder)
{
OrderObjective order_objective = ((SpecialOrder)____shownQuest).objectives[j];
if (order_objective.GetMaxCount() > 1 && order_objective.ShouldShowProgress())
parsed_text += "\n\t" + order_objective.GetCount() + " of " + order_objective.GetMaxCount() + " completed";
}
extra += $"\t\nOrder {j + 1}: {parsed_text} \t\n";
}
if (____shownQuest != null)
{
int daysLeft = ____shownQuest.GetDaysLeft();
if (daysLeft > 0)
extra += $"\t\n{daysLeft} days left.";
}
#endregion
}
isNarratingQuestInfo = true;
Task.Delay(200).ContinueWith(_ => { isNarratingQuestInfo = false; });
questLogQuery = "";
}
if (!firstTimeInIndividualQuest)
{
if (__instance.backButton != null && __instance.backButton.visible && __instance.backButton.containsPoint(x, y))
toSpeak = (___currentPage > 0) ? "Previous page button" : "Back button";
else if (__instance.forwardButton != null && __instance.forwardButton.visible && __instance.forwardButton.containsPoint(x, y))
toSpeak = "Next page button";
else if (__instance.cancelQuestButton != null && __instance.cancelQuestButton.visible && __instance.cancelQuestButton.containsPoint(x, y))
toSpeak = "Cancel quest button";
else if (__instance.upperRightCloseButton != null && __instance.upperRightCloseButton.visible && __instance.upperRightCloseButton.containsPoint(x, y))
toSpeak = "Close menu button";
else if (containsReward && __instance.rewardBox.containsPoint(x, y))
toSpeak = "Left click to collect reward";
}
if (firstTimeInIndividualQuest || (questLogQuery != toSpeak))
{
questLogQuery = toSpeak;
MainClass.ScreenReader.Say(extra + " \n\t" + toSpeak, true);
if (firstTimeInIndividualQuest) firstTimeInIndividualQuest = false;
}
}
internal static void Cleaup()
{
questLogQuery = "";
isNarratingQuestInfo = false;
firstTimeInIndividualQuest = true;
}
}
}

View File

@ -0,0 +1,84 @@
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class SpecialOrdersBoardPatch
{
private static string specialOrdersBoardQueryKey = "";
internal static void DrawPatch(SpecialOrdersBoard __instance)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
if (__instance.acceptLeftQuestButton.visible && __instance.acceptLeftQuestButton.containsPoint(x, y))
{
string toSpeak = getSpecialOrderDetails(__instance.leftOrder);
toSpeak = $"Left Quest:\n\t{toSpeak}\n\tPress left click to accept this quest.";
Speak(toSpeak);
return;
}
if (__instance.acceptRightQuestButton.visible && __instance.acceptRightQuestButton.containsPoint(x, y))
{
string toSpeak = getSpecialOrderDetails(__instance.rightOrder);
toSpeak = $"Right Quest:\n\t{toSpeak}\n\tPress left click to accept this quest.";
Speak(toSpeak);
return;
}
}
catch (Exception e)
{
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}");
}
}
private static string getSpecialOrderDetails(SpecialOrder order)
{
int daysLeft = order.GetDaysLeft();
string description = order.GetDescription();
string objectiveDescription = "";
string name = order.GetName();
int moneyReward = order.GetMoneyReward();
// Get each objectives
for (int i = 0; i < order.GetObjectiveDescriptions().Count; i++)
{
objectiveDescription += order.GetObjectiveDescriptions()[i] + ", \n";
}
string toReturn = $"{name}\n\tDescription:{description}\n\tObjectives: {objectiveDescription}";
if (order.IsTimedQuest())
{
toReturn = $"{toReturn}\n\tTime: {daysLeft} days";
}
if (order.HasMoneyReward())
{
toReturn = $"{toReturn}\n\tReward: {moneyReward}g";
}
return toReturn;
}
private static void Speak(string toSpeak)
{
if (specialOrdersBoardQueryKey == toSpeak) return;
specialOrdersBoardQueryKey = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
internal static void Cleanup()
{
specialOrdersBoardQueryKey = "";
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,85 @@
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class AdvancedGameOptionsPatch
{
public static string advancedGameOptionsQueryKey = " ";
internal static void DrawPatch(AdvancedGameOptions __instance)
{
try
{
int currentItemIndex = Math.Max(0, Math.Min(__instance.options.Count - 7, __instance.currentItemIndex));
int x = Game1.getMouseX(true), y = Game1.getMouseY(true);
if (__instance.okButton != null && __instance.okButton.containsPoint(x, y))
{
string toSpeak = "OK Button";
if (advancedGameOptionsQueryKey != toSpeak)
{
advancedGameOptionsQueryKey = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
for (int i = 0; i < __instance.optionSlots.Count; i++)
{
if (!__instance.optionSlots[i].bounds.Contains(x, y)
|| currentItemIndex + i >= __instance.options.Count
|| !__instance.options[currentItemIndex + i].bounds.Contains(x - __instance.optionSlots[i].bounds.X, y - __instance.optionSlots[i].bounds.Y))
continue;
OptionsElement optionsElement = __instance.options[currentItemIndex + i];
string toSpeak = optionsElement.label;
if (optionsElement is OptionsButton)
toSpeak = $" {toSpeak} Button";
else if (optionsElement is OptionsCheckbox)
toSpeak = (((OptionsCheckbox)optionsElement).isChecked ? "Enabled" : "Disabled") + $" {toSpeak} Checkbox";
else if (optionsElement is OptionsDropDown)
toSpeak = $"{toSpeak} Dropdown, option {((OptionsDropDown)optionsElement).dropDownDisplayOptions[((OptionsDropDown)optionsElement).selectedOption]} selected";
else if (optionsElement is OptionsSlider)
toSpeak = $"{((OptionsSlider)optionsElement).value}% {toSpeak} Slider";
else if (optionsElement is OptionsPlusMinus)
toSpeak = $"{((OptionsPlusMinus)optionsElement).displayOptions[((OptionsPlusMinus)optionsElement).selected]} selected of {toSpeak}";
else if (optionsElement is OptionsInputListener)
{
string buttons = "";
((OptionsInputListener)optionsElement).buttonNames.ForEach(name => { buttons += $", {name}"; });
toSpeak = $"{toSpeak} is bound to {buttons}. Left click to change.";
}
else if (optionsElement is OptionsTextEntry)
{
toSpeak = $"Seed text box";
}
else
{
if (toSpeak.Contains(":"))
toSpeak = toSpeak.Replace(":", "");
toSpeak = $"{toSpeak} Options:";
}
if (advancedGameOptionsQueryKey != toSpeak)
{
advancedGameOptionsQueryKey = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
}
catch (Exception e)
{
MainClass.ErrorLog($"An error occured in advanced game menu patch:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void Cleanup()
{
advancedGameOptionsQueryKey = "";
}
}
}

View File

@ -1,8 +1,10 @@
using StardewValley; using StardewValley;
using StardewValley.Menus; using StardewValley.Menus;
namespace stardew_access.Patches { namespace stardew_access.Patches
internal class CharacterCustomizationPatches { {
internal class CharacterCustomizationMenuPatch
{
private static bool isRunning = false; private static bool isRunning = false;
private static int saveGameIndex = -1; private static int saveGameIndex = -1;
public static string characterCreationMenuQueryKey = " "; public static string characterCreationMenuQueryKey = " ";
@ -28,12 +30,14 @@ namespace stardew_access.Patches {
public static bool characterDesignToggleShouldSpeak = true; public static bool characterDesignToggleShouldSpeak = true;
public static ClickableComponent? currentComponent = null; public static ClickableComponent? currentComponent = null;
internal static void CharacterCustomizationMenuPatch(CharacterCustomization __instance, bool ___skipIntro, internal static void DrawPatch(CharacterCustomization __instance, bool ___skipIntro,
ClickableComponent ___startingCabinsLabel, ClickableComponent ___difficultyModifierLabel, TextBox ___nameBox, ClickableComponent ___startingCabinsLabel, ClickableComponent ___difficultyModifierLabel, TextBox ___nameBox,
TextBox ___farmnameBox, TextBox ___favThingBox) TextBox ___farmnameBox, TextBox ___favThingBox)
{ {
try try
{ {
if (TextBoxPatch.isAnyTextBoxActive) return;
bool isEscPressed = Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Escape); // For escaping/unselecting from the animal name text box bool isEscPressed = Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Escape); // For escaping/unselecting from the animal name text box
string toSpeak = ""; string toSpeak = "";
if (characterDesignToggleShouldSpeak) if (characterDesignToggleShouldSpeak)
@ -44,37 +48,10 @@ namespace stardew_access.Patches {
string itemsToSpeak = ""; string itemsToSpeak = "";
string changesToSpeak = ""; string changesToSpeak = "";
if (___nameBox.Selected) if (MainClass.Config.CharacterCreationMenuNextKey.JustPressed() && !isRunning)
{
toSpeak = ___nameBox.Text;
if (isEscPressed)
{
___nameBox.Selected = false;
}
}
else if (___farmnameBox.Selected)
{
toSpeak = ___farmnameBox.Text;
if (isEscPressed)
{
___farmnameBox.Selected = false;
}
}
else if (___favThingBox.Selected)
{
toSpeak = ___favThingBox.Text;
if (isEscPressed)
{
___favThingBox.Selected = false;
}
}
else if (MainClass.Config.CharacterCreationMenuNextKey.JustPressed() && !isRunning)
{ {
isRunning = true; isRunning = true;
itemsToSpeak =CycleThroughItems(true, __instance, ___skipIntro, ___startingCabinsLabel, ___difficultyModifierLabel, ___nameBox, ___farmnameBox, ___favThingBox); itemsToSpeak = CycleThroughItems(true, __instance, ___skipIntro, ___startingCabinsLabel, ___difficultyModifierLabel, ___nameBox, ___farmnameBox, ___favThingBox);
if (itemsToSpeak != "") if (itemsToSpeak != "")
toSpeak = $"{itemsToSpeak} \n {toSpeak}"; toSpeak = $"{itemsToSpeak} \n {toSpeak}";
Task.Delay(200).ContinueWith(_ => { isRunning = false; }); Task.Delay(200).ContinueWith(_ => { isRunning = false; });
@ -122,7 +99,9 @@ namespace stardew_access.Patches {
if (characterDesignToggle) if (characterDesignToggle)
{ {
displayState = "shown"; displayState = "shown";
} else { }
else
{
displayState = "hidden"; displayState = "hidden";
} }
toSpeak = $"Character design controls {displayState}. \n {toSpeak}"; toSpeak = $"Character design controls {displayState}. \n {toSpeak}";
@ -140,7 +119,7 @@ namespace stardew_access.Patches {
} }
catch (Exception e) catch (Exception e)
{ {
MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}"); MainClass.ErrorLog($"An error occured in character customization menu patch:\n{e.Message}\n{e.StackTrace}");
} }
} }
@ -210,7 +189,9 @@ namespace stardew_access.Patches {
prevEyeColorHue = currentEyeColorHue; prevEyeColorHue = currentEyeColorHue;
if (currentEyeColorHue != "") if (currentEyeColorHue != "")
toSpeak = $"{toSpeak} \n Hue: {currentEyeColorHue}"; toSpeak = $"{toSpeak} \n Hue: {currentEyeColorHue}";
} else { }
else
{
prevEyeColorHue = ""; prevEyeColorHue = "";
} }
} }
@ -222,7 +203,9 @@ namespace stardew_access.Patches {
prevEyeColorSaturation = currentEyeColorSaturation; prevEyeColorSaturation = currentEyeColorSaturation;
if (currentEyeColorSaturation != "") if (currentEyeColorSaturation != "")
toSpeak = $"{toSpeak} \n Saturation: {currentEyeColorSaturation}"; toSpeak = $"{toSpeak} \n Saturation: {currentEyeColorSaturation}";
} else { }
else
{
prevEyeColorSaturation = ""; prevEyeColorSaturation = "";
} }
} }
@ -234,7 +217,9 @@ namespace stardew_access.Patches {
prevEyeColorValue = currentEyeColorValue; prevEyeColorValue = currentEyeColorValue;
if (currentEyeColorValue != "") if (currentEyeColorValue != "")
toSpeak = $"{toSpeak} \n Value: {currentEyeColorValue}"; toSpeak = $"{toSpeak} \n Value: {currentEyeColorValue}";
} else { }
else
{
prevEyeColorValue = ""; prevEyeColorValue = "";
} }
} }
@ -256,7 +241,9 @@ namespace stardew_access.Patches {
prevHairColorHue = currentHairColorHue; prevHairColorHue = currentHairColorHue;
if (currentHairColorHue != "") if (currentHairColorHue != "")
toSpeak = $"{toSpeak} \n Hue: {currentHairColorHue}"; toSpeak = $"{toSpeak} \n Hue: {currentHairColorHue}";
} else { }
else
{
prevHairColorHue = ""; prevHairColorHue = "";
} }
} }
@ -268,7 +255,9 @@ namespace stardew_access.Patches {
prevHairColorSaturation = currentHairColorSaturation; prevHairColorSaturation = currentHairColorSaturation;
if (currentHairColorSaturation != "") if (currentHairColorSaturation != "")
toSpeak = $"{toSpeak} \n Saturation: {currentHairColorSaturation}"; toSpeak = $"{toSpeak} \n Saturation: {currentHairColorSaturation}";
} else { }
else
{
prevHairColorSaturation = ""; prevHairColorSaturation = "";
} }
} }
@ -280,7 +269,9 @@ namespace stardew_access.Patches {
prevHairColorValue = currentHairColorValue; prevHairColorValue = currentHairColorValue;
if (currentHairColorValue != "") if (currentHairColorValue != "")
toSpeak = $"{toSpeak} \n Value: {currentHairColorValue}"; toSpeak = $"{toSpeak} \n Value: {currentHairColorValue}";
} else { }
else
{
prevHairColorValue = ""; prevHairColorValue = "";
} }
} }
@ -302,7 +293,9 @@ namespace stardew_access.Patches {
prevPantsColorHue = currentPantsColorHue; prevPantsColorHue = currentPantsColorHue;
if (currentPantsColorHue != "") if (currentPantsColorHue != "")
toSpeak = $"{toSpeak} \n Hue: {currentPantsColorHue}"; toSpeak = $"{toSpeak} \n Hue: {currentPantsColorHue}";
} else { }
else
{
prevPantsColorHue = ""; prevPantsColorHue = "";
} }
} }
@ -314,7 +307,9 @@ namespace stardew_access.Patches {
prevPantsColorSaturation = currentPantsColorSaturation; prevPantsColorSaturation = currentPantsColorSaturation;
if (currentPantsColorSaturation != "") if (currentPantsColorSaturation != "")
toSpeak = $"{toSpeak} \n Saturation: {currentPantsColorSaturation}"; toSpeak = $"{toSpeak} \n Saturation: {currentPantsColorSaturation}";
} else { }
else
{
prevPantsColorSaturation = ""; prevPantsColorSaturation = "";
} }
} }
@ -326,7 +321,9 @@ namespace stardew_access.Patches {
prevPantsColorValue = currentPantsColorValue; prevPantsColorValue = currentPantsColorValue;
if (currentPantsColorValue != "") if (currentPantsColorValue != "")
toSpeak = $"{toSpeak} \n Value: {currentPantsColorValue}"; toSpeak = $"{toSpeak} \n Value: {currentPantsColorValue}";
} else { }
else
{
prevPantsColorValue = ""; prevPantsColorValue = "";
} }
} }
@ -345,13 +342,12 @@ namespace stardew_access.Patches {
if (prevPetName != currentPetName) if (prevPetName != currentPetName)
{ {
prevPetName = currentPetName; prevPetName = currentPetName;
if (currentPetName != "") if (currentPetName != "")
toSpeak = $"{toSpeak} \n Current Pet: {currentPetName}"; toSpeak = $"{toSpeak} \n Current Pet: {currentPetName}";
} }
return toSpeak.Trim(); return toSpeak.Trim();
} }
private static string CycleThroughItems(bool increase, CharacterCustomization __instance, bool ___skipIntro, private static string CycleThroughItems(bool increase, CharacterCustomization __instance, bool ___skipIntro,
ClickableComponent ___startingCabinsLabel, ClickableComponent ___difficultyModifierLabel, TextBox ___nameBox, ClickableComponent ___startingCabinsLabel, ClickableComponent ___difficultyModifierLabel, TextBox ___nameBox,
TextBox ___farmnameBox, TextBox ___favThingBox) TextBox ___farmnameBox, TextBox ___favThingBox)
@ -369,7 +365,9 @@ namespace stardew_access.Patches {
if (___nameBox.Text != "") if (___nameBox.Text != "")
{ {
postText = $": {___nameBox.Text}"; postText = $": {___nameBox.Text}";
} else { }
else
{
postText = " Text Box"; postText = " Text Box";
} }
buttons.Add(__instance.nameBoxCC, $"Farmer's Name{postText}"); buttons.Add(__instance.nameBoxCC, $"Farmer's Name{postText}");
@ -380,7 +378,9 @@ namespace stardew_access.Patches {
if (___farmnameBox.Text != "") if (___farmnameBox.Text != "")
{ {
postText = $": {___farmnameBox.Text}"; postText = $": {___farmnameBox.Text}";
} else { }
else
{
postText = " Text Box"; postText = " Text Box";
} }
buttons.Add(__instance.farmnameBoxCC, $"Farm's Name{postText}"); buttons.Add(__instance.farmnameBoxCC, $"Farm's Name{postText}");
@ -391,7 +391,9 @@ namespace stardew_access.Patches {
if (___favThingBox.Text != "") if (___favThingBox.Text != "")
{ {
postText = $": {___favThingBox.Text}"; postText = $": {___favThingBox.Text}";
} else { }
else
{
postText = " Text Box"; postText = " Text Box";
} }
buttons.Add(__instance.favThingBoxCC, $"Favourite Thing{postText}"); buttons.Add(__instance.favThingBoxCC, $"Favourite Thing{postText}");
@ -411,10 +413,10 @@ namespace stardew_access.Patches {
// Controls to rotate the farmer (Potentially useful for low vision players) are first if they're available. // Controls to rotate the farmer (Potentially useful for low vision players) are first if they're available.
// They also appear above the gender buttons, so we handle them separately here. // They also appear above the gender buttons, so we handle them separately here.
if (characterDesignToggle && new[] {__instance.leftSelectionButtons.Count, __instance.rightSelectionButtons.Count }.All(c => c >= 0)) // both have Count > 0 if (characterDesignToggle && new[] { __instance.leftSelectionButtons.Count, __instance.rightSelectionButtons.Count }.All(c => c >= 0)) // both have Count > 0
{ {
if (new[] {__instance.leftSelectionButtons[DesignControlsIndex].visible, __instance.rightSelectionButtons[DesignControlsIndex].visible }.All(v => v == true) // both visible if (new[] { __instance.leftSelectionButtons[DesignControlsIndex].visible, __instance.rightSelectionButtons[DesignControlsIndex].visible }.All(v => v == true) // both visible
&& new[] {__instance.leftSelectionButtons[DesignControlsIndex].name, __instance.rightSelectionButtons[DesignControlsIndex].name }.All(n => n == "Direction")) // both named "Direction" && new[] { __instance.leftSelectionButtons[DesignControlsIndex].name, __instance.rightSelectionButtons[DesignControlsIndex].name }.All(n => n == "Direction")) // both named "Direction"
{ {
buttons.Add(__instance.leftSelectionButtons[DesignControlsIndex], "Rotate Left Button"); buttons.Add(__instance.leftSelectionButtons[DesignControlsIndex], "Rotate Left Button");
buttons.Add(__instance.rightSelectionButtons[DesignControlsIndex], "Rotate Right Button"); buttons.Add(__instance.rightSelectionButtons[DesignControlsIndex], "Rotate Right Button");
@ -428,9 +430,9 @@ namespace stardew_access.Patches {
buttons.Add(__instance.genderButtons[1], ((!Game1.player.IsMale) ? "Selected " : "") + "Gender: Female Button"); buttons.Add(__instance.genderButtons[1], ((!Game1.player.IsMale) ? "Selected " : "") + "Gender: Female Button");
} }
if (characterDesignToggle&& new[] {__instance.leftSelectionButtons.Count, __instance.rightSelectionButtons.Count }.All(c => c >= DesignControlsIndex) && new[] {__instance.leftSelectionButtons[DesignControlsIndex].visible, __instance.rightSelectionButtons[DesignControlsIndex].visible }.All(v => v == true)) if (characterDesignToggle && new[] { __instance.leftSelectionButtons.Count, __instance.rightSelectionButtons.Count }.All(c => c >= DesignControlsIndex) && new[] { __instance.leftSelectionButtons[DesignControlsIndex].visible, __instance.rightSelectionButtons[DesignControlsIndex].visible }.All(v => v == true))
{ {
while(DesignControlsIndex < __instance.leftSelectionButtons.Count) while (DesignControlsIndex < __instance.leftSelectionButtons.Count)
{ {
ClickableComponent left = __instance.leftSelectionButtons[DesignControlsIndex]; ClickableComponent left = __instance.leftSelectionButtons[DesignControlsIndex];
ClickableComponent right = __instance.rightSelectionButtons[DesignControlsIndex]; ClickableComponent right = __instance.rightSelectionButtons[DesignControlsIndex];
@ -574,7 +576,7 @@ namespace stardew_access.Patches {
} }
currentComponent = buttons.ElementAt(saveGameIndex).Key; currentComponent = buttons.ElementAt(saveGameIndex).Key;
currentComponent!.snapMouseCursor(); currentComponent!.snapMouseCursor();
__instance.setCurrentlySnappedComponentTo(currentComponent!.myID); __instance.setCurrentlySnappedComponentTo(currentComponent!.myID);
toSpeak = buttons.ElementAt(saveGameIndex).Value; toSpeak = buttons.ElementAt(saveGameIndex).Value;
@ -655,16 +657,18 @@ namespace stardew_access.Patches {
break; break;
} }
return sb; return sb;
} else { }
else
{
return null; return null;
} }
} }
private static void AdjustCurrentSlider(bool increase, CharacterCustomization __instance, int amount=1) private static void AdjustCurrentSlider(bool increase, CharacterCustomization __instance, int amount = 1)
{ {
if (currentComponent != null && currentComponent.myID >= 522 && currentComponent.myID <= 530) if (currentComponent != null && currentComponent.myID >= 522 && currentComponent.myID <= 530)
{ {
SliderBar sb = getCurrentSliderBar(currentComponent.myID, __instance) !; SliderBar sb = getCurrentSliderBar(currentComponent.myID, __instance)!;
if (sb != null) if (sb != null)
{ {
double step = ((double)sb.bounds.Width / 100d); // size of 1% change in slider value double step = ((double)sb.bounds.Width / 100d); // size of 1% change in slider value
@ -675,7 +679,9 @@ namespace stardew_access.Patches {
{ {
value = Math.Min(value + amount, 99d); value = Math.Min(value + amount, 99d);
x = Math.Min(Math.Ceiling((value * step)), (double)sb.bounds.Width); x = Math.Min(Math.Ceiling((value * step)), (double)sb.bounds.Width);
} else { }
else
{
value = Math.Max(value - amount, 0d); value = Math.Max(value - amount, 0d);
x = Math.Max(Math.Ceiling((value * step)), 0d); x = Math.Max(Math.Ceiling((value * step)), 0d);
} }
@ -821,7 +827,9 @@ namespace stardew_access.Patches {
if (currentComponent != null && currentComponent.name == "Pet") if (currentComponent != null && currentComponent.name == "Pet")
{ {
return ((Game1.player.catPerson) ? "Cat" : "Dog") + " Breed: " + Game1.player.whichPetBreed; return ((Game1.player.catPerson) ? "Cat" : "Dog") + " Breed: " + Game1.player.whichPetBreed;
} else { }
else
{
return ""; return "";
} }
} }

View File

@ -0,0 +1,59 @@
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class CoopMenuPatch
{
private static string coopMenuQueryKey = "";
internal static void DrawPatch(CoopMenu __instance, CoopMenu.Tab ___currentTab)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true);
string toSpeak = "";
#region Join/Host Button (Important! This should be checked before checking other buttons)
if (__instance.slotButtons[0].containsPoint(x, y))
{
if (___currentTab == CoopMenu.Tab.JOIN_TAB)
toSpeak = "Join lan game";
if (___currentTab == CoopMenu.Tab.HOST_TAB)
toSpeak = "Host new farm";
}
#endregion
#region Other Buttons
if (__instance.joinTab.containsPoint(x, y))
{
toSpeak = "Join Tab Button";
}
else if (__instance.hostTab.containsPoint(x, y))
{
toSpeak = "Host Tab Button";
}
else if (__instance.refreshButton.containsPoint(x, y))
{
toSpeak = "Refresh Button";
}
#endregion
if (coopMenuQueryKey != toSpeak)
{
coopMenuQueryKey = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
}
catch (Exception e)
{
MainClass.ErrorLog($"An error occured in co-op menu patch:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void Cleanup()
{
coopMenuQueryKey = "";
}
}
}

View File

@ -0,0 +1,86 @@
using StardewValley;
using StardewValley.Menus;
using static StardewValley.Menus.LoadGameMenu;
namespace stardew_access.Patches
{
internal class LoadGameMenuPatch
{
private static string loadGameMenuQueryKey = "";
private static bool firstTimeInMenu = true;
internal static void DrawPatch(SaveFileSlot __instance, LoadGameMenu ___menu, int i)
{
try
{
int x = Game1.getMouseX(true), y = Game1.getMouseY(true);
string toSpeak = "";
if (!___menu.slotButtons[i].containsPoint(x, y)) return;
if (__instance.Farmer == null) return;
if (___menu.deleteButtons.Count > 0 && ___menu.deleteButtons[i].containsPoint(x, y))
{
toSpeak = $"Delete {__instance.Farmer.farmName.Value} Farm";
if (loadGameMenuQueryKey != toSpeak)
{
loadGameMenuQueryKey = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
if (___menu.deleteConfirmationScreen)
{
if (firstTimeInMenu)
{
firstTimeInMenu = false;
toSpeak = "Really delete farm?";
}
if (___menu.okDeleteButton.containsPoint(x, y))
{
toSpeak = $"{toSpeak} Ok button";
}
else if (___menu.cancelDeleteButton.containsPoint(x, y))
{
toSpeak = $"{toSpeak} Cancel button";
}
if (loadGameMenuQueryKey != toSpeak)
{
loadGameMenuQueryKey = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
return;
}
String farmerName = __instance.Farmer.displayName;
String farmName = __instance.Farmer.farmName.Value;
String money = __instance.Farmer.Money.ToString();
String hoursPlayed = Utility.getHoursMinutesStringFromMilliseconds(__instance.Farmer.millisecondsPlayed);
string dateStringForSaveGame = ((!__instance.Farmer.dayOfMonthForSaveGame.HasValue ||
!__instance.Farmer.seasonForSaveGame.HasValue ||
!__instance.Farmer.yearForSaveGame.HasValue) ? __instance.Farmer.dateStringForSaveGame : Utility.getDateStringFor(__instance.Farmer.dayOfMonthForSaveGame.Value, __instance.Farmer.seasonForSaveGame.Value, __instance.Farmer.yearForSaveGame.Value));
toSpeak = $"{farmName} Farm Selected, \t\n Farmer: {farmerName}, \t\nMoney: {money}, \t\nHours Played: {hoursPlayed}, \t\nDate: {dateStringForSaveGame}";
if (loadGameMenuQueryKey != toSpeak)
{
loadGameMenuQueryKey = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
}
catch (Exception e)
{
MainClass.ErrorLog($"An error occured in load game menu patch:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void Cleanup()
{
loadGameMenuQueryKey = "";
firstTimeInMenu = true;
}
}
}

View File

@ -0,0 +1,81 @@
using StardewValley;
using StardewValley.Menus;
namespace stardew_access.Patches
{
internal class TitleMenuPatch
{
private static string titleMenuQueryKey = "";
internal static void DrawPatch(TitleMenu __instance, bool ___isTransitioningButtons)
{
try
{
if (___isTransitioningButtons)
return;
int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position
string toSpeak = "";
if (__instance.muteMusicButton.containsPoint(x, y))
{
toSpeak = "Mute Music Button";
}
else if (__instance.aboutButton.containsPoint(x, y))
{
toSpeak = "About Button";
}
else if (__instance.languageButton.containsPoint(x, y))
{
toSpeak = "Language Button";
}
else if (__instance.windowedButton.containsPoint(x, y))
{
toSpeak = "Fullscreen: " + ((Game1.isFullscreen) ? "enabled" : "disabled");
}
else if (TitleMenu.subMenu != null && __instance.backButton.containsPoint(x, y))
{
string text = "Back Button";
MainClass.ScreenReader.SayWithChecker(text, true);
}
else
{
__instance.buttons.ForEach(component =>
{
if (!component.containsPoint(Game1.getMouseX(true), Game1.getMouseY(true)))
return;
string name = component.name;
string label = component.label;
toSpeak = $"{name} {label} Button";
});
}
// Fix for back button not working using keyboard
if (TitleMenu.subMenu is CharacterCustomization && ((CharacterCustomization)TitleMenu.subMenu).backButton.containsPoint(x, y))
{
// Perform Left Click
if (MainClass.Config.LeftClickMainKey.JustPressed())
{
__instance.backButtonPressed();
}
}
if (TitleMenu.subMenu == null && titleMenuQueryKey!=toSpeak)
{
titleMenuQueryKey = toSpeak;
MainClass.ScreenReader.Say(toSpeak, true);
}
}
catch (Exception e)
{
MainClass.ErrorLog($"An error occured in title menu patch:\n{e.Message}\n{e.StackTrace}");
}
}
internal static void Cleanup()
{
titleMenuQueryKey = "";
}
}
}