diff --git a/LICENSE-NVDA b/LICENSE-NVDA new file mode 100644 index 0000000..602bfc9 --- /dev/null +++ b/LICENSE-NVDA @@ -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. + + + Copyright (C) + + 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. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/LICENSE-TOLK b/LICENSE-TOLK new file mode 100644 index 0000000..3a3b445 --- /dev/null +++ b/LICENSE-TOLK @@ -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. \ No newline at end of file diff --git a/invalid-selection-license.txt b/invalid-selection-license.txt new file mode 100644 index 0000000..f1b27d8 --- /dev/null +++ b/invalid-selection-license.txt @@ -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 ==== \ No newline at end of file diff --git a/stardew-access/CustomCommands.cs b/stardew-access/CustomCommands.cs index 557d7bf..8d10fc2 100644 --- a/stardew-access/CustomCommands.cs +++ b/stardew-access/CustomCommands.cs @@ -1,4 +1,5 @@ using Microsoft.Xna.Framework; +using stardew_access.Features; using stardew_access.Patches; using StardewModdingAPI; using StardewValley; @@ -327,18 +328,18 @@ namespace stardew_access 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."); }); helper.ConsoleCommands.Add("marklist", "List all marked positions.", (string commmand, string[] args) => { 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) => { - 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."); return; @@ -380,12 +381,12 @@ namespace stardew_access string? positionIndexInString = args.ElementAtOrDefault(1); 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."); 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."); return; @@ -416,7 +417,7 @@ namespace stardew_access } else { - if (BuildingNAnimalMenuPatches.availableBuildings[index] == null) + if (BuildingOperations.availableBuildings[index] == null) { MainClass.InfoLog($"No building found with index {index}. Use buildlist."); return; @@ -427,19 +428,19 @@ namespace stardew_access if (Game1.activeClickableMenu is PurchaseAnimalsMenu) { - BuildingNAnimalMenuPatches.PurchaseAnimal(BuildingNAnimalMenuPatches.availableBuildings[index]); + BuildingOperations.PurchaseAnimal(BuildingOperations.availableBuildings[index]); } else if (Game1.activeClickableMenu is AnimalQueryMenu) { - BuildingNAnimalMenuPatches.MoveAnimal(BuildingNAnimalMenuPatches.availableBuildings[index]); + BuildingOperations.MoveAnimal(BuildingOperations.availableBuildings[index]); } else { - if (BuildingNAnimalMenuPatches.isConstructing && !BuildingNAnimalMenuPatches.isUpgrading) { response = BuildingNAnimalMenuPatches.Contstruct(BuildingNAnimalMenuPatches.marked[index]); } - else if (BuildingNAnimalMenuPatches.isMoving) { response = BuildingNAnimalMenuPatches.Move(BuildingNAnimalMenuPatches.availableBuildings[index], BuildingNAnimalMenuPatches.marked[positionIndex]); } - else if (BuildingNAnimalMenuPatches.isDemolishing) { response = BuildingNAnimalMenuPatches.Demolish(BuildingNAnimalMenuPatches.availableBuildings[index]); } - else if (BuildingNAnimalMenuPatches.isUpgrading) { response = BuildingNAnimalMenuPatches.Upgrade(BuildingNAnimalMenuPatches.availableBuildings[index]); } - else if (BuildingNAnimalMenuPatches.isPainting) { response = BuildingNAnimalMenuPatches.Paint(BuildingNAnimalMenuPatches.availableBuildings[index]); } + if (CarpenterMenuPatch.isConstructing && !CarpenterMenuPatch.isUpgrading) { response = BuildingOperations.Contstruct(BuildingOperations.marked[index]); } + else if (CarpenterMenuPatch.isMoving) { response = BuildingOperations.Move(BuildingOperations.availableBuildings[index], BuildingOperations.marked[positionIndex]); } + else if (CarpenterMenuPatch.isDemolishing) { response = BuildingOperations.Demolish(BuildingOperations.availableBuildings[index]); } + else if (CarpenterMenuPatch.isUpgrading) { response = BuildingOperations.Upgrade(BuildingOperations.availableBuildings[index]); } + else if (CarpenterMenuPatch.isPainting) { response = BuildingOperations.Paint(BuildingOperations.availableBuildings[index]); } } if (response != null) @@ -517,7 +518,7 @@ namespace stardew_access string? name = buildings[i].nameOfIndoorsWithoutUnique; 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"; ++buildingIndex; } diff --git a/stardew-access/CustomSoundEffects.cs b/stardew-access/CustomSoundEffects.cs index 9303f32..f9df3d1 100644 --- a/stardew-access/CustomSoundEffects.cs +++ b/stardew-access/CustomSoundEffects.cs @@ -22,6 +22,7 @@ namespace stardew_access soundEffects.Add("drop_item", TYPE.Sound); soundEffects.Add("colliding", TYPE.Sound); + soundEffects.Add("invalid-selection", TYPE.Sound); soundEffects.Add("bobber_target_up", TYPE.Sound); soundEffects.Add("bobber_target_down", TYPE.Sound); diff --git a/stardew-access/Features/BuildingOperations.cs b/stardew-access/Features/BuildingOperations.cs new file mode 100644 index 0000000..f970ba3 --- /dev/null +++ b/stardew-access/Features/BuildingOperations.cs @@ -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 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; + } + } +} diff --git a/stardew-access/Features/InventoryUtils.cs b/stardew-access/Features/InventoryUtils.cs new file mode 100644 index 0000000..8b8e913 --- /dev/null +++ b/stardew-access/Features/InventoryUtils.cs @@ -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 inventory, IList 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 inventory, IList 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; + } + } +} diff --git a/stardew-access/Features/StaticTiles.cs b/stardew-access/Features/StaticTiles.cs index 87fa154..9b36631 100644 --- a/stardew-access/Features/StaticTiles.cs +++ b/stardew-access/Features/StaticTiles.cs @@ -77,18 +77,17 @@ namespace stardew_access.Features 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 allData = new List(); if (customTilesData != null) allData.Add(customTilesData); if (staticTilesData != null) allData.Add(staticTilesData); - foreach (JObject data in allData) - { + foreach (JObject data in allData) { foreach (KeyValuePair location in data) { - if (location.Key.Contains("||") && MainClass.ModHelper != null) + string locationName = location.Key; + if (locationName.Contains("||") && MainClass.ModHelper != null) { // Mod Specific Tiles // 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 locationName = location.Key.Remove(location.Key.LastIndexOf("||")); + string uniqueModID = locationName.Substring(locationName.LastIndexOf("||") + 2); + locationName = locationName.Remove(locationName.LastIndexOf("||")); bool isLoaded = MainClass.ModHelper.ModRegistry.IsLoaded(uniqueModID); 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) - foreach (var tile in ((JObject)location.Value)) + if (locationName.Contains("_") && locationName.ToLower().StartsWith("farm_")) + { + 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; - 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 (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())); - } + isXPresent = true; + break; } + + 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); } + + private int getFarmTypeIndex(string farmType) + { + return farmType.ToLower() switch + { + "default" => 0, + "riverlands" => 1, + "forest" => 2, + "mountains" => 3, + "combat" => 4, + "fourcorners" => 5, + "beach" => 6, + _ => 7, + }; + } } } diff --git a/stardew-access/Features/TileInfo.cs b/stardew-access/Features/TileInfo.cs index 53dff70..74adb00 100644 --- a/stardew-access/Features/TileInfo.cs +++ b/stardew-access/Features/TileInfo.cs @@ -455,15 +455,19 @@ namespace stardew_access.Features else if (Game1.currentLocation is FarmHouse farmHouse) { if (farmHouse.upgradeLevel >= 1) - if (farmHouse.getKitchenStandingSpot().X == x && (farmHouse.getKitchenStandingSpot().Y - 1) == y) // Standing spot is where the player will stand - return (CATEGORY.Interactables, "Kitchen"); + if (farmHouse.getKitchenStandingSpot().X == x && (farmHouse.getKitchenStandingSpot().Y - 1) == y) + 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) return (CATEGORY.Interactables, "Fridge"); } else if (Game1.currentLocation is IslandFarmHouse islandFarmHouse) { - if ((islandFarmHouse.fridgePosition.X - 1) == x && islandFarmHouse.fridgePosition.Y == y) - return (CATEGORY.Interactables, "Kitchen"); + if ((islandFarmHouse.fridgePosition.X - 2) == x && islandFarmHouse.fridgePosition.Y == y) + 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) return (CATEGORY.Interactables, "Fridge"); } @@ -520,7 +524,7 @@ namespace stardew_access.Features } 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"); } else if (Game1.currentLocation is IslandNorth islandNorth) diff --git a/stardew-access/HarmonyPatches.cs b/stardew-access/HarmonyPatches.cs index fd7be52..44447dd 100644 --- a/stardew-access/HarmonyPatches.cs +++ b/stardew-access/HarmonyPatches.cs @@ -16,220 +16,216 @@ namespace stardew_access #region Dialogue Patches harmony.Patch( 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( 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( 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) }), - postfix: new HarmonyMethod(typeof(DialoguePatches), nameof(DialoguePatches.HoverTextPatch)) + postfix: new HarmonyMethod(typeof(IClickableMenuPatch), nameof(IClickableMenuPatch.DrawHoverTextPatch)) ); harmony.Patch( 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 #region Title Menu Patches harmony.Patch( 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( 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( 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( 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( 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 #region Game Menu Patches harmony.Patch( 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( 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( 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( 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( 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( 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( 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( 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( 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( 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 #region Bundle Menu Patches harmony.Patch( 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( 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 #region Menu Patches harmony.Patch( 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( 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( 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( 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( 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( 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( 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( original: AccessTools.Method(typeof(LanguageSelectionMenu), nameof(LanguageSelectionMenu.draw), new Type[] { typeof(SpriteBatch) }), - postfix: new HarmonyMethod(typeof(MenuPatches), nameof(MenuPatches.LanguageSelectionMenuPatch)) - ); - - harmony.Patch( - original: AccessTools.Method(typeof(MuseumMenu), nameof(MuseumMenu.receiveKeyPress), new Type[] { typeof(Keys) }), - prefix: new HarmonyMethod(typeof(DonationMenuPatches), nameof(DonationMenuPatches.MuseumMenuKeyPressPatch)) + postfix: new HarmonyMethod(typeof(LanguageSelectionMenuPatch), nameof(LanguageSelectionMenuPatch.DrawPatch)) ); harmony.Patch( 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( 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( 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( 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( 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 #region Quest Patches harmony.Patch( 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( 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( 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 #region Chat Menu Patches harmony.Patch( 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 #region On Menu CLose Patch harmony.Patch( 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( 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 @@ -237,17 +233,17 @@ namespace stardew_access harmony.Patch( 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( 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( 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 @@ -255,24 +251,29 @@ namespace stardew_access #region Donation Menus harmony.Patch( 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( 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 #region Mini Games harmony.Patch( 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( 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( @@ -284,12 +285,17 @@ namespace stardew_access harmony.Patch( 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( 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)) ); } } diff --git a/stardew-access/ModConfig.cs b/stardew-access/ModConfig.cs index fcd3ef7..c16320a 100644 --- a/stardew-access/ModConfig.cs +++ b/stardew-access/ModConfig.cs @@ -85,6 +85,7 @@ namespace stardew_access public Boolean Warning { get; set; } = true; // Toggles the warnings feature public Boolean TTS { get; set; } = true; // Toggles the screen reader/tts. 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 public int MaximumFishingDifficulty { get; set; } = 999; // TODO Add doc diff --git a/stardew-access/ModEntry.cs b/stardew-access/ModEntry.cs index 7049d76..1830c50 100644 --- a/stardew-access/ModEntry.cs +++ b/stardew-access/ModEntry.cs @@ -13,7 +13,8 @@ namespace stardew_access public class MainClass : Mod { #region Global Vars & Properties -#pragma warning disable CS8603 + + #pragma warning disable CS8603 private static int prevDate = -99; private static ModConfig? config; private Harmony? harmony; @@ -99,7 +100,6 @@ namespace stardew_access return warnings; } } -#pragma warning restore CS8603 #endregion /********* @@ -128,14 +128,14 @@ namespace stardew_access HarmonyPatches.Initialize(harmony); //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 @@ -210,54 +210,32 @@ namespace stardew_access return; #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); #region Mouse Click Simulation - // Main Keybinds - if (Config.LeftClickMainKey.JustPressed()) + if (Config.LeftClickMainKey.JustPressed() || Config.LeftClickAlternateKey.JustPressed()) { 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 (!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 + if (Config.RightClickMainKey.JustPressed() || Config.RightClickAlternateKey.JustPressed()) { Game1.activeClickableMenu.receiveRightClick(Game1.getMouseX(true), Game1.getMouseY(true)); } #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 - // Main Keybinds - if (Config.LeftClickMainKey.JustPressed()) + if (Config.LeftClickMainKey.JustPressed() || Config.LeftClickAlternateKey.JustPressed()) { 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.LeftClickAlternateKey.JustPressed()) - { - Game1.currentMinigame.receiveLeftClick(Game1.getMouseX(true), Game1.getMouseY(true)); - } - if (Config.RightClickAlternateKey.JustPressed()) + if (Config.RightClickMainKey.JustPressed() || Config.RightClickAlternateKey.JustPressed()) { Game1.currentMinigame.receiveRightClick(Game1.getMouseX(true), Game1.getMouseY(true)); } @@ -377,4 +355,4 @@ namespace stardew_access monitor.Log(message, LogLevel.Debug); } } -} \ No newline at end of file +} diff --git a/stardew-access/Patches/BuildingNAnimalMenuPatches.cs b/stardew-access/Patches/BuildingNAnimalMenuPatches.cs deleted file mode 100644 index 910e45b..0000000 --- a/stardew-access/Patches/BuildingNAnimalMenuPatches.cs +++ /dev/null @@ -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 ___ingredients, int ___price, - List ___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 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; - } - } -} \ No newline at end of file diff --git a/stardew-access/Patches/BundleMenuPatches.cs b/stardew-access/Patches/BundleMenuPatches.cs deleted file mode 100644 index fc3dc6c..0000000 --- a/stardew-access/Patches/BundleMenuPatches.cs +++ /dev/null @@ -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 - } -} \ No newline at end of file diff --git a/stardew-access/Patches/BundleMenuPatches/JojaCDMenuPatch.cs b/stardew-access/Patches/BundleMenuPatches/JojaCDMenuPatch.cs new file mode 100644 index 0000000..2a8e1b6 --- /dev/null +++ b/stardew-access/Patches/BundleMenuPatches/JojaCDMenuPatch.cs @@ -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 = ""; + } + } +} diff --git a/stardew-access/Patches/BundleMenuPatches/JunimoNoteMenuPatch.cs b/stardew-access/Patches/BundleMenuPatches/JunimoNoteMenuPatch.cs new file mode 100644 index 0000000..96d89c5 --- /dev/null +++ b/stardew-access/Patches/BundleMenuPatches/JunimoNoteMenuPatch.cs @@ -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 = ""; + } + } +} diff --git a/stardew-access/Patches/DialoguePatches.cs b/stardew-access/Patches/DialoguePatches.cs deleted file mode 100644 index e131952..0000000 --- a/stardew-access/Patches/DialoguePatches.cs +++ /dev/null @@ -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 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}"); - } - } - } -} diff --git a/stardew-access/Patches/DonationMenuPatches.cs b/stardew-access/Patches/DonationMenuPatches.cs deleted file mode 100644 index f4969ca..0000000 --- a/stardew-access/Patches/DonationMenuPatches.cs +++ /dev/null @@ -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 inventory, IList 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>("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 - } -} \ No newline at end of file diff --git a/stardew-access/Patches/DonationMenuPatches/FieldOfficeMenuPatch.cs b/stardew-access/Patches/DonationMenuPatches/FieldOfficeMenuPatch.cs new file mode 100644 index 0000000..5eec4ba --- /dev/null +++ b/stardew-access/Patches/DonationMenuPatches/FieldOfficeMenuPatch.cs @@ -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 = ""; + } + } +} diff --git a/stardew-access/Patches/DonationMenuPatches/MuseumMenuPatch.cs b/stardew-access/Patches/DonationMenuPatches/MuseumMenuPatch.cs new file mode 100644 index 0000000..bd76c97 --- /dev/null +++ b/stardew-access/Patches/DonationMenuPatches/MuseumMenuPatch.cs @@ -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>("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 = ""; + } + } +} diff --git a/stardew-access/Patches/GameMenuPatches.cs b/stardew-access/Patches/GameMenuPatches.cs deleted file mode 100644 index e1c2d46..0000000 --- a/stardew-access/Patches/GameMenuPatches.cs +++ /dev/null @@ -1,1339 +0,0 @@ -using StardewValley; -using StardewValley.Locations; -using StardewValley.Menus; -using StardewValley.Objects; - -namespace stardew_access.Patches -{ - // Menus in the game menu i.e., the menu which opens when we press `e` - internal class GameMenuPatches - { - internal static string hoveredItemQueryKey = ""; - internal static string geodeMenuQueryKey = ""; - internal static string gameMenuQueryKey = ""; - internal static string itemGrabMenuQueryKey = ""; - internal static string craftingPageQueryKey = ""; - internal static string inventoryPageQueryKey = ""; - internal static string exitPageQueryKey = ""; - internal static string optionsPageQueryKey = ""; - internal static string shopMenuQueryKey = ""; - internal static string socialPageQuery = ""; - internal static string profilePageQuery = ""; - internal static int currentSelectedCraftingRecipe = -1; - internal static bool isSelectingRecipe = false; - - internal static void CollectionsPagePatch(CollectionsPage __instance) - { - try - { - int x = Game1.getMousePosition().X, y = Game1.getMousePosition().Y; - if (__instance.letterviewerSubMenu != null) - { - DialoguePatches.NarrateLetterContent(__instance.letterviewerSubMenu); - } - } - catch (System.Exception e) - { - MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}"); - } - } - - internal static void SocialPagePatch(SocialPage __instance, List ___sprites, int ___slotPosition, List ___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) - { - if (__instance.names[i] is string) - { - #region For NPCs - if (__instance.characterSlots[i].bounds.Contains(Game1.getMouseX(true), Game1.getMouseY(true))) - { - 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; - } - #endregion - } - else if (__instance.names[i] is long) - { - #region For Farmers - - long farmerID = (long)__instance.names[i]; - Farmer farmer = Game1.getFarmerMaybeOffline(farmerID); - if (farmer != null) - { - int gender = (!farmer.IsMale) ? 1 : 0; - ClickableTextureComponent clickableTextureComponent = ___sprites[i]; - if (clickableTextureComponent.containsPoint(x, y)) - { - 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; - } - } - - #endregion - } - } - } - } - catch (Exception e) - { - MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}"); - } - } - - internal static void ShopMenuPatch(ShopMenu __instance) - { - try - { - int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position - - if (MainClass.Config.SnapToFirstInventorySlotKey.JustPressed() && __instance.inventory.inventory.Count > 0) - { - __instance.inventory.inventory[0].snapMouseCursorToCenter(); - __instance.setCurrentlySnappedComponentTo(__instance.inventory.inventory[0].myID); - } - else if (MainClass.Config.SnapToFirstSecondaryInventorySlotKey.JustPressed() && __instance.forSaleButtons.Count > 0) - { - __instance.forSaleButtons[0].snapMouseCursorToCenter(); - __instance.setCurrentlySnappedComponentTo(__instance.forSaleButtons[0].myID); - } - - #region Narrate buttons in the menu - if (__instance.inventory.dropItemInvisibleButton != null && __instance.inventory.dropItemInvisibleButton.containsPoint(x, y)) - { - string toSpeak = "Drop Item"; - if (shopMenuQueryKey != toSpeak) - { - shopMenuQueryKey = toSpeak; - hoveredItemQueryKey = ""; - MainClass.ScreenReader.Say(toSpeak, true); - Game1.playSound("drop_item"); - } - return; - } - if (__instance.upArrow != null && __instance.upArrow.containsPoint(x, y)) - { - string toSpeak = "Up Arrow Button"; - if (shopMenuQueryKey != toSpeak) - { - shopMenuQueryKey = toSpeak; - hoveredItemQueryKey = ""; - MainClass.ScreenReader.Say(toSpeak, true); - } - return; - } - if (__instance.downArrow != null && __instance.downArrow.containsPoint(x, y)) - { - string toSpeak = "Down Arrow Button"; - if (shopMenuQueryKey != toSpeak) - { - shopMenuQueryKey = toSpeak; - hoveredItemQueryKey = ""; - MainClass.ScreenReader.Say(toSpeak, true); - } - return; - } - #endregion - - #region Narrate hovered item - if (narrateHoveredItemInInventory(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y, hoverPrice: __instance.hoverPrice)) - { - shopMenuQueryKey = ""; - return; - } - #endregion - - #region Narrate hovered selling item - if (__instance.hoveredItem != null) - { - string name = __instance.hoveredItem.DisplayName; - string price = $"Buy Price: {__instance.hoverPrice} g"; - string description = __instance.hoveredItem.getDescription(); - string requirements = ""; - - #region Narrate 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) - { - shopMenuQueryKey = toSpeak; - hoveredItemQueryKey = ""; - MainClass.ScreenReader.Say(toSpeak, true); - } - } - #endregion - } - catch (Exception e) - { - MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}"); - } - } - - internal static void GameMenuPatch(GameMenu __instance) - { - try - { - // Continue if only in the Inventory Page or Crafting Page - if (__instance.currentTab != 0 && __instance.currentTab != 4 && __instance.currentTab != 6 && __instance.currentTab != 7) - 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)) - { - string toSpeak = $"{GameMenu.getLabelOfTabFromIndex(i)} Tab"; - if (gameMenuQueryKey != toSpeak) - { - gameMenuQueryKey = 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 GeodeMenuPatch(GeodeMenu __instance) - { - try - { - int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position - - #region Narrate the treasure recieved on breaking the geode - if (__instance.geodeTreasure != null) - { - 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; - } - #endregion - - #region Narrate hovered buttons in the menu - if (__instance.geodeSpot != null && __instance.geodeSpot.containsPoint(x, y)) - { - string toSpeak = "Place geode here"; - if (geodeMenuQueryKey != toSpeak) - { - geodeMenuQueryKey = toSpeak; - MainClass.ScreenReader.Say(toSpeak, true); - } - return; - } - - if (__instance.dropItemInvisibleButton != null && __instance.dropItemInvisibleButton.containsPoint(x, y)) - { - string toSpeak = "Drop item here"; - - if (geodeMenuQueryKey != toSpeak) - { - geodeMenuQueryKey = toSpeak; - MainClass.ScreenReader.Say(toSpeak, true); - Game1.playSound("drop_item"); - } - return; - } - - if (__instance.trashCan != null && __instance.trashCan.containsPoint(x, y)) - { - string toSpeak = "Trash can"; - - if (geodeMenuQueryKey != toSpeak) - { - geodeMenuQueryKey = toSpeak; - MainClass.ScreenReader.Say(toSpeak, true); - } - return; - } - - if (__instance.okButton != null && __instance.okButton.containsPoint(x, y)) - { - string toSpeak = "Ok button"; - - if (geodeMenuQueryKey != toSpeak) - { - geodeMenuQueryKey = toSpeak; - MainClass.ScreenReader.Say(toSpeak, true); - } - return; - } - #endregion - - #region Narrate hovered item - if (narrateHoveredItemInInventory(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y)) - geodeMenuQueryKey = ""; - #endregion - } - catch (Exception e) - { - MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}"); - } - } - - internal static void ItemGrabMenuPatch(ItemGrabMenu __instance) - { - try - { - int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position - - if (MainClass.Config.SnapToFirstInventorySlotKey.JustPressed() && __instance.inventory.inventory.Count > 0) - { - __instance.setCurrentlySnappedComponentTo(__instance.inventory.inventory[0].myID); - __instance.inventory.inventory[0].snapMouseCursorToCenter(); - } - else if (MainClass.Config.SnapToFirstSecondaryInventorySlotKey.JustPressed() && __instance.ItemsToGrabMenu.inventory.Count > 0 && !__instance.shippingBin) - { - __instance.setCurrentlySnappedComponentTo(__instance.ItemsToGrabMenu.inventory[0].myID); - __instance.ItemsToGrabMenu.inventory[0].snapMouseCursorToCenter(); - } - - #region Narrate buttons in the menu - if (__instance.okButton != null && __instance.okButton.containsPoint(x, y)) - { - string toSpeak = "Ok Button"; - if (itemGrabMenuQueryKey != toSpeak) - { - itemGrabMenuQueryKey = toSpeak; - hoveredItemQueryKey = ""; - gameMenuQueryKey = ""; - MainClass.ScreenReader.Say(toSpeak, true); - } - return; - } - if (__instance.trashCan != null && __instance.trashCan.containsPoint(x, y)) - { - string toSpeak = "Trash Can"; - if (itemGrabMenuQueryKey != toSpeak) - { - itemGrabMenuQueryKey = toSpeak; - gameMenuQueryKey = ""; - hoveredItemQueryKey = ""; - MainClass.ScreenReader.Say(toSpeak, true); - } - return; - } - - if (__instance.organizeButton != null && __instance.organizeButton.containsPoint(x, y)) - { - string toSpeak = "Organize Button"; - if (itemGrabMenuQueryKey != toSpeak) - { - itemGrabMenuQueryKey = toSpeak; - gameMenuQueryKey = ""; - hoveredItemQueryKey = ""; - MainClass.ScreenReader.Say(toSpeak, true); - } - return; - } - - if (__instance.fillStacksButton != null && __instance.fillStacksButton.containsPoint(x, y)) - { - string toSpeak = "Add to existing stacks button"; - if (itemGrabMenuQueryKey != toSpeak) - { - itemGrabMenuQueryKey = toSpeak; - gameMenuQueryKey = ""; - hoveredItemQueryKey = ""; - MainClass.ScreenReader.Say(toSpeak, true); - } - return; - } - - if (__instance.specialButton != null && __instance.specialButton.containsPoint(x, y)) - { - string toSpeak = "Special Button"; - if (itemGrabMenuQueryKey != toSpeak) - { - itemGrabMenuQueryKey = toSpeak; - gameMenuQueryKey = ""; - hoveredItemQueryKey = ""; - MainClass.ScreenReader.Say(toSpeak, true); - } - return; - } - - if (__instance.colorPickerToggleButton != null && __instance.colorPickerToggleButton.containsPoint(x, y)) - { - - string toSpeak = "Color Picker: " + (__instance.chestColorPicker.visible ? "Enabled" : "Disabled"); - if (itemGrabMenuQueryKey != toSpeak) - { - itemGrabMenuQueryKey = toSpeak; - gameMenuQueryKey = ""; - hoveredItemQueryKey = ""; - MainClass.ScreenReader.Say(toSpeak, true); - } - return; - } - - if (__instance.junimoNoteIcon != null && __instance.junimoNoteIcon.containsPoint(x, y)) - { - - string toSpeak = "Community Center Button"; - if (itemGrabMenuQueryKey != toSpeak) - { - itemGrabMenuQueryKey = toSpeak; - gameMenuQueryKey = ""; - hoveredItemQueryKey = ""; - MainClass.ScreenReader.Say(toSpeak, true); - } - return; - } - - if (__instance.dropItemInvisibleButton != null && __instance.dropItemInvisibleButton.containsPoint(x, y)) - { - string toSpeak = "Drop Item"; - if (itemGrabMenuQueryKey != toSpeak) - { - itemGrabMenuQueryKey = toSpeak; - gameMenuQueryKey = ""; - hoveredItemQueryKey = ""; - MainClass.ScreenReader.Say(toSpeak, true); - Game1.playSound("drop_item"); - } - return; - } - - // 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; - gameMenuQueryKey = ""; - hoveredItemQueryKey = ""; - ScreenReader.say(toSpeak, true); - Game1.playSound("sa_drop_item"); - } - return; - } - } - }*/ - #endregion - - #region Narrate the last shipped item if in the shipping bin - if (__instance.shippingBin && Game1.getFarm().lastItemShipped != null && __instance.lastShippedHolder.containsPoint(x, y)) - { - Item lastShippedItem = Game1.getFarm().lastItemShipped; - string name = lastShippedItem.DisplayName; - int count = lastShippedItem.Stack; - - string toSpeak = $"Last Shipped: {count} {name}"; - - if (itemGrabMenuQueryKey != toSpeak) - { - itemGrabMenuQueryKey = toSpeak; - gameMenuQueryKey = ""; - hoveredItemQueryKey = ""; - MainClass.ScreenReader.Say(toSpeak, true); - } - return; - } - #endregion - - #region Narrate hovered item - if (narrateHoveredItemInInventory(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y, true)) - { - gameMenuQueryKey = ""; - itemGrabMenuQueryKey = ""; - return; - } - - if (narrateHoveredItemInInventory(__instance.ItemsToGrabMenu, __instance.ItemsToGrabMenu.inventory, __instance.ItemsToGrabMenu.actualInventory, x, y, true)) - { - gameMenuQueryKey = ""; - itemGrabMenuQueryKey = ""; - return; - } - - #endregion - } - catch (Exception e) - { - MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}"); - } - } - - // 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 CraftingPagePatch(CraftingPage __instance, CraftingRecipe ___hoverRecipe, int ___currentCraftingPage) - { - try - { - int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position - - 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.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.CraftingMenuCycleThroughRecipiesKey.JustPressed() && !isSelectingRecipe) - { - isSelectingRecipe = true; - CycleThroughRecipies(__instance.pagesOfCraftingRecipes, ___currentCraftingPage, __instance); - Task.Delay(200).ContinueWith(_ => { isSelectingRecipe = false; }); - } - - #region Narrate buttons in the menu - if (__instance.upButton != null && __instance.upButton.containsPoint(x, y)) - { - string toSpeak = "Previous Recipe List"; - if (craftingPageQueryKey != toSpeak) - { - craftingPageQueryKey = toSpeak; - hoveredItemQueryKey = ""; - MainClass.ScreenReader.Say(toSpeak, true); - } - return; - } - - if (__instance.downButton != null && __instance.downButton.containsPoint(x, y)) - { - string toSpeak = "Next Recipe List"; - if (craftingPageQueryKey != toSpeak) - { - craftingPageQueryKey = toSpeak; - hoveredItemQueryKey = ""; - MainClass.ScreenReader.Say(toSpeak, true); - } - return; - } - - if (__instance.trashCan.containsPoint(x, y)) - { - string toSpeak = "Trash Can"; - if (craftingPageQueryKey != toSpeak) - { - craftingPageQueryKey = toSpeak; - hoveredItemQueryKey = ""; - MainClass.ScreenReader.Say(toSpeak, true); - } - return; - } - - if (__instance.dropItemInvisibleButton.containsPoint(x, y)) - { - string toSpeak = "Drop Item"; - if (craftingPageQueryKey != toSpeak) - { - craftingPageQueryKey = toSpeak; - hoveredItemQueryKey = ""; - MainClass.ScreenReader.Say(toSpeak, true); - Game1.playSound("drop_item"); - } - return; - } - #endregion - - #region Narrate hovered recipe - if (___hoverRecipe != null) - { - 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; - gameMenuQueryKey = ""; - hoveredItemQueryKey = ""; - MainClass.ScreenReader.Say(toSpeak, true); - } - return; - } - else - { - var isRecipeInFocus = false; - foreach (var item in __instance.pagesOfCraftingRecipes[___currentCraftingPage]) - { - if (item.Key.containsPoint(x, y)) - { - isRecipeInFocus = true; - break; - } - } - - if (isRecipeInFocus) - { - string query = $"unknown recipe:{__instance.getCurrentlySnappedComponent().myID}"; - - if (craftingPageQueryKey != query) - { - craftingPageQueryKey = query; - gameMenuQueryKey = ""; - hoveredItemQueryKey = ""; - MainClass.ScreenReader.Say("unknown recipe", true); - } - return; - } - } - #endregion - - #region Narrate hovered item - if (narrateHoveredItemInInventory(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y)) - { - gameMenuQueryKey = ""; - craftingPageQueryKey = ""; - return; - } - #endregion - } - catch (Exception e) - { - MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}"); - } - } - - private static void CycleThroughRecipies(List> 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? getContainerContents(List materialContainers) - { - if (materialContainers == null) - { - return null; - } - List items = new List(); - for (int i = 0; i < materialContainers.Count; i++) - { - items.AddRange(materialContainers[i].items); - } - return items; - } - - internal static void InventoryPagePatch(InventoryPage __instance) - { - try - { - int x = Game1.getMouseX(true), y = Game1.getMouseY(true); // Mouse x and y position - - #region Narrate buttons in the menu - if (__instance.inventory.dropItemInvisibleButton != null && __instance.inventory.dropItemInvisibleButton.containsPoint(x, y)) - { - string toSpeak = "Drop Item"; - if (inventoryPageQueryKey != toSpeak) - { - inventoryPageQueryKey = toSpeak; - gameMenuQueryKey = ""; - hoveredItemQueryKey = ""; - MainClass.ScreenReader.Say(toSpeak, true); - Game1.playSound("drop_item"); - } - } - - if (__instance.organizeButton != null && __instance.organizeButton.containsPoint(x, y)) - { - string toSpeak = "Organize Inventory Button"; - if (inventoryPageQueryKey != toSpeak) - { - inventoryPageQueryKey = toSpeak; - gameMenuQueryKey = ""; - hoveredItemQueryKey = ""; - MainClass.ScreenReader.Say(toSpeak, true); - } - } - - if (__instance.trashCan != null && __instance.trashCan.containsPoint(x, y)) - { - string toSpeak = "Trash Can"; - if (inventoryPageQueryKey != toSpeak) - { - inventoryPageQueryKey = toSpeak; - gameMenuQueryKey = ""; - hoveredItemQueryKey = ""; - MainClass.ScreenReader.Say(toSpeak, true); - } - } - - if (__instance.organizeButton != null && __instance.organizeButton.containsPoint(x, y)) - { - string toSpeak = "Organize Button"; - if (itemGrabMenuQueryKey != toSpeak) - { - itemGrabMenuQueryKey = toSpeak; - gameMenuQueryKey = ""; - hoveredItemQueryKey = ""; - MainClass.ScreenReader.Say(toSpeak, true); - } - } - - if (__instance.junimoNoteIcon != null && __instance.junimoNoteIcon.containsPoint(x, y)) - { - - string toSpeak = "Community Center Button"; - if (itemGrabMenuQueryKey != toSpeak) - { - itemGrabMenuQueryKey = toSpeak; - gameMenuQueryKey = ""; - hoveredItemQueryKey = ""; - MainClass.ScreenReader.Say(toSpeak, true); - } - } - #endregion - - #region Narrate equipment slots - for (int i = 0; i < __instance.equipmentIcons.Count; i++) - { - if (__instance.equipmentIcons[i].containsPoint(x, y)) - { - string toSpeak = ""; - - #region Get name and description of the item - switch (__instance.equipmentIcons[i].name) - { - case "Hat": - { - if (Game1.player.hat.Value != null) - { - toSpeak = $"{Game1.player.hat.Value.DisplayName}, {Game1.player.hat.Value.getDescription()}"; - } - else - { - toSpeak = "Hat slot"; - } - } - break; - case "Left Ring": - { - if (Game1.player.leftRing.Value != null) - { - toSpeak = $"{Game1.player.leftRing.Value.DisplayName}, {Game1.player.leftRing.Value.getDescription()}"; - } - else - { - toSpeak = "Left Ring slot"; - } - } - break; - case "Right Ring": - { - if (Game1.player.rightRing.Value != null) - { - toSpeak = $"{Game1.player.rightRing.Value.DisplayName}, {Game1.player.rightRing.Value.getDescription()}"; - } - else - { - toSpeak = "Right ring slot"; - } - } - break; - case "Boots": - { - if (Game1.player.boots.Value != null) - { - toSpeak = $"{Game1.player.boots.Value.DisplayName}, {Game1.player.boots.Value.getDescription()}"; - } - else - { - toSpeak = "Boots slot"; - } - } - break; - case "Shirt": - { - if (Game1.player.shirtItem.Value != null) - { - toSpeak = $"{Game1.player.shirtItem.Value.DisplayName}, {Game1.player.shirtItem.Value.getDescription()}"; - } - else - { - toSpeak = "Shirt slot"; - } - } - break; - case "Pants": - { - if (Game1.player.pantsItem.Value != null) - { - toSpeak = $"{Game1.player.pantsItem.Value.DisplayName}, {Game1.player.pantsItem.Value.getDescription()}"; - } - else - { - toSpeak = "Pants slot"; - } - } - break; - } - #endregion - - if (inventoryPageQueryKey != toSpeak) - { - inventoryPageQueryKey = toSpeak; - gameMenuQueryKey = ""; - hoveredItemQueryKey = ""; - MainClass.ScreenReader.Say(toSpeak, true); - } - } - } - #endregion - - #region Narrate hovered item - if (narrateHoveredItemInInventory(__instance.inventory, __instance.inventory.inventory, __instance.inventory.actualInventory, x, y, true)) - { - gameMenuQueryKey = ""; - inventoryPageQueryKey = ""; - } - #endregion - - if (MainClass.Config.MoneyKey.JustPressed()) - { - 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); - } - } - catch (Exception e) - { - MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}"); - } - } - - internal static void OptionsPagePatch(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)) - { - 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) - { - gameMenuQueryKey = ""; - 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 ExitPagePatch(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) - { - gameMenuQueryKey = ""; - 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) - { - gameMenuQueryKey = ""; - 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 bool narrateHoveredItemInInventory(InventoryMenu inventoryMenu, List inventory, IList actualInventory, int x, int y, bool giveExtraDetails = false, int hoverPrice = -1, int extraItemToShowIndex = -1, int extraItemToShowAmount = -1) - { - #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 = ""; - string healthNStamine = ""; - string buffs = ""; - string description = ""; - string price = ""; - string requirements = ""; - - #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 (giveExtraDetails) - { - description = actualInventory[i].getDescription(); - #region Add health & stamina provided by the item - if (actualInventory[i] is StardewValley.Object && ((StardewValley.Object)actualInventory[i]).Edibility != -300) - { - int stamina_recovery = ((StardewValley.Object)actualInventory[i]).staminaRecoveredOnConsumption(); - healthNStamine += $"{stamina_recovery} Energy"; - if (stamina_recovery >= 0) - { - int health_recovery = ((StardewValley.Object)actualInventory[i]).healthRecoveredOnConsumption(); - healthNStamine += $"\n\t{health_recovery} Health"; - } - } - #endregion - - #region Add buff items (effects like +1 walking speed) - // These variables are taken from the game's code itself (IClickableMenu.cs -> 1016 line) - bool edibleItem = actualInventory[i] != null && actualInventory[i] is StardewValley.Object && (int)((StardewValley.Object)actualInventory[i]).Edibility != -300; - string[]? buffIconsToDisplay = (edibleItem && Game1.objectInformation[((StardewValley.Object)actualInventory[i]).ParentSheetIndex].Split('/').Length > 7) ? actualInventory[i].ModifyItemBuffs(Game1.objectInformation[((StardewValley.Object)actualInventory[i]).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) { } - } - } - #endregion - } - - #region Narrate hovered required ingredients - if (extraItemToShowIndex != -1) - { - string itemName = Game1.objectInformation[extraItemToShowIndex].Split('/')[0]; - - if (extraItemToShowAmount != -1) - requirements = $"Required: {extraItemToShowAmount} {itemName}"; - else - requirements = $"Required: {itemName}"; - } - #endregion - - if (hoverPrice != -1) - { - price = $"Sell Price: {hoverPrice} g"; - } - - if (!inventoryMenu.highlightMethod(actualInventory[i])) - { - name = $"{name} not usable here"; - } - - 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}"; - } - } - else - { - // For empty slot - toSpeak = "Empty Slot"; - } - } - else - { - // For empty slot - toSpeak = "Empty Slot"; - } - - if (hoveredItemQueryKey != $"{toSpeak}:{i}") - { - hoveredItemQueryKey = $"{toSpeak}:{i}"; - MainClass.ScreenReader.Say(toSpeak, true); - } - return true; - } - } - #endregion - return false; - } - } -} diff --git a/stardew-access/Patches/GameMenuPatches/CollectionsPagePatch.cs b/stardew-access/Patches/GameMenuPatches/CollectionsPagePatch.cs new file mode 100644 index 0000000..fdbaee4 --- /dev/null +++ b/stardew-access/Patches/GameMenuPatches/CollectionsPagePatch.cs @@ -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}"); + } + } + } +} diff --git a/stardew-access/Patches/GameMenuPatches/CraftingPagePatch.cs b/stardew-access/Patches/GameMenuPatches/CraftingPagePatch.cs new file mode 100644 index 0000000..1cad73e --- /dev/null +++ b/stardew-access/Patches/GameMenuPatches/CraftingPagePatch.cs @@ -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> 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? getContainerContents(List materialContainers) + { + if (materialContainers == null) + { + return null; + } + List items = new List(); + 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; + } + } +} diff --git a/stardew-access/Patches/GameMenuPatches/ExitPagePatch.cs b/stardew-access/Patches/GameMenuPatches/ExitPagePatch.cs new file mode 100644 index 0000000..81431f7 --- /dev/null +++ b/stardew-access/Patches/GameMenuPatches/ExitPagePatch.cs @@ -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 = ""; + } + } +} diff --git a/stardew-access/Patches/GameMenuPatches/GameMenuPatch.cs b/stardew-access/Patches/GameMenuPatches/GameMenuPatch.cs new file mode 100644 index 0000000..0255dbb --- /dev/null +++ b/stardew-access/Patches/GameMenuPatches/GameMenuPatch.cs @@ -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 = ""; + } + } +} diff --git a/stardew-access/Patches/GameMenuPatches/InventoryPagePatch.cs b/stardew-access/Patches/GameMenuPatches/InventoryPagePatch.cs new file mode 100644 index 0000000..cbb8dd5 --- /dev/null +++ b/stardew-access/Patches/GameMenuPatches/InventoryPagePatch.cs @@ -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 = ""; + } + } +} diff --git a/stardew-access/Patches/GameMenuPatches/OptionsPagePatch.cs b/stardew-access/Patches/GameMenuPatches/OptionsPagePatch.cs new file mode 100644 index 0000000..f4d9879 --- /dev/null +++ b/stardew-access/Patches/GameMenuPatches/OptionsPagePatch.cs @@ -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 = ""; + } + } +} diff --git a/stardew-access/Patches/GameMenuPatches/SocialPagePatch.cs b/stardew-access/Patches/GameMenuPatches/SocialPagePatch.cs new file mode 100644 index 0000000..81b2ad0 --- /dev/null +++ b/stardew-access/Patches/GameMenuPatches/SocialPagePatch.cs @@ -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 ___sprites, int ___slotPosition, List ___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 ___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 ___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 = ""; + } + } +} diff --git a/stardew-access/Patches/MenuPatches.cs b/stardew-access/Patches/MenuPatches.cs deleted file mode 100644 index 993d580..0000000 --- a/stardew-access/Patches/MenuPatches.cs +++ /dev/null @@ -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 ___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 ___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 ___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 ___professionsToChoose, List ___leftProfessionDescription, List ___rightProfessionDescription, List ___extraInfoForLevel, List ___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 ___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(); - } - } -} diff --git a/stardew-access/Patches/MenuWithInventoryPatches/ForgeMenuPatch.cs b/stardew-access/Patches/MenuWithInventoryPatches/ForgeMenuPatch.cs new file mode 100644 index 0000000..d5b6671 --- /dev/null +++ b/stardew-access/Patches/MenuWithInventoryPatches/ForgeMenuPatch.cs @@ -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 = ""; + } + } +} diff --git a/stardew-access/Patches/MenuWithInventoryPatches/GeodeMenuPatch.cs b/stardew-access/Patches/MenuWithInventoryPatches/GeodeMenuPatch.cs new file mode 100644 index 0000000..e9ca166 --- /dev/null +++ b/stardew-access/Patches/MenuWithInventoryPatches/GeodeMenuPatch.cs @@ -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 = ""; + } + } +} diff --git a/stardew-access/Patches/MenuWithInventoryPatches/ItemGrabMenuPatch.cs b/stardew-access/Patches/MenuWithInventoryPatches/ItemGrabMenuPatch.cs new file mode 100644 index 0000000..b9b7674 --- /dev/null +++ b/stardew-access/Patches/MenuWithInventoryPatches/ItemGrabMenuPatch.cs @@ -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 = ""; + } + } +} diff --git a/stardew-access/Patches/MenuWithInventoryPatches/ShopMenuPatch.cs b/stardew-access/Patches/MenuWithInventoryPatches/ShopMenuPatch.cs new file mode 100644 index 0000000..8e000d8 --- /dev/null +++ b/stardew-access/Patches/MenuWithInventoryPatches/ShopMenuPatch.cs @@ -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 = ""; + } + } +} diff --git a/stardew-access/Patches/MenuWithInventoryPatches/TailoringMenuPatch.cs b/stardew-access/Patches/MenuWithInventoryPatches/TailoringMenuPatch.cs new file mode 100644 index 0000000..3718730 --- /dev/null +++ b/stardew-access/Patches/MenuWithInventoryPatches/TailoringMenuPatch.cs @@ -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 = ""; + } + } +} diff --git a/stardew-access/Patches/MiniGamesPatches.cs b/stardew-access/Patches/MiniGamesPatches/GrandpaStoryPatch.cs similarity index 70% rename from stardew-access/Patches/MiniGamesPatches.cs rename to stardew-access/Patches/MiniGamesPatches/GrandpaStoryPatch.cs index 8addd64..90e93d2 100644 --- a/stardew-access/Patches/MiniGamesPatches.cs +++ b/stardew-access/Patches/MiniGamesPatches/GrandpaStoryPatch.cs @@ -4,43 +4,11 @@ using StardewValley.Minigames; namespace stardew_access.Patches { - public class MiniGamesPatches + public class GrandpaStoryPatch { public static string grandpaStoryQuery = " "; - public static string introQuery = " "; - internal static void IntroPatch(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($"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 ___grandpaSpeech, int ___grandpaSpeechTimer, int ___totalMilliseconds, int ___scene, int ___parallaxPan) + internal static void DrawPatch(GrandpaStory __instance, StardewValley.Menus.LetterViewerMenu ___letterView, bool ___drawGrandpa, bool ___letterReceived, bool ___mouseActive, Queue ___grandpaSpeech, int ___grandpaSpeechTimer, int ___totalMilliseconds, int ___scene, int ___parallaxPan) { try { @@ -49,7 +17,7 @@ namespace stardew_access.Patches if (___letterView != null) { - DialoguePatches.NarrateLetterContent(___letterView); + LetterViwerMenuPatch.narrateLetterContent(___letterView); } 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); } } -} \ No newline at end of file +} diff --git a/stardew-access/Patches/MiniGamesPatches/IntroPatch.cs b/stardew-access/Patches/MiniGamesPatches/IntroPatch.cs new file mode 100644 index 0000000..08759a7 --- /dev/null +++ b/stardew-access/Patches/MiniGamesPatches/IntroPatch.cs @@ -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}"); + } + } + } +} diff --git a/stardew-access/Patches/ChatMenuPatches.cs b/stardew-access/Patches/MiscPatches/ChatBoxPatch.cs similarity index 63% rename from stardew-access/Patches/ChatMenuPatches.cs rename to stardew-access/Patches/MiscPatches/ChatBoxPatch.cs index 6155a70..8ded908 100644 --- a/stardew-access/Patches/ChatMenuPatches.cs +++ b/stardew-access/Patches/MiscPatches/ChatBoxPatch.cs @@ -1,14 +1,13 @@ -using StardewValley; -using StardewValley.Menus; +using StardewValley.Menus; namespace stardew_access.Patches { - internal class ChatMenuPatches + internal class ChatBoxPatch { private static int currentChatMessageIndex = 0; private static bool isChatRunning = false; - internal static void ChatBoxPatch(ChatBox __instance, List ___messages) + internal static void UpdatePatch(ChatBox __instance, List ___messages) { try { @@ -19,23 +18,22 @@ namespace stardew_access.Patches bool isPrevButtonPressed = MainClass.Config.ChatMenuNextKey.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 - if (isNextButtonPressed && !isChatRunning) - { - 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 + 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 (___messages.Count > 0) { diff --git a/stardew-access/Patches/MiscPatches/DialogueBoxPatch.cs b/stardew-access/Patches/MiscPatches/DialogueBoxPatch.cs new file mode 100644 index 0000000..bc471d6 --- /dev/null +++ b/stardew-access/Patches/MiscPatches/DialogueBoxPatch.cs @@ -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 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; + } + } +} diff --git a/stardew-access/Patches/MiscPatches/Game1Patch.cs b/stardew-access/Patches/MiscPatches/Game1Patch.cs new file mode 100644 index 0000000..04ba91f --- /dev/null +++ b/stardew-access/Patches/MiscPatches/Game1Patch.cs @@ -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; + } + } +} diff --git a/stardew-access/Patches/MiscPatches/IClickableMenuPatch.cs b/stardew-access/Patches/MiscPatches/IClickableMenuPatch.cs new file mode 100644 index 0000000..7069fb5 --- /dev/null +++ b/stardew-access/Patches/MiscPatches/IClickableMenuPatch.cs @@ -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 = ""; + } + } +} diff --git a/stardew-access/Patches/MiscPatches/InstanceGamePatch.cs b/stardew-access/Patches/MiscPatches/InstanceGamePatch.cs new file mode 100644 index 0000000..93affb1 --- /dev/null +++ b/stardew-access/Patches/MiscPatches/InstanceGamePatch.cs @@ -0,0 +1,11 @@ +namespace stardew_access.Patches +{ + internal class InstanceGamePatch + { + internal static void ExitPatch() + { + if (MainClass.ScreenReader != null) + MainClass.ScreenReader.CloseScreenReader(); + } + } +} diff --git a/stardew-access/Patches/MiscPatches/NPCPatch.cs b/stardew-access/Patches/MiscPatches/NPCPatch.cs new file mode 100644 index 0000000..41f9306 --- /dev/null +++ b/stardew-access/Patches/MiscPatches/NPCPatch.cs @@ -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}"); + } + } + } +} diff --git a/stardew-access/Patches/MiscPatches/TextBoxPatch.cs b/stardew-access/Patches/MiscPatches/TextBoxPatch.cs new file mode 100644 index 0000000..cc3de3e --- /dev/null +++ b/stardew-access/Patches/MiscPatches/TextBoxPatch.cs @@ -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}"); + } + } + } +} diff --git a/stardew-access/Patches/OtherMenuPatches/AnimalQueryMenuPatch.cs b/stardew-access/Patches/OtherMenuPatches/AnimalQueryMenuPatch.cs new file mode 100644 index 0000000..334e580 --- /dev/null +++ b/stardew-access/Patches/OtherMenuPatches/AnimalQueryMenuPatch.cs @@ -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; + } + } +} diff --git a/stardew-access/Patches/OtherMenuPatches/CarpenterMenuPatch.cs b/stardew-access/Patches/OtherMenuPatches/CarpenterMenuPatch.cs new file mode 100644 index 0000000..81f4d29 --- /dev/null +++ b/stardew-access/Patches/OtherMenuPatches/CarpenterMenuPatch.cs @@ -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 ___ingredients, int ___price, + List ___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 ___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 ___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; + } + } +} diff --git a/stardew-access/Patches/OtherMenuPatches/ChooseFromListMenuPatch.cs b/stardew-access/Patches/OtherMenuPatches/ChooseFromListMenuPatch.cs new file mode 100644 index 0000000..2e19781 --- /dev/null +++ b/stardew-access/Patches/OtherMenuPatches/ChooseFromListMenuPatch.cs @@ -0,0 +1,32 @@ +using StardewValley; +using StardewValley.Menus; + +namespace stardew_access.Patches +{ + internal class ChooseFromListMenuPatch + { + internal static void DrawPatch(ChooseFromListMenu __instance, List ___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}"); + } + } + } +} diff --git a/stardew-access/Patches/OtherMenuPatches/ConfirmationDialogMenuPatch.cs b/stardew-access/Patches/OtherMenuPatches/ConfirmationDialogMenuPatch.cs new file mode 100644 index 0000000..ffde9ec --- /dev/null +++ b/stardew-access/Patches/OtherMenuPatches/ConfirmationDialogMenuPatch.cs @@ -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}"); + } + } + } +} diff --git a/stardew-access/Patches/OtherMenuPatches/ItemListMenuPatch.cs b/stardew-access/Patches/OtherMenuPatches/ItemListMenuPatch.cs new file mode 100644 index 0000000..e94a206 --- /dev/null +++ b/stardew-access/Patches/OtherMenuPatches/ItemListMenuPatch.cs @@ -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 ___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 = ""; + } + } +} diff --git a/stardew-access/Patches/OtherMenuPatches/LanguageSelectionMenuPatch.cs b/stardew-access/Patches/OtherMenuPatches/LanguageSelectionMenuPatch.cs new file mode 100644 index 0000000..968a171 --- /dev/null +++ b/stardew-access/Patches/OtherMenuPatches/LanguageSelectionMenuPatch.cs @@ -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}"); + } + } + } +} diff --git a/stardew-access/Patches/OtherMenuPatches/LetterViewerMenuPatch.cs b/stardew-access/Patches/OtherMenuPatches/LetterViewerMenuPatch.cs new file mode 100644 index 0000000..de7eabb --- /dev/null +++ b/stardew-access/Patches/OtherMenuPatches/LetterViewerMenuPatch.cs @@ -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 = ""; + } + } +} diff --git a/stardew-access/Patches/OtherMenuPatches/LevelUpMenuPatch.cs b/stardew-access/Patches/OtherMenuPatches/LevelUpMenuPatch.cs new file mode 100644 index 0000000..cb4535b --- /dev/null +++ b/stardew-access/Patches/OtherMenuPatches/LevelUpMenuPatch.cs @@ -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 ___professionsToChoose, List ___leftProfessionDescription, List ___rightProfessionDescription, List ___extraInfoForLevel, List ___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 = ""; + } + } +} diff --git a/stardew-access/Patches/OtherMenuPatches/MineElevatorMenuPatch.cs b/stardew-access/Patches/OtherMenuPatches/MineElevatorMenuPatch.cs new file mode 100644 index 0000000..c1110f1 --- /dev/null +++ b/stardew-access/Patches/OtherMenuPatches/MineElevatorMenuPatch.cs @@ -0,0 +1,28 @@ +using StardewValley; +using StardewValley.Menus; + +namespace stardew_access.Patches +{ + internal class MineElevatorMenuPatch + { + internal static void DrawPatch(List ___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}"); + } + } + } +} diff --git a/stardew-access/Patches/OtherMenuPatches/NamingMenuPatch.cs b/stardew-access/Patches/OtherMenuPatches/NamingMenuPatch.cs new file mode 100644 index 0000000..06e8f0e --- /dev/null +++ b/stardew-access/Patches/OtherMenuPatches/NamingMenuPatch.cs @@ -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}"); + } + } + } +} diff --git a/stardew-access/Patches/OtherMenuPatches/PondQuerMenuPatch.cs b/stardew-access/Patches/OtherMenuPatches/PondQuerMenuPatch.cs new file mode 100644 index 0000000..a370fc1 --- /dev/null +++ b/stardew-access/Patches/OtherMenuPatches/PondQuerMenuPatch.cs @@ -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; + } + } +} diff --git a/stardew-access/Patches/OtherMenuPatches/PurchaseAnimalsMenuPatch.cs b/stardew-access/Patches/OtherMenuPatches/PurchaseAnimalsMenuPatch.cs new file mode 100644 index 0000000..8ff6dff --- /dev/null +++ b/stardew-access/Patches/OtherMenuPatches/PurchaseAnimalsMenuPatch.cs @@ -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; + } + } +} diff --git a/stardew-access/Patches/OtherMenuPatches/ShippingMenuPatch.cs b/stardew-access/Patches/OtherMenuPatches/ShippingMenuPatch.cs new file mode 100644 index 0000000..692d9ee --- /dev/null +++ b/stardew-access/Patches/OtherMenuPatches/ShippingMenuPatch.cs @@ -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 ___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}"); + } + } + } +} diff --git a/stardew-access/Patches/OtherMenuPatches/TitleTextInputMenuPatch.cs b/stardew-access/Patches/OtherMenuPatches/TitleTextInputMenuPatch.cs new file mode 100644 index 0000000..2ed19ac --- /dev/null +++ b/stardew-access/Patches/OtherMenuPatches/TitleTextInputMenuPatch.cs @@ -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}"); + } + } + } +} diff --git a/stardew-access/Patches/QuestPatches.cs b/stardew-access/Patches/QuestPatches.cs deleted file mode 100644 index 0b38787..0000000 --- a/stardew-access/Patches/QuestPatches.cs +++ /dev/null @@ -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> ___pages, int ___currentPage, IQuest ____shownQuest, List ____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 - - } -} diff --git a/stardew-access/Patches/QuestPatches/BillboardPatch.cs b/stardew-access/Patches/QuestPatches/BillboardPatch.cs new file mode 100644 index 0000000..f8e4f47 --- /dev/null +++ b/stardew-access/Patches/QuestPatches/BillboardPatch.cs @@ -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 = ""; + } + } +} diff --git a/stardew-access/Patches/QuestPatches/QuestLogPatch.cs b/stardew-access/Patches/QuestPatches/QuestLogPatch.cs new file mode 100644 index 0000000..c93ddc1 --- /dev/null +++ b/stardew-access/Patches/QuestPatches/QuestLogPatch.cs @@ -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> ___pages, int ___currentPage, IQuest ____shownQuest, List ____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> ___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 ____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; + } + } +} diff --git a/stardew-access/Patches/QuestPatches/SpecialOrdersBoardPatch.cs b/stardew-access/Patches/QuestPatches/SpecialOrdersBoardPatch.cs new file mode 100644 index 0000000..eb27f5a --- /dev/null +++ b/stardew-access/Patches/QuestPatches/SpecialOrdersBoardPatch.cs @@ -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 = ""; + } + } +} diff --git a/stardew-access/Patches/TitleMenuPatches.cs b/stardew-access/Patches/TitleMenuPatches.cs deleted file mode 100644 index c93e2b6..0000000 --- a/stardew-access/Patches/TitleMenuPatches.cs +++ /dev/null @@ -1,1061 +0,0 @@ -using StardewValley; -using StardewValley.Menus; -using static StardewValley.Menus.CharacterCustomization; -using static StardewValley.Menus.LoadGameMenu; - -namespace stardew_access.Patches -{ - internal class TitleMenuPatches - { - private static int saveGameIndex = -1; - private static bool isRunning = false; - public static string characterCreationMenuQueryKey = " "; - public static string advancedGameOptionsQueryKey = " "; - public static string prevPants = " "; - public static string prevShirt = " "; - public static string prevHair = " "; - public static string prevAccessory = " "; - public static string prevSkin = " "; - public static string prevEyeColor = " "; - public static string prevEyeColorHue = " "; - public static string prevEyeColorSaturation = " "; - public static string prevEyeColorValue = " "; - public static string prevHairColor = " "; - public static string prevHairColorHue = " "; - public static string prevHairColorSaturation = " "; - public static string prevHairColorValue = " "; - public static string prevPantsColor = " "; - public static string prevPantsColorHue = " "; - public static string prevPantsColorSaturation = " "; - public static string prevPantsColorValue = " "; - public static string prevPetName = " "; - public static bool characterDesignToggle = false; - public static bool characterDesignToggleShouldSpeak = true; - public static ClickableComponent? currentComponent = null; - - internal static void AdvancedGameOptionsPatch(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)) - { - 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($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}"); - } - } - - internal static void CoopMenuPatch(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 (toSpeak != " ") - MainClass.ScreenReader.SayWithChecker(toSpeak, true); - } - catch (Exception e) - { - MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}"); - } - } - - internal static void TitleMenuPatch(TitleMenu __instance, bool ___isTransitioningButtons) - { - try - { - if (___isTransitioningButtons) - return; - - string toSpeak = ""; - - __instance.buttons.ForEach(component => - { - if (component.containsPoint(Game1.getMouseX(true), Game1.getMouseY(true))) - { - string name = component.name; - string label = component.label; - toSpeak = $"{name} {label} Button"; - } - }); - - if (__instance.muteMusicButton.containsPoint(Game1.getMouseX(true), Game1.getMouseY(true))) - { - toSpeak = "Mute Music Button"; - } - - if (__instance.aboutButton.containsPoint(Game1.getMouseX(true), Game1.getMouseY(true))) - { - toSpeak = "About Button"; - } - - if (__instance.languageButton.containsPoint(Game1.getMouseX(true), Game1.getMouseY(true))) - { - toSpeak = "Language Button"; - } - - if (__instance.windowedButton.containsPoint(Game1.getMouseX(true), Game1.getMouseY(true))) - { - toSpeak = "Fullscreen: " + ((Game1.isFullscreen) ? "enabled" : "disabled"); - } - - if (TitleMenu.subMenu != null && __instance.backButton.containsPoint(Game1.getMouseX(true), Game1.getMouseY(true))) - { - string text = "Back Button"; - MainClass.ScreenReader.SayWithChecker(text, true); - } - - // Fix for back button not working using keyboard - if (TitleMenu.subMenu is CharacterCustomization && ((CharacterCustomization)TitleMenu.subMenu).backButton.containsPoint(Game1.getMouseX(true), Game1.getMouseY(true))) - { - // Perform Left Click - if (MainClass.Config.LeftClickMainKey.JustPressed()) - { - __instance.backButtonPressed(); - } - } - - if (TitleMenu.subMenu == null && toSpeak != "") - MainClass.ScreenReader.SayWithChecker(toSpeak, true); - } - catch (Exception e) - { - MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}"); - } - } - - internal static void LoadGameMenuPatch(SaveFileSlot __instance, LoadGameMenu ___menu, int i) - { - try - { - int x = Game1.getMouseX(true), y = Game1.getMouseY(true); - if (___menu.slotButtons[i].containsPoint(x, y)) - { - if (__instance.Farmer != null) - { - #region Farms - if (___menu.deleteButtons.Count > 0 && ___menu.deleteButtons[i].containsPoint(x, y)) - { - MainClass.ScreenReader.SayWithChecker($"Delete {__instance.Farmer.farmName.Value} Farm", true); - return; - } - - if (___menu.deleteConfirmationScreen) - { - // Used diff. functions to narrate to prevent it from speaking the message again on selecting another button. - string message = "Really delete farm?"; - - MainClass.ScreenReader.SayWithChecker(message, true); - if (___menu.okDeleteButton.containsPoint(x, y)) - { - MainClass.ScreenReader.SayWithMenuChecker("Ok Button", false); - } - else if (___menu.cancelDeleteButton.containsPoint(x, y)) - { - MainClass.ScreenReader.SayWithMenuChecker("Cancel Button", false); - } - 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)); - - string toSpeak = $"{farmName} Farm Selected, \t\n Farmer: {farmerName}, \t\nMoney: {money}, \t\nHours Played: {hoursPlayed}, \t\nDate: {dateStringForSaveGame}"; - - MainClass.ScreenReader.SayWithChecker(toSpeak, true); - #endregion - } - } - } - catch (Exception e) - { - MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}"); - } - } - - internal static void CharacterCustomizationMenuPatch(CharacterCustomization __instance, bool ___skipIntro, - ClickableComponent ___startingCabinsLabel, ClickableComponent ___difficultyModifierLabel, TextBox ___nameBox, - TextBox ___farmnameBox, TextBox ___favThingBox) - { - try - { - bool isEscPressed = Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.Escape); // For escaping/unselecting from the animal name text box - string toSpeak = ""; - if (characterDesignToggleShouldSpeak) - { - toSpeak = "Press left control + space to toggle character appearance controls"; - characterDesignToggleShouldSpeak = false; - } - string itemsToSpeak = ""; - string changesToSpeak = ""; - - if (___nameBox.Selected) - { - 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; - itemsToSpeak =CycleThroughItems(true, __instance, ___skipIntro, ___startingCabinsLabel, ___difficultyModifierLabel, ___nameBox, ___farmnameBox, ___favThingBox); - if (itemsToSpeak != "") - toSpeak = $"{itemsToSpeak} \n {toSpeak}"; - Task.Delay(200).ContinueWith(_ => { isRunning = false; }); - } - else if (MainClass.Config.CharacterCreationMenuPreviousKey.JustPressed() && !isRunning) - { - isRunning = true; - toSpeak = CycleThroughItems(false, __instance, ___skipIntro, ___startingCabinsLabel, ___difficultyModifierLabel, ___nameBox, ___farmnameBox, ___favThingBox); - Task.Delay(200).ContinueWith(_ => { isRunning = false; }); - } - - else if (characterDesignToggle && MainClass.Config.CharacterCreationMenuSliderIncreaseKey.JustPressed() && !isRunning) - { - isRunning = true; - AdjustCurrentSlider(true, __instance); - Task.Delay(200).ContinueWith(_ => { isRunning = false; }); - } - - else if (characterDesignToggle && MainClass.Config.CharacterCreationMenuSliderLargeIncreaseKey.JustPressed() && !isRunning) - { - isRunning = true; - AdjustCurrentSlider(true, __instance, 10); - Task.Delay(200).ContinueWith(_ => { isRunning = false; }); - } - - else if (characterDesignToggle && MainClass.Config.CharacterCreationMenuSliderDecreaseKey.JustPressed() && !isRunning) - { - isRunning = true; - AdjustCurrentSlider(false, __instance); - Task.Delay(200).ContinueWith(_ => { isRunning = false; }); - } - - else if (characterDesignToggle && MainClass.Config.CharacterCreationMenuSliderLargeDecreaseKey.JustPressed() && !isRunning) - { - isRunning = true; - AdjustCurrentSlider(false, __instance, 10); - Task.Delay(200).ContinueWith(_ => { isRunning = false; }); - } - - else if (Game1.input.GetKeyboardState().IsKeyDown(Microsoft.Xna.Framework.Input.Keys.LeftControl) && MainClass.Config.CharacterCreationMenuDesignToggleKey.JustPressed() && !isRunning) - { - string displayState = ""; - characterDesignToggle = !characterDesignToggle; - saveGameIndex = Math.Min(saveGameIndex, 5); // move to random skin button if focus was beyond that point - if (characterDesignToggle) - { - displayState = "shown"; - } else { - displayState = "hidden"; - } - toSpeak = $"Character design controls {displayState}. \n {toSpeak}"; - } - - changesToSpeak = getChangesToSpeak(__instance); - if (changesToSpeak != "") - toSpeak = $"{toSpeak} \n {changesToSpeak}"; - - if (characterCreationMenuQueryKey != toSpeak && toSpeak.Trim() != "") - { - characterCreationMenuQueryKey = toSpeak; - MainClass.ScreenReader.Say(toSpeak, true); - } - } - catch (Exception e) - { - MainClass.ErrorLog($"Unable to narrate Text:\n{e.Message}\n{e.StackTrace}"); - } - } - - private static string getChangesToSpeak(CharacterCustomization __instance) - { - string toSpeak = ""; - string currentPetName = getCurrentPetName(); - string currentSkin = getCurrentSkin(); - string currentHair = getCurrentHair(); - string currentShirt = getCurrentShirt(); - string currentPants = getCurrentPants(); - string currentAccessory = getCurrentAccessory(); - string currentEyeColor = getCurrentEyeColor(); - string currentEyeColorHue = getCurrentEyeColorHue(__instance); - string currentEyeColorSaturation = getCurrentEyeColorSaturation(__instance); - string currentEyeColorValue = getCurrentEyeColorValue(__instance); - string currentHairColor = getCurrentHairColor(); - string currentHairColorHue = getCurrentHairColorHue(__instance); - string currentHairColorSaturation = getCurrentHairColorSaturation(__instance); - string currentHairColorValue = getCurrentHairColorValue(__instance); - string currentPantsColor = getCurrentPantsColor(); - string currentPantsColorHue = getCurrentPantsColorHue(__instance); - string currentPantsColorSaturation = getCurrentPantsColorSaturation(__instance); - string currentPantsColorValue = getCurrentPantsColorValue(__instance); - - if (characterDesignToggle) - { - if (prevSkin != currentSkin) - { - prevSkin = currentSkin; - if (currentSkin != "") - toSpeak = $"{toSpeak} \n {currentSkin}"; - } - - if (prevHair != currentHair) - { - prevHair = currentHair; - if (currentHair != "") - toSpeak = $"{toSpeak} \n {currentHair}"; - } - - if (prevShirt != currentShirt) - { - prevShirt = currentShirt; - if (currentShirt != "") - toSpeak = $"{toSpeak} \n {currentShirt}"; - } - - if (prevPants != currentPants) - { - prevPants = currentPants; - if (currentPants != "") - toSpeak = $"{toSpeak} \n {currentPants}"; - } - - if (prevAccessory != currentAccessory) - { - prevAccessory = currentAccessory; - if (currentAccessory != "") - toSpeak = $"{toSpeak} \n {currentAccessory}"; - } - - if (prevEyeColorHue != currentEyeColorHue) - { - if (currentComponent != null && currentComponent.myID == 522) - { - prevEyeColorHue = currentEyeColorHue; - if (currentEyeColorHue != "") - toSpeak = $"{toSpeak} \n Hue: {currentEyeColorHue}"; - } else { - prevEyeColorHue = ""; - } - } - - if (prevEyeColorSaturation != currentEyeColorSaturation) - { - if (currentComponent != null && currentComponent.myID == 523) - { - prevEyeColorSaturation = currentEyeColorSaturation; - if (currentEyeColorSaturation != "") - toSpeak = $"{toSpeak} \n Saturation: {currentEyeColorSaturation}"; - } else { - prevEyeColorSaturation = ""; - } - } - - if (prevEyeColorValue != currentEyeColorValue) - { - if (currentComponent != null && currentComponent.myID == 524) - { - prevEyeColorValue = currentEyeColorValue; - if (currentEyeColorValue != "") - toSpeak = $"{toSpeak} \n Value: {currentEyeColorValue}"; - } else { - prevEyeColorValue = ""; - } - } - - if (prevEyeColor != currentEyeColor) - { - if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 522 || currentComponent.myID <= 524))) - { - prevEyeColor = currentEyeColor; - if (currentEyeColor != "") - toSpeak = $"{toSpeak} \n {currentEyeColor}"; - } - } - - if (prevHairColorHue != currentHairColorHue) - { - if (currentComponent != null && currentComponent.myID == 525) - { - prevHairColorHue = currentHairColorHue; - if (currentHairColorHue != "") - toSpeak = $"{toSpeak} \n Hue: {currentHairColorHue}"; - } else { - prevHairColorHue = ""; - } - } - - if (prevHairColorSaturation != currentHairColorSaturation) - { - if (currentComponent != null && currentComponent.myID == 526) - { - prevHairColorSaturation = currentHairColorSaturation; - if (currentHairColorSaturation != "") - toSpeak = $"{toSpeak} \n Saturation: {currentHairColorSaturation}"; - } else { - prevHairColorSaturation = ""; - } - } - - if (prevHairColorValue != currentHairColorValue) - { - if (currentComponent != null && currentComponent.myID == 527) - { - prevHairColorValue = currentHairColorValue; - if (currentHairColorValue != "") - toSpeak = $"{toSpeak} \n Value: {currentHairColorValue}"; - } else { - prevHairColorValue = ""; - } - } - - if (prevHairColor != currentHairColor) - { - if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 525 || currentComponent.myID <= 527))) - { - prevHairColor = currentHairColor; - if (currentHairColor != "") - toSpeak = $"{toSpeak} \n {currentHairColor}"; - } - } - - if (prevPantsColorHue != currentPantsColorHue) - { - if (currentComponent != null && currentComponent.myID == 528) - { - prevPantsColorHue = currentPantsColorHue; - if (currentPantsColorHue != "") - toSpeak = $"{toSpeak} \n Hue: {currentPantsColorHue}"; - } else { - prevPantsColorHue = ""; - } - } - - if (prevPantsColorSaturation != currentPantsColorSaturation) - { - if (currentComponent != null && currentComponent.myID == 529) - { - prevPantsColorSaturation = currentPantsColorSaturation; - if (currentPantsColorSaturation != "") - toSpeak = $"{toSpeak} \n Saturation: {currentPantsColorSaturation}"; - } else { - prevPantsColorSaturation = ""; - } - } - - if (prevPantsColorValue != currentPantsColorValue) - { - if (currentComponent != null && currentComponent.myID == 530) - { - prevPantsColorValue = currentPantsColorValue; - if (currentPantsColorValue != "") - toSpeak = $"{toSpeak} \n Value: {currentPantsColorValue}"; - } else { - prevPantsColorValue = ""; - } - } - - if (prevPantsColor != currentPantsColor) - { - if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 528 || currentComponent.myID <= 530))) - { - prevPantsColor = currentPantsColor; - if (currentPantsColor != "") - toSpeak = $"{toSpeak} \n {currentPantsColor}"; - } - } - } - - if (prevPetName != currentPetName) - { - prevPetName = currentPetName; - if (currentPetName != "") - toSpeak = $"{toSpeak} \n Current Pet: {currentPetName}"; - } - return toSpeak.Trim(); - } - - - private static string CycleThroughItems(bool increase, CharacterCustomization __instance, bool ___skipIntro, - ClickableComponent ___startingCabinsLabel, ClickableComponent ___difficultyModifierLabel, TextBox ___nameBox, - TextBox ___farmnameBox, TextBox ___favThingBox) - { - string toSpeak = " "; - int DesignControlsIndex = 0; - Dictionary buttons = new(); - - #region Add buttons with their names IF they are available - - #region Character related - string postText = ""; - if (__instance.nameBoxCC != null && __instance.nameBoxCC.visible) - { - if (___nameBox.Text != "") - { - postText = $": {___nameBox.Text}"; - } else { - postText = " Text Box"; - } - buttons.Add(__instance.nameBoxCC, $"Farmer's Name{postText}"); - } - - if (__instance.farmnameBoxCC != null && __instance.farmnameBoxCC.visible) - { - if (___farmnameBox.Text != "") - { - postText = $": {___farmnameBox.Text}"; - } else { - postText = " Text Box"; - } - buttons.Add(__instance.farmnameBoxCC, $"Farm's Name{postText}"); - } - - if (__instance.favThingBoxCC != null && __instance.favThingBoxCC.visible) - { - if (___favThingBox.Text != "") - { - postText = $": {___favThingBox.Text}"; - } else { - postText = " Text Box"; - } - buttons.Add(__instance.favThingBoxCC, $"Favourite Thing{postText}"); - } - - if (__instance.petPortraitBox.HasValue) // Cannot get petButtons like with others - { - ClickableComponent petPrev = __instance.getComponentWithID(511); - buttons.Add(petPrev, "Previous pet button"); - - ClickableComponent petNext = __instance.getComponentWithID(510); - buttons.Add(petNext, "Next pet button"); - } - - if (__instance.randomButton != null && __instance.randomButton.visible) - buttons.Add(__instance.randomButton, "Random Skin Button"); - - // 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. - 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 - && 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.rightSelectionButtons[DesignControlsIndex], "Rotate Right Button"); - ++DesignControlsIndex; - } - } - - if (__instance.genderButtons.Count > 0) - { - buttons.Add(__instance.genderButtons[0], ((Game1.player.IsMale) ? "Selected " : "") + "Gender: Male 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)) - { - while(DesignControlsIndex < __instance.leftSelectionButtons.Count) - { - ClickableComponent left = __instance.leftSelectionButtons[DesignControlsIndex]; - ClickableComponent right = __instance.rightSelectionButtons[DesignControlsIndex]; - string name = left.name; - // minor cleanup on names to be slightly more descriptive - switch (name) - { - case "Skin": - name += " Tone"; - break; - case "Hair": - name += " Style"; - break; - case "Acc": - name = "Accessory"; - break; - default: - break; - } - if (!buttons.ContainsKey(left) || !buttons.ContainsKey(right)) - { - buttons.Add(left, $"Previous {name} button"); - buttons.Add(right, $"Next {name} button"); - } - //MainClass.ScreenReader.Say($"Left {DesignControlsIndex}: {__instance.leftSelectionButtons[DesignControlsIndex]} {__instance.leftSelectionButtons[DesignControlsIndex].name}\n", true); - //MainClass.ScreenReader.Say($"Right {DesignControlsIndex}: {__instance.rightSelectionButtons[DesignControlsIndex]} {__instance.rightSelectionButtons[DesignControlsIndex].name}\n", true); - ++DesignControlsIndex; - } - - ClickableComponent eyeColorHue = __instance.getComponentWithID(522); - if (eyeColorHue != null && eyeColorHue.visible) - buttons.Add(eyeColorHue, "eye color hue slider"); - - ClickableComponent eyeColorSaturation = __instance.getComponentWithID(523); - if (eyeColorSaturation != null && eyeColorSaturation.visible) - buttons.Add(eyeColorSaturation, "eye color saturation slider"); - - ClickableComponent eyeColorValue = __instance.getComponentWithID(524); - if (eyeColorValue != null && eyeColorValue.visible) - buttons.Add(eyeColorValue, "eye color Value slider"); - - ClickableComponent hairColorHue = __instance.getComponentWithID(525); - if (hairColorHue != null && hairColorHue.visible) - buttons.Add(hairColorHue, "hair color hue slider"); - - ClickableComponent hairColorSaturation = __instance.getComponentWithID(526); - if (hairColorSaturation != null && hairColorSaturation.visible) - buttons.Add(hairColorSaturation, "hair color saturation slider"); - - ClickableComponent hairColorValue = __instance.getComponentWithID(527); - if (hairColorValue != null && hairColorValue.visible) - buttons.Add(hairColorValue, "hair color Value slider"); - - ClickableComponent pantsColorHue = __instance.getComponentWithID(528); - if (pantsColorHue != null && pantsColorHue.visible) - buttons.Add(pantsColorHue, "pants color hue slider"); - - ClickableComponent pantsColorSaturation = __instance.getComponentWithID(529); - if (pantsColorSaturation != null && pantsColorSaturation.visible) - buttons.Add(pantsColorSaturation, "pants color saturation slider"); - - ClickableComponent pantsColorValue = __instance.getComponentWithID(530); - if (pantsColorValue != null && pantsColorValue.visible) - buttons.Add(pantsColorValue, "pants color Value slider"); - } - - #endregion - - #region Farm layout related - if (__instance.farmTypeButtons.Count > 0) - { - for (int i = 0; i < __instance.farmTypeButtons.Count; i++) - { - buttons.Add(__instance.farmTypeButtons[i], ((i == Game1.whichFarm) ? "Selected " : "") + getFarmHoverText(__instance.farmTypeButtons[i])); - } - } - - if (__instance.farmTypeNextPageButton != null && __instance.farmTypeNextPageButton.visible) - buttons.Add(__instance.farmTypeNextPageButton, "Next Farm Type Page Button"); - - if (__instance.farmTypePreviousPageButton != null && __instance.farmTypePreviousPageButton.visible) - buttons.Add(__instance.farmTypePreviousPageButton, "Previous Farm Type Page Button"); - #endregion - - #region Co-op related - if (__instance.source == Source.HostNewFarm) - { - ClickableComponent cabinLeft = __instance.getComponentWithID(621); - if (Game1.startingCabins > 0) - buttons.Add(cabinLeft, "Decrease starting cabins button"); - - buttons.Add(___startingCabinsLabel, $"Starting cabins: {Game1.startingCabins}"); - - ClickableComponent cabinRight = __instance.getComponentWithID(622); - if (Game1.startingCabins < 3) - buttons.Add(cabinRight, "Increase starting cabins button"); - - if (Game1.startingCabins > 0) - { - buttons.Add(__instance.cabinLayoutButtons[0], "Cabin layout to nearby Button"); - buttons.Add(__instance.cabinLayoutButtons[1], "Cabin layout to separate Button"); - } - - ClickableComponent difficultyLeft = __instance.getComponentWithID(627); - buttons.Add(difficultyLeft, "Increase profit margin button"); - buttons.Add(___difficultyModifierLabel, "Profit Margin: " + (((Game1.player.difficultyModifier * 100) == 100f) ? "normal" : Game1.player.difficultyModifier.ToString())); - ClickableComponent difficultyRight = __instance.getComponentWithID(628); - buttons.Add(difficultyRight, "Decrease profit margin button"); - - ClickableComponent walletLeft = __instance.getComponentWithID(631); - buttons.Add(walletLeft, "Money style to " + ((!Game1.player.team.useSeparateWallets.Value) ? "separate wallets" : "shared wallets") + " button"); - } - #endregion - - if (__instance.skipIntroButton != null && __instance.skipIntroButton.visible) - buttons.Add(__instance.skipIntroButton, (___skipIntro ? "Enabled" : "Disabled") + " Skip Intro Button"); - - if (__instance.advancedOptionsButton != null && __instance.advancedOptionsButton.visible) - buttons.Add(__instance.advancedOptionsButton, "Advanced Options Button"); - - if (__instance.okButton != null && __instance.okButton.visible) - buttons.Add(__instance.okButton, "OK Button"); - - if (__instance.backButton != null && __instance.backButton.visible) - buttons.Add(__instance.backButton, "Back Button"); - #endregion - - int size = buttons.Count - 1; - - if (increase) - { - saveGameIndex++; - if (saveGameIndex > size) - saveGameIndex = 0; - } - else - { - saveGameIndex--; - if (saveGameIndex < 0) - saveGameIndex = size; - } - - currentComponent = buttons.ElementAt(saveGameIndex).Key; - currentComponent!.snapMouseCursor(); - __instance.setCurrentlySnappedComponentTo(currentComponent!.myID); - - toSpeak = buttons.ElementAt(saveGameIndex).Value; - - return toSpeak.Trim(); - } - - private static SliderBar? getCurrentSliderBar(int id, CharacterCustomization __instance) - { - if (id >= 522 && id <= 530) - { - // Three ColorPickers with 3 SliderBars each. - // First group ids by ColorPicker. - // Maps 522-524 -> 0, 525-527 -> 1, 528-530 -> 2 - int whichColorPicker = (int)Math.Floor(((float)id - 522f) / 3f); - // Next group ids by slider type. - // Maps [522,525,528] -> 0, [523,526,529] -> 1, [524,527,530] -> 2 - int whichSliderBar = (int)Math.Floor((float)id % 3f); - ColorPicker cp; - switch (whichColorPicker) - { - default: - case 0: - // 522-524 == eye color - cp = __instance.eyeColorPicker; - break; - case 1: - // 525-527 == hair color - cp = __instance.hairColorPicker; - break; - case 2: - // 528-530 == pants color - cp = __instance.pantsColorPicker; - break; - } - SliderBar sb; - switch (whichSliderBar) - { - default: - case 0: - // 522, 525, 528 == hue slider - sb = cp.hueBar; - break; - case 1: - // 523, 526, 529 == saturation slider - sb = cp.saturationBar; - break; - case 2: - // 524, 527, 530 == value slider - sb = cp.valueBar; - break; - } - return sb; - } else { - return null; - } - } - - private static void AdjustCurrentSlider(bool increase, CharacterCustomization __instance, int amount=1) - { - if (currentComponent != null && currentComponent.myID >= 522 && currentComponent.myID <= 530) - { - SliderBar sb = getCurrentSliderBar(currentComponent.myID, __instance) !; - if (sb != null) - { - double step = ((double)sb.bounds.Width / 100d); // size of 1% change in slider value - double value = (double)sb.value; - double x = 0d; - int y = currentComponent.bounds.Center.Y; - if (increase) - { - value = Math.Min(value + amount, 99d); - x = Math.Min(Math.Ceiling((value * step)), (double)sb.bounds.Width); - } else { - value = Math.Max(value - amount, 0d); - x = Math.Max(Math.Ceiling((value * step)), 0d); - } - x += (double)currentComponent.bounds.Left; - Game1.setMousePosition((int)x, y); - Game1.activeClickableMenu.receiveLeftClick((int)x, y); - } - } - } - - // Most values (exception noted below) are 0 indexed internally but visually start from 1. Thus we increment before returning. - private static string getCurrentSkin() - { - if (currentComponent != null && (currentComponent.myID == 507 || currentComponent.name == "Skin")) - return $"Skin tone: {Game1.player.skin.Value + 1}"; - return ""; - } - - private static string getCurrentHair() - { - if (currentComponent != null && (currentComponent.myID == 507 || currentComponent.name == "Hair")) - return $"hair style: {Game1.player.hair.Value + 1}"; - return ""; - } - - private static string getCurrentShirt() - { - if (currentComponent != null && (currentComponent.myID == 507 || currentComponent.name == "Shirt")) - return $"Shirt: {Game1.player.shirt.Value + 1}"; - return ""; - } - - private static string getCurrentPants() - { - if (currentComponent != null && (currentComponent.myID == 507 || currentComponent.name == "Pants Style")) - return $"Pants: {Game1.player.pants.Value + 1}"; - return ""; - } - - private static string getCurrentAccessory() - { - // Internally accessory starts from -1 while displaying +1 on screen. - if (currentComponent != null && (currentComponent.myID == 507 || currentComponent.name == "Acc")) - return $"accessory: {Game1.player.accessory.Value + 2}"; - return ""; - } - - private static string getCurrentEyeColor() - { - if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 522 && currentComponent.myID <= 524))) - return $"Eye color: {Game1.player.newEyeColor.R}, {Game1.player.newEyeColor.G}, {Game1.player.newEyeColor.B}"; - return ""; - } - - private static string getCurrentEyeColorHue(CharacterCustomization __instance) - { - SliderBar sb = getCurrentSliderBar(522, __instance)!; - if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 522 && currentComponent.myID <= 524))) - return sb.value!.ToString(); - return ""; - } - - private static string getCurrentEyeColorSaturation(CharacterCustomization __instance) - { - SliderBar sb = getCurrentSliderBar(523, __instance)!; - if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 522 && currentComponent.myID <= 524))) - return sb.value!.ToString(); - return ""; - } - - private static string getCurrentEyeColorValue(CharacterCustomization __instance) - { - SliderBar sb = getCurrentSliderBar(524, __instance)!; - if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 522 && currentComponent.myID <= 524))) - return sb.value!.ToString(); - return ""; - } - - private static string getCurrentHairColor() - { - if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 525 && currentComponent.myID <= 527))) - return $"Hair color: {Game1.player.hairstyleColor.R}, {Game1.player.hairstyleColor.G}, {Game1.player.hairstyleColor.B}"; - return ""; - } - - private static string getCurrentHairColorHue(CharacterCustomization __instance) - { - SliderBar sb = getCurrentSliderBar(525, __instance)!; - if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 525 && currentComponent.myID <= 527))) - return sb.value!.ToString(); - return ""; - } - - private static string getCurrentHairColorSaturation(CharacterCustomization __instance) - { - SliderBar sb = getCurrentSliderBar(526, __instance)!; - if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 525 && currentComponent.myID <= 527))) - return sb.value!.ToString(); - return ""; - } - - private static string getCurrentHairColorValue(CharacterCustomization __instance) - { - SliderBar sb = getCurrentSliderBar(527, __instance)!; - if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 525 && currentComponent.myID <= 527))) - return sb.value!.ToString(); - return ""; - } - - private static string getCurrentPantsColor() - { - if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 528 && currentComponent.myID <= 530))) - return $"Pants color: {Game1.player.pantsColor.R}, {Game1.player.pantsColor.G}, {Game1.player.pantsColor.B}"; - return ""; - } - - private static string getCurrentPantsColorHue(CharacterCustomization __instance) - { - SliderBar sb = getCurrentSliderBar(528, __instance)!; - if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 528 && currentComponent.myID <= 530))) - return sb.value!.ToString(); - return ""; - } - - private static string getCurrentPantsColorSaturation(CharacterCustomization __instance) - { - SliderBar sb = getCurrentSliderBar(529, __instance)!; - if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 528 && currentComponent.myID <= 530))) - return sb.value!.ToString(); - return ""; - } - - private static string getCurrentPantsColorValue(CharacterCustomization __instance) - { - SliderBar sb = getCurrentSliderBar(530, __instance)!; - if (currentComponent != null && (currentComponent.myID == 507 || (currentComponent.myID >= 528 && currentComponent.myID <= 530))) - return sb.value!.ToString(); - return ""; - } - - private static string getCurrentPetName() - { - if (currentComponent != null && currentComponent.name == "Pet") - { - return ((Game1.player.catPerson) ? "Cat" : "Dog") + " Breed: " + Game1.player.whichPetBreed; - } else { - return ""; - } - } - - private static string getFarmHoverText(ClickableTextureComponent farm) - { - string hoverTitle = " ", hoverText = " "; - if (!farm.name.Contains("Gray")) - { - if (farm.hoverText.Contains('_')) - { - hoverTitle = farm.hoverText.Split('_')[0]; - hoverText = farm.hoverText.Split('_')[1]; - } - else - { - hoverTitle = " "; - hoverText = farm.hoverText; - } - } - else - { - if (farm.name.Contains("Gray")) - { - hoverText = "Reach level 10 " + Game1.content.LoadString("Strings\\UI:Character_" + farm.name.Split('_')[1]) + " to unlock."; - } - } - - return $"{hoverTitle}: {hoverText}"; - } - } -} diff --git a/stardew-access/Patches/TitleMenuPatches/AdvancedGameOptionsPatch.cs b/stardew-access/Patches/TitleMenuPatches/AdvancedGameOptionsPatch.cs new file mode 100644 index 0000000..606ab1c --- /dev/null +++ b/stardew-access/Patches/TitleMenuPatches/AdvancedGameOptionsPatch.cs @@ -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 = ""; + } + } +} diff --git a/stardew-access/Patches/CharacterCustomizationMenuPatches.cs b/stardew-access/Patches/TitleMenuPatches/CharacterCustomizationMenuPatches.cs similarity index 92% rename from stardew-access/Patches/CharacterCustomizationMenuPatches.cs rename to stardew-access/Patches/TitleMenuPatches/CharacterCustomizationMenuPatches.cs index 52c4323..005456d 100644 --- a/stardew-access/Patches/CharacterCustomizationMenuPatches.cs +++ b/stardew-access/Patches/TitleMenuPatches/CharacterCustomizationMenuPatches.cs @@ -1,8 +1,10 @@ using StardewValley; using StardewValley.Menus; -namespace stardew_access.Patches { - internal class CharacterCustomizationPatches { +namespace stardew_access.Patches +{ + internal class CharacterCustomizationMenuPatch + { private static bool isRunning = false; private static int saveGameIndex = -1; public static string characterCreationMenuQueryKey = " "; @@ -28,12 +30,14 @@ namespace stardew_access.Patches { public static bool characterDesignToggleShouldSpeak = true; 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, TextBox ___farmnameBox, TextBox ___favThingBox) { 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 string toSpeak = ""; if (characterDesignToggleShouldSpeak) @@ -44,37 +48,10 @@ namespace stardew_access.Patches { string itemsToSpeak = ""; string changesToSpeak = ""; - if (___nameBox.Selected) - { - 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) + if (MainClass.Config.CharacterCreationMenuNextKey.JustPressed() && !isRunning) { isRunning = true; - itemsToSpeak =CycleThroughItems(true, __instance, ___skipIntro, ___startingCabinsLabel, ___difficultyModifierLabel, ___nameBox, ___farmnameBox, ___favThingBox); + itemsToSpeak = CycleThroughItems(true, __instance, ___skipIntro, ___startingCabinsLabel, ___difficultyModifierLabel, ___nameBox, ___farmnameBox, ___favThingBox); if (itemsToSpeak != "") toSpeak = $"{itemsToSpeak} \n {toSpeak}"; Task.Delay(200).ContinueWith(_ => { isRunning = false; }); @@ -122,7 +99,9 @@ namespace stardew_access.Patches { if (characterDesignToggle) { displayState = "shown"; - } else { + } + else + { displayState = "hidden"; } toSpeak = $"Character design controls {displayState}. \n {toSpeak}"; @@ -140,7 +119,7 @@ namespace stardew_access.Patches { } 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; if (currentEyeColorHue != "") toSpeak = $"{toSpeak} \n Hue: {currentEyeColorHue}"; - } else { + } + else + { prevEyeColorHue = ""; } } @@ -222,7 +203,9 @@ namespace stardew_access.Patches { prevEyeColorSaturation = currentEyeColorSaturation; if (currentEyeColorSaturation != "") toSpeak = $"{toSpeak} \n Saturation: {currentEyeColorSaturation}"; - } else { + } + else + { prevEyeColorSaturation = ""; } } @@ -234,7 +217,9 @@ namespace stardew_access.Patches { prevEyeColorValue = currentEyeColorValue; if (currentEyeColorValue != "") toSpeak = $"{toSpeak} \n Value: {currentEyeColorValue}"; - } else { + } + else + { prevEyeColorValue = ""; } } @@ -256,7 +241,9 @@ namespace stardew_access.Patches { prevHairColorHue = currentHairColorHue; if (currentHairColorHue != "") toSpeak = $"{toSpeak} \n Hue: {currentHairColorHue}"; - } else { + } + else + { prevHairColorHue = ""; } } @@ -268,7 +255,9 @@ namespace stardew_access.Patches { prevHairColorSaturation = currentHairColorSaturation; if (currentHairColorSaturation != "") toSpeak = $"{toSpeak} \n Saturation: {currentHairColorSaturation}"; - } else { + } + else + { prevHairColorSaturation = ""; } } @@ -280,7 +269,9 @@ namespace stardew_access.Patches { prevHairColorValue = currentHairColorValue; if (currentHairColorValue != "") toSpeak = $"{toSpeak} \n Value: {currentHairColorValue}"; - } else { + } + else + { prevHairColorValue = ""; } } @@ -302,7 +293,9 @@ namespace stardew_access.Patches { prevPantsColorHue = currentPantsColorHue; if (currentPantsColorHue != "") toSpeak = $"{toSpeak} \n Hue: {currentPantsColorHue}"; - } else { + } + else + { prevPantsColorHue = ""; } } @@ -314,7 +307,9 @@ namespace stardew_access.Patches { prevPantsColorSaturation = currentPantsColorSaturation; if (currentPantsColorSaturation != "") toSpeak = $"{toSpeak} \n Saturation: {currentPantsColorSaturation}"; - } else { + } + else + { prevPantsColorSaturation = ""; } } @@ -326,7 +321,9 @@ namespace stardew_access.Patches { prevPantsColorValue = currentPantsColorValue; if (currentPantsColorValue != "") toSpeak = $"{toSpeak} \n Value: {currentPantsColorValue}"; - } else { + } + else + { prevPantsColorValue = ""; } } @@ -345,13 +342,12 @@ namespace stardew_access.Patches { if (prevPetName != currentPetName) { prevPetName = currentPetName; - if (currentPetName != "") + if (currentPetName != "") toSpeak = $"{toSpeak} \n Current Pet: {currentPetName}"; } return toSpeak.Trim(); } - private static string CycleThroughItems(bool increase, CharacterCustomization __instance, bool ___skipIntro, ClickableComponent ___startingCabinsLabel, ClickableComponent ___difficultyModifierLabel, TextBox ___nameBox, TextBox ___farmnameBox, TextBox ___favThingBox) @@ -369,7 +365,9 @@ namespace stardew_access.Patches { if (___nameBox.Text != "") { postText = $": {___nameBox.Text}"; - } else { + } + else + { postText = " Text Box"; } buttons.Add(__instance.nameBoxCC, $"Farmer's Name{postText}"); @@ -380,7 +378,9 @@ namespace stardew_access.Patches { if (___farmnameBox.Text != "") { postText = $": {___farmnameBox.Text}"; - } else { + } + else + { postText = " Text Box"; } buttons.Add(__instance.farmnameBoxCC, $"Farm's Name{postText}"); @@ -391,7 +391,9 @@ namespace stardew_access.Patches { if (___favThingBox.Text != "") { postText = $": {___favThingBox.Text}"; - } else { + } + else + { postText = " Text Box"; } 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. // 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 - && new[] {__instance.leftSelectionButtons[DesignControlsIndex].name, __instance.rightSelectionButtons[DesignControlsIndex].name }.All(n => n == "Direction")) // both named "Direction" + 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" { buttons.Add(__instance.leftSelectionButtons[DesignControlsIndex], "Rotate Left 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"); } - 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 right = __instance.rightSelectionButtons[DesignControlsIndex]; @@ -574,7 +576,7 @@ namespace stardew_access.Patches { } currentComponent = buttons.ElementAt(saveGameIndex).Key; - currentComponent!.snapMouseCursor(); + currentComponent!.snapMouseCursor(); __instance.setCurrentlySnappedComponentTo(currentComponent!.myID); toSpeak = buttons.ElementAt(saveGameIndex).Value; @@ -655,16 +657,18 @@ namespace stardew_access.Patches { break; } return sb; - } else { + } + else + { 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) { - SliderBar sb = getCurrentSliderBar(currentComponent.myID, __instance) !; + SliderBar sb = getCurrentSliderBar(currentComponent.myID, __instance)!; if (sb != null) { 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); x = Math.Min(Math.Ceiling((value * step)), (double)sb.bounds.Width); - } else { + } + else + { value = Math.Max(value - amount, 0d); x = Math.Max(Math.Ceiling((value * step)), 0d); } @@ -821,7 +827,9 @@ namespace stardew_access.Patches { if (currentComponent != null && currentComponent.name == "Pet") { return ((Game1.player.catPerson) ? "Cat" : "Dog") + " Breed: " + Game1.player.whichPetBreed; - } else { + } + else + { return ""; } } diff --git a/stardew-access/Patches/TitleMenuPatches/CoopMenuPatch.cs b/stardew-access/Patches/TitleMenuPatches/CoopMenuPatch.cs new file mode 100644 index 0000000..3f4eb6a --- /dev/null +++ b/stardew-access/Patches/TitleMenuPatches/CoopMenuPatch.cs @@ -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 = ""; + } + } +} diff --git a/stardew-access/Patches/TitleMenuPatches/LoadGameMenuPatch.cs b/stardew-access/Patches/TitleMenuPatches/LoadGameMenuPatch.cs new file mode 100644 index 0000000..df16a22 --- /dev/null +++ b/stardew-access/Patches/TitleMenuPatches/LoadGameMenuPatch.cs @@ -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; + } + } +} diff --git a/stardew-access/Patches/TitleMenuPatches/TitleMenuPatches.cs b/stardew-access/Patches/TitleMenuPatches/TitleMenuPatches.cs new file mode 100644 index 0000000..7dbf06f --- /dev/null +++ b/stardew-access/Patches/TitleMenuPatches/TitleMenuPatches.cs @@ -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 = ""; + } + } +} diff --git a/stardew-access/assets/sounds/invalid-selection.wav b/stardew-access/assets/sounds/invalid-selection.wav new file mode 100644 index 0000000..83e63ba Binary files /dev/null and b/stardew-access/assets/sounds/invalid-selection.wav differ