commit 7b3ddc064a66dd17697eefa0400badcaa30bb31f Author: Shu-Ying <1754798088@qq.com> Date: Fri Jul 31 00:58:15 2026 +0800 初始化仓库 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d981fa7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +log.txt +dist/ +tools/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, 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 +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If 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 convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU 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 +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "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 PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM 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 PROGRAM (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 PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state 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 program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/extension.css b/extension.css new file mode 100644 index 0000000..31d989c --- /dev/null +++ b/extension.css @@ -0,0 +1,494 @@ +.manual { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + background: rgba(0,0,0,0.4); + z-index: 4; +} + +.manual>.menu { + display: block; + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 50px; + background: rgba(0,0,0,0.6); +} + +.manual>.content { + position: absolute; + left: 0; + top: 50px; + width: 100%; + height: calc(100% - 50px) !important; + background: none; + overflow-y: auto; +} + +.manual>.content>.content-container>.content { + width: 100%; + height: 100%; +} + +.manual>.content>.content-container>.content>.caption { + display: inline-block; + width: 100%; + height: auto; + margin: 0; +} + +.manual>.content>.content-container>.content>.caption div { + position: static; + height: auto; +} + +.manual>.menu>.addNewBoss { + position: absolute; + left: calc(50% + 110px); + top: calc(50% - 15px); + width: 60px; + height: 30px; + line-height: 30px; + text-align: center; + background: red; +} + +.manual>.menu>.newBossList { + position: absolute; + left: calc(5% - 15px); + top: calc(50% - 15px); + width: 60px; + height: 30px; + line-height: 30px; + text-align: center; + background: red; +} + +.manual>.menu>.deleteNewBossAllList { + position: absolute; + left: calc(20% - 15px); + top: calc(50% - 15px); + width: 60px; + height: 30px; + line-height: 30px; + text-align: center; + background: red; +} + +.manual>.menu>.deleteNewBoss { + position: absolute; + left: calc(50% - 163px); + top: calc(50% - 15px); + width: 60px; + height: 30px; + line-height: 30px; + text-align: center; + background: red; +} +.manual>.menu>.playerList { + position: absolute; + left: calc(80% - 15px); + top: calc(50% - 15px); + width: 60px; + height: 30px; + line-height: 30px; + text-align: center; + background: red; +} + +.manual>.menu>.close { + position: absolute; + right: 5px; + top: calc(50% - 15px); + width: 60px; + height: 30px; + line-height: 30px; + text-align: center; + background: red; +} + +.manual>.menu>input { + position: absolute; + left: calc(50% - 100px); + top: calc(50% - 15px); + width: 200px; + height: 30px; + color: white; + font-size: 20px; + text-align: center; + border: 2px solid rgba(255,255,100,0.4); + border-radius: 2px; + background: transparent; +} + +.shuYing-character-dialog-mask { + position: fixed !important; + inset: 0; + z-index: 99999; + display: flex !important; + align-items: center; + justify-content: center; + background: rgba(0, 0, 0, 0.58); + font-size: 14px; + line-height: normal; +} + +.shuYing-character-dialog-mask *, +.shuYing-character-dialog-mask *::before, +.shuYing-character-dialog-mask *::after { + box-sizing: border-box; + float: none !important; + writing-mode: horizontal-tb !important; + letter-spacing: 0 !important; +} + +.shuYing-character-dialog { + position: relative !important; + width: min(680px, 92vw); + max-height: 86vh; + display: flex !important; + flex-direction: column !important; + gap: 10px; + height: auto !important; + padding: 16px 18px 14px; + border: 1px solid rgba(246, 211, 101, 0.7); + border-radius: 8px; + background: rgba(24, 21, 26, 0.96); + box-shadow: 0 20px 54px rgba(0, 0, 0, 0.65), inset 0 1px 0 rgba(255, 255, 255, 0.08); + color: #f6d365; + font-family: "Microsoft YaHei", "SimHei", sans-serif; + text-align: left; + transform: none; + overflow: hidden; +} + +.shuYing-character-dialog>.shuYing-character-dialog-header, +.shuYing-character-dialog>.shuYing-character-dialog-intro, +.shuYing-character-dialog>.shuYing-character-dialog-search, +.shuYing-character-dialog>.shuYing-character-dialog-toolbar, +.shuYing-character-dialog>.shuYing-character-dialog-grid-wrap, +.shuYing-character-dialog>.shuYing-character-dialog-controls { + position: static !important; + left: auto !important; + top: auto !important; + right: auto !important; + bottom: auto !important; + transform: none !important; +} + +.shuYing-character-dialog-header { + display: flex !important; + flex: none !important; + flex-direction: row !important; + align-items: baseline; + justify-content: space-between; + gap: 16px; + min-height: 30px; + width: 100%; +} + +.shuYing-character-dialog-title { + display: block !important; + position: static !important; + flex: 1 1 auto !important; + width: auto !important; + height: auto !important; + font-size: 22px; + line-height: 30px; + font-weight: 600; + white-space: nowrap !important; + writing-mode: horizontal-tb !important; + text-shadow: 0 0 5px #000; +} + +.shuYing-character-dialog-count { + display: block !important; + position: static !important; + flex: none; + width: auto !important; + height: auto !important; + font-size: 14px; + line-height: 22px; + color: rgba(255, 255, 255, 0.82); +} + +.shuYing-character-dialog-intro { + display: block !important; + flex: none !important; + width: 100%; + height: auto !important; + font-size: 14px; + line-height: 22px; + color: rgba(255, 255, 255, 0.82); + text-align: left; +} + +.shuYing-character-dialog-toolbar { + display: flex !important; + flex: none !important; + flex-direction: column !important; + gap: 8px; + width: 100%; + max-height: 74px; + overflow-y: auto; + padding: 2px 0 4px; +} + +.shuYing-character-dialog-search { + all: unset; + display: block !important; + flex: none !important; + width: 100%; + height: 32px; + padding: 4px 10px; + border: 1px solid rgba(246, 211, 101, 0.55); + border-radius: 6px; + background: rgba(0, 0, 0, 0.32); + color: #fff; + font-size: 15px; + line-height: 24px; + text-align: left; +} + +.shuYing-character-dialog-groups { + display: flex !important; + flex-wrap: wrap; + gap: 8px; + width: 100%; + min-height: 28px; + padding: 1px 0 3px; +} + +.shuYing-character-dialog-group { + all: unset; + display: inline-flex !important; + align-items: center; + gap: 5px; + min-height: 28px; + padding: 3px 9px; + border: 1px solid rgba(255, 255, 255, 0.22); + border-radius: 6px; + background: rgba(255, 255, 255, 0.06); + color: rgba(255, 255, 255, 0.78); + font-size: 14px; + line-height: 20px; + text-align: center; + cursor: pointer; +} + +.shuYing-character-dialog-group.selected { + border-color: rgba(246, 211, 101, 0.75); + color: #f6d365; +} + +.shuYing-character-dialog-check { + position: relative; + display: inline-block; + width: 13px; + height: 13px; + flex: none; + border: 1px solid rgba(255, 255, 255, 0.45); + border-radius: 3px; + background: rgba(0, 0, 0, 0.28); +} + +.shuYing-character-dialog-group.selected .shuYing-character-dialog-check { + border-color: rgba(246, 211, 101, 0.9); + background: rgba(246, 211, 101, 0.85); +} + +.shuYing-character-dialog-group.selected .shuYing-character-dialog-check::after { + content: ""; + position: absolute; + left: 3px; + top: 1px; + width: 4px; + height: 7px; + border: solid #24170b; + border-width: 0 2px 2px 0; + transform: rotate(45deg); +} + +.shuYing-character-dialog-group-text { + display: inline-block; + min-width: 1em; + white-space: nowrap; +} + +.shuYing-character-dialog-grid-wrap { + position: relative !important; + display: block !important; + flex: 1 1 auto !important; + width: 100%; + min-height: 168px; + margin-top: 35px; + padding: 12px 10px 10px; + border: 1px solid rgba(246, 211, 101, 0.34); + border-radius: 7px; + background: rgba(255, 255, 255, 0.035); + overflow: hidden; +} + +.shuYing-character-dialog-grid { + position: relative !important; + display: grid !important; + flex: none !important; + grid-template-columns: repeat(auto-fill, 104px); + justify-content: center; + align-content: start; + gap: 12px; + min-height: 152px; + max-height: min(39vh, 350px); + overflow-y: auto; + padding: 0 3px 2px; + width: 100%; +} + +.shuYing-character-dialog-card { + all: unset; + position: relative !important; + display: flex !important; + flex-direction: column !important; + align-items: center; + justify-content: flex-start; + gap: 7px; + width: 104px; + min-height: 152px; + padding: 8px 7px; + border: 1px solid rgba(255, 255, 255, 0.18); + border-radius: 8px; + background: rgba(255, 255, 255, 0.06); + cursor: pointer; + transform: none; + text-align: center; +} + +.shuYing-character-dialog-card.selected { + border-color: #f6d365; + background: rgba(246, 211, 101, 0.15); + box-shadow: 0 0 16px rgba(246, 211, 101, 0.34); +} + +.shuYing-character-dialog-card.selected::after { + content: "已选"; + position: absolute; + right: 6px; + top: 6px; + padding: 2px 7px; + border-radius: 999px; + background: rgba(246, 211, 101, 0.95); + color: #22160d; + font-size: 12px; + line-height: 18px; +} + +.shuYing-character-dialog-avatar { + position: relative !important; + display: block !important; + width: 80px; + height: 108px; + flex: none; + margin: 0; + border-radius: 5px; + background-position: center; + background-size: cover; + box-shadow: 0 3px 9px rgba(0, 0, 0, 0.42); +} + +.shuYing-character-dialog-name { + width: 100%; + overflow: hidden; + color: #fff; + font-size: 15px; + line-height: 22px; + text-align: center; + text-overflow: ellipsis; + white-space: nowrap; + text-shadow: 0 0 4px #000; +} + +.shuYing-character-dialog-empty { + grid-column: 1 / -1; + padding: 34px 0; + color: rgba(255, 255, 255, 0.72); + font-size: 16px; + line-height: 26px; + text-align: center; +} + +.shuYing-character-dialog-controls { + display: flex !important; + flex: none !important; + justify-content: center; + flex-wrap: wrap; + gap: 10px; + padding-top: 2px; +} + +.shuYing-character-dialog-button { + all: unset; + display: inline-block !important; + min-width: 72px; + height: 30px; + padding: 0 14px; + border: 1px solid rgba(246, 211, 101, 0.58); + border-radius: 5px; + background: linear-gradient(180deg, rgba(111, 78, 32, 0.92), rgba(55, 35, 18, 0.92)); + color: #f6d365; + font-size: 15px; + line-height: 28px; + text-align: center; + text-shadow: 0 1px 2px #000; + cursor: pointer; +} + +.shuYing-character-dialog-button:hover { + border-color: rgba(246, 211, 101, 0.9); + background: linear-gradient(180deg, rgba(139, 99, 42, 0.95), rgba(70, 45, 22, 0.95)); +} + +.shuYing-character-dialog-button.disabled { + opacity: 0.45; + pointer-events: none; +} + +@media screen and (max-width: 520px) { + .shuYing-character-dialog { + width: 96vw; + padding: 14px 12px 12px; + } + + .shuYing-character-dialog-header { + align-items: flex-start; + flex-direction: column; + gap: 2px; + } + + .shuYing-character-dialog-toolbar { + max-height: 64px; + } + + .shuYing-character-dialog-grid-wrap { + min-height: 154px; + padding: 10px 7px 8px; + } + + .shuYing-character-dialog-grid { + grid-template-columns: repeat(auto-fill, 92px); + gap: 10px; + max-height: 36vh; + } + + .shuYing-character-dialog-card { + width: 92px; + min-height: 138px; + } + + .shuYing-character-dialog-avatar { + width: 72px; + height: 98px; + } +} diff --git a/extension.js b/extension.js new file mode 100644 index 0000000..ac4d52d --- /dev/null +++ b/extension.js @@ -0,0 +1,1007 @@ +import { lib, game, ui, get, ai, _status } from "../../noname.js"; +import updater from "./update.js" +import func from "./function.js" +import initShuYingMenu from "./menu.js"; + +export const type = "extension"; +export let shuYing = new Object(); +const shuYingLocalVersion = "2.0.0.8"; + +shuYing.name = "术樱包"; +shuYing.editable = false; +shuYing.localVersion = shuYingLocalVersion; +shuYing.m_bIsDownload = true; //用于判断下载状态,true: 可以下载,false:下载被占用 +shuYing.url = `${lib.assetURL}extension/术樱包`; //包路径 +shuYing.text = {}; //中上方文本 +shuYing.text_style = {}; //中上方文本样式 +shuYing.moduleCache = {}; //动态加载模块缓存,避免同一文件重复import +shuYing.voices = {}; //语音台词表,voices.js缺失时保持为空 + +//按需加载扩展内部模块。optional为true时,文件缺失只关闭对应功能,不中断整个扩展入口。 +shuYing.loadModule = async (path, label, optional = true) => { + if (!shuYing.moduleCache[path]) { + shuYing.moduleCache[path] = import(path) + .then(module => module.default ?? module) + .catch(error => { + const message = `术樱包:${label || path}加载失败`; + if (!optional) throw error; + console.warn(message, error); + return null; + }); + } + return shuYing.moduleCache[path]; +}; + +//加载语音翻译表。该文件只影响台词显示,缺失时不影响武将包主体加载。 +shuYing.loadVoices = async () => { + const voices = await shuYing.loadModule("./voices.js", "语音台词表", true); + shuYing.voices = voices || {}; + return shuYing.voices; +}; + +// 公共函数需要在菜单创建前初始化,扩展菜单可在选将预备阶段被点击,此时还没有 player。 +shuYing.initFunction = () => { + if (shuYing.functionInitialized) return; + if (typeof func == "function") { + func(lib, game, ui, get, ai, _status, shuYing); + shuYing.functionInitialized = true; + } +}; + +shuYing.getOnlineVersionText = () => `最新版本:${lib.config.shuYing_online_version || "检测中..."}`; +shuYing.updateOnlineVersionMenu = (version) => { + if (!version) return; + + game.saveConfig("shuYing_online_version", version); + + const text = `最新版本:${version}`; + if (lib.extensionMenu.extension_术樱包?.online_version) { + lib.extensionMenu.extension_术樱包.online_version.name = text; + } + + if (typeof document != "undefined") { + const nodes = document.querySelectorAll(".config"); + for (const node of nodes) { + if (node.textContent && node.textContent.trim().startsWith("最新版本:")) { + node.innerHTML = `${text}`; + } + } + } +}; + +/** + * @param { String } name 扩展包英文名 + * @param { String } translate 扩展包翻译名 + * @param { Object } obj 类对象 + */ +shuYing.appendExtension = function (name, translate, obj) { + let oobj = get.copy(obj); + oobj.name = name; + oobj.character = obj.character; + oobj.skill = obj.skill; + Object.assign(obj.translate, shuYing.voices || {}); + oobj.translate = obj.translate; + game.import('character', () => { return oobj; }); + lib.config.all.characters.push(name); + if (!lib.config.characters.contains(name)) { + lib.config.characters.push(name); + } + lib.translate[name + '_character_config'] = translate; +}; + +shuYing.getVersion = (callback) => { + return updater.getOnlineVersion() + .then(version => { + shuYing.updateOnlineVersionMenu(version?.online_version); + if (typeof callback == "function") { + callback(version); + } + return version; + }) + .catch(error => { + console.error("术樱包获取在线版本失败:", error); + return null; + }); +} + +//进入游戏时 +shuYing.content = (config, pack) => { +}; + +//术樱包初始化CSS样式 +shuYing.initCSS = () => { + //初始化加载CSS + lib.init.css(lib.assetURL + 'extension/术樱包', 'extension'); + + shuYing.text = document.createElement("div"); + shuYing.text_style = + { + width: "360px", + minHeight: "92px", + boxSizing: "border-box", + display: "flex", + flexDirection: "column", + alignItems: "stretch", + gap: "8px", + background: "linear-gradient(135deg, rgba(22,24,30,0.94), rgba(42,38,50,0.94))", + border: "1px solid rgba(255,255,255,0.18)", + borderRadius: "8px", + boxShadow: "0 12px 32px rgba(0,0,0,0.38), inset 0 1px 0 rgba(255,255,255,0.08)", + backdropFilter: "blur(8px)", + padding: "14px 16px", + position: "absolute", + top: "14px", + left: "50%", + transform: "translateX(-50%)", + zIndex: "10", + color: "#f5f1e8", + textAlign: "left", + writingMode: "horizontal-tb", + textOrientation: "mixed", + whiteSpace: "normal", + wordBreak: "keep-all", + fontSize: "14px", + lineHeight: "1.35", + fontFamily: "'Microsoft YaHei','STXinwei','xinwei',sans-serif", + pointerEvents: "none", + }; + + for (let k in shuYing.text_style) { + shuYing.text.style[k] = shuYing.text_style[k]; + }; +} + +//术樱包初始化Config配置文件 +shuYing.initConfig = () => { + if (lib.config.show_splash != 'off') game.saveConfig('show_splash', 'off'); //关闭启动页 + + //崩坏3包 + if (lib.config.extension_shuYing_bengHuai_Off == undefined) { + game.saveConfig('extension_shuYing_bengHuai_Off', true); + } + + //原神包 + if (lib.config.extension_术樱包_yuanShenOff == undefined) { + game.saveConfig('extension_术樱包_yuanShenOff', true); + } + + //崩铁包 + if (lib.config.extension_术樱包_bengTieOff == undefined) { + game.saveConfig('extension_术樱包_bengTieOff', true); + } + + //天书包 + if (lib.config.extension_shuYing_tianShu_Off == undefined) { + game.saveConfig('extension_shuYing_tianShu_Off', true); + } + + //天书一共多少层 + if (lib.config.extension_shuYing_tianShu_Instances == undefined) { + game.saveConfig('extension_shuYing_tianShu_Instances', 4); + } + + //天书自定义Boss列表 + if (lib.config.extension_shuYing_tianShu_DiyBoss == undefined) { + game.saveConfig('extension_shuYing_tianShu_DiyBoss', ''); + } + + //天书狂暴 + if (lib.config.extension_shuYing_tianShu_KuangBao == undefined) { + game.saveConfig('extension_shuYing_tianShu_KuangBao', ''); + } + + //百战天书 + if (lib.config.extension_shuYing_baiZhan_Off == undefined) { + game.saveConfig('extension_shuYing_baiZhan_Off', false); + } + + // + // if(lib.config.extension_术樱包_skillsoff==undefined) game.saveConfig('extension_术樱包_skillsoff',false); + + //版本号 + game.saveConfig('shuYing_local_version', shuYingLocalVersion); + +}; + +//术樱包菜单配置由 ./menu.js 初始化 + +//术樱包初始化武将包 +shuYing.initCharacter = async () => { + let shuYingList; + if (lib.config.mode == 'boss') { + const shuying_boss = await shuYing.loadModule("./pve/boss/index.js", "活动BOSS", true); + if (shuying_boss) { + shuYing.appendExtension("shuYing_pve_boss", "活动BOSS", shuying_boss); + } + + const initTianShu = await shuYing.loadModule("./tianshu/extension.js", "天书乱斗", true); + if (typeof initTianShu == "function") { + initTianShu(lib, game, ui, get, ai, _status, shuYing); + } + // shuYingList = + // [ + // "yuanshen", + // ]; + } + else { + // initBengTie(lib, game, ui, get, ai, _status, shuYing); + + // shuYingList = + // [ + // "yuanshen", + // ]; + } + + // shuYingList.forEach(url => { + // let extUrl = shuYing.url + "/" + url; + + // lib.init.js(extUrl, "extension", () => { + // window.func(lib, game, ui, get, ai, _status, shuYing); + // }); + // }); +} + +//初始化游戏时 +shuYing.precontent = async () => { + await shuYing.loadVoices(); + shuYing.initCSS(); + shuYing.initFunction(); + shuYing.initConfig(); + initShuYingMenu({ lib, game, ui, shuYing, updater }); + shuYing.getVersion(); + await shuYing.initCharacter(); +}; + +shuYing.config = () => { + +}; + +shuYing.help = () => { + +}; + +//添加自定义Boss TODO +shuYing.newTianShuBossList = function () { + _status.buttonNode; //用于判断输入框回车之后响应事件 + _status.nameList; + let manual = ui.create.div('.manual', manual); + let menu = ui.create.div('.menu', manual); + let input = menu.appendChild(document.createElement('input')); + input.onkeydown = function (e) { + if (e && e.keyCode == 13) { + if (_status.buttonNode == 2) { + this.deleteBossList(dialog, manual, input.value); + } + else { + this.searchBossList(dialog, manual, input.value); + } + } + }; + + let addNewBoss = ui.create.div('.addNewBoss', menu); //加入新boss + let newBossList = ui.create.div('.newBossList', menu); + let deleteNewBossAllList = ui.create.div('.deleteNewBossAllList', menu); + let deleteNewBoss = ui.create.div('.deleteNewBoss', menu); + let playerList = ui.create.div('.playerList', menu); + + let close = ui.create.div('.close', menu); + let dialog = ui.create.dialog(); + dialog.noImage = true; + dialog.style.backgroundImage = ""; + + let content = manual.appendChild(dialog); + content.classList.remove('nobutton'); + content.classList.add('content'); + content.style.transform = ''; + content.style.opacity = ''; + content.style.height = ''; + + newBossList.innerHTML = '列表'; + newBossList.id = 'newBossList'; + newBossList.addEventListener('click', function () { + this.addBossShow(dialog, manual); + }); + + deleteNewBossAllList.innerHTML = '清除'; + deleteNewBossAllList.addEventListener('click', function () { + if (confirm('点击确定清除全部自定义Boss')) { + game.saveConfig('extension_shuYing_tianShu_DiyBoss', ''); + this.addBossShow(dialog, manual); + alert('清除完毕!'); + } + }); + + addNewBoss.innerHTML = '搜索'; + addNewBoss.addEventListener('click', function () { + this.searchBossList(dialog, manual, input.value); + + input.value = ""; + _status.buttonNode = 1; + }); + + deleteNewBoss.innerHTML = '登陆'; //暂时弃用 + deleteNewBoss.addEventListener('click', function () { + alert('暂未开放'); + //deleteNewBossList(dialog,manual,input.value); + //input.value=""; + //_status.buttonNode=2; + }); + + playerList.innerHTML = '排行榜'; + playerList.addEventListener('click', function () { + alert('暂未开放'); + }); + + dialog.addText("使用说明: 点击列表查看自定义BOSS列表

" + + "点击清空按钮清空自定义BOSS列表中全部Boss

" + + "点击列表列举出Boss列表,并可以删除列表中的Boss

" + + "点击搜索并在输入框内输入武将名称即可罗列相似武将

" + + "需要注意的是,输入框需要输入武将名称而不是英文
一旦启用自定义Boss,那么必须保持不禁用武将和Boss列表大于1"); + + // close1.addEventListener('click', function(){ + // alert(close1.value); + // //game.tujianBegin(content , close , input.value , manual); + // //input.value=""; + // }); + + close.innerHTML = '关闭'; + close.addEventListener('click', function () { + manual.remove(); + ui.arena.show(); + ui.system.show(); + + game.resume2(); + if (game.onresume2) { + game.onresume2(); + } + ui.arena.classList.remove('menupaused'); + ui.historybar.classList.remove('menupaused'); + ui.window.classList.remove('touchinfohidden'); + ui.config2.classList.remove('pressdown2'); + ui.menuContainer.show(); + }); + + _status.paused = true; + ui.arena.classList.remove('menupaused'); + ui.arena.hide(); + ui.system.hide(); + ui.menuContainer.hide(); + ui.window.appendChild(manual); +}, + + //添加自定义Boss TODO + shuYing.addBossShow = function (dialog, manual) { + dialog = ui.create.dialog(); + dialog.noImage = true; + dialog.style.backgroundImage = ""; + + let content = manual.appendChild(dialog); + content.classList.remove('nobutton'); + content.classList.add('content'); + content.style.transform = ''; + content.style.opacity = ''; + content.style.height = ''; + + let list = lib.config.extension_术樱_tianshu_add_list; + if (list == undefined || list == '') { + dialog.addText("无BOSS"); + } else { + list = list.split(","); + + for (let i = 0; i < list.length; i++) { + for (let j in lib.character) { + if (j == list[i]) { + dialog.addSmall([[j], 'character']); + let del = ui.create.div('.shadowed.reduce_radius.pointerdiv.tdnode'); + del.link = i; + del.innerHTML = '' + '删除' + ''; + del.addEventListener(lib.config.touchscreen ? 'touchend' : 'click', function () { + if (lib.config.extension_术樱_tianshu_add_list.length <= 1) { + game.saveConfig('extension_术樱_tianshu_add_list', ''); + } else if (lib.config.extension_术樱_tianshu_add_list.length == 2) { + if (this.link != 1) { + game.saveConfig('extension_术樱_tianshu_add_list', list[this.link]); + } else { + game.saveConfig('extension_术樱_tianshu_add_list', list[0]); + } + } else { + game.saveConfig('extension_术樱_tianshu_add_list', ''); + for (let k = 0; k < list.length; k++) { + if (k == this.link) continue; + if (lib.config.extension_术樱_tianshu_add_list == undefined || lib.config.extension_术樱_tianshu_add_list == '') { + game.saveConfig('extension_术樱_tianshu_add_list', list[k]); + } else { + game.saveConfig('extension_术樱_tianshu_add_list', lib.config.extension_术樱_tianshu_add_list + ',' + list[k]); + } + } + document.getElementById('newBossList').click(); //刷新列表 + } + }); + dialog.addAuto(del); + } + } + } + } + }, + + //搜索Boss TODO + shuYing.searchBossList = function (dialog, manual, val) { + dialog = ui.create.dialog(); + dialog.noImage = true; + dialog.style.backgroundImage = ""; + + let content = manual.appendChild(dialog); + content.classList.remove('nobutton'); + content.classList.add('content'); + content.style.transform = ''; + content.style.opacity = ''; + content.style.height = ''; + + let result = val; + let value = false; + let list = []; + + if (result == "" || result == null) { + result = "请输入武将名称"; + alert(result); + } else { + for (let a in lib.character) { + if (lib.translate[a] && lib.translate[a].indexOf(result) != -1) { + list.add(a); + value = true; + } + } + + for (let i = 0; i < list.length; i++) { + dialog.addSmall([[list[i]], 'character']); + let add = ui.create.div('.shadowed.reduce_radius.pointerdiv.tdnode'); + add.link = i; + add.innerHTML = '' + '加入' + ''; + add.addEventListener(lib.config.touchscreen ? 'touchend' : 'click', function () { + if (lib.config.extension_术樱_tianshu_add_list == undefined || lib.config.extension_术樱_tianshu_add_list == '') { + game.saveConfig('extension_术樱_tianshu_add_list', list[this.link]); + } else { + game.saveConfig('extension_术樱_tianshu_add_list', lib.config.extension_术樱_tianshu_add_list + ',' + list[this.link]); + } + alert('成功加入至列表'); + }); + dialog.addAuto(add); + } + + _status.nameList = list; + + if (value == false) { + alert('找不到名为' + result + '的武将!'); + } + } + }, + + //删除Boss TODO + shuYing.deleteBossList = function (dialog, manual, val) { + dialog = ui.create.dialog(); + dialog.noImage = true; + dialog.style.backgroundImage = ""; + + let content = manual.appendChild(dialog); + content.classList.remove('nobutton'); + content.classList.add('content'); + content.style.transform = ''; + content.style.opacity = ''; + content.style.height = ''; + + let result = val; + + if (result == "" || result == null) { + result = "你没有输入名称"; + alert(result); + return; + } + + let list = lib.config.extension_术樱_tianshu_add_list.split(","); + let value = false; + let newList = []; + + for (let i = 0; i < list.length; i++) { + let pushBool = true; + if (String(lib.translate[list[i]]) == String(result)) { + if (confirm('已找到' + result + '武将,点击确定移除自定义Boss列表。')) { + pushBool = false; + } + value = true; + } + if (pushBool) newList.push(list[i]); + } + if (newList) { + game.saveConfig('extension_术樱_tianshu_add_list', ''); + for (let j = 0; j < newList.length; j++) { + if (lib.config.extension_术樱_tianshu_add_list == undefined || lib.config.extension_术樱_tianshu_add_list == '') { + game.saveConfig('extension_术樱_tianshu_add_list', newList[j]); + } else { + game.saveConfig('extension_术樱_tianshu_add_list', lib.config.extension_术樱_tianshu_add_list + ',' + newList[j]); + } + } + } + + + if (value == false) { + alert('找不到名为' + result + '的武将或者武将不存在自定义Boss列表中。'); + } + }, + + shuYing.download_beta_version = function () { + let online_version; + let httpRequest = new XMLHttpRequest(); + httpRequest.open("GET", 'http://diuse.hao1237.top/Diuse/beta_extension/online_version.js', true); + httpRequest.send(null); + httpRequest.onreadystatechange = function () { + if (httpRequest.readyState == 4 && httpRequest.status == 200) { + online_version = httpRequest.responseText; + game.saveConfig('Diuse_online_version', httpRequest.responseText) + lib.init.js(url, 'version', function () { + try { + let local_version = Diuse_version; + let Diuse_num = 1; + } catch (error) { + if (confirm('本地资源不完整!点击确认重新获取!')) { + game.download('http://diuse.hao1237.top/Diuse/beta_extension/files.js', 'extension/术樱/files.js', function () { }, function () { }); + game.download('http://diuse.hao1237.top/Diuse/beta_extension/version.js', 'extension/术樱/version.js', function () { }, function () { }); + game.download('http://diuse.hao1237.top/Diuse/beta_extension/extension.js', 'extension/术樱/extension.js', function () { + game.saveConfig('Diuse_local_version', online_version); + Diuse_Button = true; + alert('下载完成,重启生效'); + }, function () { + Diuse_Button = true; + alert('下载失败'); + }); + } else { + Diuse_Button = true; + } + } + if (local_version != online_version && Diuse_num == 1) { + if (confirm('检测到最新测试版本为:' + online_version + '本地版本为:' + local_version)) { + game.download('http://diuse.hao1237.top/Diuse/beta_extension/files.js', 'extension/术樱/files.js', function () { }, function () { }); + game.download('http://diuse.hao1237.top/Diuse/beta_extension/version.js', 'extension/术樱/version.js', function () { }, function () { }); + game.download('http://diuse.hao1237.top/Diuse/beta_extension/extension.js', 'extension/术樱/extension.js', function () { + game.saveConfig('Diuse_local_version', online_version); + Diuse_Button = true; + alert('下载完成,重启生效'); + }, function () { + Diuse_Button = true; + alert('下载失败'); + }); + } else { + Diuse_Button = true; + } + } else { + if (Diuse_num == 1) { + Diuse_Button = true; + alert('本地版本为最新版'); + } + } + }, function () { + if (confirm('本地资源不完整!点击确认重新获取!')) { + game.download('http://diuse.hao1237.top/Diuse/beta_extension/files.js', 'extension/术樱/files.js', function () { }, function () { }); + game.download('http://diuse.hao1237.top/Diuse/beta_extension/version.js', 'extension/术樱/version.js', function () { }, function () { }); + game.download('http://diuse.hao1237.top/Diuse/beta_extension/extension.js', 'extension/术樱/extension.js', function () { + game.saveConfig('Diuse_local_version', online_version); + Diuse_Button = true; + alert('下载完成,重启生效'); + }, function () { + Diuse_Button = true; + alert('下载失败'); + }); + } else { + Diuse_Button = true; + } + }); + } + }; + }; + +shuYing.download_mp3 = function () { + lib.init.js(url, 'files', function () { + let list = Diuse_mp3; + let num = 0; + let num1 = list.length; + document.body.appendChild(Diuse_Text); + let download1 = function () { + game.download('http://diuse.hao1237.top/Diuse/skin/' + list[0], 'extension/术樱/' + list[0], function () { + num++ + list.remove(list[0]); + if (list.length > 0) { + Diuse_Text.innerHTML = '正在下载(' + num + '/' + num1 + ')'; + download1(); + } else { + Diuse_Text.innerHTML = '下载完毕'; + Diuse_Button = true; + alert('语音下载完毕!'); + document.body.removeChild(Diuse_Text); + }; + }, function () { + if (confirm('下载' + list[0] + '失败,是否继续下载?')) { + download1(); + } + }); + } + download1(); + }, function () { + Diuse_Button = true; + alert('本地资源不完整!请检查文件完整性。'); + }); +}; + +shuYing.download_static = function () { + lib.init.js(url, 'files', function () { + let list = Diuse_static; + let num = 0; + let num1 = list.length; + document.body.appendChild(Diuse_Text); + let download1 = function () { + game.download('http://diuse.hao1237.top/Diuse/image/static/' + list[0], 'extension/术樱/' + list[0], function () { + num++ + list.remove(list[0]); + if (list.length > 0) { + Diuse_Text.innerHTML = '正在下载(' + num + '/' + num1 + ')'; + download1(); + } else { + Diuse_Text.innerHTML = '下载完毕'; + Diuse_Button = true; + alert('静态皮肤下载完毕!'); + document.body.removeChild(Diuse_Text); + }; + }, function () { + if (confirm('下载' + list[0] + '失败,是否继续下载?')) { + download1(); + } + }); + } + download1(); + }, function () { + Diuse_Button = true; + alert('本地资源不完整!请检查文件完整性。'); + }); +}; + +shuYing.download_tianshu = function () { + lib.init.js(url, 'files', function () { + let list = Diuse_tianshu; + let num = 0; + let num1 = list.length; + document.body.appendChild(Diuse_Text); + let download1 = function () { + game.download('http://diuse.hao1237.top/Diuse/image/tianshu/' + list[0], 'extension/术樱/' + list[0], function () { + num++ + list.remove(list[0]); + if (list.length > 0) { + Diuse_Text.innerHTML = '正在下载(' + num + '/' + num1 + ')'; + download1(); + } else { + Diuse_Text.innerHTML = '下载完毕'; + Diuse_Button = true; + alert('天书皮肤下载完毕!'); + document.body.removeChild(Diuse_Text); + }; + }, function () { + if (confirm('下载' + list[0] + '失败,是否继续下载?')) { + download1(); + } + }); + } + download1(); + }, function () { + Diuse_Button = true; + alert('本地资源不完整!请检查文件完整性。'); + }); +}; + +shuYing.download_dynamic = function (dynamic_name) { + if (lib.config.extension_术樱_Skin) { + let url_files = dynamic_name.substring(0, dynamic_name.length - 4); + skinList(url_files); + } else { + game.download('http://diuse.hao1237.top/Diuse/image/dynamic/' + dynamic_name, 'extension/术樱/' + dynamic_name, function () { + Diuse_Button = true; + alert('所选皮肤下载完毕!'); + }, function () { + if (confirm('下载' + dynamic_name + '失败,是否继续下载?')) { + download_dynamic(dynamic_name); + } + }); + } +}; + +shuYing.download_dynamic_all = function () { + lib.init.js(url, 'files', function () { + let list = Diuse_dynamic; + let num = 0; + let num1 = list.length; + document.body.appendChild(Diuse_Text); + let download1 = function () { + game.download('http://diuse.hao1237.top/Diuse/image/dynamic/' + list[0], 'extension/术樱/' + list[0], function () { + num++ + list.remove(list[0]); + if (list.length > 0) { + Diuse_Text.innerHTML = '正在下载(' + num + '/' + num1 + ')'; + download1(); + } else { + Diuse_Text.innerHTML = '下载完毕'; + Diuse_Button = true; + alert('动态皮肤下载完毕!'); + document.body.removeChild(Diuse_Text); + }; + }, function () { + if (confirm('下载' + list[0] + '失败,是否继续下载?')) { + download1(); + } + }); + } + download1(); + }, function () { + Diuse_Button = true; + alert('本地资源不完整!请检查文件完整性。'); + }); +}; + +shuYing.download_files_all = function () { + lib.init.js(url, 'files', function () { + let list = Diuse_dynamic; + let num = 0; + let num1 = list.length; + document.body.appendChild(Diuse_Text); + let download1 = function () { + game.download('http://diuse.hao1237.top/Diuse/image/dynamic/' + list[0], 'extension/术樱/' + list[0], function () { + num++ + list.remove(list[0]); + if (list.length > 0) { + Diuse_Text.innerHTML = '正在下载(' + num + '/' + num1 + ')'; + download1(); + } else { + Diuse_Text.innerHTML = '下载完毕'; + Diuse_Button = true; + alert('动态皮肤下载完毕!'); + document.body.removeChild(Diuse_Text); + }; + }, function () { + if (confirm('下载' + list[0] + '失败,是否继续下载?')) { + download1(); + } + }); + } + download1(); + }, function () { + Diuse_Button = true; + alert('本地资源不完整!请检查文件完整性。'); + }); +}; + +shuYing.skinList = function () { + lib.init.js(url, 'files', function () { + let num = 0; + let skin_list = Skin; + let skin_num; + let file_url = 'image/skin/'; + let file_length = 0; + document.body.appendChild(Diuse_Text); + let down = function () { + switch (skin_list[0]) { + case 'Diuse_Fuhua': { + skin_num = Diuse_Fuhua_Skin; + break; + } + case 'Diuse_Bachongying': { + skin_num = Diuse_Bachongying_Skin; + break; + } + case 'Diuse_Buluoniya': { + skin_num = Diuse_Buluoniya_Skin; + break; + } + case 'Diuse_Kalian': { + skin_num = Diuse_Kalian_Skin; + break; + } + case 'Diuse_Shangxian': { + skin_num = Diuse_Shangxian_Skin; + break; + } + case 'Diuse_Shilv': { + skin_num = Diuse_Shilv_Skin; + break; + } + case 'Diuse_Xier': { + skin_num = Diuse_Xier_Skin; + break; + } + case 'Diuse_Yayi': { + skin_num = Diuse_Yayi_Skin; + break; + } + case 'Diuse_Yuexia': { + skin_num = Diuse_Yuexia_Skin; + break; + } + case 'Diuse_Konglv': { + skin_num = Diuse_Konglv_Skin; + break; + } + case 'Diuse_Heixi': { + skin_num = Diuse_Heixi_Skin; + break; + } + case 'Diuse_Bachongshenzi': { + skin_num = Diuse_Bachongshenzi_Skin; + break; + } + case 'Diuse_Keli': { + skin_num = Diuse_Keli_Skin; + break; + } + case 'Diuse_Shuangzi': { + skin_num = Diuse_Shuangzi_Skin; + break; + } + case 'Diuse_Lanmei': { + skin_num = Diuse_Lanmei_Skin; + break; + } + case 'Diuse_Yingtao': { + skin_num = Diuse_Yingtao_Skin; + break; + } + } + url_Diuse_search = file_url + skin_list[0] + '/' + skin_num[0] + '.jpg'; + file_length += skin_list.length; + game.readFile(url_Diuse_search, function () { + num++; + skin_num.remove(skin_num[0]); + if (skin_list.length > 0 || skin_num.length > 0) { + if (skin_num.length <= 0) skin_list.remove(skin_list[0]); + Diuse_Text.innerHTML = '正在补充Skin(' + num + '/' + file_length + ')'; + down(); + } else { + Diuse_Text.innerHTML = '补充完毕'; + Diuse_Button = true; + alert('皮肤下载完毕!'); + document.body.removeChild(Diuse_Text); + } + }, function () { + num++; + skin_num.remove(skin_num[0]); + if (skin_list.length > 0 || skin_num.length > 0) { + if (skin_num.length <= 0) skin_list.remove(skin_list[0]); + Diuse_Text.innerHTML = '正在补充Skin(' + num + '/' + file_length + ')'; + down(); + } else { + Diuse_Text.innerHTML = '补充完毕'; + Diuse_Button = true; + alert('皮肤下载完毕!'); + document.body.removeChild(Diuse_Text); + }; + // game.download('https://diuse.coding.net/p/extension/d/noname_Skin/git/raw/master/Skin/'+skin_list[0]+'/'+skin_num[0]+'.jpg','image/skin/'+skin_list[0]+'/'+skin_num[0]+'.jpg',function(){ + // num++; + // skin_num.remove(skin_num[0]); + // if(skin_list.length>0||skin_num.length>0){ + // if(skin_num.length<=0) skin_list.remove(skin_list[0]); + // Diuse_Text.innerHTML='正在补充Skin('+num+'/'+file_length+')'; + // down(); + // }else{ + // Diuse_Text.innerHTML='补充完毕'; + // Diuse_Button=true; + // alert('皮肤下载完毕!'); + // document.body.removeChild(Diuse_Text); + // }; + // },function(){ + // if(confirm('下载'+skin_list[0]+'失败,是否继续下载?')){ + // down(); + // } + // }); + }); + } + down(); + }, function () { + Diuse_Button = true; + alert('本地资源不完整!请检查文件完整性。'); + }); +}; + +//本地资源修复 +shuYing.RepairBug = () => { + updater.repairCoreFiles(shuYing); +}; + +//重新下载重要js文件 +shuYing.RepairBugGo = () => { + updater.repairCoreFiles(shuYing); +}; + +shuYing.Diuse_downText = function () { + time = setTimeout(() => { + }, 1000) +}; + +shuYing.Diuse_upText = function (type) { + clearTimeout(time); + switch (type) { + case 0: + alert(); + case 1: + alert('三张重复' + + '\n【桃】 恢复两点体力并摸三张牌' + + '\n【闪】 此杀必须两张闪响应 然后你摸两张牌' + + '\n【杀】 此杀不可被响应 出杀次数上限+2' + + '\n【酒】 此杀伤害+3\n' + + '\n两张重复' + + '\n【桃】 恢复一点体力并摸两张牌' + + '\n【闪】 此杀需要打出两张闪响应' + + '\n【杀】 不可以被响应' + + '\n【酒】 此杀伤害+2\n' + + '\n一张重复' + + '\n【桃】 命中后恢复一点体力' + + '\n【闪】 命中后摸一张牌' + + '\n【杀】 出杀次数上限+1' + + '\n【酒】 此杀伤害+1'); + break; + case 2: + alert('根据武器攻击距离获得相应技能\n' + + '一:当你于你的回合内使用一张牌后,你可以弃置一张手牌并摸一张牌。\n' + + '二:当你于回合内获得一张牌且不是因为此技能获得牌时,你摸一张牌。\n' + + '三:出牌阶段限两次。你造成伤害后你可以让场上的一名角色受到一点无伤害来源的伤害。\n' + + '四:你使用杀或普通锦囊后你可以多增加一个目标,如果取消则摸X张牌(X为你已损失的体力,如果为0则摸1)\n' + + '五:出牌阶段限一次,当你使用可造成伤害的牌指定目标后你可以选择其一个目标然后你摸X张牌。(X为目标当前体力)\n' + + '六:获得全部技能效果。\n'); + + break; + } +}; + +// game.导入card=function(英文名,翻译名,obj){let oobj=get.copy(obj);oobj.list=obj.card.list;oobj.card=obj.card.card;oobj.skill=obj.skill.skill;oobj.translate=Object.assign({},obj.card.translate,obj.skill.translate);game.import('card',function(){return oobj});lib.config.all.cards.push(英文名);if(!lib.config.cards.contains(英文名))lib.config.cards.push(英文名);lib.translate[英文名+'_card_config']=翻译名;}; +// game.新增势力=function(名字,映射,渐变){let n,t;if(!名字)return;if(typeof 名字=="string"){n=名字;t=名字}else if(Array.isArray(名字)&&名字.length==2&&typeof 名字[0]=="string"){n=名字[0];t=名字[1]}else return;if(!映射||!Array.isArray(映射)||映射.length!=3)映射=[199,21,133];let y="("+映射[0]+","+映射[1]+","+映射[2];let y1=y+",1)",y2=y+")";let s=document.createElement('style');let l;l=".player .identity[data-color='diy"+n+"'],";l+="div[data-nature='diy"+n+"'],";l+="span[data-nature='diy"+n+"'] {text-shadow: black 0 0 1px,rgba"+y1+" 0 0 2px,rgba"+y1+" 0 0 5px,rgba"+y1+" 0 0 10px,rgba"+y1+" 0 0 10px}";l+="div[data-nature='diy"+n+"m'],";l+="span[data-nature='diy"+n+"m'] {text-shadow: black 0 0 1px,rgba"+y1+" 0 0 2px,rgba"+y1+" 0 0 5px,rgba"+y1+" 0 0 5px,rgba"+y1+" 0 0 5px,black 0 0 1px;}";l+="div[data-nature='diy"+n+"mm'],";l+="span[data-nature='diy"+n+"mm'] {text-shadow: black 0 0 1px,rgba"+y1+" 0 0 2px,rgba"+y1+" 0 0 2px,rgba"+y1+" 0 0 2px,rgba"+y1+" 0 0 2px,black 0 0 1px;}";s.innerHTML=l;document.head.appendChild(s);if(渐变&&Array.isArray(渐变)&&Array.isArray(渐变[0])&&渐变[0].length==3){let str="",st2=[];for(let i=0;i<渐变.length;i++){str+=",rgb("+渐变[i][0]+","+渐变[i][1]+","+渐变[i][2]+")";if(i<2)st2[i]="rgb("+渐变[i][0]+","+渐变[i][1]+","+渐变[i][2]+")";}let tenUi = document.createElement('style');tenUi.innerHTML = ".player>.camp-zone[data-camp='"+n+"']>.camp-back {background: linear-gradient(to bottom"+str+");}";tenUi.innerHTML += ".player>.camp-zone[data-camp='"+n+"']>.camp-name {text-shadow: 0 0 5px "+st2[0]+", 0 0 10px "+st2[1]+";}";document.head.appendChild(tenUi);}lib.group.add(n);lib.translate[n]= t;lib.groupnature[n]= "diy"+n;}; + + +shuYing.package = +{ + character: { + character: { + }, + translate: { + }, + }, + card: { + game: + { + }, + card: + { + }, + skill: + { + }, + translate: + { + }, + list: [], + }, + skill: { + skill: { + }, + translate: { + }, + }, + intro: "所有素材均来自互联网,侵权必删。", + author: "术樱", + diskURL: "", + forumURL: "", + version: "1.0", +}; + +shuYing.files = +{ + "character": [], + "card": [], + "skill": [], + "audio": [] +} + +export default function () { + return shuYing; +} diff --git a/function.js b/function.js new file mode 100644 index 0000000..d6243e6 --- /dev/null +++ b/function.js @@ -0,0 +1,226 @@ +export default function initFunction(lib, game, ui, get, ai, _status, shuYing = {}) { + game.shuying_getDiscardNamesNum = function () { + return Array.from(ui.discardPile.childNodes) + .map(card => get.name(card)) + .unique() + .length; + }; + + game.shuying_getDiscardNum = function () { + return game + .getGlobalHistory("cardMove", evt => evt.name == "cardsDiscard" || evt.name == "lose" && evt.position == ui.discardPile) + .reduce((num, evt) => num + (evt.cards?.length || 0), 0); + }; + + lib.element.player.shuYing_randomNum = function (max, min = 0) { + const upper = Number(max); + const lower = Number(min); + if (!Number.isFinite(upper) || !Number.isFinite(lower) || upper <= lower) return lower || 0; + return Math.floor(Math.random() * (upper - lower)) + lower; + }; + + lib.element.player.shuYing_numRandom = function () { + return this.shuYing_randomNum(100, 1); + }; + + const getCharacterPack = id => { + for (const packName in lib.characterPack) { + if (id in lib.characterPack[packName]) return packName; + } + return "unknown"; + }; + + const getCharacterName = id => get.translation(id) || id; + + const normalizeCharacterDialogItems = list => { + return (list || []).map(item => { + if (typeof item == "string") { + return { + key: item, + id: item, + name: getCharacterName(item), + group: getCharacterPack(item), + }; + } + const id = item.id || item.name || item.key; + return { + key: item.key || id, + id, + name: item.name || getCharacterName(id), + group: item.group || getCharacterPack(id), + }; + }).filter(item => item.key && item.id); + }; + + const getGroupText = group => { + if (group == "unknown") return "未知"; + return lib.translate[`${group}_character_config`] || get.translation(group) || group; + }; + + const makeSearchMatcher = text => { + const value = String(text || "").trim(); + if (!value) return () => true; + try { + const regexp = new RegExp(value, "i"); + return item => regexp.test(item.key) || regexp.test(item.id) || regexp.test(item.name) || regexp.test(get.translation(item.id)); + } catch (e) { + return item => [item.key, item.id, item.name, get.translation(item.id)].some(str => String(str || "").includes(value)); + } + }; + + shuYing.chooseCharacterPoolDialog = ({ title = "请选择角色", intro = "", list = [], selected = [] } = {}) => { + return new Promise(resolve => { + const items = normalizeCharacterDialogItems(list); + const selectedSet = new Set(selected); + const activeGroups = new Set(items.map(item => item.group)); + const createNode = (tag, className, text, parent) => { + const node = document.createElement(tag); + if (className) node.className = className; + if (text !== undefined && text !== null) node.textContent = text; + if (parent) parent.appendChild(node); + return node; + }; + const mask = createNode("div", "shuYing-character-dialog-mask", null, document.body); + const dialog = createNode("div", "shuYing-character-dialog", null, mask); + const header = createNode("div", "shuYing-character-dialog-header", null, dialog); + createNode("div", "shuYing-character-dialog-title", title, header); + const countNode = createNode("div", "shuYing-character-dialog-count", "", header); + if (intro) createNode("div", "shuYing-character-dialog-intro", intro, dialog); + const searchInput = createNode("input", "shuYing-character-dialog-search", null, dialog); + searchInput.type = "text"; + searchInput.placeholder = "搜索武将名 / ID / 正则"; + searchInput.autocomplete = "off"; + searchInput.spellcheck = false; + const toolbar = createNode("div", "shuYing-character-dialog-toolbar", null, dialog); + const groupBox = createNode("div", "shuYing-character-dialog-groups", null, toolbar); + const gridWrap = createNode("div", "shuYing-character-dialog-grid-wrap", null, dialog); + const grid = createNode("div", "shuYing-character-dialog-grid", null, gridWrap); + const controls = createNode("div", "shuYing-character-dialog-controls", null, dialog); + let closed = false; + let visibleItems = items.slice(); + + const close = result => { + if (closed) return; + closed = true; + mask.remove(); + resolve(result); + }; + + const refreshCard = card => { + card.classList.toggle("selected", selectedSet.has(card.dataset.key)); + }; + + const updateCount = () => { + countNode.textContent = `已选择 ${selectedSet.size}/${items.length}`; + confirmNode.classList.toggle("disabled", selectedSet.size == 0); + confirmNode.disabled = selectedSet.size == 0; + }; + + const renderGrid = () => { + grid.innerHTML = ""; + const matcher = makeSearchMatcher(searchInput.value); + visibleItems = items.filter(item => activeGroups.has(item.group) && matcher(item)); + if (!visibleItems.length) { + createNode("div", "shuYing-character-dialog-empty", "没有符合条件的武将", grid); + updateCount(); + return; + } + visibleItems.forEach(item => { + const card = createNode("div", "shuYing-character-dialog-card", null, grid); + card.tabIndex = 0; + card.dataset.key = item.key; + const avatar = createNode("div", "shuYing-character-dialog-avatar", null, card); + avatar.setBackground(item.id, "character"); + createNode("span", "shuYing-character-dialog-name", item.name || getCharacterName(item.id), card); + card.addEventListener("click", event => { + event.stopPropagation(); + if (selectedSet.has(item.key)) selectedSet.delete(item.key); + else selectedSet.add(item.key); + refreshCard(card); + updateCount(); + }); + refreshCard(card); + }); + updateCount(); + }; + + const makeControl = (text, handler) => { + const button = createNode("div", "shuYing-character-dialog-button", text, controls); + button.tabIndex = 0; + button.addEventListener("click", event => { + event.stopPropagation(); + handler(); + }); + button.addEventListener("keydown", event => { + event.stopPropagation(); + if (event.key == "Enter" || event.key == " ") { + event.preventDefault(); + handler(); + } + }); + return button; + }; + + mask.addEventListener("click", event => { + event.stopPropagation(); + close(false); + }); + dialog.addEventListener("click", event => event.stopPropagation()); + searchInput.addEventListener("click", event => event.stopPropagation()); + searchInput.addEventListener("input", renderGrid); + searchInput.addEventListener("keydown", event => { + event.stopPropagation(); + if (event.key == "Escape") close(false); + if (event.key == "Enter") { + event.preventDefault(); + renderGrid(); + } + }); + + items.map(item => item.group).unique().forEach(group => { + const label = createNode("div", "shuYing-character-dialog-group selected", null, groupBox); + label.tabIndex = 0; + label.dataset.group = group; + createNode("span", "shuYing-character-dialog-check", "", label); + createNode("span", "shuYing-character-dialog-group-text", getGroupText(group), label); + label.addEventListener("click", event => { + event.stopPropagation(); + const checked = !label.classList.contains("selected"); + label.classList.toggle("selected", checked); + if (checked) activeGroups.add(group); + else activeGroups.delete(group); + renderGrid(); + }); + label.addEventListener("keydown", event => { + event.stopPropagation(); + if (event.key == "Enter" || event.key == " ") { + event.preventDefault(); + label.click(); + } + }); + }); + + makeControl("全选", () => { + visibleItems.forEach(item => selectedSet.add(item.key)); + grid.querySelectorAll(".shuYing-character-dialog-card").forEach(refreshCard); + updateCount(); + }); + makeControl("清空", () => { + visibleItems.forEach(item => selectedSet.delete(item.key)); + grid.querySelectorAll(".shuYing-character-dialog-card").forEach(refreshCard); + updateCount(); + }); + makeControl("反选", () => { + visibleItems.forEach(item => selectedSet.has(item.key) ? selectedSet.delete(item.key) : selectedSet.add(item.key)); + grid.querySelectorAll(".shuYing-character-dialog-card").forEach(refreshCard); + updateCount(); + }); + const confirmNode = makeControl("确定", () => { + if (!selectedSet.size) return; + close(items.map(item => item.key).filter(key => selectedSet.has(key))); + }); + makeControl("取消", () => close(false)); + renderGrid(); + }); + }; +} diff --git a/menu.js b/menu.js new file mode 100644 index 0000000..f5e3267 --- /dev/null +++ b/menu.js @@ -0,0 +1,271 @@ +export default function initShuYingMenu({ lib, game, ui, shuYing, updater }) { + const menu = lib.extensionMenu.extension_术樱包; + const createCollapseMenu = (title, endId) => ({ + name: `
▼ ${title}
`, + clear: true, + onclick() { + if (!this.shuyingCollapseNodes) { + const nodes = []; + let node = this.nextSibling; + + while (node && !node.querySelector?.(`#${endId}`)) { + nodes.push(node); + node = node.nextSibling; + } + this.shuyingCollapseNodes = nodes; + } + + const collapsed = !this.shuyingCollapsed; + this.shuyingCollapsed = collapsed; + this.shuyingCollapseNodes.forEach(node => { + node.style.display = collapsed ? "none" : ""; + }); + this.innerHTML = `
${collapsed ? "▶" : "▼"} ${title}
`; + }, + }); + + menu.local_version = { + name: `扩展版本:${lib.config.shuYing_local_version}`, + clear: true, + nopointer: true, + }; + + menu.online_version = { + name: shuYing.getOnlineVersionText(), + clear: true, + nopointer: true, + }; + + menu.updateLog = { + name: '
▶更新日志
', + clear: true, + onclick() { + if (this.hth_more) { + this.hth_more.remove(); + delete this.hth_more; + this.innerHTML = '
▶更新日志
'; + return; + } + + const logs = [ + "-----< 202607 改动 >-----", + "新增可以自定义虚拟偶像随机助战池功能", + "现在过关奖励提供的技能池会过滤武将牌上的技能", + "-----< 202607 修复 >-----", + "修复BOSS死亡尸体不会消失的BUG", + "修正过关后BOSS的初始手牌不再是摸牌事件", + "适配部分技能需要依赖BOSS的起始手牌", + "适配部分技能需要选择已经死亡的BOSS为目标时,提供一个按钮来列出已死亡的BOSS列表", + "-----< 20260722 改动 >-----", + "新增BOSS:玄女!", + "不用担心困难,虚拟偶像前来援助!", + "新增BOSS选项开启,现在可以在扩展选项中开启新增1位BOSS来提高难度!", + "-----< 20260722 修复 >-----", + "修复当过关奖励执行完毕轮数和下轮角色异常的BUG", + "修复了一个BUG,该BUG会导致进入下关时BOSS的初始牌会被技能检索", + "修复黄蜂技能【冥虫】无法正常发动的BUG", + "修正司马懿AI向其友方发动【反馈】【狼顾】的问题", + ]; + const keywordColors = { + BOSS: "#ff6b6b", + 武将牌: "#ff6b6b", + BUG: "#ff9800", + 虚拟偶像: "#66ccff", + 玄女: "#d77cff", + "【冥虫】": "#7ed957", + "【反馈】": "#7ed957", + "【狼顾】": "#7ed957", + 黄蜂: "#d77cff", + 司马懿: "#d77cff", + }; + const highlightKeywords = text => { + for (const [keyword, color] of Object.entries(keywordColors)) { + text = text.replaceAll( + keyword, + `${keyword}` + ); + } + return text; + }; + const formatLog = text => { + if (text.includes("改动 >")) { + return `${text}`; + } + if (text.includes("修复 >")) { + return `${text}`; + } + return `${highlightKeywords(text)}`; + }; + const more = ui.create.div( + ".hth_more", + `
${logs.map(formatLog).join("
")}
` + ); + + this.parentNode.insertBefore(more, this.nextSibling); + this.hth_more = more; + this.innerHTML = '
▼更新日志
'; + }, + }; + + menu.updata = { + name: "版本检测", + clear: true, + onclick() { + if (confirm("点击确定会检测版本") && shuYing.m_bIsDownload) { + shuYing.m_bIsDownload = false; + updater.checkVersion(shuYing); + } else if (!shuYing.m_bIsDownload) { + alert("有其他文件正在下载,请稍后再试吧。"); + } + }, + }; + + menu.repairBug = { + name: "本地资源修复", + clear: true, + onclick() { + if (confirm("点击确定会检测本地资源,并尝试修复。") && shuYing.m_bIsDownload) { + shuYing.m_bIsDownload = false; + shuYing.RepairBug(); + } else if (!shuYing.m_bIsDownload) { + alert("有其他文件正在下载,请稍后再试吧。"); + } + }, + }; + + menu.filling = { + name: "查漏补缺 <--补齐丢失资源 建议更新后必点", + clear: true, + onclick() { + if ( + confirm("点击确定会检测本地丢失资源,并开始修补;若是武将立绘则是静态,若需动态请手动下载(请注意流量使用情况!务必在WIFI状态下修补)") && + shuYing.m_bIsDownload + ) { + shuYing.m_bIsDownload = false; + updater.repairMissingFiles(shuYing); + } else if (!shuYing.m_bIsDownload) { + alert("有其他文件正在下载,请稍后再试吧。"); + } + }, + }; + + menu.br4 = { + clear: true, + nopointer: true, + name: "--------------------", + }; + + menu.tianShuGroup = createCollapseMenu("天书乱斗选项", "shuying_tianshu_menu_end"); + + menu.tianShuOff = { + name: "天书乱斗开关", + init: true, + intro: "关闭后,关闭天书乱斗模式(在非挑战模式下自动关闭)", + }; + + menu.giveAiSkill = { + name: "单人控制AI技能", + init: false, + intro: "开启后,天书乱斗模式每次过关且处于单人控制规则下则玩家给AI选择技能。", + }; + + menu.tianShu_Xvni = { + name: "虚拟偶像", + init: "random", + item: { + Xiaojiu: "小酒", + Xiaosha: "小杀", + Xiaoshan: "小闪", + Xiaole: "小乐", + Xiaotao: "小桃", + random: "随机", + off: "关闭", + }, + onclick(layout) { + game.saveConfig("extension_术樱包_tianShu_Xvni", layout); + }, + }; + + const virtualIdols = [ + { key: "Xiaojiu", id: "vtb_xiaojiu", name: "小酒" }, + { key: "Xiaosha", id: "vtb_xiaosha", name: "小杀" }, + { key: "Xiaoshan", id: "vtb_xiaoshan", name: "小闪" }, + { key: "Xiaole", id: "vtb_xiaole", name: "小乐" }, + { key: "Xiaotao", id: "vtb_xiaotao", name: "小桃" }, + ]; + const virtualIdolNames = Object.fromEntries(virtualIdols.map(item => [item.key, item.name])); + const getVirtualIdolPoolName = () => { + const list = lib.config.extension_术樱包_tianShu_XvniRandomPool; + if (!Array.isArray(list) || !list.length) return "虚拟偶像随机池:全部"; + return `虚拟偶像随机池:${list.map(key => virtualIdolNames[key]).filter(Boolean).join("、") || "全部"}`; + }; + + menu.tianShu_XvniRandomPool = { + name: getVirtualIdolPoolName(), + clear: true, + intro: "点击后选择允许被随机到的虚拟偶像。全选时视为全部随机。", + async onclick() { + const current = + Array.isArray(lib.config.extension_术樱包_tianShu_XvniRandomPool) && + lib.config.extension_术樱包_tianShu_XvniRandomPool.length + ? lib.config.extension_术樱包_tianShu_XvniRandomPool + : virtualIdols.map(item => item.key); + const node = this; + const result = await shuYing.chooseCharacterPoolDialog({ + title: "选择虚拟偶像随机池", + intro: "只会影响“虚拟偶像:随机”。全选时保存为全部随机。", + list: virtualIdols, + selected: current, + }); + if (result === false) return; + + const list = result.length === virtualIdols.length ? [] : result; + game.saveConfig("extension_术樱包_tianShu_XvniRandomPool", list); + node.firstChild.innerHTML = getVirtualIdolPoolName(); + }, + }; + + menu.tianShu_dead = { + name: "虚拟偶像复活", + init: false, + intro: "开启后,进入下一关后若虚拟偶像阵亡则复活", + }; + + menu.tianShu_revivePlayers = { + name: "过关复活玩家", + init: false, + intro: "开启后,进入下一关前复活玩家方已阵亡的角色。虚拟偶像不会因此复活。", + }; + + menu.tianShu_addBoss = { + name: "增加Boss", + init: false, + intro: "开启后,可以新增一位Boss", + }; + + menu.br5 = { + clear: true, + nopointer: true, + name: '--------------------', + }; + + menu.Log = { + name: "反馈BUG", + clear: true, + onclick() { + game.open("https://tieba.baidu.com/p/7934495658"); + }, + }; + + menu.qq = { + name: "QQ交流群:957537184", + clear: true, + nopointer: true, + }; + + menu.thank = { + name: "感谢极光佬,诗笺佬和其他大佬网络上的文献。以及反馈BUG的玩家。谢谢!特别感谢少年A对本扩展进行的优化描述。", + clear: true, + nopointer: true, + }; +} diff --git a/pve/audio/dead/KuaFu_Dead.mp3 b/pve/audio/dead/KuaFu_Dead.mp3 new file mode 100644 index 0000000..ec3f11d Binary files /dev/null and b/pve/audio/dead/KuaFu_Dead.mp3 differ diff --git a/pve/audio/dead/十二生肖/chenlong.mp3 b/pve/audio/dead/十二生肖/chenlong.mp3 new file mode 100644 index 0000000..88de656 Binary files /dev/null and b/pve/audio/dead/十二生肖/chenlong.mp3 differ diff --git a/pve/audio/dead/十二生肖/chouniu.mp3 b/pve/audio/dead/十二生肖/chouniu.mp3 new file mode 100644 index 0000000..09b32a4 Binary files /dev/null and b/pve/audio/dead/十二生肖/chouniu.mp3 differ diff --git a/pve/audio/dead/十二生肖/haizhu.mp3 b/pve/audio/dead/十二生肖/haizhu.mp3 new file mode 100644 index 0000000..3024033 Binary files /dev/null and b/pve/audio/dead/十二生肖/haizhu.mp3 differ diff --git a/pve/audio/dead/十二生肖/maotu.mp3 b/pve/audio/dead/十二生肖/maotu.mp3 new file mode 100644 index 0000000..62af6f8 Binary files /dev/null and b/pve/audio/dead/十二生肖/maotu.mp3 differ diff --git a/pve/audio/dead/十二生肖/shenhou.mp3 b/pve/audio/dead/十二生肖/shenhou.mp3 new file mode 100644 index 0000000..0741c8f Binary files /dev/null and b/pve/audio/dead/十二生肖/shenhou.mp3 differ diff --git a/pve/audio/dead/十二生肖/sishe.mp3 b/pve/audio/dead/十二生肖/sishe.mp3 new file mode 100644 index 0000000..fe75880 Binary files /dev/null and b/pve/audio/dead/十二生肖/sishe.mp3 differ diff --git a/pve/audio/dead/十二生肖/weiyang.mp3 b/pve/audio/dead/十二生肖/weiyang.mp3 new file mode 100644 index 0000000..f4452ae Binary files /dev/null and b/pve/audio/dead/十二生肖/weiyang.mp3 differ diff --git a/pve/audio/dead/十二生肖/wuma.mp3 b/pve/audio/dead/十二生肖/wuma.mp3 new file mode 100644 index 0000000..f83d3a0 Binary files /dev/null and b/pve/audio/dead/十二生肖/wuma.mp3 differ diff --git a/pve/audio/dead/十二生肖/xvgou.mp3 b/pve/audio/dead/十二生肖/xvgou.mp3 new file mode 100644 index 0000000..0207c67 Binary files /dev/null and b/pve/audio/dead/十二生肖/xvgou.mp3 differ diff --git a/pve/audio/dead/十二生肖/yinhu.mp3 b/pve/audio/dead/十二生肖/yinhu.mp3 new file mode 100644 index 0000000..bbecf76 Binary files /dev/null and b/pve/audio/dead/十二生肖/yinhu.mp3 differ diff --git a/pve/audio/dead/十二生肖/youji.mp3 b/pve/audio/dead/十二生肖/youji.mp3 new file mode 100644 index 0000000..d306845 Binary files /dev/null and b/pve/audio/dead/十二生肖/youji.mp3 differ diff --git a/pve/audio/dead/十二生肖/zishu.mp3 b/pve/audio/dead/十二生肖/zishu.mp3 new file mode 100644 index 0000000..1af3e73 Binary files /dev/null and b/pve/audio/dead/十二生肖/zishu.mp3 differ diff --git a/pve/audio/dead/天书乱斗/xuannv_boss_shuying.mp3 b/pve/audio/dead/天书乱斗/xuannv_boss_shuying.mp3 new file mode 100644 index 0000000..9c1d4b2 Binary files /dev/null and b/pve/audio/dead/天书乱斗/xuannv_boss_shuying.mp3 differ diff --git a/pve/audio/dead/青青子衿/caocao_boss_shuying.mp3 b/pve/audio/dead/青青子衿/caocao_boss_shuying.mp3 new file mode 100644 index 0000000..f9d7130 Binary files /dev/null and b/pve/audio/dead/青青子衿/caocao_boss_shuying.mp3 differ diff --git a/pve/audio/dead/青青子衿/dongzhuo_boss_shuying.mp3 b/pve/audio/dead/青青子衿/dongzhuo_boss_shuying.mp3 new file mode 100644 index 0000000..926606d Binary files /dev/null and b/pve/audio/dead/青青子衿/dongzhuo_boss_shuying.mp3 differ diff --git a/pve/audio/dead/青青子衿/lvbu_boss_shuying.mp3 b/pve/audio/dead/青青子衿/lvbu_boss_shuying.mp3 new file mode 100644 index 0000000..2286f16 Binary files /dev/null and b/pve/audio/dead/青青子衿/lvbu_boss_shuying.mp3 differ diff --git a/pve/audio/dead/青青子衿/simayi_boss_shuying.mp3 b/pve/audio/dead/青青子衿/simayi_boss_shuying.mp3 new file mode 100644 index 0000000..a26df55 Binary files /dev/null and b/pve/audio/dead/青青子衿/simayi_boss_shuying.mp3 differ diff --git a/pve/audio/skill/KuaFu_ZhuRi_01.mp3 b/pve/audio/skill/KuaFu_ZhuRi_01.mp3 new file mode 100644 index 0000000..65e9518 Binary files /dev/null and b/pve/audio/skill/KuaFu_ZhuRi_01.mp3 differ diff --git a/pve/audio/skill/spell8014_1.mp3 b/pve/audio/skill/spell8014_1.mp3 new file mode 100644 index 0000000..6431060 Binary files /dev/null and b/pve/audio/skill/spell8014_1.mp3 differ diff --git a/pve/audio/skill/spell8016_1.mp3 b/pve/audio/skill/spell8016_1.mp3 new file mode 100644 index 0000000..a0e87c3 Binary files /dev/null and b/pve/audio/skill/spell8016_1.mp3 differ diff --git a/pve/audio/skill/spell8017_1.mp3 b/pve/audio/skill/spell8017_1.mp3 new file mode 100644 index 0000000..ded6517 Binary files /dev/null and b/pve/audio/skill/spell8017_1.mp3 differ diff --git a/pve/audio/skill/十二生肖/chenlong_shuying1.mp3 b/pve/audio/skill/十二生肖/chenlong_shuying1.mp3 new file mode 100644 index 0000000..23cc56a Binary files /dev/null and b/pve/audio/skill/十二生肖/chenlong_shuying1.mp3 differ diff --git a/pve/audio/skill/十二生肖/chouniu_shuying1.mp3 b/pve/audio/skill/十二生肖/chouniu_shuying1.mp3 new file mode 100644 index 0000000..fe8cfe4 Binary files /dev/null and b/pve/audio/skill/十二生肖/chouniu_shuying1.mp3 differ diff --git a/pve/audio/skill/十二生肖/haizhu_shuying1.mp3 b/pve/audio/skill/十二生肖/haizhu_shuying1.mp3 new file mode 100644 index 0000000..40054e1 Binary files /dev/null and b/pve/audio/skill/十二生肖/haizhu_shuying1.mp3 differ diff --git a/pve/audio/skill/十二生肖/moutu_shuying1.mp3 b/pve/audio/skill/十二生肖/moutu_shuying1.mp3 new file mode 100644 index 0000000..d007ed2 Binary files /dev/null and b/pve/audio/skill/十二生肖/moutu_shuying1.mp3 differ diff --git a/pve/audio/skill/十二生肖/shenhou_shuying1.mp3 b/pve/audio/skill/十二生肖/shenhou_shuying1.mp3 new file mode 100644 index 0000000..a7c5600 Binary files /dev/null and b/pve/audio/skill/十二生肖/shenhou_shuying1.mp3 differ diff --git a/pve/audio/skill/十二生肖/sishe_shuying1.mp3 b/pve/audio/skill/十二生肖/sishe_shuying1.mp3 new file mode 100644 index 0000000..b1dc21d Binary files /dev/null and b/pve/audio/skill/十二生肖/sishe_shuying1.mp3 differ diff --git a/pve/audio/skill/十二生肖/weiyang_shuying1.mp3 b/pve/audio/skill/十二生肖/weiyang_shuying1.mp3 new file mode 100644 index 0000000..edbd2f0 Binary files /dev/null and b/pve/audio/skill/十二生肖/weiyang_shuying1.mp3 differ diff --git a/pve/audio/skill/十二生肖/wuma_shuying1.mp3 b/pve/audio/skill/十二生肖/wuma_shuying1.mp3 new file mode 100644 index 0000000..1b1714e Binary files /dev/null and b/pve/audio/skill/十二生肖/wuma_shuying1.mp3 differ diff --git a/pve/audio/skill/十二生肖/xvgou_shuying1.mp3 b/pve/audio/skill/十二生肖/xvgou_shuying1.mp3 new file mode 100644 index 0000000..647097a Binary files /dev/null and b/pve/audio/skill/十二生肖/xvgou_shuying1.mp3 differ diff --git a/pve/audio/skill/十二生肖/yinhu_shuying1.mp3 b/pve/audio/skill/十二生肖/yinhu_shuying1.mp3 new file mode 100644 index 0000000..b7df02c Binary files /dev/null and b/pve/audio/skill/十二生肖/yinhu_shuying1.mp3 differ diff --git a/pve/audio/skill/十二生肖/youji_shuying1.mp3 b/pve/audio/skill/十二生肖/youji_shuying1.mp3 new file mode 100644 index 0000000..4149062 Binary files /dev/null and b/pve/audio/skill/十二生肖/youji_shuying1.mp3 differ diff --git a/pve/audio/skill/十二生肖/zishu_shuying1.mp3 b/pve/audio/skill/十二生肖/zishu_shuying1.mp3 new file mode 100644 index 0000000..0797e4b Binary files /dev/null and b/pve/audio/skill/十二生肖/zishu_shuying1.mp3 differ diff --git a/pve/audio/skill/天书乱斗/dishi_shuying.mp3 b/pve/audio/skill/天书乱斗/dishi_shuying.mp3 new file mode 100644 index 0000000..a71a4c3 Binary files /dev/null and b/pve/audio/skill/天书乱斗/dishi_shuying.mp3 differ diff --git a/pve/audio/skill/天书乱斗/jiutian_shuying.mp3 b/pve/audio/skill/天书乱斗/jiutian_shuying.mp3 new file mode 100644 index 0000000..45ca089 Binary files /dev/null and b/pve/audio/skill/天书乱斗/jiutian_shuying.mp3 differ diff --git a/pve/audio/skill/天书乱斗/xuanlie_shuying.mp3 b/pve/audio/skill/天书乱斗/xuanlie_shuying.mp3 new file mode 100644 index 0000000..91a8839 Binary files /dev/null and b/pve/audio/skill/天书乱斗/xuanlie_shuying.mp3 differ diff --git a/pve/audio/skill/青青子衿/baonue_shuying1.mp3 b/pve/audio/skill/青青子衿/baonue_shuying1.mp3 new file mode 100644 index 0000000..3c0dd43 Binary files /dev/null and b/pve/audio/skill/青青子衿/baonue_shuying1.mp3 differ diff --git a/pve/audio/skill/青青子衿/fankui_shuying1.mp3 b/pve/audio/skill/青青子衿/fankui_shuying1.mp3 new file mode 100644 index 0000000..908bb86 Binary files /dev/null and b/pve/audio/skill/青青子衿/fankui_shuying1.mp3 differ diff --git a/pve/audio/skill/青青子衿/guicai_shuying1.mp3 b/pve/audio/skill/青青子衿/guicai_shuying1.mp3 new file mode 100644 index 0000000..271588a Binary files /dev/null and b/pve/audio/skill/青青子衿/guicai_shuying1.mp3 differ diff --git a/pve/audio/skill/青青子衿/jianxiong_shuying1.mp3 b/pve/audio/skill/青青子衿/jianxiong_shuying1.mp3 new file mode 100644 index 0000000..66520f7 Binary files /dev/null and b/pve/audio/skill/青青子衿/jianxiong_shuying1.mp3 differ diff --git a/pve/audio/skill/青青子衿/jiuchi_shuying1.mp3 b/pve/audio/skill/青青子衿/jiuchi_shuying1.mp3 new file mode 100644 index 0000000..9ef04fe Binary files /dev/null and b/pve/audio/skill/青青子衿/jiuchi_shuying1.mp3 differ diff --git a/pve/audio/skill/青青子衿/langgu_shuying1.mp3 b/pve/audio/skill/青青子衿/langgu_shuying1.mp3 new file mode 100644 index 0000000..ba42fcf Binary files /dev/null and b/pve/audio/skill/青青子衿/langgu_shuying1.mp3 differ diff --git a/pve/audio/skill/青青子衿/lingba_shuying1.mp3 b/pve/audio/skill/青青子衿/lingba_shuying1.mp3 new file mode 100644 index 0000000..9ab259b Binary files /dev/null and b/pve/audio/skill/青青子衿/lingba_shuying1.mp3 differ diff --git a/pve/audio/skill/青青子衿/qubu_shuying1.mp3 b/pve/audio/skill/青青子衿/qubu_shuying1.mp3 new file mode 100644 index 0000000..c6e1905 Binary files /dev/null and b/pve/audio/skill/青青子衿/qubu_shuying1.mp3 differ diff --git a/pve/audio/skill/青青子衿/roulin_shuying1.mp3 b/pve/audio/skill/青青子衿/roulin_shuying1.mp3 new file mode 100644 index 0000000..a935182 Binary files /dev/null and b/pve/audio/skill/青青子衿/roulin_shuying1.mp3 differ diff --git a/pve/audio/skill/青青子衿/shenij_shuying1.mp3 b/pve/audio/skill/青青子衿/shenij_shuying1.mp3 new file mode 100644 index 0000000..3dfa7ae Binary files /dev/null and b/pve/audio/skill/青青子衿/shenij_shuying1.mp3 differ diff --git a/pve/audio/skill/青青子衿/shenij_shuying2.mp3 b/pve/audio/skill/青青子衿/shenij_shuying2.mp3 new file mode 100644 index 0000000..b1f3995 Binary files /dev/null and b/pve/audio/skill/青青子衿/shenij_shuying2.mp3 differ diff --git a/pve/audio/skill/青青子衿/wushuang_shuying1.mp3 b/pve/audio/skill/青青子衿/wushuang_shuying1.mp3 new file mode 100644 index 0000000..d440921 Binary files /dev/null and b/pve/audio/skill/青青子衿/wushuang_shuying1.mp3 differ diff --git a/pve/audio/skill/青青子衿/wushuang_shuying2.mp3 b/pve/audio/skill/青青子衿/wushuang_shuying2.mp3 new file mode 100644 index 0000000..2507a35 Binary files /dev/null and b/pve/audio/skill/青青子衿/wushuang_shuying2.mp3 differ diff --git a/pve/audio/skill/青青子衿/yishen_shuying1.mp3 b/pve/audio/skill/青青子衿/yishen_shuying1.mp3 new file mode 100644 index 0000000..3976627 Binary files /dev/null and b/pve/audio/skill/青青子衿/yishen_shuying1.mp3 differ diff --git a/pve/audio/skill/青青子衿/yuanlv_shuying1.mp3 b/pve/audio/skill/青青子衿/yuanlv_shuying1.mp3 new file mode 100644 index 0000000..d548b90 Binary files /dev/null and b/pve/audio/skill/青青子衿/yuanlv_shuying1.mp3 differ diff --git a/pve/audio/skill/青青子衿/zhanjia_shuying1.mp3 b/pve/audio/skill/青青子衿/zhanjia_shuying1.mp3 new file mode 100644 index 0000000..17cec94 Binary files /dev/null and b/pve/audio/skill/青青子衿/zhanjia_shuying1.mp3 differ diff --git a/pve/audio/skill/青青子衿/zhanjia_shuying2.mp3 b/pve/audio/skill/青青子衿/zhanjia_shuying2.mp3 new file mode 100644 index 0000000..d93b6a1 Binary files /dev/null and b/pve/audio/skill/青青子衿/zhanjia_shuying2.mp3 differ diff --git a/pve/audio/skill/驱鬼逐邪/anchao_shuying1.mp3 b/pve/audio/skill/驱鬼逐邪/anchao_shuying1.mp3 new file mode 100644 index 0000000..9580a90 Binary files /dev/null and b/pve/audio/skill/驱鬼逐邪/anchao_shuying1.mp3 differ diff --git a/pve/audio/skill/驱鬼逐邪/duane_shuying1.mp3 b/pve/audio/skill/驱鬼逐邪/duane_shuying1.mp3 new file mode 100644 index 0000000..c9ff749 Binary files /dev/null and b/pve/audio/skill/驱鬼逐邪/duane_shuying1.mp3 differ diff --git a/pve/audio/skill/驱鬼逐邪/duzhen_shuying1.mp3 b/pve/audio/skill/驱鬼逐邪/duzhen_shuying1.mp3 new file mode 100644 index 0000000..6623679 Binary files /dev/null and b/pve/audio/skill/驱鬼逐邪/duzhen_shuying1.mp3 differ diff --git a/pve/audio/skill/驱鬼逐邪/guixi_shuying1.mp3 b/pve/audio/skill/驱鬼逐邪/guixi_shuying1.mp3 new file mode 100644 index 0000000..710f203 Binary files /dev/null and b/pve/audio/skill/驱鬼逐邪/guixi_shuying1.mp3 differ diff --git a/pve/audio/skill/驱鬼逐邪/huiyun_shuying1.mp3 b/pve/audio/skill/驱鬼逐邪/huiyun_shuying1.mp3 new file mode 100644 index 0000000..f0372d0 Binary files /dev/null and b/pve/audio/skill/驱鬼逐邪/huiyun_shuying1.mp3 differ diff --git a/pve/audio/skill/驱鬼逐邪/mingchong_shuying1.mp3 b/pve/audio/skill/驱鬼逐邪/mingchong_shuying1.mp3 new file mode 100644 index 0000000..68c9b6a Binary files /dev/null and b/pve/audio/skill/驱鬼逐邪/mingchong_shuying1.mp3 differ diff --git a/pve/audio/skill/驱鬼逐邪/mizui_shuying1.mp3 b/pve/audio/skill/驱鬼逐邪/mizui_shuying1.mp3 new file mode 100644 index 0000000..fd23ad0 Binary files /dev/null and b/pve/audio/skill/驱鬼逐邪/mizui_shuying1.mp3 differ diff --git a/pve/audio/skill/驱鬼逐邪/qiangzheng_shuying1.mp3 b/pve/audio/skill/驱鬼逐邪/qiangzheng_shuying1.mp3 new file mode 100644 index 0000000..839b35c Binary files /dev/null and b/pve/audio/skill/驱鬼逐邪/qiangzheng_shuying1.mp3 differ diff --git a/pve/audio/skill/驱鬼逐邪/taiping_shuying1.mp3 b/pve/audio/skill/驱鬼逐邪/taiping_shuying1.mp3 new file mode 100644 index 0000000..7fc9431 Binary files /dev/null and b/pve/audio/skill/驱鬼逐邪/taiping_shuying1.mp3 differ diff --git a/pve/audio/skill/驱鬼逐邪/xixing_shuying1.mp3 b/pve/audio/skill/驱鬼逐邪/xixing_shuying1.mp3 new file mode 100644 index 0000000..fef6098 Binary files /dev/null and b/pve/audio/skill/驱鬼逐邪/xixing_shuying1.mp3 differ diff --git a/pve/audio/skill/驱鬼逐邪/yezhong_shuying1.mp3 b/pve/audio/skill/驱鬼逐邪/yezhong_shuying1.mp3 new file mode 100644 index 0000000..aa6468e Binary files /dev/null and b/pve/audio/skill/驱鬼逐邪/yezhong_shuying1.mp3 differ diff --git a/pve/audio/skill/驱鬼逐邪/zhoucha_shuying1.mp3 b/pve/audio/skill/驱鬼逐邪/zhoucha_shuying1.mp3 new file mode 100644 index 0000000..f297654 Binary files /dev/null and b/pve/audio/skill/驱鬼逐邪/zhoucha_shuying1.mp3 differ diff --git a/pve/boss/character.js b/pve/boss/character.js new file mode 100644 index 0000000..9c50019 --- /dev/null +++ b/pve/boss/character.js @@ -0,0 +1,217 @@ +const characters = { + zishu_boss_shuying: { + sex: "male", + group: "qun", + hp: 4, + skills: ["zishu_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/十二生肖/zishu_boss_shuying.png", + dieAudios: ["ext:术樱包/pve/audio/dead/十二生肖/zishu.mp3"] + }, + // chouniu_boss_shuying: {}, + // yinhu_boss_shuying: {}, + // maotu_boss_shuying: {}, + chenlong_boss_shuying: { + sex: "male", + group: "qun", + hp: 4, + skills: ["chenlong_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/十二生肖/chenlong_boss_shuying.png", + dieAudios: ["ext:术樱包/pve/audio/dead/十二生肖/chenlong.mp3"] + }, + // sishe_boss_shuying: {}, + wuma_boss_shuying: { + sex: "male", + group: "qun", + hp: 4, + skills: ["wuma_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/十二生肖/wuma_boss_shuying.png", + dieAudios: ["ext:术樱包/pve/audio/dead/十二生肖/wuma_shuying.mp3"] + }, + weiyang_boss_shuying: { + sex: "female", + group: "qun", + hp: 4, + skills: ["weiyang_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/十二生肖/weiyang_boss_shuying.png", + dieAudios: ["ext:术樱包/pve/audio/dead/十二生肖/weiyang.mp3"] + }, + shenhou_boss_shuying: { + sex: "male", + group: "qun", + hp: 4, + skills: ["shenhou_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/十二生肖/shenhou_boss_shuying.png", + dieAudios: ["ext:术樱包/pve/audio/dead/十二生肖/shenhou.mp3"] + }, + // youji_boss_shuying: {}, + xvgou_boss_shuying: { + sex: "male", + group: "qun", + hp: 4, + skills: ["xvgou_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/十二生肖/xvgou_boss_shuying.png", + dieAudios: ["ext:术樱包/pve/audio/dead/十二生肖/xvgou.mp3"] + }, + // haizhu_boss_shuying: {}, + + heibaiwuchang_boss_shuying: + { + sex: "male", + group: "qun", + hp: 4, + skills: ["mizui_shuying", "qiangzheng_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/捉鬼驱邪/heibaiwuchang_boss_shuying.png" + }, + niutoumamian_boss_shuying: { + sex: "male", + group: "qun", + hp: 4, + skills: ["shiyv_shuying", "manji_shuying", "xiaoshou_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/捉鬼驱邪/niutoumamian_boss_shuying.png" + }, + yvsai_boss_shuying: { + sex: "female", + group: "qun", + hp: 4, + skills: ["anchao_shuying", "guixi_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/捉鬼驱邪/yvsai_boss_shuying.png" + }, + huangfeng_boss_shuying: { + sex: "male", + group: "qun", + hp: 4, + skills: ["mingchong_shuying", "duzhen_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/捉鬼驱邪/huangfeng_boss_shuying.png" + }, + riyeyoushen_boss_shuying: { + sex: "male", + group: "qun", + hp: 4, + skills: ["huiyun_shuying", "yezhong_shuying", "zhoucha_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/捉鬼驱邪/riyeyoushen_boss_shuying.png" + }, + + + + caocao_boss_shuying: { + sex: "male", + group: "wei", + hp: 4, + skills: ["jianxiong_shuying", "lingba_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/青青子衿/caocao_boss_shuying.jpg", + dieAudios: ["ext:术樱包/pve/audio/dead/青青子衿/caocao_boss_shuying.mp3"] + }, + simayi_boss_shuying: { + sex: "male", + group: "wei", + hp: 4, + skills: ["fankui_shuying", "guicai_shuying", "langgu_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/青青子衿/simayi_boss_shuying.jpg", + dieAudios: ["ext:术樱包/pve/audio/dead/青青子衿/simayi_boss_shuying.mp3"] + }, + lvbu_boss_shuying: { + sex: "male", + group: "qun", + hp: 4, + skills: ["mashu", "wushuang_shuying", "shenij_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/青青子衿/lvbu_boss_shuying.jpg", + dieAudios: ["ext:术樱包/pve/audio/dead/青青子衿/lvbu_boss_shuying.mp3"] + }, + dongzhuo_boss_shuying: { + sex: "male", + group: "qun", + hp: 4, + skills: ["jiuchi_shuying", "roulin_shuying", "baonue_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/青青子衿/dongzhuo_boss_shuying.jpg", + dieAudios: ["ext:术樱包/pve/audio/dead/青青子衿/dongzhuo_boss_shuying.mp3"] + }, + + shuishengonggong_boss_shuying: + { + sex: "male", + group: "shen", + hp: 4, + skills: ["tuanliu_shuying", "juehong_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/天书乱斗/shuishengonggong_boss_shuying.png" + }, + // huoshenzhurong_boss_shuying: {}, + + + baiqi_boss_shuying: + { + sex: "male", + group: "shen", + hp: 4, + skills: ["changsheng_shuying", "shashen_shuying", "wuan_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/天书乱斗/baiqi_boss_shuying.png" + }, + + kuafu_boss_shuying: + { + sex: "male", + group: "shen", + hp: 4, + skills: ["shenqu_shuying", "lieben_shuying", "yinjiang_shuying", "zhuri_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/天书乱斗/kuafu_boss_shuying.png" + }, + + shaohao_boss_shuying: + { + sex: "male", + group: "shen", + hp: 4, + skills: ["baiyi_shuying", "shenen_shuying",], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/天书乱斗/shaohao_boss_shuying.png" + }, + xuannv_boss_shuying: { + sex: "female", + group: "shen", + hp: 4, + skills: ["xuanlie_shuying", "jiutian_shuying", "dishi_shuying"], + isHiddenBoss: true, + isBossAllowed: true, + img: "extension/术樱包/pve/images/天书乱斗/xuannv_boss_shuying.png", + dieAudios: ["ext:术樱包/pve/audio/dead/天书乱斗/xuannv_boss_shuying.mp3"] + }, + +}; + +export default characters; diff --git a/pve/boss/characterFilter.js b/pve/boss/characterFilter.js new file mode 100644 index 0000000..ef70aca --- /dev/null +++ b/pve/boss/characterFilter.js @@ -0,0 +1,4 @@ +const characterFilters = { +}; + +export default characterFilters; diff --git a/pve/boss/characterTitle.js b/pve/boss/characterTitle.js new file mode 100644 index 0000000..b901a42 --- /dev/null +++ b/pve/boss/characterTitle.js @@ -0,0 +1,4 @@ +const characterTitles = { +}; + +export default characterTitles; diff --git a/pve/boss/dynamicTranslate.js b/pve/boss/dynamicTranslate.js new file mode 100644 index 0000000..2df76fb --- /dev/null +++ b/pve/boss/dynamicTranslate.js @@ -0,0 +1,187 @@ +import { lib, game, ui, get, ai, _status } from "../../../../noname.js"; +import tianshuConfig from "../../tianshu/config.js"; + +const getTianshuDifficulty = () => { + return _status[tianshuConfig.settings.difficultyStatusKey] || "normal"; +}; + +const dynamicTranslates = { + //黑白无常 + mizui_shuying(player, skill) { + const num = getTianshuDifficulty() == "normal" ? 1 : 2 + + return `锁定技,你的伤害牌造成伤害后,你随机弃置受伤角色的${num}张牌。` + }, + qiangzheng_shuying(player, skill) { + let num = 2; + if (getTianshuDifficulty() == "hard") num = 4; + if (getTianshuDifficulty() == "nightmare") num = 6; + + return `锁定技,敌方角色的结束阶段,若其手牌数小于${num},你获得其所有手牌。` + }, + xixing_shuying(player, skill) { + const difficulty = getTianshuDifficulty(); + if (difficulty == "nightmare") { + return "锁定技,准备阶段,你对每名敌方角色各造成2点雷电伤害,然后你回复等量体力"; + } + + return "锁定技,准备阶段,随机对敌方体力最多的一名角色造成2点雷电伤害,然后你回复等量体力。"; + }, + + //鱼鳃 + guixi_shuying(player, skill) { + const num = getTianshuDifficulty() == "nightmare" ? 2 : 1 + + return `锁定技,当你受到伤害后,你进行一次判定,若结果为红色,你回复1点体力;若结果不为红色,你摸${num}张牌。`; + }, + + //黄蜂 + mingchong_shuying(player, skill) { + if (getTianshuDifficulty() == "nightmare") { + return "锁定技,你死亡时,杀死你的角色弃置所有牌。" + } + + return "锁定技,你死亡时,杀死你的角色随机弃置一半的牌。" + }, + duzhen_shuyingg(player, skill) { + if (getTianshuDifficulty() == "nightmare") { + return "锁定技,你使用牌指定单一敌方角色为目标后,该角色随机弃置1张牌(优先装备区的牌)。" + } + + return "锁定技,你使用牌指定单一敌方角色为目标后,该角色随机弃置2张牌(优先装备区的牌)。" + + }, + + //日夜游神 + mingchong_shuying(player, skill) { + const num = getTianshuDifficulty() == "normal" ? 1 : 2 + + return `出牌阶段限一次,你可以展示一名敌方角色的手牌,并弃置其中至多两张牌。然后你可以弃置一张与该角色弃置牌牌名相同的牌,对其造成${num}点伤害。` + }, + yezhong_shuying(player, skill) { + let num = 1; + if (getTianshuDifficulty() == "hard") num = 2; + if (getTianshuDifficulty() == "nightmare") num = 3; + + return `锁定技,结束阶段,你进行一次判定并获得判定牌,若次牌为黑色,每名敌方角色随机弃${num}张手牌。` + }, + zhoucha_shuying(player, skill) { + let num = 1; + if (getTianshuDifficulty() == "hard") num = 2; + if (getTianshuDifficulty() == "nightmare") num = 3; + + return `锁定技,准备阶段,你进行一次判定并获得判定牌,若此牌为红色,你本回合使用【杀】的次数+${num}。` + }, + + + //曹操 + lingba_shuying(player, skill) { + const num = getTianshuDifficulty() == "nightmare" ? 2 : 1 + + return `锁定技,你的回合开始时,若你手牌数为全场最多,则对一名随机敌人造成${num}点伤害。若你手牌数大于等于你体力值的两倍,则改为对所有敌人造成伤害。` + }, + yishen_shuying(player, skill) { + const difficulty = getTianshuDifficulty(); + if (difficulty == "nightmare") { + return "当你回复体力时,可以改为获得所有敌人各一张随机装备。"; + } + + return "当你回复体力时,可以改为获得一名随机敌方角色一张随机装备。"; + }, + + //司马懿 + yuanlv_shuying(player, skill) { + const difficulty = getTianshuDifficulty(); + + if (difficulty == "nightmare") { + return "当你使用锦囊牌对敌方角色造成伤害时,你可以防止该伤害,改为摸一张牌且该敌方角色对你造成过1点伤害。"; + } + + return "当你使用锦囊牌对敌方角色造成伤害时,你可以防止该伤害,改为摸一张牌且该敌方角色对你造成1点伤害。"; + + }, + + //吕布 + wushuang_shuying(player, skill) { + const num = getTianshuDifficulty() == "nightmare" ? 2 : 1 + + return `锁定技。①你使用的【杀】需两张【闪】才能抵消;与你进行【决斗】的角色每次需要打出两张【杀】。②每回合限${num}次,当你使用【杀】或【决斗】造成伤害时,若受伤角色没有使用或打出过【杀】或【闪】响应此牌,则此伤害+1。`; + }, + shenij_shuying(player, skill) { + const difficulty = getTianshuDifficulty(); + let card = 2; + let sha = 1; + let target = 1; + + if (difficulty == "hard") { + card = 3; + sha = 2; + } else if (difficulty == "nightmare") { + card = 4; + sha = 2; + target = 2; + } + + return `判定阶段,你可以弃置两张手牌,然后弃置你判定区里的牌;摸牌阶段,你多摸${card}张牌;出牌阶段,你可以多使用${sha}张【杀】,你的【杀】可以多指定${target}名角色为目标。`; + }, + zhanjia_shuying(player, skill) { + const num = getTianshuDifficulty() == "nightmare" ? 3 : 2 + + return `锁定技,每回合限一次,当你受到大于2点的伤害时,将此伤害减至2点,然后摸${num}张牌。` + }, + + //董卓 + baonue_shuying(player, skill) { + let num = 3; + if (getTianshuDifficulty() == "hard") num = 4; + if (getTianshuDifficulty() == "nightmare") num = 5; + + return `锁定技,回合开始时,你摸X张牌并对至多X名角色造成1点伤害,然后你失去1点体力。X为你已损失体力且最大为${num}。` + }, + + //水神共工 + tuanliu_shuying(player, skill) { + let hp = 2; + let card = 4; + let damage = 1; + + if (getTianshuDifficulty() == "nightmare") { + hp = 3; + card = 6; + } + return `锁定技,结束阶段,若本回合进入弃牌堆的卡牌数量:大于4,你回复${hp}点体力;大于5,你摸${card}张牌;大于9,你对所有敌方角色造成${damage}点伤害。` + }, + + //少昊 + baiyi_shuying(player, skill) { + let num = 1; + const difficulty = getTianshuDifficulty(); + if (difficulty == "hard") { + num = 2; + } else if (difficulty == "nightmare") { + num = 3; + } + + return `锁定技,每名敌方角色的回合开始时,若当前轮数小于3,你随机获得其${num}张牌;若当前轮数小于5,对其造成${num}点雷电伤害;若当前轮数小于7,其随机弃置${num}张牌。`; + }, + + //玄女 + xuanlie_shuying(player, skill) { + let num = 2; + if (getTianshuDifficulty() == "normal") num = 1; + + return `锁定技,回合结束时,对所有本回合你获得过其牌的敌方角色依次造成${num}点伤害。`; + }, + jiutian_shuying(player, skill) { + const difficulty = getTianshuDifficulty(); + const num = difficulty == "normal" ? 1 : 2; + const extra = + difficulty == "normal" || difficulty == "hard" + ? "" + : "若这些牌包含4种花色,这些角色再额外失去1点体力。"; + + return `锁定技,准备阶段,你获得所有敌方角色各${num}张手牌。若你以此法获得的牌包含2种颜色,则对所有你以此法获得其牌的敌方角色造成1点伤害。${extra}`; + } +}; + +export default dynamicTranslates; diff --git a/pve/boss/index.js b/pve/boss/index.js new file mode 100644 index 0000000..2cefd62 --- /dev/null +++ b/pve/boss/index.js @@ -0,0 +1,28 @@ +import characters from "./character.js"; +import skills from "./skill.js"; +import translates from "./translate.js"; +import characterFilters from "./characterFilter.js"; +import characterTitles from "./characterTitle.js"; +import characterIntros from "./intro.js"; +import dynamicTranslates from "./dynamicTranslate.js"; +import perfectPairs from "./perfectPairs.js"; +import voices from "./voices.js"; +import { characterSort, characterSortTranslate } from "./sort.js"; + +const shuying_boss = { + name: "shuYing_pve_boss", + connect: true, + character: { ...characters }, + characterSort: { + shuying_boss: characterSort, + }, + characterFilter: { ...characterFilters }, + characterTitle: { ...characterTitles }, + dynamicTranslate: { ...dynamicTranslates }, + characterIntro: { ...characterIntros }, + skill: { ...skills }, + perfectPair: { ...perfectPairs }, + translate: { ...translates, ...voices, ...characterSortTranslate }, +}; + +export default shuying_boss; diff --git a/pve/boss/intro.js b/pve/boss/intro.js new file mode 100644 index 0000000..15a8a83 --- /dev/null +++ b/pve/boss/intro.js @@ -0,0 +1,4 @@ +const characterIntros = { +}; + +export default characterIntros; diff --git a/pve/boss/perfectPairs.js b/pve/boss/perfectPairs.js new file mode 100644 index 0000000..47a0a7b --- /dev/null +++ b/pve/boss/perfectPairs.js @@ -0,0 +1,4 @@ +const perfectPairs = { +}; + +export default perfectPairs; diff --git a/pve/boss/skill.js b/pve/boss/skill.js new file mode 100644 index 0000000..944e815 --- /dev/null +++ b/pve/boss/skill.js @@ -0,0 +1,2020 @@ +import { lib, game, ui, get, ai, _status } from "../../../../noname.js"; +import tianshuConfig from "../../tianshu/config.js"; + +const getTianshuDifficulty = () => { + return _status[tianshuConfig.settings.difficultyStatusKey] || "normal"; +}; + +const isTianshuDifficultyAtLeast = difficulty => { + return tianshuDifficultyRank[getTianshuDifficulty()] >= tianshuDifficultyRank[difficulty]; +}; + +const skills = { + //子鼠 + zishu_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/十二生肖:1", + enable: "phaseUse", + usable: 1, + filter(event, player) { + return game.hasPlayer(current => { + return current != player && current.countCards("h") > player.countCards("h"); + }); + }, + async content(event, trigger, player) { + while (player.isIn()) { + const max = Math.max(...game.players.map(current => current.countCards("h"))); + if (player.countCards("h") >= max) break; + + const targets = game.filterPlayer(current => { + return current != player && current.countCards("h") > player.countCards("h"); + }); + if (!targets.length) break; + + const hasEnemy = targets.some(target => player.getEnemies(null, false).includes(target)); + + const result = await player + .chooseTarget("恣疏:获得一名手牌数大于你的角色一张手牌", (card, player, target) => { + return target != player && target.countCards("h") > player.countCards("h"); + }) + .set("ai", target => { + const player = get.player(); + const enemies = player.getEnemies(null, false); + const isEnemy = enemies.includes(target); + const hasEnemy = game.hasPlayer(current => { + return current != player && current.countCards("h") > player.countCards("h") && enemies.includes(current); + }); + + if (hasEnemy && !isEnemy) return 0; + if (isEnemy) return 8 + target.countCards("h") - get.attitude(player, target); + return 1 + target.countCards("h") / 10; + }) + .forResult(); + + if (!result.bool) break; + + const target = result.targets[0]; + player.line(target); + await player.gainPlayerCard({ + target, + position: "h", + forced: true, + }); + } + }, + ai: { + order: 4.5, + result: { + player(player) { + const enemies = player.getEnemies(null, false); + if (game.hasPlayer(current => current != player && current.countCards("h") > player.countCards("h") && enemies.includes(current))) { + return 1; + } + if (game.hasPlayer(current => current != player && current.countCards("h") > player.countCards("h") && !enemies.includes(current))) { + return 0.2; + } + return 0; + }, + }, + threaten: 1.4, + }, + }, + + //辰龙 + chenlong_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/十二生肖:1", + enable: "phaseUse", + limited: true, + skillAnimation: true, + animationColor: "fire", + selectTarget: 1, + filter(event, player) { + return !player.storage.chenlong_shuying_used && game.hasPlayer(current => current != player && player.getEnemies(null, false).includes(current)); + }, + filterTarget(card, player, target) { + return target != player && player.getEnemies(null, false).includes(target); + }, + async content(event, trigger, player) { + const target = event.targets[0]; + const result = await player.chooseControl([1, 2, 3, 4, 5]) + .set("prompt", "失去任意点体力,然后对一名其他角色造成等量伤害") + .set("ai", () => 4) + .forResult(); + const num = result.control; + if (!num) return; + player.storage.chenlong_shuying_used = true; + player.storage.chenlong_shuying_dying = true; + await player.loseHp(num); + delete player.storage.chenlong_shuying_dying; + player.awakenSkill(event.name); + if (target.isIn()) { + player.line(target); + await target.damage(num, player); + } + }, + ai: { + order: 4, + result: { + target(player, target) { + if (!player.getEnemies(null, false).includes(target)) return 0; + const effect = get.damageEffect(target, player, target); + if (effect >= 0) return 0; + return target.hp <= 2 ? effect * 2 : effect; + }, + }, + }, + group: "chenlong_shuying_dying", + subSkill: { + dying: { + trigger: { player: ["dying", "dyingAfter"] }, + forced: true, + popup: false, + charlotte: true, + filter(event, player) { + return player.storage.chenlong_shuying_dying && player.hp <= 0; + }, + async content(event, trigger, player) { + await player.recoverTo(1); + if (player.maxHp > 0) await player.loseMaxHp(); + }, + }, + }, + }, + + //午马 + wuma_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/十二生肖:1", + trigger: { target: "useCardToTargeted" }, + forced: true, + locked: true, + filter(event, player) { + return event.player != player && get.type2(event.card) == "trick"; + }, + async content(event, trigger, player) { + await player.draw(); + }, + group: ["wuma_shuying_turn", "wuma_shuying_skip"], + subSkill: { + turn: { + audio: "wuma_shuying", + trigger: { player: "turnOverBefore" }, + forced: true, + filter(event, player) { + return !player.isTurnedOver(); + }, + async content(event, trigger, player) { + trigger.cancel(); + }, + }, + skip: { + audio: "wuma_shuying", + trigger: { player: ["phaseAnySkipped", "phaseAnyCancelled"] }, + forced: true, + async content(event, trigger, player) { + game.log(player, "恢复了", trigger.name); + await player[trigger.name](); + }, + }, + }, + ai: { + effect: { + target(card, player, target) { + if (player != target && get.type2(card) == "trick") return [1, 0.5]; + }, + }, + }, + }, + //未羊 + weiyang_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/十二生肖:1", + enable: "phaseUse", + usable: 1, + position: "he", + selectCard: [1, Infinity], + filter(event, player) { + return game.hasPlayer(current => current.isDamaged() && !player.getEnemies(null, false).includes(current)); + }, + filterCard(card, player) { + const type = get.type(card); + const friends = game.countPlayer(current => current.isDamaged() && !player.getEnemies(null, false).includes(current)); + return ui.selected.cards.length < friends && !ui.selected.cards.some(cardx => get.type(cardx) == type); + }, + check(card) { + return 6 - get.value(card); + }, + async content(event, trigger, player) { + const num = event.cards.length; + const friends = game.filterPlayer(current => current.isDamaged() && !player.getEnemies(null, false).includes(current)); + if (!friends.length) return; + const result = await player.chooseTarget(`令至多${get.cnNumber(num)}名角色回复1点体力`, [1, Math.min(num, friends.length)], (card, player, target) => { + return target.isDamaged() && !player.getEnemies(null, false).includes(target); + }).set("ai", target => get.recoverEffect(target, get.player(), get.player())).forResult(); + if (result.bool) { + for (const target of result.targets) { + player.line(target); + await target.recover(); + } + } + }, + ai: { + order: 6, + result: { + player(player) { + return game.hasPlayer(current => current.isDamaged() && !player.getEnemies(null, false).includes(current) && get.recoverEffect(current, player, player) > 0) ? 1 : 0; + }, + }, + }, + }, + + //申猴 + shenhou_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/十二生肖:1", + trigger: { target: "useCardToTargeted" }, + filter(event, player) { + return event.card.name == "sha"; + }, + async cost(event, trigger, player) { + event.result = await player + .chooseBool(get.prompt2(event.skill)) + .set("ai", () => { + const player = get.player(); + const trigger = get.event().getTrigger(); + return get.effect(player, trigger.card, trigger.player, player) < 0; + }) + .forResult(); + }, + async content(event, trigger, player) { + const result = await player + .judge(card => get.color(card) == "red" ? 2 : -1) + .set("judge2", result => result.bool) + .forResult(); + + if (result.bool) { + trigger.getParent().excluded.add(player); + } + }, + ai: { + effect: { + target(card, player, target) { + if (card.name == "sha") return [1, 0.6]; + }, + }, + }, + }, + + //戌狗 + xvgou_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/十二生肖:1", + forced: true, + group: ["xvgou_shuying_cancel", "xvgou_shuying_damage"], + mod: { + targetInRange(card, player, target) { + if (card.name == "sha" && get.color(card) == "red") return true; + }, + }, + subSkill: { + cancel: { + trigger: { target: "useCardToTargeted" }, + forced: true, + filter(event, player) { + return event.card.name == "sha" && get.color(event.card) == "red"; + }, + async content(event, trigger, player) { + trigger.getParent().excluded.add(player); + }, + }, + damage: { + trigger: { source: "damageBegin1" }, + forced: true, + filter(event, player) { + return event.card?.name == "sha" && get.color(event.card) == "red"; + }, + async content(event, trigger, player) { + trigger.num++; + }, + }, + }, + }, + + //黑白无常 over + mizui_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", + trigger: { source: "damageEnd" }, + forced: true, + filter(event, player) { + return event.player?.isIn() && event.player.countDiscardableCards(player, "he") > 0 && event.card && get.is.damageCard(event.card); + }, + async content(event, trigger, player) { + const num = getTianshuDifficulty() == "normal" ? 1 : 2; + + player.line(trigger.player); + await player.discardPlayerCard(trigger.player, num, "he", true); + }, + ai: { expose: 0.2 }, + }, + qiangzheng_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", + trigger: { global: "phaseEnd" }, + forced: true, + logTarget: "player", + filter(event, player) { + let num = 2; + if (getTianshuDifficulty() == "hard") num = 4; + if (getTianshuDifficulty() == "nightmare") num = 6; + + return event.player?.isIn() && player.getEnemies(null, false).includes(event.player) && event.player.countCards("h") > 0 && event.player.countCards("h") < num; + }, + async content(event, trigger, player) { + player.line(trigger.player); + await player.gain(trigger.player.getCards("h"), trigger.player, "giveAuto"); + }, + }, + xixing_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", + trigger: { player: "phaseZhunbeiBegin" }, + forced: true, + filter(event, player) { + return player.getEnemies(null, false).some(target => target.isIn()); + }, + async content(event, trigger, player) { + const enemies = player.getEnemies(null, false).filter(target => target.isIn()); + + if (difficulty == "nightmare") { + for (const target of enemies) { + player.line(target, "thunder"); + await target.damage(2, "thunder", player); + } + } else { + const max = Math.max(...enemies.map(target => target.hp)); + const target = enemies.filter(target => target.hp == max).randomGet(); + player.line(target, "thunder"); + await target.damage(2, "thunder", player); + } + + await player.recover(2); + }, + }, + taiping_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", + trigger: { player: "damageEnd" }, + forced: true, + locked: true, + logTarget: "source", + filter(event, player) { + return ( + event.num > 0 && + event.source?.isIn() && + player.getEnemies(null, false).includes(event.source) + ); + }, + async content(event, trigger, player) { + const source = trigger.source; + player.line(source); + + const result = await source + .chooseToDiscard("h", 2, `太平:弃置两张花色不同的手牌,否则失去1点体力`, card => { + const cards = ui.selected.cards; + return !cards.some(cardx => get.suit(cardx) == get.suit(card)); + }) + .set("ai", card => { + const player = get.player(); + if (player.hp <= 1) return 8 - get.value(card); + return 6 - get.value(card); + }) + .forResult(); + + if (!result.bool) { + await source.loseHp(); + } + }, + ai: { + maixie_defend: true, + effect: { + target(card, player, target) { + if (get.tag(card, "damage") && target.getEnemies(null, false).includes(player)) { + if (player.countCards("h") < 2) return [1, -1]; + return [1, 0.4]; + } + }, + }, + }, + }, + + //牛头马面 + shiyv_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", + trigger: { player: "phaseDrawBegin2" }, + forced: true, + filter(event, player) { + return !event.numFixed; + }, + async content(event, trigger, player) { + trigger.num = 0; + const cards = []; + for (const suit of lib.suit) { + const card = get.cardPile(card => get.suit(card) == suit, "cardPile", "random"); + if (card) cards.push(card); + } + if (cards.length) await player.gain(cards, "gain2"); + }, + }, + manji_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", + trigger: { player: "useCardToPlayered" }, + filter(event, player) { + return event.card.name == "sha" && event.targets?.length == 1 && player.getEnemies(null, false).includes(event.target) && event.target.countDiscardableCards(player, "h") > 0; + }, + async cost(event, trigger, player) { + event.result = await player.chooseBool(get.prompt(event.skill, trigger.target)) + .set("ai", () => get.attitude(get.player(), get.event().getTrigger().target) < 0) + .forResult(); + }, + async content(event, trigger, player) { + const target = trigger.target; + player.line(target); + const cards = target.getCards("h").randomGets(Math.min(2, target.countCards("h"))); + await target.discard(cards); + if (cards.some(card => get.name(card, target) == "sha")) trigger.getParent().baseDamage++; + }, + ai: { expose: 0.2 }, + }, + xiaoshou_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", + trigger: { player: "phaseZhunbeiBegin" }, + forced: true, + filter(event, player) { + return player.getEnemies(null, false).some(target => target.isIn()); + }, + async content(event, trigger, player) { + const target = player.getEnemies(null, false).filter(target => target.isIn()).randomGet(); + player.line(target); + await target.damage(2, player); + }, + }, + + //鱼鳃 over + anchao_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", + forced: true, + locked: true, + group: ["anchao_shuying_gain", "anchao_shuying_start"], + marktext: "潮", + intro: { + name: "暗潮", + content: "mark", + }, + subSkill: { + gain: { + audio: "anchao_shuying", + trigger: { global: "phaseJieshuBegin" }, + forced: true, + logTarget: "player", + filter(event, player) { + return ( + event.player?.isIn() && + !player.getEnemies(null, false).includes(event.player) && + !event.player.getHistory("sourceDamage", evt => evt.num > 0).length + ); + }, + async content(event, trigger, player) { + trigger.player.addMark("anchao_shuying", 1); + }, + }, + start: { + audio: "anchao_shuying", + trigger: { global: "phaseBegin" }, + forced: true, + logTarget: "player", + filter(event, player) { + return ( + event.player?.isIn() && + !player.getEnemies(null, false).includes(event.player) && + event.player.countMark("anchao_shuying") > 0 + ); + }, + async content(event, trigger, player) { + const target = trigger.player; + target.storage.anchao_shuying_effect = target.countMark("anchao_shuying"); + target.addTempSkill("anchao_shuying_effect", { player: "phaseAfter" }); + }, + }, + effect: { + charlotte: true, + group: ["anchao_shuying_draw", "anchao_shuying_damage"], + onremove(player) { + delete player.storage.anchao_shuying_effect; + }, + }, + draw: { + audio: "anchao_shuying", + trigger: { player: "phaseDrawBegin2" }, + forced: true, + filter(event, player) { + return !event.numFixed && player.storage.anchao_shuying_effect > 0; + }, + async content(event, trigger, player) { + trigger.num += player.storage.anchao_shuying_effect; + }, + }, + damage: { + audio: "anchao_shuying", + trigger: { source: "damageBegin1" }, + forced: true, + filter(event, player) { + return ( + player.storage.anchao_shuying_effect > 0 && + event.player?.isIn() && + player.getEnemies(null, false).includes(event.player) + ); + }, + async content(event, trigger, player) { + trigger.num += player.storage.anchao_shuying_effect; + }, + }, + }, + ai: { + threaten: 2, + }, + }, + guixi_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", + trigger: { player: "damageEnd" }, + forced: true, + locked: true, + filter(event, player) { + return event.num > 0; + }, + async content(event, trigger, player) { + const num = getTianshuDifficulty() == "nightmare" ? 2 : 1; + const result = await player + .judge(card => get.color(card) == "red" ? 2 : 0) + .set("judge2", result => result.bool) + .forResult(); + + if (result.bool && player.isDamaged()) { + await player.recover(); + } + else { + await player.draw(num); + } + }, + ai: { + maixie: true, + maixie_hp: true, + effect: { + target(card, player, target) { + if (get.tag(card, "damage")) { + if (player.hasSkillTag("jueqing", false, target)) return [1, -1]; + return [1, 0.7]; + } + }, + }, + }, + }, + + //黄蜂 over + mingchong_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", + trigger: { player: "die" }, + forced: true, + locked: true, + forceDie: true, + logTarget: "source", + filter(event, player) { + return event.source?.isIn() && event.source.countDiscardableCards(player, "he") > 0; + }, + async content(event, trigger, player) { + const source = trigger.source; + player.line(source); + + const cards = source + .getCards("he") + .filter(card => lib.filter.cardDiscardable(card, source, "mingchong_shuying")); + + if (cards.length) { + if (getTianshuDifficulty() == "nightmare") { + await source.discard(cards); + } + else { + await source.discard(cards.randomGets(Math.ceil(cards.length / 2))); + } + } + }, + ai: { + threaten: 0.8, + effect: { + target(card, player, target) { + if (get.tag(card, "damage") && target.hp <= 1 && player.countCards("he") > 2) { + return [1, -0.8]; + } + }, + }, + }, + }, + duzhen_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", + trigger: { player: "useCardToPlayered" }, + forced: true, + locked: true, + logTarget: "target", + filter(event, player) { + return ( + event.targets?.length == 1 && + event.target?.isIn() && + player.getEnemies(null, false).includes(event.target) && + event.target.countDiscardableCards(player, "he") > 0 + ); + }, + async content(event, trigger, player) { + const target = trigger.target; + const num = getTianshuDifficulty() == "nightmare" ? 2 : 1; + const cards = []; + + player.line(target); + + const equips = target + .getCards("e") + .filter(card => lib.filter.cardDiscardable(card, target, "duzhen_shuying")) + .randomGets(num); + cards.addArray(equips); + + if (cards.length < num) { + const hands = target + .getCards("h") + .filter(card => lib.filter.cardDiscardable(card, target, "duzhen_shuying")) + .randomGets(num - cards.length); + cards.addArray(hands); + } + + if (cards.length) { + await target.discard(cards); + } + }, + ai: { + threaten: 1.8, + effect: { + player_use(card, player, target) { + if (target && player.getEnemies(null, false).includes(target) && target.countCards("he") > 0) { + return [1, 0.5]; + } + }, + }, + }, + }, + + //日夜游神 over + huiyun_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", + enable: "phaseUse", + usable: 1, + filter(event, player) { + return player.getEnemies(null, false).some(target => target.isIn() && target.countCards("h") > 0); + }, + filterTarget(card, player, target) { + return target.isIn() && player.getEnemies(null, false).includes(target) && target.countCards("h") > 0; + }, + async content(event, trigger, player) { + const target = event.targets[0]; + const hs = target.getCards("h"); + player.line(target); + await target.showCards(hs, `${get.translation(player)}对${get.translation(target)}发动了【${get.translation(event.name)}】`); + + const result = await player + .choosePlayerCard(target, "h", [1, 2], `弃置${get.translation(target)}至多两张手牌`) + .set("filterButton", button => { + const player = get.player(); + const target = get.event().target; + return lib.filter.canBeDiscarded(button.link, player, target); + }) + .set("ai", button => { + const card = button.link; + const player = get.player(); + const target = get.event().target; + let value = get.value(card, target); + if (get.name(card, target) == "tao") value += 3; + if (get.name(card, target) == "shan") value += 1; + return value; + }) + .set("target", target) + .forResult(); + + if (!result.bool || !result.cards?.length) return; + + const names = result.cards.map(card => get.name(card, target)).unique(); + await target.discard(result.cards); + + if (!target.isIn()) return; + + const result2 = await player + .chooseToDiscard("he", `是否弃置一张同名牌,对${get.translation(target)}造成2点伤害?`, card => { + return get.event().names.includes(get.name(card)); + }) + .set("names", names) + .set("ai", card => { + const player = get.player(); + const target = get.event().target; + if (!player.getEnemies(null, false).includes(target)) return 0; + const effect = get.damageEffect(target, player, player); + if (effect <= 0) return 0; + return effect * 3 - get.value(card); + }) + .set("target", target) + .forResult(); + + if (result2.bool) { + const num = getTianshuDifficulty() == "normal" ? 1 : 2 + + player.line(target); + await target.damage(num, player); + } + }, + ai: { + order: 5, + result: { + target(player, target) { + if (!player.getEnemies(null, false).includes(target)) return 0; + return -Math.min(2, target.countCards("h")); + }, + }, + threaten: 1.6, + }, + }, + yezhong_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", + trigger: { player: "phaseJieshuBegin" }, + forced: true, + locked: true, + async content(event, trigger, player) { + const result = await player + .judge(card => get.color(card) == "black" ? 2 : -1) + .set("judge2", result => result.bool) + .forResult(); + + if (result.card) { + await player.gain(result.card, "gain2"); + } + + if (result.bool) { + let num = 1; + if (getTianshuDifficulty() == "hard") num = 2; + if (getTianshuDifficulty() == "nightmare") num = 3; + + const targets = player.getEnemies(null, false).filter(target => target.isIn() && target.countCards("h") > 0); + for (const target of targets) { + player.line(target); + const cards = target + .getCards("h") + .filter(card => lib.filter.cardDiscardable(card, target, "yezhong_shuying")) + .randomGets(num); + + if (cards.length) { + await target.discard(cards); + } + } + } + }, + ai: { + threaten: 1.8, + }, + }, + zhoucha_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", + trigger: { player: "phaseZhunbeiBegin" }, + forced: true, + locked: true, + async content(event, trigger, player) { + const result = await player + .judge(card => get.color(card) == "red" ? 2 : -1) + .set("judge2", result => result.bool) + .forResult(); + + if (result.card) { + await player.gain(result.card, "gain2"); + } + + if (result.bool) { + player.addTempSkill("zhoucha_shuying_effect", { player: "phaseAfter" }); + } + }, + subSkill: { + effect: { + charlotte: true, + mod: { + cardUsable(card, player, num) { + let number = 1; + if (getTianshuDifficulty() == "hard") number = 2; + if (getTianshuDifficulty() == "nightmare") number = 3; + + if (card.name == "sha") return num + number; + }, + }, + }, + }, + ai: { + threaten: 1.4, + }, + }, + duane_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/驱鬼逐邪:1", + trigger: { global: "phaseDiscardEnd" }, + forced: true, + locked: true, + logTarget: "player", + filter(event, player) { + const target = event.player; + return ( + target?.isIn() && + player.getEnemies(null, false).includes(target) && + target.getHistory("lose", evt => { + return ( + evt.type == "discard" && + evt.getParent("phaseDiscard") == event && + evt.cards2?.some(card => get.color(card, target) == "black") + ); + }).length + ); + }, + async content(event, trigger, player) { + const target = trigger.player; + player.line(target); + await target.loseHp(2); + }, + ai: { + threaten: 1.5, + }, + }, + + + //曹操 over + jianxiong_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/青青子衿:1", + trigger: { player: "damageEnd" }, + filter(event, player) { + return event.num > 0; + }, + async cost(event, trigger, player) { + event.result = await player.chooseBool(get.prompt2(event.skill)) + .set("ai", () => true) + .forResult(); + }, + async content(event, trigger, player) { + if (get.itemtype(trigger.cards) == "cards" && get.position(trigger.cards[0], true) == "o") { + await player.gain(trigger.cards, "gain2"); + } + await player.draw(); + }, + ai: { + maixie: true, + maixie_hp: true, + effect: { + target(card, player, target) { + if (player.hasSkillTag("jueqing", false, target)) return [1, -1]; + if (get.tag(card, "damage")) return [1, 0.6]; + }, + }, + }, + }, + lingba_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/青青子衿:1", + trigger: { player: "phaseBegin" }, + forced: true, + filter(event, player) { + return player.getEnemies(null, false).some(target => target.isIn()) && game.players.every(current => player.countCards("h") >= current.countCards("h")); + }, + async content(event, trigger, player) { + const num = getTianshuDifficulty() == "nightmare" ? 2 : 1 + const enemies = player.getEnemies(null, false).filter(target => target.isIn()); + if (player.countCards("h") >= player.hp * 2) { + for (const target of enemies) { + player.line(target); + await target.damage(num, player); + } + } + else { + const target = enemies.randomGet(); + player.line(target); + await target.damage(num, player); + } + }, + }, + yishen_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/青青子衿:1", + trigger: { player: "recoverBegin" }, + filter(event, player) { + return player.getEnemies(null, false).some(target => target.countGainableCards(player, "e") > 0); + }, + async cost(event, trigger, player) { + event.result = await player.chooseBool(get.prompt2(event.skill)) + .set("ai", () => { + const player = get.player(); + const trigger = get.event().getTrigger(); + + if (player.hp <= 1) return false; + if (player.hp + trigger.num <= 2) return false; + + const enemies = player.getEnemies(null, false).filter(target => { + return target.isIn() && target.countGainableCards(player, "e") > 0; + }); + if (!enemies.length) return false; + + const recoverEffect = get.recoverEffect(player, player, player); + const difficulty = getTianshuDifficulty(); + + if (difficulty == "nightmare") { + let total = 0; + let max = 0; + for (const target of enemies) { + for (const card of target.getCards("e")) { + const value = get.value(card, target); + total += value; + if (value > max) max = value; + } + } + if (max < 5) return false; + return total >= recoverEffect + 4; + } + + let best = 0; + for (const target of enemies) { + for (const card of target.getCards("e")) { + const value = get.value(card, target); + if (value > best) best = value; + } + } + + if (best < 5) return false; + return best >= recoverEffect; + }) + .forResult(); + }, + async content(event, trigger, player) { + const enemies = player.getEnemies(null, false).filter(target => target.countGainableCards(player, "e") > 0); + const difficulty = getTianshuDifficulty(); + + trigger.cancel(); + + if (difficulty == "nightmare") { + for (const target of enemies) { + player.line(target); + await player.gainPlayerCard({ target, position: "e", forced: true }); + } + } + else { + const target = enemies.randomGet(); + player.line(target); + await player.gainPlayerCard({ target, position: "e", forced: true }); + } + }, + ai: { + threaten: 1.4, + effect: { + target(card, player, target) { + if (get.tag(card, "recover") && target.hasSkill("yishen_shuying")) { + if (target.hp <= 1) return [1, 1.2]; + if (target.hp <= 2) return [1, 0.8]; + + const enemies = target.getEnemies(null, false).filter(current => { + return current.isIn() && current.countGainableCards(target, "e") > 0; + }); + if (!enemies.length) return; + + let best = 0; + for (const enemy of enemies) { + for (const equip of enemy.getCards("e")) { + best = Math.max(best, get.value(equip, enemy)); + } + } + + if (best >= 5) return [1, 0.4]; + } + }, + }, + }, + }, + + //司马懿 over + fankui_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/青青子衿:1", + trigger: { player: "damageEnd" }, + logTarget: "source", + filter(event, player) { + return ( + event.num > 0 && + event.source?.isIn() && + player.getEnemies(null, false).includes(event.source) && + event.source.hasGainableCards(player, "he") + ); + }, + async content(event, trigger, player) { + await player.gainPlayerCard({ + target: trigger.source, + position: "he", + forced: true, + }); + }, + ai: { + maixie_defend: true, + effect: { + target(card, player, target) { + if (player.countCards("he") > 1 && get.tag(card, "damage")) { + if (player.hasSkillTag("jueqing", false, target)) return [1, -1.5]; + if (target.getEnemies(null, false).includes(player)) return [1, 1]; + } + }, + }, + }, + }, + guicai_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/青青子衿:1", + trigger: { global: "judge" }, + filter(event, player) { + return player.countCards("hes") > 0; + }, + async cost(event, trigger, player) { + event.result = await player.chooseCard(`${get.translation(trigger.player)}的${trigger.judgestr || ""}判定为${get.translation(trigger.player.judging[0])},${get.prompt(event.skill)}`, "hes", card => { + const player = get.player(); + const mod2 = game.checkMod(card, player, "unchanged", "cardEnabled2", player); + if (mod2 != "unchanged") return mod2; + const mod = game.checkMod(card, player, "unchanged", "cardRespondable", player); + if (mod != "unchanged") return mod; + return true; + }).set("ai", card => { + const trigger = get.event().getTrigger(), player = get.player(), judging = trigger.player.judging[0]; + const result = trigger.judge(card) - trigger.judge(judging); + const attitude = player.getEnemies(null, false).includes(trigger.player) ? -1 : 1; + let value = get.value(card, player); + value /= get.subtype(card) == "equip2" ? 2 : 4; + if (result == 0) return 0; + return attitude > 0 ? result - value : -result - value; + }).set("judging", trigger.player.judging[0]).forResult(); + }, + popup: false, + async content(event, trigger, player) { + const next = player.respond(event.cards, event.name, "highlight", "noOrdering"); + await next; + const { cards } = next; + if (cards?.length) { + if (trigger.player.judging[0].clone) { + trigger.player.judging[0].clone.classList.remove("thrownhighlight"); + game.broadcast(card => { + if (card.clone) card.clone.classList.remove("thrownhighlight"); + }, trigger.player.judging[0]); + game.addVideo("deletenode", player, get.cardsInfo([trigger.player.judging[0].clone])); + } + await game.cardsDiscard(trigger.player.judging[0]); + trigger.player.judging[0] = cards[0]; + trigger.orderingCards.addArray(cards); + game.log(trigger.player, "的判定牌改为", cards); + await game.delay(2); + } + }, + ai: { rejudge: true, tag: { rejudge: 1 } }, + }, + langgu_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/青青子衿:1", + trigger: { player: "gainAfter" }, + forced: true, + filter(event, player) { + if (player.hasSkill("langgu_shuying_used")) return false; + return game.hasPlayer(current => { + return ( + current != player && + player.getEnemies(null, false).includes(current) && + event.getl?.(current)?.cards2?.length + ); + }); + }, + async content(event, trigger, player) { + const source = game.findPlayer(current => { + return ( + current != player && + player.getEnemies(null, false).includes(current) && + trigger.getl?.(current)?.cards2?.length + ); + }); + if (!source) return; + + player.addTempSkill("langgu_shuying_used", "roundStart"); + + const result = await player.judge(card => get.suit(card) == "spade" ? 2 : 0).forResult(); + if (result.bool && source.isIn() && source.countDiscardableCards(player, "h") > 0) { + player.removeSkill("langgu_shuying_used"); + player.line(source); + const card = source.getCards("h").randomGet(); + await source.discard(card); + } + }, + subSkill: { + used: { charlotte: true }, + }, + }, + yuanlv_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/青青子衿:1", + trigger: { source: "damageBegin4" }, + filter(event, player) { + return event.card && get.type2(event.card) == "trick" && event.player?.isIn() && player.getEnemies(null, false).includes(event.player); + }, + async cost(event, trigger, player) { + event.result = await player.chooseBool(get.prompt(event.skill, trigger.player)) + .set("ai", () => true) + .forResult(); + }, + async content(event, trigger, player) { + const target = trigger.player; + trigger.cancel(); + await player.draw(); + target.line(player); + if (getTianshuDifficulty() == "nightmare") { + await player.damage(1, target, "unreal"); + } + else { + await player.damage(1, target); + } + }, + }, + + //吕布 over + wushuang_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/青青子衿:2", + trigger: { source: "damageBegin1" }, + forced: true, + locked: true, + usable(skill, player) { + return getTianshuDifficulty() == "nightmare" ? 2 : 1; + }, + logTarget: "player", + filter(event, player) { + const target = event.player; + const evtx = event.getParent(2); + const card = event.card; + if (!card || !["sha", "juedou"].includes(card.name)) return false; + + if (card.name == "sha") { + return !target.hasHistory("useCard", evt => { + return evt.card.name == "shan" && evt.respondTo && evt.getParent(3) == evtx; + }); + } + + return !target.hasHistory("respond", evt => { + return evt.card.name == "sha" && evt.respondTo && evt.getParent(3) == evtx; + }); + }, + async content(event, trigger, player) { + trigger.num++; + }, + group: ["wushuang_shuying_1", "wushuang_shuying_2"], + subSkill: { + 1: { + audio: "wushuang_shuying", + sourceSkill: "wushuang_shuying", + trigger: { player: "useCardToPlayered" }, + forced: true, + logTarget: "target", + filter(event, player) { + return event.card.name == "sha" && !event.getParent().directHit.includes(event.target); + }, + async content(event, trigger, player) { + const id = trigger.target.playerid; + const map = trigger.getParent().customArgs; + if (!map[id]) map[id] = {}; + + if (typeof map[id].shanRequired == "number") map[id].shanRequired++; + else map[id].shanRequired = 2; + }, + ai: { + directHit_ai: true, + skillTagFilter(player, tag, arg) { + if (arg.card.name != "sha" || arg.target.countCards("h", "shan") > 1) return false; + }, + }, + }, + 2: { + audio: "wushuang_shuying", + sourceSkill: "wushuang_shuying", + trigger: { player: "useCardToPlayered", target: "useCardToTargeted" }, + forced: true, + logTarget(trigger, player) { + return player == trigger.player ? trigger.target : trigger.player; + }, + filter(event, player) { + return event.card.name == "juedou"; + }, + async content(event, trigger, player) { + const id = (player == trigger.player ? trigger.target : trigger.player).playerid; + const idt = trigger.target.playerid; + const map = trigger.getParent().customArgs; + + if (!map[idt]) map[idt] = {}; + if (!map[idt].shaReq) map[idt].shaReq = {}; + if (!map[idt].shaReq[id]) map[idt].shaReq[id] = 1; + + map[idt].shaReq[id]++; + }, + ai: { + directHit_ai: true, + skillTagFilter(player, tag, arg) { + if ( + arg.card.name != "juedou" || + Math.floor(arg.target.countCards("h", "sha") / 2) > player.countCards("h", "sha") + ) { + return false; + } + }, + }, + }, + }, + ai: { + threaten: 1.8, + directHit_ai: true, + }, + }, + shenij_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/青青子衿:2", + group: [ + "shenij_shuying_judge", + "shenij_shuying_draw", + "shenij_shuying_sha", + "shenij_shuying_target", + ], + subSkill: { + judge: { + audio: "shenij_shuying", + trigger: { player: "phaseJudgeBegin" }, + filter(event, player) { + return player.countCards("h") >= 2 && player.countCards("j") > 0; + }, + async cost(event, trigger, player) { + event.result = await player + .chooseToDiscard("h", 2, get.prompt2(event.skill)) + .set("ai", card => { + const player = get.player(); + if (!player.countCards("j")) return 0; + return 7 - get.value(card); + }) + .forResult(); + }, + async content(event, trigger, player) { + const cards = player.getCards("j"); + await player.discard(cards); + }, + ai: { + effect: { + target(card, player, target) { + if (get.type(card) == "delay") return [0, 2]; + }, + }, + }, + }, + draw: { + audio: "shenij_shuying", + trigger: { player: "phaseDrawBegin2" }, + forced: true, + filter(event, player) { + return !event.numFixed; + }, + async content(event, trigger, player) { + let num = 2; + if (getTianshuDifficulty() == "hard") num = 3; + if (getTianshuDifficulty() == "nightmare") num = 4; + + trigger.num += num; + }, + }, + sha: { + audio: "shenij_shuying", + forced: true, + mod: { + cardUsable(card, player, num) { + let shaNum = 2; + if (getTianshuDifficulty() == "normal") shaNum = 1; + + if (card.name == "sha") return num + shaNum; + }, + }, + }, + target: { + audio: "shenij_shuying", + trigger: { player: "useCard2" }, + filter(event, player) { + if (event.card.name != "sha") return false; + + const info = get.info(event.card); + if (info.allowMultiple == false || info.multitarget || !event.targets) return false; + + const num = getTianshuDifficulty() == "nightmare" ? 2 : 1; + return game.countPlayer(current => { + return !event.targets.includes(current) && + lib.filter.targetEnabled2(event.card, player, current) && + lib.filter.targetInRange(event.card, player, current); + }) > 0; + }, + async cost(event, trigger, player) { + const num = getTianshuDifficulty() == "nightmare" ? 2 : 1; + + event.result = await player + .chooseTarget( + get.prompt(event.skill), + `为此【杀】额外指定至多${get.cnNumber(num)}名目标`, + [1, num], + (card, player, target) => { + const trigger = get.event().getTrigger(); + return !trigger.targets.includes(target) && + lib.filter.targetEnabled2(trigger.card, player, target) && + lib.filter.targetInRange(trigger.card, player, target); + } + ) + .set("ai", target => { + const player = get.player(); + if (!player.getEnemies(null, false).includes(target)) return 0; + return get.effect(target, get.event().getTrigger().card, player, player); + }) + .forResult(); + }, + async content(event, trigger, player) { + for (const target of event.targets) { + player.line(target); + trigger.targets.add(target); + game.log(target, "成为了", trigger.card, "的额外目标"); + } + }, + ai: { + effect: { + player_use(card, player, target) { + if (card.name == "sha" && player.getEnemies(null, false).includes(target)) return [1, 0.5]; + }, + }, + }, + }, + }, + }, + zhanjia_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/青青子衿:2", + trigger: { player: "damageBegin4" }, + forced: true, + locked: true, + usable: 1, + filter(event, player) { + return event.num > 2; + }, + async content(event, trigger, player) { + trigger.num = 2; + await player.draw(getTianshuDifficulty() == "nightmare" ? 3 : 2); + }, + ai: { + filterDamage: true, + effect: { + target(card, player, target) { + if (get.tag(card, "damage")) { + if (player.hasSkillTag("jueqing", false, target)) return; + return [1, 0.8]; + } + }, + }, + }, + }, + + //董卓 over + jiuchi_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/青青子衿:1", + enable: "chooseToUse", + filterCard(card, player) { + return get.suit(card, player) == "spade"; + }, + position: "h", + viewAs: { name: "jiu" }, + viewAsFilter(player) { + return player.countCards("h", card => get.suit(card, player) == "spade") > 0; + }, + prompt: "将一张黑桃手牌当【酒】使用", + check(card) { + return 6 - get.value(card); + }, + ai: { + order: 3.5, + respondJiu: true, + skillTagFilter(player) { + return player.countCards("h", card => get.suit(card, player) == "spade") > 0; + }, + }, + }, + roulin_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/青青子衿:1", + trigger: { player: "useCardToPlayered", target: "useCardToTargeted" }, + forced: true, + locked: true, + logTarget(trigger, player) { + return player == trigger.player ? trigger.target : trigger.player; + }, + filter(event, player) { + if (event.card.name != "sha") return false; + if (player == event.player) return event.target.hasSex("female"); + return event.player.hasSex("female"); + }, + async content(event, trigger, player) { + const id = (player == trigger.player ? trigger.target : player).playerid; + const map = trigger.getParent().customArgs; + if (!map[id]) map[id] = {}; + + if (typeof map[id].shanRequired == "number") map[id].shanRequired++; + else map[id].shanRequired = 2; + }, + ai: { + halfneg: true, + directHit_ai: true, + skillTagFilter(player, tag, arg) { + if (tag === "directHit_ai") return; + if (arg.card.name != "sha" || !arg.target.hasSex("female") || arg.target.countCards("h", "shan") > 1) { + return false; + } + }, + }, + }, + baonue_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/青青子衿:1", + trigger: { player: "phaseBegin" }, + forced: true, + locked: true, + filter(event, player) { + return player.getDamagedHp() > 0; + }, + async content(event, trigger, player) { + let maxNum = 3; + if (getTianshuDifficulty() == "hard") maxNum = 4; + if (getTianshuDifficulty() == "nightmare") maxNum = 5; + + const num = Math.min(maxNum, player.getDamagedHp()); + await player.draw(num); + + const result = await player + .chooseTarget(`对至多${get.cnNumber(num)}名角色造成1点伤害`, [1, num], (card, player, target) => { + return target.isIn() && player.getEnemies(null, false).includes(target); + }) + .set("ai", target => { + const player = get.player(); + return get.damageEffect(target, player, player); + }) + .forResult(); + + if (result.bool) { + for (const target of result.targets) { + player.line(target); + await target.damage(1, player); + } + } + + await player.loseHp(); + }, + ai: { + threaten: 2, + }, + }, + qubu_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/青青子衿:1", + trigger: { global: "useCardToPlayered" }, + forced: true, + locked: true, + filter(event, player) { + return ( + event.card.name == "sha" && + event.player?.isIn() && + !player.getEnemies(null, false).includes(event.player) && + event.targets?.length > 0 + ); + }, + async content(event, trigger, player) { + const result = await player + .judge(card => get.color(card) == "black" ? 2 : 1) + .set("judge2", result => result.bool) + .forResult(); + + if (result.bool) { + const targets = trigger.targets.filter(target => target.isIn()); + for (const target of targets) { + player.line(target); + await target.damage(1, player); + } + } + }, + ai: { + threaten: 1.8, + }, + }, + + + //水神共工 over + shuishen_shuying: { + mode: ["boss"], + forced: true, + group: ["shuishen_shuying_recover", "shuishen_shuying_draw", "shuishen_shuying_damage"], + subSkill: { + recover: { + trigger: { player: "recoverBegin" }, + audio: false, + forced: true, + filter(event, player) { + return game.shuying_getDiscardNamesNum() >= 4; + }, + async content(event, trigger, player) { + trigger.num++; + }, + }, + draw: { + trigger: { player: "phaseDrawBegin2" }, + audio: false, + forced: true, + filter(event, player) { + return !event.numFixed && game.shuying_getDiscardNamesNum() >= 5; + }, + async content(event, trigger, player) { + trigger.num += 5; + }, + }, + damage: { + trigger: { source: "damageBegin1" }, + audio: false, + forced: true, + filter(event, player) { + return game.shuying_getDiscardNamesNum() >= 9; + }, + async content(event, trigger, player) { + trigger.num++; + }, + }, + }, + }, + tuanliu_shuying: { + mode: ["boss"], + trigger: { player: "phaseJieshuBegin" }, + audio: false, + forced: true, + filter(event, player) { + return game.shuying_getDiscardNum() > 4; + }, + async content(event, trigger, player) { + let hp = 2; + let card = 4; + let damage = 1; + + if (getTianshuDifficulty() == "nightmare") { + hp = 3; + card = 6; + } + + + const num = game.shuying_getDiscardNum(); + if (num > 4) await player.recover(hp); + if (num > 5) await player.draw(card); + if (num > 9) { + for (const target of player.getEnemies(null, false).filter(target => target.isIn())) { + player.line(target); + await target.damage(player, damage); + } + } + }, + }, + juehong_shuying: { + mode: ["boss"], + trigger: { player: "phaseZhunbeiBegin" }, + audio: false, + forced: true, + filter(event, player) { + return player.getEnemies(null, false).some(target => target.isIn()); + }, + async content(event, trigger, player) { + for (const target of player.getEnemies(null, false).filter(target => target.isIn())) { + player.line(target); + if (target.countCards("e")) await target.chooseToDiscard("e", true, target.countCards("e")); + else if (target.countCards("h")) await target.discard(target.getCards("h").randomGets(Math.min(2, target.countCards("h")))); + } + }, + }, + + //白起 + changsheng_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill:1", + forced: true, + mod: { + targetInRange(card, player, target) { + if (card.name == "sha") return true; + }, + }, + }, + shashen_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill:1", + enable: ["chooseToUse", "chooseToRespond"], + filterCard: true, + position: "h", + viewAs: { name: "sha" }, + viewAsFilter(player) { + return player.countCards("h") > 0; + }, + prompt: "将一张手牌当【杀】使用或打出", + check(card) { + return 6 - get.value(card); + }, + group: "shashen_shuying_draw", + subSkill: { + draw: { + trigger: { source: "damageEnd" }, + filter(event, player) { + return event.card?.name == "sha"; + }, + async content(event, trigger, player) { + await player.draw(3); + }, + }, + }, + ai: { + respondSha: true, + skillTagFilter(player) { + return player.countCards("h") > 0; + }, + order: 4, + }, + }, + wuan_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill:1", + forced: true, + mod: { + cardUsable(card, player, num) { + if (card.name == "sha") return num + 3; + }, + }, + trigger: { source: "damageBegin1" }, + filter(event, player) { + return event.card?.name == "sha"; + }, + async content(event, trigger, player) { + trigger.num++; + }, + }, + + //盘古 + shenqu_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill:1", + trigger: { player: "damageEnd" }, + forced: true, + filter(event, player) { + return player.countCards("h", card => get.color(card) == "red") > 0; + }, + async content(event, trigger, player) { + const cards = player.getCards("h", card => get.color(card) == "red"); + await player.lose(cards, ui.cardPile, "insert"); + await player.draw(cards.length); + }, + }, + lieben_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill:1", + trigger: { player: "useCardToPlayered" }, + forced: true, + filter(event, player) { + return event.card.name == "sha" && ui.cardPile.lastChild; + }, + async content(event, trigger, player) { + const judge = player.judge(card => get.color(card) == "red" ? 2 : -1); + judge.directresult = ui.cardPile.lastChild; + judge.judge2 = result => result.bool; + const result = await judge.forResult(); + if (result.bool) { + trigger.getParent().addCount = false; + trigger.getParent().baseDamage++; + } + }, + }, + yinjiang_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill:1", + trigger: { player: "drawEnd" }, + forced: true, + filter(event, player) { + return player.isPhaseUsing() && !player.hasSkill("yinjiang_shuying_disabled") && ui.cardPile.lastChild; + }, + async content(event, trigger, player) { + const card = ui.cardPile.lastChild; + await player.gain(card, "gain2"); + if (get.color(card) == "red") { + let count = 0; + for (const target of player.getEnemies(null, false).filter(target => target.isIn())) { + player.line(target); + await target.damage(player); + count++; + } + player.storage.yinjiang_shuying_count = (player.storage.yinjiang_shuying_count || 0) + count; + if (player.storage.yinjiang_shuying_count >= 2) player.addTempSkill("yinjiang_shuying_disabled", { player: "phaseUseEnd" }); + } + }, + group: "yinjiang_shuying_clear", + subSkill: { + clear: { + trigger: { player: "phaseUseEnd" }, + silent: true, + async content(event, trigger, player) { + delete player.storage.yinjiang_shuying_count; + }, + }, + disabled: { charlotte: true }, + }, + }, + zhuri_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill:1", + forced: true, + group: ["zhuri_shuying_draw", "zhuri_shuying_bottom"], + subSkill: { + draw: { + trigger: { player: "useCard" }, + forced: true, + filter(event, player) { + return get.color(event.card) == "red"; + }, + async content(event, trigger, player) { + await player.draw(); + }, + }, + bottom: { + trigger: { global: "useCardAfter" }, + forced: true, + filter(event, player) { + return event.targets?.includes(player) && (event.card.name == "sha" || get.type(event.card) == "trick") && event.cards?.filterInD("d").length; + }, + async content(event, trigger, player) { + const cards = trigger.cards.filterInD("d"); + game.log(cards, "被置于牌堆底"); + for (const card of cards) ui.cardPile.appendChild(card); + }, + }, + }, + }, + + //少昊 over + baiyi_shuying: { + mode: ["boss"], + trigger: { global: "phaseBegin" }, + forced: true, + logTarget: "player", + filter(event, player) { + return ( + game.roundNumber < 7 && + event.player?.isIn() && + player.getEnemies(null, false).includes(event.player) + ); + }, + async content(event, trigger, player) { + let num = 1; + const target = trigger.player; + player.line(target); + + const difficulty = getTianshuDifficulty(); + + if (difficulty == "hard") { + num = 2; + } else if (difficulty == "nightmare") { + num = 3; + } + + if (game.roundNumber < 3 && target.countGainableCards(player, "he") > 0) { + const cards = target.getCards("he").randomGets(Math.min(num, target.countCards("he"))); + await player.gain(cards, target, "giveAuto"); + } + + if (game.roundNumber < 5 && target.isIn()) { + await target.damage(num, "thunder", player); + } + + if (game.roundNumber < 7 && target.isIn()) { + const cards = target + .getCards("he") + .filter(card => lib.filter.cardDiscardable(card, target, "baiyi_shuying")) + .randomGets(num); + + if (cards.length) { + await target.discard(cards); + } + } + }, + ai: { + threaten: 3, + }, + }, + shenen_shuying: { + mode: ["boss"], + forced: true, + global: "shenen_shuying_global", + subSkill: { + global: { + mod: { + targetInRange(card, player, target) { + const owner = game.findPlayer(current => current.hasSkill("shenen_shuying")); + if (owner && !owner.getEnemies(null, false).includes(player)) return true; + }, + }, + }, + }, + ai: { + threaten: 1.5, + }, + }, + + //玄女 over + xuanlie_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/天书乱斗:1", + trigger: { player: "phaseJieshuBegin" }, + forced: true, + locked: true, + filter(event, player) { + return game.hasPlayer(current => { + return ( + current.isIn() && + player.getEnemies(null, false).includes(current) && + player.getHistory("gain", evt => evt.getl?.(current)?.cards2?.length).length + ); + }); + }, + async content(event, trigger, player) { + const targets = game.filterPlayer(current => { + return ( + current.isIn() && + player.getEnemies(null, false).includes(current) && + player.getHistory("gain", evt => evt.getl?.(current)?.cards2?.length).length + ); + }); + + let num = 2; + if (getTianshuDifficulty() == "normal") num = 1; + + for (const target of targets) { + player.line(target); + await target.damage(num, player); + } + }, + ai: { + threaten: 2, + }, + }, + jiutian_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/天书乱斗:1", + trigger: { player: "phaseZhunbeiBegin" }, + forced: true, + locked: true, + filter(event, player) { + return player.getEnemies(null, false).some(target => target.isIn() && target.countGainableCards(player, "h") > 0); + }, + async content(event, trigger, player) { + let num = 2; + const difficulty = getTianshuDifficulty(); + if (difficulty == "normal") num = 1; + + const targets = player.getEnemies(null, false).filter(target => target.isIn() && target.countGainableCards(player, "h") > 0); + const gained = []; + + for (const target of targets) { + const cards = target.getCards("h").randomGets(Math.min(num, target.countCards("h"))); + if (cards.length) { + player.line(target); + await player.gain(cards, target, "giveAuto"); + gained.addArray(cards); + } + } + + if (!gained.length) return; + + const colors = gained.map(card => get.color(card, player)).unique(); + const suits = gained.map(card => get.suit(card, player)).unique(); + + if (colors.length >= 2) { + for (const target of targets) { + if (!target.isIn()) continue; + player.line(target); + + target.damage(1, player); + } + } + + if (difficulty == "nightmare" && suits.length >= 4) { + for (const target of targets) { + if (!target.isIn()) continue; + player.line(target); + await target.loseHp(); + } + } + }, + ai: { + threaten: 3, + }, + }, + dishi_shuying: { + mode: ["boss"], + audio: "ext:术樱包/pve/audio/skill/天书乱斗:1", + trigger: { player: "useCard2" }, + filter(event, player) { + if (!event.targets?.length) return false; + if (event.card.name != "sha" && get.type(event.card) != "trick") return false; + + const info = get.info(event.card); + if (event.targets.length == 1) { + if (info.allowMultiple == false || info.multitarget) return false; + return game.hasPlayer(current => { + return ( + !event.targets.includes(current) && + lib.filter.targetEnabled2(event.card, player, current) && + lib.filter.targetInRange(event.card, player, current) + ); + }); + } + + return event.targets.length > 1; + }, + async cost(event, trigger, player) { + if (trigger.targets.length == 1) { + event.result = await player + .chooseTarget(get.prompt(event.skill), `为${get.translation(trigger.card)}增加一个目标`, (card, player, target) => { + const trigger = get.event().getTrigger(); + return ( + !trigger.targets.includes(target) && + lib.filter.targetEnabled2(trigger.card, player, target) && + lib.filter.targetInRange(trigger.card, player, target) + ); + }) + .set("ai", target => { + const player = get.player(); + const trigger = get.event().getTrigger(); + const effect = get.effect(target, trigger.card, player, player); + if (player.getEnemies(null, false).includes(target)) return effect; + return Math.min(0, effect); + }) + .forResult(); + } + else { + event.result = await player + .chooseTarget(get.prompt(event.skill), `令${get.translation(trigger.card)}减少一个目标`, (card, player, target) => { + return get.event().getTrigger().targets.includes(target); + }) + .set("ai", target => { + const player = get.player(); + const trigger = get.event().getTrigger(); + return -get.effect(target, trigger.card, player, player); + }) + .forResult(); + } + }, + async content(event, trigger, player) { + const target = event.targets[0]; + player.line(target); + + if (trigger.targets.includes(target)) { + trigger.targets.remove(target); + game.log(target, "不再是", trigger.card, "的目标"); + } + else { + trigger.targets.add(target); + game.log(target, "成为了", trigger.card, "的额外目标"); + } + }, + ai: { + threaten: 1.6, + }, + }, +}; + +export default skills; diff --git a/pve/boss/sort.js b/pve/boss/sort.js new file mode 100644 index 0000000..740f802 --- /dev/null +++ b/pve/boss/sort.js @@ -0,0 +1,9 @@ +const characterSort = { + shuYing_pve_tianshu: ["chenlong_shuying"], +}; + +const characterSortTranslate = { + shuYing_pve_tianshu: "天书乱斗", +}; + +export { characterSort, characterSortTranslate }; diff --git a/pve/boss/translate.js b/pve/boss/translate.js new file mode 100644 index 0000000..8e9cb62 --- /dev/null +++ b/pve/boss/translate.js @@ -0,0 +1,144 @@ +const translates = { + zishu_boss_shuying: "子鼠", + zishu_shuying: "子鼠", + zishu_shuying_info: "出牌阶段限一次,你可以获得手牌数大于你的其他角色一张手牌,你可以重复此流程,直到你的手牌数为全场最多。", + + chenlong_boss_shuying: "辰龙", + chenlong_shuying: "辰龙", + chenlong_shuying_info: "限定技,出牌阶段,你可以失去任意点体力(至多为5),然后对一名其他角色造成等量的伤害。若你以此法进入濒死状态,则你将体力值回复至1,然后减少1点体力上限。", + + wuma_boss_shuying: "午马", + wuma_shuying: "午马", + wuma_shuying_info: "锁定技,你不能被翻面;你的阶段不能被跳过;当你成为其他角色使用锦囊牌的目标后,摸一张牌。", + + weiyang_boss_shuying: "未羊", + weiyang_shuying: "未羊", + weiyang_shuying_info: "出牌阶段限一次,你可以弃置任意张不同类型的牌,然后令至多等量角色回复1点体力。", + + shenhou_boss_shuying: "申猴", + shenhou_shuying: "申猴", + shenhou_shuying_info: "当你成为【杀】的目标时,你可以进行判定,若结果为红色,则此【杀】对你无效。", + + xvgou_boss_shuying: "戌狗", + xvgou_shuying: "戌狗", + xvgou_shuying_info: "锁定技,红色【杀】对你无效;你使用红色【杀】无距离限制且造成伤害+1.", + + heibaiwuchang_boss_shuying: "黑白无常", + mizui_shuying: "迷醉", + mizui_shuying_info: "锁定技,你的伤害牌造成伤害后,你随机弃置受伤角色的1张牌。", + qiangzheng_shuying: "强征", + qiangzheng_shuying_info: "锁定技,敌方角色的结束阶段,若其手牌数小于4,你获得其所有手牌。", + xixing_shuying: "吸星", + xixing_shuying_info: "锁定技,准备阶段,随机对敌方体力最多的一名角色造成2点雷电伤害,然后你回复等量体力", + + niutoumamian_boss_shuying: "牛头马面", + shiyv_shuying: "施狱", + shiyv_shuying_info: "锁定技,摸牌阶段,你放弃摸牌改为随机获得牌堆中每种花色的牌各一张。", + manji_shuying: "蛮击", + manji_shuying_info: "你使用【杀】指定单一目标后,你可以弃置该角色2张手牌。若其中有【杀】,你本次【杀】造成的伤害+1。", + xiaoshou_shuying: "枭首", + xiaoshou_shuying_info: "锁定技,准备阶段,你对随机一名敌方角色造成2点伤害", + + yvsai_boss_shuying: "鱼鳃", + anchao_shuying: "暗潮", + anchao_shuying_info: "锁定技,己方角色的回合结束时,若此回合该角色没有造成过伤害,则获得一个“暗潮”标记。己方回合开始时,若该角色有“暗潮”标记,则本回合摸牌阶段多摸X张牌且对敌方角色造成的伤害+X(X为“暗潮”标记数)", + guixi_shuying: "鬼吸", + guixi_shuying_info: "锁定技,当你受到伤害后,你进行一次判定,若结果为红色,你回复1点体力;若结果不为红色,你摸1张牌。", + + huangfeng_boss_shuying: "黄蜂", + mingchong_shuying: "冥虫", + mingchong_shuying_info: "锁定技,你死亡时,杀死你的角色随机弃置一半的牌。", + duzhen_shuying: "毒针", + duzhen_shuying_info: "锁定技,你使用牌指定单一敌方角色为目标后,该角色随机弃置1张牌(优先装备区的牌)。", + + riyeyoushen_boss_shuying: "日夜游神", + huiyun_shuying: "晦运", + huiyun_shuying_info: "出牌阶段限一次,你可以展示一名敌方角色的手牌,并弃置其中至多两张牌。然后你可以弃置一张与该角色弃置牌牌名相同的牌,对其造成2点伤害。", + yezhong_shuying: "夜冢", + yezhong_shuying_info: "锁定技,结束阶段,你进行一次判定并获得判定牌,若次牌为黑色,每名敌方角色随机弃1张手牌。", + zhoucha_shuying: "昼刹", + zhoucha_shuying_info: "锁定技,准备阶段,你进行一次判定并获得判定牌,若此牌为红色,你本回合使用【杀】的次数+1。", + duane_shuying: "断恶", + duane_shuying_info: "锁定技,敌方角色的弃牌阶段结束时,若其此阶段弃置了黑色牌,则该角色失去2点体力。", + + + caocao_boss_shuying: "曹操", + jianxiong_shuying: "奸雄", + jianxiong_shuying_info: "当你受到伤害后,你可以获得对你造成伤害的牌并摸一张牌。", + lingba_shuying: "凌霸", + lingba_shuying_info: "锁定技,你的回合开始时,若你手牌数为全场最多,则对一名随机敌人造成1点伤害。若你手牌数大于等于你体力值的两倍,则改为对所有敌人造成伤害。", + yishen_shuying: "疑神", + yishen_shuying_info: "当你回复体力时,可以改为获得一名随机敌方角色一张随机装备。", + + simayi_boss_shuying: "司马懿", + fankui_shuying: "反馈", + fankui_shuying_info: "当你受到1点伤害后,你可以获得伤害来源的一张牌。", + guicai_shuying: "鬼才", + guicai_shuying_info: "当一名角色的判定牌生效前,你可以打出一张牌代替之。", + langgu_shuying: "狼顾", + langgu_shuying_info: " 锁定技,每回合限一次,当你获得其他角色的牌时,进行一次判定:若结果为黑桃,随机弃置其1张手牌,且视为此技能本回合未发动过。", + yuanlv_shuying: "远虑", + yuanlv_shuying_info: "当你使用锦囊牌对敌方角色造成伤害时,你可以防止该伤害,改为摸一张牌且该敌方角色对你造成1点伤害。", + + lvbu_boss_shuying: "吕布", + wushuang_shuying: "无双", + wushuang_shuying_info: "锁定技。①你使用的【杀】需两张【闪】才能抵消;与你进行【决斗】的角色每次需要打出两张【杀】。②每回合限1次,当你使用【杀】或【决斗】造成伤害时,若受伤角色没有使用或打出过【杀】或【闪】响应此牌,则此伤害+1。", + shenij_shuying: "神戟", + shenij_shuying_info: "判定阶段,你可以弃置两张手牌,然后弃置你判定区里的牌;摸牌阶段,你多摸两张牌;出牌阶段,你可以多使用一张【杀】,你的【杀】可以多指定一名角色为目标。", + zhanjia_shuying: "战甲", + zhanjia_shuying_info: "锁定技,每回合限一次,当你受到大于2点的伤害时,将此伤害减至2点,然后摸两张牌。", + + dongzhuo_boss_shuying: "董卓", + jiuchi_shuying: "酒池", + jiuchi_shuying_info: "你可以将一张黑桃手牌当【酒】使用。", + roulin_shuying: "肉林", + roulin_shuying_info: "锁定技,你对女性角色使用的【杀】和女性角色对你使用的【杀】均需使用两张【闪】才能抵消。", + baonue_shuying: "暴虐", + baonue_shuying_info: "锁定技,回合开始时,你摸X张牌并对至多X名角色造成1点伤害,然后你失去1点体力。X为你已损失体力且最大为3。", + qubu_shuying: "驭布", + qubu_shuying_info: "锁定技,当友方角色使用【杀】指定目标时,进行一次判定:若结果为黑桃,你对此【杀】的所有目标造成1点伤害。", + + + shuishengonggong_boss_shuying: "水神共工", + shuishen_shuying: "水神", + shuishen_shuying_info: "锁定技,弃牌堆中,不同牌名的牌数达到4时,你回复体力时回复量+1;达到5时,摸牌阶段额外摸5张牌;达到9时,你造成伤害时伤害+1。", + tuanliu_shuying: "湍流", + tuanliu_shuying_info: "锁定技,结束阶段,若本回合进入弃牌堆的卡牌数量:大于4,你回复2点体力;大于5,你摸4张牌;大于9,你对所有敌方角色造成1点伤害。", + juehong_shuying: "决洪", + juehong_shuying_info: "锁定技,准备阶段,你令所有敌方角色自己弃置自己的装备区内的所有牌,若其装备区内没有牌,则改为随机弃置2张手牌", + + baiqi_boss_shuying: "白起", + changsheng_shuying: "常胜", + changsheng_shuying_info: "锁定技,你使用【杀】无距离限制。", + shashen_shuying: "杀神", + shashen_shuying_info: "你可以将手牌中的任意牌当【杀】使用或打出。你使用的【杀】造成伤害后,摸三张牌。", + wuan_shuying: "武安", + wuan_shuying_info: "锁定技,你可使用的【杀】的次数+3,【杀】造成的伤害+1。", + + kuafu_boss_shuying: "盘古", + shenqu_shuying: "神躯", + shenqu_shuying_info: "锁定技,当你受到伤害后,将所有红色手牌置于牌堆底,并摸等量的牌。", + lieben_shuying: "烈奔", + lieben_shuying_info: "锁定技,当你使用【杀】指定目标后,使用牌堆底的牌进行一次判定:若判定结果为红色,则此杀不计入出牌阶段使用次数且伤害+1。", + yinjiang_shuying: "饮江", + yinjiang_shuying_info: "锁定技,当你在出牌阶段摸牌后,获得牌堆底的1张牌。若该牌为红色,则对所有敌方角色造成1点伤害。当你在同一个阶段内以此法造成过两次活更多的伤害后,该技能失效直到回合结束。", + zhuri_shuying: "逐日", + zhuri_shuying_info: "锁定技,你使用红色牌时摸1张牌。当你成为【杀】或普通锦囊牌的目标后,结算完毕后将该牌放置在牌堆底。", + + shaohao_boss_shuying: "少昊", + baiyi_shuying: "白仪", + baiyi_shuying_info: "锁定技,每名敌方角色的回合开始时,若当前轮数小于3,你随机获得其1张牌;若当前轮数小于5,对其造成1点雷电伤害;若当前轮数小于7,其随机弃置1张牌。", + shenen_shuying: "神恩", + shenen_shuying_info: "锁定技,所有己方角色使用牌锁定技,所有己方角色使用牌没有距离限制。", + + + xuannv_boss_shuying: "玄女", + xuanlie_shuying: "玄烈", + xuanlie_shuying_info: "锁定技,回合结束时,对所有本回合你获得过其牌的敌方角色依次造成1点伤害。", + jiutian_shuying: "九天", + jiutian_shuying_info: "锁定技,准备阶段,你获得所有敌方角色各1张手牌。若你以此法获得的牌包含2种颜色,则对所有你以此法获得其牌的敌方角色造成1点伤害。", + dishi_shuying: "帝师", + dishi_shuying_info: "当你使用【杀】或普通锦囊牌指定目标时,如果目标数为1,可以增加一个目标;如果目标数大于1,你可以减少一个目标。", +}; + +export default translates; diff --git a/pve/boss/voices.js b/pve/boss/voices.js new file mode 100644 index 0000000..9b345cd --- /dev/null +++ b/pve/boss/voices.js @@ -0,0 +1,4 @@ +const voices = { +}; + +export default voices; diff --git a/pve/images/tianshu.jpg b/pve/images/tianshu.jpg new file mode 100644 index 0000000..233b774 Binary files /dev/null and b/pve/images/tianshu.jpg differ diff --git a/pve/images/十二生肖/chenlong_boss_shuying.png b/pve/images/十二生肖/chenlong_boss_shuying.png new file mode 100644 index 0000000..34d3a00 Binary files /dev/null and b/pve/images/十二生肖/chenlong_boss_shuying.png differ diff --git a/pve/images/十二生肖/chouniu_boss_shuying.png b/pve/images/十二生肖/chouniu_boss_shuying.png new file mode 100644 index 0000000..3161535 Binary files /dev/null and b/pve/images/十二生肖/chouniu_boss_shuying.png differ diff --git a/pve/images/十二生肖/haizhu_boss_shuying.png b/pve/images/十二生肖/haizhu_boss_shuying.png new file mode 100644 index 0000000..3c3a191 Binary files /dev/null and b/pve/images/十二生肖/haizhu_boss_shuying.png differ diff --git a/pve/images/十二生肖/maotu_boss_shuying.png b/pve/images/十二生肖/maotu_boss_shuying.png new file mode 100644 index 0000000..454bd87 Binary files /dev/null and b/pve/images/十二生肖/maotu_boss_shuying.png differ diff --git a/pve/images/十二生肖/shenhou_boss_shuying.png b/pve/images/十二生肖/shenhou_boss_shuying.png new file mode 100644 index 0000000..dfb1171 Binary files /dev/null and b/pve/images/十二生肖/shenhou_boss_shuying.png differ diff --git a/pve/images/十二生肖/sishe_boss_shuying.png b/pve/images/十二生肖/sishe_boss_shuying.png new file mode 100644 index 0000000..8d7a1a3 Binary files /dev/null and b/pve/images/十二生肖/sishe_boss_shuying.png differ diff --git a/pve/images/十二生肖/weiyang_boss_shuying.png b/pve/images/十二生肖/weiyang_boss_shuying.png new file mode 100644 index 0000000..b8dcd54 Binary files /dev/null and b/pve/images/十二生肖/weiyang_boss_shuying.png differ diff --git a/pve/images/十二生肖/wuma_boss_shuying.png b/pve/images/十二生肖/wuma_boss_shuying.png new file mode 100644 index 0000000..5483a47 Binary files /dev/null and b/pve/images/十二生肖/wuma_boss_shuying.png differ diff --git a/pve/images/十二生肖/xvgou_boss_shuying.png b/pve/images/十二生肖/xvgou_boss_shuying.png new file mode 100644 index 0000000..9e84659 Binary files /dev/null and b/pve/images/十二生肖/xvgou_boss_shuying.png differ diff --git a/pve/images/十二生肖/yinhu_boss_shuying.png b/pve/images/十二生肖/yinhu_boss_shuying.png new file mode 100644 index 0000000..6f205ea Binary files /dev/null and b/pve/images/十二生肖/yinhu_boss_shuying.png differ diff --git a/pve/images/十二生肖/youji_boss_shuying.png b/pve/images/十二生肖/youji_boss_shuying.png new file mode 100644 index 0000000..5d80a28 Binary files /dev/null and b/pve/images/十二生肖/youji_boss_shuying.png differ diff --git a/pve/images/十二生肖/zishu_boss_shuying.png b/pve/images/十二生肖/zishu_boss_shuying.png new file mode 100644 index 0000000..9fb920f Binary files /dev/null and b/pve/images/十二生肖/zishu_boss_shuying.png differ diff --git a/pve/images/天书乱斗/baiqi_boss_shuying.png b/pve/images/天书乱斗/baiqi_boss_shuying.png new file mode 100644 index 0000000..3201079 Binary files /dev/null and b/pve/images/天书乱斗/baiqi_boss_shuying.png differ diff --git a/pve/images/天书乱斗/hanba_boss_shuying.png b/pve/images/天书乱斗/hanba_boss_shuying.png new file mode 100644 index 0000000..6d9a59d Binary files /dev/null and b/pve/images/天书乱斗/hanba_boss_shuying.png differ diff --git a/pve/images/天书乱斗/huoshenzhurong_boss_shuying.png b/pve/images/天书乱斗/huoshenzhurong_boss_shuying.png new file mode 100644 index 0000000..d962f63 Binary files /dev/null and b/pve/images/天书乱斗/huoshenzhurong_boss_shuying.png differ diff --git a/pve/images/天书乱斗/kuafu_boss_shuying.png b/pve/images/天书乱斗/kuafu_boss_shuying.png new file mode 100644 index 0000000..f0efcc0 Binary files /dev/null and b/pve/images/天书乱斗/kuafu_boss_shuying.png differ diff --git a/pve/images/天书乱斗/shaohao_boss_shuying.png b/pve/images/天书乱斗/shaohao_boss_shuying.png new file mode 100644 index 0000000..5729059 Binary files /dev/null and b/pve/images/天书乱斗/shaohao_boss_shuying.png differ diff --git a/pve/images/天书乱斗/shuishengonggong_boss_shuying.png b/pve/images/天书乱斗/shuishengonggong_boss_shuying.png new file mode 100644 index 0000000..8cbd69b Binary files /dev/null and b/pve/images/天书乱斗/shuishengonggong_boss_shuying.png differ diff --git a/pve/images/天书乱斗/xuannv_boss_shuying.png b/pve/images/天书乱斗/xuannv_boss_shuying.png new file mode 100644 index 0000000..946b610 Binary files /dev/null and b/pve/images/天书乱斗/xuannv_boss_shuying.png differ diff --git a/pve/images/捉鬼驱邪/baiwuchang_boss_shuying.png b/pve/images/捉鬼驱邪/baiwuchang_boss_shuying.png new file mode 100644 index 0000000..1b72569 Binary files /dev/null and b/pve/images/捉鬼驱邪/baiwuchang_boss_shuying.png differ diff --git a/pve/images/捉鬼驱邪/baowei_boss_shuying.png b/pve/images/捉鬼驱邪/baowei_boss_shuying.png new file mode 100644 index 0000000..ab813a0 Binary files /dev/null and b/pve/images/捉鬼驱邪/baowei_boss_shuying.png differ diff --git a/pve/images/捉鬼驱邪/guiwang_boss_shuying.png b/pve/images/捉鬼驱邪/guiwang_boss_shuying.png new file mode 100644 index 0000000..85a485f Binary files /dev/null and b/pve/images/捉鬼驱邪/guiwang_boss_shuying.png differ diff --git a/pve/images/捉鬼驱邪/heibaiwuchang_boss_shuying.png b/pve/images/捉鬼驱邪/heibaiwuchang_boss_shuying.png new file mode 100644 index 0000000..0d9f072 Binary files /dev/null and b/pve/images/捉鬼驱邪/heibaiwuchang_boss_shuying.png differ diff --git a/pve/images/捉鬼驱邪/heiwuchang_boss_shuying.png b/pve/images/捉鬼驱邪/heiwuchang_boss_shuying.png new file mode 100644 index 0000000..5a74ac5 Binary files /dev/null and b/pve/images/捉鬼驱邪/heiwuchang_boss_shuying.png differ diff --git a/pve/images/捉鬼驱邪/huangfeng_boss_shuying.png b/pve/images/捉鬼驱邪/huangfeng_boss_shuying.png new file mode 100644 index 0000000..5035267 Binary files /dev/null and b/pve/images/捉鬼驱邪/huangfeng_boss_shuying.png differ diff --git a/pve/images/捉鬼驱邪/mamian_boss_shuying.png b/pve/images/捉鬼驱邪/mamian_boss_shuying.png new file mode 100644 index 0000000..dcaf7b8 Binary files /dev/null and b/pve/images/捉鬼驱邪/mamian_boss_shuying.png differ diff --git a/pve/images/捉鬼驱邪/mengpo_boss_shuying.png b/pve/images/捉鬼驱邪/mengpo_boss_shuying.png new file mode 100644 index 0000000..66753d0 Binary files /dev/null and b/pve/images/捉鬼驱邪/mengpo_boss_shuying.png differ diff --git a/pve/images/捉鬼驱邪/niaoshou_boss_shuying.png b/pve/images/捉鬼驱邪/niaoshou_boss_shuying.png new file mode 100644 index 0000000..34fdb85 Binary files /dev/null and b/pve/images/捉鬼驱邪/niaoshou_boss_shuying.png differ diff --git a/pve/images/捉鬼驱邪/niutou_boss_shuying.png b/pve/images/捉鬼驱邪/niutou_boss_shuying.png new file mode 100644 index 0000000..7d2a5f1 Binary files /dev/null and b/pve/images/捉鬼驱邪/niutou_boss_shuying.png differ diff --git a/pve/images/捉鬼驱邪/niutoumamian_boss_shuying.png b/pve/images/捉鬼驱邪/niutoumamian_boss_shuying.png new file mode 100644 index 0000000..1989fe7 Binary files /dev/null and b/pve/images/捉鬼驱邪/niutoumamian_boss_shuying.png differ diff --git a/pve/images/捉鬼驱邪/riyeyoushen_boss_shuying.png b/pve/images/捉鬼驱邪/riyeyoushen_boss_shuying.png new file mode 100644 index 0000000..6714f43 Binary files /dev/null and b/pve/images/捉鬼驱邪/riyeyoushen_boss_shuying.png differ diff --git a/pve/images/捉鬼驱邪/yanluowang.png b/pve/images/捉鬼驱邪/yanluowang.png new file mode 100644 index 0000000..7248c72 Binary files /dev/null and b/pve/images/捉鬼驱邪/yanluowang.png differ diff --git a/pve/images/捉鬼驱邪/yvsai_boss_shuying.png b/pve/images/捉鬼驱邪/yvsai_boss_shuying.png new file mode 100644 index 0000000..3f9971f Binary files /dev/null and b/pve/images/捉鬼驱邪/yvsai_boss_shuying.png differ diff --git a/pve/images/虚拟偶像/xiaojiu_xvni_shuying.png b/pve/images/虚拟偶像/xiaojiu_xvni_shuying.png new file mode 100644 index 0000000..35606b4 Binary files /dev/null and b/pve/images/虚拟偶像/xiaojiu_xvni_shuying.png differ diff --git a/pve/images/虚拟偶像/xiaole_xvni_shuying.png b/pve/images/虚拟偶像/xiaole_xvni_shuying.png new file mode 100644 index 0000000..9033635 Binary files /dev/null and b/pve/images/虚拟偶像/xiaole_xvni_shuying.png differ diff --git a/pve/images/虚拟偶像/xiaosha_xvni_shuying.png b/pve/images/虚拟偶像/xiaosha_xvni_shuying.png new file mode 100644 index 0000000..bc803ee Binary files /dev/null and b/pve/images/虚拟偶像/xiaosha_xvni_shuying.png differ diff --git a/pve/images/虚拟偶像/xiaoshan_xvni_shuying.png b/pve/images/虚拟偶像/xiaoshan_xvni_shuying.png new file mode 100644 index 0000000..cf92b65 Binary files /dev/null and b/pve/images/虚拟偶像/xiaoshan_xvni_shuying.png differ diff --git a/pve/images/虚拟偶像/xiaotao_xvni_shuying.png b/pve/images/虚拟偶像/xiaotao_xvni_shuying.png new file mode 100644 index 0000000..daf7f76 Binary files /dev/null and b/pve/images/虚拟偶像/xiaotao_xvni_shuying.png differ diff --git a/pve/images/青青子衿/caocao_boss_shuying.jpg b/pve/images/青青子衿/caocao_boss_shuying.jpg new file mode 100644 index 0000000..3596fb6 Binary files /dev/null and b/pve/images/青青子衿/caocao_boss_shuying.jpg differ diff --git a/pve/images/青青子衿/dongzhuo_boss_shuying.jpg b/pve/images/青青子衿/dongzhuo_boss_shuying.jpg new file mode 100644 index 0000000..86f94ab Binary files /dev/null and b/pve/images/青青子衿/dongzhuo_boss_shuying.jpg differ diff --git a/pve/images/青青子衿/lvbu_boss_shuying.jpg b/pve/images/青青子衿/lvbu_boss_shuying.jpg new file mode 100644 index 0000000..64b5572 Binary files /dev/null and b/pve/images/青青子衿/lvbu_boss_shuying.jpg differ diff --git a/pve/images/青青子衿/simayi_boss_shuying.jpg b/pve/images/青青子衿/simayi_boss_shuying.jpg new file mode 100644 index 0000000..23da0e8 Binary files /dev/null and b/pve/images/青青子衿/simayi_boss_shuying.jpg differ diff --git a/pve/images/青青子衿/yuanshu_boss_shuying.jpg b/pve/images/青青子衿/yuanshu_boss_shuying.jpg new file mode 100644 index 0000000..e2fc340 Binary files /dev/null and b/pve/images/青青子衿/yuanshu_boss_shuying.jpg differ diff --git a/pve/images/青青子衿/zhangjiao_boss_shuying.jpg b/pve/images/青青子衿/zhangjiao_boss_shuying.jpg new file mode 100644 index 0000000..babc962 Binary files /dev/null and b/pve/images/青青子衿/zhangjiao_boss_shuying.jpg differ diff --git a/pve/images/龙舟/caoe_boss_shuying.png b/pve/images/龙舟/caoe_boss_shuying.png new file mode 100644 index 0000000..54c9b50 Binary files /dev/null and b/pve/images/龙舟/caoe_boss_shuying.png differ diff --git a/pve/images/龙舟/hebo_boss_shuying.png b/pve/images/龙舟/hebo_boss_shuying.png new file mode 100644 index 0000000..516e6d7 Binary files /dev/null and b/pve/images/龙舟/hebo_boss_shuying.png differ diff --git a/pve/images/龙舟/taoshen_boss_shuying.png b/pve/images/龙舟/taoshen_boss_shuying.png new file mode 100644 index 0000000..5644fd0 Binary files /dev/null and b/pve/images/龙舟/taoshen_boss_shuying.png differ diff --git a/pve/images/龙舟/yuershen_boss_shuying.png b/pve/images/龙舟/yuershen_boss_shuying.png new file mode 100644 index 0000000..390bf70 Binary files /dev/null and b/pve/images/龙舟/yuershen_boss_shuying.png differ diff --git a/tianshu/config.js b/tianshu/config.js new file mode 100644 index 0000000..e473275 --- /dev/null +++ b/tianshu/config.js @@ -0,0 +1,293 @@ +const bossList = { + stage1: ["riyeyoushen_boss_shuying", "wuma_boss_shuying"], + //stage1: ["chenlong_boss_shuying", "weiyang_boss_shuying", "shenhou_boss_shuying", "xvgou_boss_shuying"], + stage2: ["heibaiwuchang_boss_shuying", "yvsai_boss_shuying", "huangfeng_boss_shuying", "riyeyoushen_boss_shuying"], + stage3: ["caocao_boss_shuying", "simayi_boss_shuying", "lvbu_boss_shuying"], + stage4: ["shuishengonggong_boss_shuying", "shaohao_boss_shuying", "xuannv_boss_shuying"], +}; + +const virtualIdolList = ["vtb_xiaotao", "vtb_xiaosha", "vtb_xiaoshan", "vtb_xiaole", "vtb_xiaojiu"]; + +const difficultyOrder = ["normal", "hard", "nightmare"]; + +const bossDifficultyTemplates = { + shiershengxiao: + { + normal: { + startCards: 4, + }, + hard: { + startCards: 6, + hpBonus: 1, + maxHpBonus: 1, + }, + nightmare: { + startCards: 8, + hpBonus: 4, + maxHpBonus: 4, + }, + }, + + zhuoguiquxie: { + normal: { + startCards: 4, + hpBonus: 2, + maxHpBonus: 2, + }, + hard: { + startCards: 6, + hpBonus: 4, + maxHpBonus: 4, + }, + nightmare: { + startCards: 8, + hpBonus: 8, + maxHpBonus: 8, + }, + }, + + qingqingzijin: { + normal: { + startCards: 6, + hpBonus: 6, + maxHpBonus: 6, + }, + hard: { + startCards: 8, + hpBonus: 16, + maxHpBonus: 16, + }, + nightmare: { + startCards: 10, + hpBonus: 26, + maxHpBonus: 26, + }, + }, + + tianshuluandou: + { + normal: { + startCards: 6, + hpBonus: 6, + maxHpBonus: 6, + }, + hard: { + startCards: 8, + hpBonus: 11, + maxHpBonus: 11, + }, + nightmare: { + startCards: 12, + hpBonus: 21, + maxHpBonus: 21, + }, + }, +}; + +const bossDifficulty = { + default: { + normal: { + startCards: 4, + hp: null, + maxHp: null, + hpBonus: 0, + maxHpBonus: 0, + skills: [], + }, + hard: { + startCards: 5, + hp: null, + maxHp: null, + hpBonus: 2, + maxHpBonus: 2, + skills: [], + }, + nightmare: { + startCards: 6, + hp: null, + maxHp: null, + hpBonus: 4, + maxHpBonus: 4, + skills: [], + }, + }, + bosses: { + chenlong_boss_shuying: { ...bossDifficultyTemplates.shiershengxiao, }, + weiyang_boss_shuying: { ...bossDifficultyTemplates.shiershengxiao, }, + shenhou_boss_shuying: { ...bossDifficultyTemplates.shiershengxiao, }, + xvgou_boss_shuying: { ...bossDifficultyTemplates.shiershengxiao, }, + + heibaiwuchang_boss_shuying: { + ...bossDifficultyTemplates.zhuoguiquxie, + hard: { + ...bossDifficultyTemplates.zhuoguiquxie.hard, + skill: ["xixing_shuying"], + }, + nightmare: { + ...bossDifficultyTemplates.zhuoguiquxie.nightmare, + skill: ["xixing_shuying", "taiping_shuying"], + }, + }, + yvsai_boss_shuying: { + ...bossDifficultyTemplates.zhuoguiquxie, + }, + huangfeng_boss_shuying: + { + ...bossDifficultyTemplates.zhuoguiquxie, + }, + riyeyoushen_boss_shuying: { + ...bossDifficultyTemplates.zhuoguiquxie, + nightmare: { + ...bossDifficultyTemplates.zhuoguiquxie.nightmare, + skill: ["duane_shuying"], + }, + }, + + caocao_boss_shuying: + { + ...bossDifficultyTemplates.qingqingzijin, + hard: { + ...bossDifficultyTemplates.qingqingzijin.hard, + skills: ["yishen_shuying"], + }, + nightmare: { + ...bossDifficultyTemplates.qingqingzijin.nightmare, + skills: ["yishen_shuying"], + }, + }, + simayi_boss_shuying: + { + ...bossDifficultyTemplates.qingqingzijin, + hard: { + ...bossDifficultyTemplates.qingqingzijin.hard, + skills: ["yuanlv_shuying"], + }, + nightmare: { + ...bossDifficultyTemplates.qingqingzijin.nightmare, + skills: ["yuanlv_shuying"], + }, + }, + lvbu_boss_shuying: { + ...bossDifficultyTemplates.qingqingzijin, + hard: { + ...bossDifficultyTemplates.qingqingzijin.hard, + skills: ["zhanjia_shuying"], + }, + nightmare: { + ...bossDifficultyTemplates.qingqingzijin.nightmare, + skills: ["zhanjia_shuying"], + }, + }, + + shuishengonggong_boss_shuying: { + ...bossDifficultyTemplates.tianshuluandou, + hard: { + ...bossDifficultyTemplates.tianshuluandou.hard, + skill: ["shuishen_shuying"], + }, + nightmare: { + ...bossDifficultyTemplates.tianshuluandou.nightmare, + skill: ["shuishen_shuying"], + }, + }, + shaohao_boss_shuying: { + ...bossDifficultyTemplates.tianshuluandou, + }, + xuannv_boss_shuying: { + ...bossDifficultyTemplates.tianshuluandou, + }, + + + baiqi_boss_shuying: {}, + pangu_boss_shuying: {}, + }, +}; + +const tianshuConfig = { + bossList, + stages: [ + { + id: "stage1", + name: "第一关", + bosses: bossList.stage1, + }, + { + id: "stage2", + name: "第二关", + bosses: bossList.stage2, + }, + { + id: "stage3", + name: "第三关", + bosses: bossList.stage3, + }, + { + id: "stage4", + name: "第四关", + bosses: bossList.stage4, + }, + ], + difficulties: { + normal: { + name: "普通", + }, + hard: { + name: "困难", + }, + nightmare: { + name: "阴间", + }, + }, + difficultyOrder, + bossDifficulty, + virtualIdolList, + virtualIdols: { + random: virtualIdolList, + Xiaojiu: "vtb_xiaojiu", + Xiaosha: "vtb_xiaosha", + Xiaoshan: "vtb_xiaoshan", + Xiaole: "vtb_xiaole", + Xiaotao: "vtb_xiaotao", + }, + reward: { + killRecover: 1, + killDrawIfFullHp: 2, + stageRecover: 1, + stageDraw: 2, + skillChoiceCount: 5, + }, + settings: { + virtualIdolConfigKey: "extension_术樱包_tianShu_Xvni", + virtualIdolRandomPoolConfigKey: "extension_术樱包_tianShu_XvniRandomPool", + virtualIdolReviveConfigKey: "extension_术樱包_tianShu_dead", + addBossConfigKey: "extension_术樱包_tianShu_addBoss", + revivePlayersConfigKey: "extension_术樱包_tianShu_revivePlayers", + difficultyStatusKey: "shuYing_tianshuDifficulty", + }, + skillPool: { + banned: ["huoxin", "jueqing", "qinqing", "beige", "huashen", "drlt_zhiti", "olzhiti", "xinfu_pdgyingshi", "rebeige"], + }, + keywordBuffs: [ + { + keyword: "杀", + levels: [ + { count: 2, damageBonus: 1 }, + { count: 3, damageBonus: 2 }, + ], + }, + { + keyword: "闪", + levels: [ + { count: 2, drawAfterShan: 1 }, + ], + }, + { + keyword: "桃", + levels: [ + { count: 2, recoverBonus: 1 }, + ], + }, + ], +}; + +export default tianshuConfig; diff --git a/tianshu/extension.js b/tianshu/extension.js new file mode 100644 index 0000000..2b07447 --- /dev/null +++ b/tianshu/extension.js @@ -0,0 +1,829 @@ +import tianshuConfig from "./config.js"; +import { lib, game, ui, get, ai, _status } from "../../../noname.js"; + +export default function initTianshu(lib, game, ui, get, ai, _status, shuYing) { + if (!lib.config.extension_术樱包_tianShuOff) return; + + let tianshu = new Object(); + + const state = { + difficulty: "normal", + stageIndex: 0, + currentBosses: [], + selectedSkills: {}, + selectedSkillSet: {}, + playerSideIds: {}, + roundStartPlayer: null, + roundStartSeat: 0, + originalIsRoundFilter: null, + roundFilterInstalled: false, + originalGameCheck: null, + checkHookInstalled: false, + corpseChooseCleanup: null, + corpseChooseEvent: null, + initialBoss: null, + virtualIdol: null, + running: false, + pendingSpawn: false, + clearingStage: false, + }; + + // 生成玩家的稳定标识,用于跨死亡/复活状态追踪玩家方成员。 + const getPlayerKey = player => player ? (player.playerid || player.dataset?.position || player.name || "") : ""; + // 获取菜单中选择的虚拟偶像配置。 + const getVirtualIdolConfig = () => lib.config[tianshuConfig.settings.virtualIdolConfigKey] || "random"; + // 判断是否启用虚拟偶像。 + const isVirtualIdolEnabled = () => getVirtualIdolConfig() != "off"; + // 读取虚拟偶像随机池;仅在菜单选择“随机”时用于筛选可随机角色。 + const getVirtualIdolRandomPool = () => { + const list = lib.config[tianshuConfig.settings.virtualIdolRandomPoolConfigKey]; + if (!Array.isArray(list) || !list.length) return tianshuConfig.virtualIdolList; + const pool = list.map(key => tianshuConfig.virtualIdols?.[key]).filter(id => typeof id == "string" && tianshuConfig.virtualIdolList.includes(id)); + return pool.length ? pool : tianshuConfig.virtualIdolList; + }; + // 根据菜单选择获取本局虚拟偶像角色 id。 + const getSelectedVirtualIdol = () => { + const key = getVirtualIdolConfig(); + if (key == "random") return getVirtualIdolRandomPool().randomGet(); + const value = tianshuConfig.virtualIdols?.[key]; + if (Array.isArray(value)) return value.randomGet ? value.randomGet() : value[Math.floor(Math.random() * value.length)]; + return value || null; + }; + // 判断是否启用第三个 Boss。 + const shouldAddExtraBoss = () => Boolean(lib.config[tianshuConfig.settings.addBossConfigKey]); + // 根据 Boss 数量返回实战确认后的 Boss 座位 + const getBossSeats = () => shouldAddExtraBoss() ? [5, 6, 7] : [5, 7]; + // 根据 Boss 数量返回虚拟偶像座位;未启用时返回 null。 + const getVirtualIdolSeat = () => !isVirtualIdolEnabled() ? null : (shouldAddExtraBoss() ? 4 : 6); + // 从 config 中获取所有虚拟偶像角色 id。 + const getVirtualIdolIds = () => tianshuConfig.virtualIdolList || Object.values(tianshuConfig.virtualIdols).flat(); + // 判断指定角色是否为虚拟偶像。 + const isVirtualIdol = player => Boolean(player && (getVirtualIdolIds().includes(player.name1) || getVirtualIdolIds().includes(player.name))); + // 判断指定角色是否属于当前关卡 Boss 列表。 + const isCurrentStageBoss = player => Boolean(player && (state.currentBosses.includes(player.name1) || state.currentBosses.includes(player.name))); + // 判断指定角色是否属于开局捕获的玩家方。 + const isCapturedPlayerSide = player => Boolean(getPlayerKey(player) && state.playerSideIds[getPlayerKey(player)]); + // 获取玩家方角色列表,可按是否存活、是否包含虚拟偶像过滤。 + const getPlayerSidePlayers = ({ aliveOnly = true, includeVirtual = true } = {}) => { + const list = aliveOnly ? game.players : game.players.concat(game.dead); + return list.filter(current => { + if (!current || !isCapturedPlayerSide(current)) return false; + if (!includeVirtual && isVirtualIdol(current)) return false; + if (aliveOnly && !game.players.includes(current)) return false; + return true; + }); + }; + // 判断玩家方是否还有非虚拟偶像角色存活,供死亡事件中避开通用列表过滤。 + const hasAliveRealPlayerSide = () => game.players.some(current => current && isCapturedPlayerSide(current) && !isVirtualIdol(current)); + // 将虚拟偶像登记为玩家方 AI 队友。 + const markVirtualIdolAsPlayerSide = player => { + if (!player) return; + player.side = false; + player.identity = "cai"; + player.setIdentity?.("cai"); + game.addVideo?.("setIdentity", player, "cai"); + const key = getPlayerKey(player); + if (key) state.playerSideIds[key] = true; + }; + // 获取玩家方 1 号位,用于首轮起手和回合游标修正。 + const getPlayerSeatOne = () => { + const players = getPlayerSidePlayers({ aliveOnly: true, includeVirtual: false }); + players.sort((a, b) => (Number(a.dataset.position) || 0) - (Number(b.dataset.position) || 0)); + return players[0] || null; + }; + // 获取开局保存的玩家方 1 号位;该角色即使阵亡,也作为天书乱斗的理论轮起点。 + const getTianshuRoundStartPlayer = () => { + if (state.roundStartPlayer) return state.roundStartPlayer; + return getPlayerSeatOne(); + }; + // 当理论 1 号位阵亡时,选其后第一个仍存活的玩家方角色作为 roundStart 触发兜底。 + const getAliveRoundStartFallback = () => { + const players = getPlayerSidePlayers({ aliveOnly: true, includeVirtual: false }); + if (!players.length) return null; + const baseSeat = Number(state.roundStartSeat || getTianshuRoundStartPlayer()?.dataset?.position || 0); + players.sort((a, b) => { + const seatA = Number(a.dataset.position) || 0; + const seatB = Number(b.dataset.position) || 0; + const offsetA = seatA >= baseSeat ? seatA - baseSeat : seatA + 100 - baseSeat; + const offsetB = seatB >= baseSeat ? seatB - baseSeat : seatB + 100 - baseSeat; + return offsetA - offsetB; + }); + return players[0] || null; + }; + // 判断某个即将进入回合的角色是否会被天书规则视为新一轮起点。 + const isTianshuRoundStartTrigger = player => { + const roundStart = getTianshuRoundStartPlayer(); + if (!roundStart || !player) return false; + if (player == roundStart) return true; + if (!game.players.includes(roundStart)) return player == getAliveRoundStartFallback(); + return false; + }; + // 安装天书专用轮起点判断。1号位活着时按1号位;1号位死亡时按其后的首个存活玩家方角色推进轮数。 + const installTianshuRoundFilter = () => { + if (!state.roundFilterInstalled) { + state.originalIsRoundFilter = _status.isRoundFilter || null; + state.roundFilterInstalled = true; + } + _status.isRoundFilter = (event, player) => { + if (isTianshuRoundStartTrigger(player)) return true; + return state.originalIsRoundFilter ? state.originalIsRoundFilter(event, player) : false; + }; + }; + // 获取天书奖励选择的主控视角;game.me 在 Boss 模式换控后不一定是玩家方 1 号位。 + const getMainControlPlayer = () => game.me?._trueMe || getPlayerSeatOne() || game.me; + // 从当前事件链向上查找指定名称的父事件。 + const getParentEvent = name => { + let evt = _status.event; + while (evt && evt.name != name) evt = evt.parent; + return evt || null; + }; + // 将 boss 模式 phaseLoop 游标设置到目标角色前一位,使下一轮轮到目标角色。 + const setPhaseLoopBefore = target => { + if (!target) return; + const phaseLoop = getParentEvent("phaseLoop"); + if (phaseLoop) phaseLoop.player = target.previous || target.previousSeat || target; + _status.roundStart = getTianshuRoundStartPlayer() || target; + installTianshuRoundFilter(); + if (game.bossinfo) game.bossinfo.loopType = 1; + }; + // 结束当前 phase 事件,把后续行动权交给指定角色;保留旧版重置轮数与阶段的做法。 + const finishCurrentPhaseTo = target => { + if (!target) return; + const phase = _status.event?.getParent ? _status.event.getParent("phase") : getParentEvent("phase"); + if (phase) { + if (typeof game.resetSkills == "function") game.resetSkills(); + let current = _status.event; + let guard = 20; + while (current && current != phase && guard-- > 0) { + current.finish(); + current.untrigger(true); + current = current.getParent ? current.getParent() : current.parent; + } + phase.finish(); + phase.untrigger(true); + } + const phaseLoop = _status.event?.getParent ? _status.event.getParent("phaseLoop") : getParentEvent("phaseLoop"); + if (phaseLoop) phaseLoop.player = target.previous || target.previousSeat || target; + if (_status.event) { + _status.event.player = target; + _status.event.step = 0; + } + const roundStart = getTianshuRoundStartPlayer() || target; + _status.roundStart = roundStart; + installTianshuRoundFilter(); + game.phaseNumber = 1; + game.roundNumber = isTianshuRoundStartTrigger(target) ? 0 : 1; + _status.paused = false; + if (phase) { + game.broadcastAll?.(() => { + _status.paused = false; + }); + } + }; + // 开局捕获玩家方成员,避免后续 game.boss 变化导致阵营误判。 + const capturePlayerSide = () => { + state.playerSideIds = {}; + const initialBoss = state.initialBoss || game.boss; + game.players.forEach(current => { + if (!current || current == initialBoss || current == game.boss) return; + if (initialBoss && current.isFriendOf(initialBoss)) return; + const key = getPlayerKey(current); + if (key) state.playerSideIds[key] = true; + }); + if (state.virtualIdol) markVirtualIdolAsPlayerSide(state.virtualIdol); + const players = getPlayerSidePlayers({ aliveOnly: true, includeVirtual: false }); + players.sort((a, b) => (Number(a.dataset.position) || 0) - (Number(b.dataset.position) || 0)); + state.roundStartPlayer = players[0] || null; + state.roundStartSeat = Number(state.roundStartPlayer?.dataset?.position) || 0; + if (state.roundStartPlayer) _status.roundStart = state.roundStartPlayer; + installTianshuRoundFilter(); + }; + // 开局按配置召唤虚拟偶像,并固定到预留座位。 + const spawnVirtualIdol = () => { + if (!isVirtualIdolEnabled() || state.virtualIdol) return null; + const name = getSelectedVirtualIdol(); + const seat = getVirtualIdolSeat(); + if (!name || seat == null) return null; + const idol = game.addFellow(seat, name, "zoominanim"); + idol.dataset.position = seat; + state.virtualIdol = idol; + markVirtualIdolAsPlayerSide(idol); + game.arrangePlayers(); + return idol; + }; + // 按设置在过关时复活虚拟偶像;虚拟偶像不参与玩家复活配置。 + const reviveVirtualIdolIfNeeded = async () => { + const idol = state.virtualIdol; + if (!idol || !lib.config[tianshuConfig.settings.virtualIdolReviveConfigKey] || !idol.isDead()) return; + if (idol.maxHp <= 0) return; + await idol.revive(Math.max(1, idol.maxHp)); + markVirtualIdolAsPlayerSide(idol); + }; + // 开局弹出难度选择,并把难度写入运行状态与全局状态。 + const chooseDifficulty = async (player = game.me) => { + const keys = Object.keys(tianshuConfig.difficulties); + const controls = keys.map(key => tianshuConfig.difficulties[key].name); + const result = await player.chooseControl(controls).set("prompt", "请选择天书乱斗难度").set("ai", () => 0).forResult(); + state.difficulty = keys[Math.max(0, controls.indexOf(result.control))] || "normal"; + _status[tianshuConfig.settings.difficultyStatusKey] = state.difficulty; + }; + // 在指定座位创建关卡 Boss;初始主 Boss 已静默死亡,所以关卡 Boss 全部使用忠臣身份。 + const addStageBoss = (position, name) => { + const boss = game.addFellow(position, name, "zoominanim"); + boss.dataset.position = position; + boss.side = true; + boss.identity = "zhong"; + boss.setIdentity(boss.identity); + boss.storage.shuYing_tianshu_stageBoss = true; + boss.storage.shuYing_tianshu_stageIndex = state.stageIndex; + game.addVideo("setIdentity", boss, boss.identity); + if (game.playerMap) game.playerMap[boss.dataset.position] = boss; + game.arrangePlayers(); + return boss; + }; + const getConfigNumber = value => { + if (value === null || value === undefined || value === "") return null; + const num = Number(value); + return Number.isFinite(num) ? num : null; + }; + const getBossConfigName = player => player?.name1 || player?.name || ""; + // 读取当前 Boss 在当前难度下的数值配置;单 Boss 配置覆盖默认难度配置。 + const getBossDifficultyConfig = player => { + const defaultConfig = tianshuConfig.bossDifficulty?.default?.[state.difficulty] || {}; + const bossConfig = tianshuConfig.bossDifficulty?.bosses?.[getBossConfigName(player)]?.[state.difficulty] || {}; + return Object.assign({}, defaultConfig, bossConfig); + }; + // 补充技能只读取当前难度配置,不跨难度继承低难度技能。 + const getBossDifficultySkills = player => { + const defaultSkills = tianshuConfig.bossDifficulty?.default?.[state.difficulty]?.skills || []; + const bossSkills = tianshuConfig.bossDifficulty?.bosses?.[getBossConfigName(player)]?.[state.difficulty]?.skills || []; + const skills = []; + for (const skill of defaultSkills.concat(bossSkills)) { + if (skill && !skills.includes(skill)) skills.push(skill); + } + return skills; + }; + // Boss 难度数值属于出场面板修正 + const applyBossDifficultyConfig = player => { + const config = getBossDifficultyConfig(player); + const maxHp = getConfigNumber(config.maxHp); + const maxHpBonus = getConfigNumber(config.maxHpBonus) || 0; + const hp = getConfigNumber(config.hp); + const hpBonus = getConfigNumber(config.hpBonus) || 0; + player.maxHp = Math.max(1, maxHp ?? player.maxHp + maxHpBonus); + player.hp = Math.min(Math.max(0, hp ?? player.hp + hpBonus), player.maxHp); + for (const skill of getBossDifficultySkills(player)) { + if (!player.hasSkill(skill)) player.addSkill(skill); + } + player.update(); + return config; + }; + // 让开局占位主 Boss 静默死亡,避免作为关卡 Boss 参与后续流程。 + const killInitialBossSilently = async () => { + const boss = state.initialBoss; + if (!boss || boss.isDead() || boss.storage.shuYing_tianshu_initialBossKilled) return; + boss.storage.shuYing_tianshu_initialBossKilled = true; + boss.storage.shuYing_tianshu_ignoreDie = true; + if (_status.roundStart == boss) _status.roundStart = boss.next || boss.getNext() || game.players[0]; + game.broadcastAll(player => { + player.classList.add("dead"); + player.removeLink(); + player.classList.remove("turnedover"); + player.classList.remove("out"); + player.node.count.innerHTML = "0"; + player.node.hp.hide(); + player.node.equips.hide(); + player.node.count.hide(); + if (player.previous && player.next) { + player.previous.next = player.next; + player.next.previous = player.previous; + } + game.players.remove(player); + game.dead.add(player); + _status.dying.remove(player); + player.hp = 0; + player.update(); + }, boss); + boss.hide(); + game.addVideo("hidePlayer", boss); + }; + // 隐藏已经阵亡的当前关卡 BOSS 尸体;在召唤下一关前执行,避免尸体继续占用视觉布局。 + const hideCurrentStageBossCorpses = () => { + game.dead.filter(player => player?.storage?.shuYing_tianshu_stageBoss && player.storage.shuYing_tianshu_stageIndex == state.stageIndex).forEach(player => { + player.storage.shuYing_tianshu_hiddenBossCorpse = true; + player.hide(); + game.addVideo("hidePlayer", player); + }); + }; + // 获取当前事件可选的隐藏 Boss 尸体;仅当本次选择只面向阵亡角色时返回列表。 + const getHiddenBossCorpseTargets = event => { + if (!event?.filterTarget) return []; + const card = get.card(); + const skillInfo = event.skill ? get.info(event.skill) : null; + const cardInfo = card ? get.info(card) : null; + if (!event.deadTarget && !skillInfo?.deadTarget && !cardInfo?.deadTarget) return []; + const canSelect = target => { + try { + return Boolean(event.filterTarget(card, event.player, target)); + } catch (e) { + return false; + } + }; + if (game.players.some(canSelect)) return []; + return game.dead.filter(player => player?.storage?.shuYing_tianshu_hiddenBossCorpse && canSelect(player)); + }; + // 在纯阵亡目标选择时提供独立列表,避免隐藏尸体因同座位或 UI 扩展而互相遮挡。 + const addHiddenBossCorpseChooseButton = event => { + const main = getMainControlPlayer(); + const canOperate = Boolean(event?.player && (event.isMine?.() || !_status.connectMode && main && event.player.isUnderControl?.(true, main))); + const corpses = canOperate && !event.shuYing_tianshu_corpseButton ? getHiddenBossCorpseTargets(event) : []; + if (!corpses.length) return; + let dialog = null; + let control = null; + let closed = false; + const closeDialog = () => { + if (dialog) { + dialog.close(); + dialog = null; + } + }; + const cleanup = () => { + if (closed) return; + closed = true; + closeDialog(); + if (control) { + control.close(); + control = null; + } + delete event.shuYing_tianshu_corpseButton; + if (state.corpseChooseEvent == event) { + state.corpseChooseEvent = null; + state.corpseChooseCleanup = null; + } + }; + const refreshRows = () => { + if (!dialog) return; + dialog.content.querySelectorAll(".shuYing-tianshu-corpse-row").forEach(row => { + row.style.outline = ui.selected.targets.includes(row.link) ? "2px solid #f6d365" : ""; + }); + }; + const chooseTarget = target => { + const max = get.select(event.selectTarget)[1]; + if (ui.selected.targets.includes(target)) ui.selected.targets.remove(target); + else { + if (max == 1) ui.selected.targets.length = 0; + ui.selected.targets.add(target); + } + refreshRows(); + if (typeof event.custom?.add?.target == "function") event.custom.add.target(); + game.check(); + if (max == 1 && ui.selected.targets.includes(target)) { + closeDialog(); + ui.click.ok(); + } + }; + const openDialog = () => { + closeDialog(); + dialog = ui.create.dialog("hidden"); + dialog.classList.add("fullwidth"); + dialog.style.maxWidth = "560px"; + dialog.style.border = "1px solid rgba(246,211,101,0.75)"; + dialog.style.boxShadow = "0 0 18px rgba(0,0,0,0.65)"; + dialog.add('
选择隐藏Boss尸体
'); + dialog.add('
点击列表项后直接指定对应阵亡Boss
'); + corpses.forEach(target => { + const stage = Number(target.storage.shuYing_tianshu_stageIndex || 0) + 1; + const row = ui.create.div(".shuYing-tianshu-corpse-row", dialog.content); + row.link = target; + row.style.cssText = "display:flex;align-items:center;justify-content:space-between;gap:12px;box-sizing:border-box;margin:7px auto;padding:10px 14px;width:86%;max-width:500px;border:1px solid rgba(246,211,101,0.55);background:rgba(20,16,14,0.82);color:#f6d365;text-align:left;line-height:22px;cursor:pointer;border-radius:6px;box-shadow:inset 0 0 10px rgba(246,211,101,0.08);"; + row.innerHTML = `${get.translation(target)}第${stage}关 / 已阵亡`; + row.listen(() => chooseTarget(target)); + }); + dialog.open(); + refreshRows(); + }; + const createControl = () => { + if (closed || event.finished || event.shuYing_tianshu_corpseButton) return; + control = ui.create.control("隐藏Boss", openDialog); + event.shuYing_tianshu_corpseButton = control; + }; + setTimeout(createControl, 0); + event.custom ??= { add: {}, replace: {} }; + event.custom.add ??= {}; + const oldAddTarget = event.custom.add.target; + event.custom.add.target = function () { + createControl(); + if (typeof oldAddTarget == "function") oldAddTarget.apply(this, arguments); + }; + const oldAddConfirm = event.custom.add.confirm; + event.custom.add.confirm = function (bool) { + if (bool === false || bool === true) cleanup(); + if (typeof oldAddConfirm == "function") oldAddConfirm.apply(this, arguments); + }; + const oldOnResult = event.onresult; + event.onresult = function () { + cleanup(); + if (typeof oldOnResult == "function") oldOnResult.apply(this, arguments); + }; + state.corpseChooseEvent = event; + state.corpseChooseCleanup = cleanup; + }; + // 当选择事件被 restore、切换或通过点击屏幕取消时,清理上一次隐藏 Boss 按钮。 + const cleanupStaleCorpseChooseButton = event => { + if (!state.corpseChooseCleanup || !state.corpseChooseEvent) return; + if (state.corpseChooseEvent != event || !getHiddenBossCorpseTargets(event).length) state.corpseChooseCleanup(); + }; + // 包装 game.check,从统一目标检查入口补充隐藏 Boss 尸体选择按钮,兼容 chooseToUse/chooseTarget 等多种事件。 + const installHiddenBossCorpseCheckHook = () => { + if (state.checkHookInstalled) return; + state.originalGameCheck = game.check; + state.checkHookInstalled = true; + game.check = function (event = _status.event) { + const result = arguments.length ? state.originalGameCheck.apply(this, arguments) : state.originalGameCheck.call(this, event); + cleanupStaleCorpseChooseButton(event); + if (state.running) addHiddenBossCorpseChooseButton(event); + return result; + }; + }; + // 召唤指定关卡的 Boss,并完成初始 Boss 静默退场和难度强化。 + const spawnStage = async (stageIndex = state.stageIndex) => { + state.stageIndex = stageIndex; + state.clearingStage = false; + const pool = tianshuConfig.stages[stageIndex]?.bosses?.slice() || []; + const bosses = []; + const bossCount = Math.min(shouldAddExtraBoss() ? 3 : 2, pool.length); + while (bosses.length < bossCount) { + const boss = pool.randomRemove(); + if (boss) bosses.push(boss); + } + state.currentBosses = bosses.slice(); + const seats = getBossSeats(); + if (stageIndex == 0) await killInitialBossSilently(); + bosses.forEach((boss, index) => { + addStageBoss(seats[index] || 8, boss); + }); + game.arrangePlayers(); + for (const player of game.players.filter(current => isCurrentStageBoss(current))) { + const config = applyBossDifficultyConfig(player); + const startCards = getConfigNumber(config.startCards); + if (startCards === null) continue; + const needCards = startCards - player.countCards("h"); + if (needCards > 0) { + const cards = get.cards(needCards); + player.directgain(cards); + if (!Array.isArray(player._start_cards)) player._start_cards = []; + player._start_cards.addArray(cards); + } + } + }; + // 读取玩家当前武将牌上自带的技能,避免过关奖励重复刷出本体技能。 + const getCharacterCardSkills = player => { + const skills = []; + for (const name of [player?.name, player?.name1, player?.name2]) { + if (!name || !lib.character[name]) continue; + const character = lib.character[name]; + const characterSkills = Array.isArray(character) ? character[3] : character.skills; + if (Array.isArray(characterSkills)) skills.addArray(characterSkills); + } + return skills; + }; + // 从全武将技能中构建可供指定玩家过关选择的技能池。 + const getAllSkillPool = player => { + const skills = []; + const banned = tianshuConfig.skillPool.banned || []; + const ownCharacterSkills = getCharacterCardSkills(player); + for (const name in lib.character) { + const character = lib.character[name]; + if (!character || lib.filter.characterDisabled(name) || name.indexOf("boss_") == 0) continue; + const characterSkills = Array.isArray(character) ? character[3] : character.skills; + if (!Array.isArray(characterSkills)) continue; + const list = characterSkills.slice(); + for (const skill of characterSkills) { + const info = get.info(skill); + if (!info) continue; + if (typeof info.derivation == "string") list.add(info.derivation); + else if (Array.isArray(info.derivation)) list.addArray(info.derivation); + } + for (const skill of list) { + if (skills.includes(skill) || banned.includes(skill) || state.selectedSkillSet[skill] || ownCharacterSkills.includes(skill)) continue; + const info = get.info(skill); + if (!info || info.charlotte || info.equipSkill || info.cardSkill || info.ruleSkill || info.temp || info.sub || info.silent) continue; + if (info.unique || info.juexingji || info.limited || info.zhuSkill || info.hiddenSkill || info.dutySkill || info.boss || info.superCharlotte) continue; + if (skill.includes("_boss") || skill.startsWith("boss_") || skill.startsWith("_") || skill.includes("_sub")) continue; + skills.push(skill); + } + } + return skills; + }; + // 单人控制规则下,可由主视角玩家代替同阵营 AI 选择过关奖励技能。 + const getRewardSkillChooser = player => { + const main = getMainControlPlayer(); + const underControl = Boolean(main && typeof player.isUnderControl == "function" && player.isUnderControl(true, main)); + const tianshuSingleControl = Boolean(main && !_status.connectMode && lib.config.mode == "boss" && isCapturedPlayerSide(player) && !isVirtualIdol(player)); + let chooser = player; + if (lib.config.extension_术樱包_giveAiSkill && main && player != main && (underControl || tianshuSingleControl)) chooser = main; + return chooser; + }; + // 让指定玩家从 UI 中选择一个奖励技能。 + const chooseRewardSkill = async (player, choices) => { + const chooser = getRewardSkillChooser(player); + const controls = choices.map(skill => get.translation(skill)); + const result = await chooser.chooseControl(controls) + .set("prompt", `${get.translation(player)} 请选择获得一个技能`) + .set("choiceList", choices.map(skill => `
【${get.translation(skill)}】
`)) + .set("displayIndex", false) + .set("ai", () => controls[0]) + .forResult(); + const index = controls.indexOf(result.control); + return choices[index] || null; + }; + // 过关后让每个存活玩家方角色依次选择奖励技能。 + const chooseSkillsForStageClear = async () => { + for (const player of getPlayerSidePlayers({ aliveOnly: true, includeVirtual: false })) { + const pool = getAllSkillPool(player); + const choices = []; + while (choices.length < tianshuConfig.reward.skillChoiceCount && pool.length) { + const choice = pool.randomRemove(); + if (choice) choices.push(choice); + } + if (!choices.length) continue; + const skill = await chooseRewardSkill(player, choices); + if (!skill || !choices.includes(skill)) continue; + await player.addSkill(skill); + const id = getPlayerKey(player); + state.selectedSkills[id] ??= []; + state.selectedSkills[id].push(skill); + state.selectedSkillSet[skill] = true; + player.storage.shuYing_tianshu_selectedSkills = state.selectedSkills[id].slice(); + game.log(player, "获得了技能", `#g【${get.translation(skill)}】`); + } + }; + // 获取 1 号位上家的最终 Boss,用于让 Boss 回合结束后立刻回到 1 号位玩家。 + const getFinalBossBeforeSeatOne = () => { + const firstPlayer = getPlayerSeatOne(); + if (!firstPlayer) return game.boss || null; + const target = firstPlayer.previous || game.boss || null; + return target; + }; + // 完成关卡清算:发奖励、选技能、进入下一关或结束游戏。 + const clearStage = async () => { + if (state.clearingStage) return; + state.clearingStage = true; + if (state.stageIndex >= tianshuConfig.stages.length - 1) { + game.over(game.me !== game.boss); + return; + } + await reviveVirtualIdolIfNeeded(); + if (lib.config[tianshuConfig.settings.revivePlayersConfigKey]) { + const deadPlayers = getPlayerSidePlayers({ aliveOnly: false, includeVirtual: false }).filter(player => player.isDead()); + const reviveConfig = { + hard: { hp: 4, cards: 4 }, + nightmare: { hp: 1, cards: 2 }, + }[state.difficulty] || null; + for (const player of deadPlayers) { + if (player.maxHp <= 0) continue; + const hp = reviveConfig ? Math.min(player.maxHp, reviveConfig.hp) : Math.max(1, player.maxHp); + await player.revive(Math.max(1, hp)); + if (reviveConfig?.cards) { + const needCards = reviveConfig.cards - player.countCards("h"); + if (needCards > 0) await player.draw(needCards); + } + } + } + for (const player of getPlayerSidePlayers({ aliveOnly: true, includeVirtual: true })) { + if (tianshuConfig.reward.stageRecover) await player.recover(tianshuConfig.reward.stageRecover); + if (tianshuConfig.reward.stageDraw) await player.draw(tianshuConfig.reward.stageDraw); + } + await chooseSkillsForStageClear(); + await game.washCard(); + hideCurrentStageBossCorpses(); + const currentTurnPlayer = getParentEvent("phase")?.player || _status.currentPhase; + state.stageIndex++; + await spawnStage(state.stageIndex); + let nextTurnTarget = null; + if (currentTurnPlayer && isCapturedPlayerSide(currentTurnPlayer)) { + if (isVirtualIdol(currentTurnPlayer)) { + nextTurnTarget = getFinalBossBeforeSeatOne(); + } + else { + const next = currentTurnPlayer.next; + nextTurnTarget = next && !next.isDead() && isCapturedPlayerSide(next) && !isVirtualIdol(next) ? next : getFinalBossBeforeSeatOne(); + } + } + if (nextTurnTarget) finishCurrentPhaseTo(nextTurnTarget); + }; + // 处理 Boss 死亡事件:击杀奖励、隐藏尸体、判断是否清关。 + const handleBossDie = async event => { + const deadBoss = event.player; + if (!isCurrentStageBoss(deadBoss)) return; + if (deadBoss.storage.shuYing_tianshu_bossDieHandled) return; + deadBoss.storage.shuYing_tianshu_bossDieHandled = true; + const stageCleared = !state.currentBosses.some(name => game.players.some(current => current.name1 == name || current.name == name)); + const finalCleared = stageCleared && state.stageIndex >= tianshuConfig.stages.length - 1; + if (!finalCleared && event.source && event.source.isIn()) { + if (event.source.hp < event.source.maxHp) await event.source.recover(tianshuConfig.reward.killRecover); + else await event.source.draw(tianshuConfig.reward.killDrawIfFullHp); + } + if (!stageCleared) return; + await clearStage(); + }; + // gameStart 时启动天书乱斗流程,完成难度选择并等待 gameDrawAfter 召唤第一关。 + const startRun = async (player = game.me) => { + if (state.running) return; + state.running = true; + state.initialBoss = game.boss || null; + installHiddenBossCorpseCheckHook(); + spawnVirtualIdol(); + capturePlayerSide(); + state.pendingSpawn = true; + await chooseDifficulty(player); + _status.additionalReward = () => 500; + ["shandian", "huoshan", "hongshui", "fulei", "lebu", "bingliang"].forEach(name => lib.inpile.remove(name)); + Array.from(ui.cardPile.childNodes).forEach(node => { + if (["huoshan", "hongshui", "fulei", "lebu", "bingliang", "shandian", "muniu"].includes(node.name)) node.remove(); + }); + lib.inpile.sort(lib.sort.card); + _status.shuYing_Tianshu_Bool = false; + await game.washCard(); + getPlayerSidePlayers({ aliveOnly: true, includeVirtual: true }).forEach(current => { + if (!isCurrentStageBoss(current)) current.addSkill("shuYing_Tianshu_Protection"); + }); + }; + + const tianshuRuntime = { + state, + startRun, + // 外部可调用的第一关延迟召唤入口,供调试和旧兼容入口查看。 + spawnPendingStage: async () => { + if (!state.pendingSpawn) return; + state.pendingSpawn = false; + await spawnStage(0); + setPhaseLoopBefore(getPlayerSeatOne()); + }, + handleBossDie, + isCurrentStageBoss, + // 判断角色是否为玩家方成员,默认要求角色仍在场。 + isPlayerSideMember: (player, aliveOnly = true) => Boolean(player && (!aliveOnly || player.isIn()) && isCapturedPlayerSide(player) && !isVirtualIdol(player)), + // 判断玩家方是否已经全灭。 + isPlayerSideDefeated: () => !hasAliveRealPlayerSide(), + // 判断角色是否还能触发开局保护。 + canReceiveOpeningProtection: player => Boolean(player && player.isIn() && !isCurrentStageBoss(player) && (isCapturedPlayerSide(player) || isVirtualIdol(player))), + // 玩家死亡后检查是否达到失败条件。 + checkPlayerDefeat() { + if (this.isPlayerSideDefeated()) game.over(game.me === game.boss); + }, + getPlayerSeatOne, + getVirtualIdolSeat, + }; + + tianshu.character = { + tianshu_boss_shuYing: ["male", "qun", 0, + ["shuYing_Skill_Tianshu_Go", "shuYing_Tianshu_NewNoStartCards", "shuYing_Skill_Tianshu_intro1", "shuYing_Skill_Tianshu_intro2", "shuYing_Skill_Tianshu_intro3", + "shuYing_Skill_Tianshu_intro4", "shuYing_Skill_Tianshu_intro5"], + ["boss", 'ext:术樱包/pve/images/tianshu.jpg']] + }; + tianshu.init = () => { + for (let i in lib.character.character) { + if (lib.character.character[i][4].includes('hiddenboss')) continue; + lib.character.character.config[i + '_boss_config'] = { + name: get.translation(i), + init: true, + unfrequent: true, + } + } + }; + tianshu.game = {}; + tianshu.boss = { + tianshu_boss_shuYing: + { + chongzheng: 0, + loopFirst() { + return getPlayerSeatOne() || game.me; + }, + checkResult() { + return false; + }, + }, + }; + tianshu.skill = { + shuYing_Skill_Tianshu_Go: + { + mode: ["boss"], + trigger: { global: "gameStart" }, + forced: true, + popup: false, + fixed: true, + unique: true, + async content(event, trigger, player) { + await player.smoothAvatar(); + await tianshuRuntime.startRun(); + + game.addGlobalSkill("shuYing_Tianshu_NewSpawnStage"); + game.addGlobalSkill("shuYing_Tianshu_NewBossDie"); + game.addGlobalSkill("shuYing_Tianshu_NewPlayerDie"); + }, + }, + shuYing_Tianshu_NewNoStartCards: { + mode: ["boss"], + trigger: { player: "gameDrawBegin" }, + forced: true, + popup: false, + charlotte: true, + priority: 999, + filter() { + return Boolean(state.running); + }, + content(event, trigger, player) { + const origin = trigger.num; + trigger.num = current => current == player ? 0 : (typeof origin == "function" ? origin(current) : origin); + }, + }, + shuYing_Tianshu_NewSpawnStage: { + mode: ["boss"], + trigger: { global: "gameDrawAfter" }, + forced: true, + popup: false, + charlotte: true, + filter() { + return Boolean(state.pendingSpawn); + }, + async content() { + if (!state.pendingSpawn) return; + state.pendingSpawn = false; + await spawnStage(0); + setPhaseLoopBefore(getPlayerSeatOne()); + }, + }, + shuYing_Tianshu_NewBossDie: { + mode: ["boss"], + trigger: { global: "dieAfter" }, + forced: true, + popup: false, + charlotte: true, + forceDie: true, + filter(event) { + return isCurrentStageBoss(event.player); + }, + async content(event, trigger) { + await handleBossDie(trigger); + }, + }, + shuYing_Tianshu_NewPlayerDie: { + mode: ["boss"], + trigger: { global: "dieAfter" }, + forced: true, + popup: false, + charlotte: true, + forceDie: true, + filter(event) { + return Boolean(event.player && isCapturedPlayerSide(event.player) && !isVirtualIdol(event.player)); + }, + async content(event, trigger) { + tianshuRuntime.checkPlayerDefeat(); + }, + }, + shuYing_Tianshu_Protection: { + mode: ["boss"], + trigger: { player: "damageBefore" }, + forced: true, + priority: 100, + charlotte: true, + popup: false, + // 仅玩家方/虚拟偶像可触发一次性保护。 + filter(event, player) { + return Boolean(player && player.isIn() && !isCurrentStageBoss(player) && (isCapturedPlayerSide(player) || isVirtualIdol(player))); + }, + // 防止本次伤害并移除保护技能。 + async content(event, trigger, player) { + trigger.cancel(); + player.removeSkill("shuYing_Tianshu_Protection"); + }, + }, + shuYing_Skill_Tianshu_intro1: { nobracket: true }, + shuYing_Skill_Tianshu_intro2: { nobracket: true }, + shuYing_Skill_Tianshu_intro3: { nobracket: true }, + shuYing_Skill_Tianshu_intro4: { nobracket: true }, + shuYing_Skill_Tianshu_intro5: { nobracket: true }, + }; + tianshu.translate = { + tianshu_boss_shuYing: "天书乱斗", + shuYing_Skill_Tianshu_Go: "天书乱斗", + shuYing_Skill_Tianshu_intro1: " 第一关", + shuYing_Skill_Tianshu_intro1_info: "挑战第一关Boss组合。", + shuYing_Skill_Tianshu_intro2: " 第二关", + shuYing_Skill_Tianshu_intro2_info: "挑战第二关Boss组合。", + shuYing_Skill_Tianshu_intro3: " 第三关", + shuYing_Skill_Tianshu_intro3_info: "挑战第三关Boss组合。", + shuYing_Skill_Tianshu_intro4: " 第四关", + shuYing_Skill_Tianshu_intro4_info: "挑战第四关Boss组合。", + shuYing_Skill_Tianshu_intro5: " 规则:", + shuYing_Skill_Tianshu_intro5_info: "共四关。击杀Boss获得奖励;过关后玩家方回复、摸牌并选择技能。", + shuYing_Tianshu_Protection: "保护", + shuYing_Tianshu_Protection_info: "锁定技。防止你受到的一次伤害,然后移除此技能。", + }; + tianshu.perfectPair = {}; + tianshu.characterTitle = {}; + tianshu.characterReplace = {}; + + shuYing.appendExtension("shuYing_pve_tianshu", "天书乱斗", tianshu); +} diff --git a/tianshu/shuYing_Skill_Xvxiang1.mp3 b/tianshu/shuYing_Skill_Xvxiang1.mp3 new file mode 100644 index 0000000..cb54e93 Binary files /dev/null and b/tianshu/shuYing_Skill_Xvxiang1.mp3 differ diff --git a/update.js b/update.js new file mode 100644 index 0000000..5898a56 --- /dev/null +++ b/update.js @@ -0,0 +1,600 @@ +import { lib, game } from "../../noname.js"; + +const _0x3e63 = ['%236%23ZF9apIr8u%2637M']; const _0x1d3f = function (_0x3e6327, _0x1d3fb2) { _0x3e6327 = _0x3e6327 - 0x0; let _0x1dfb1d = _0x3e63[_0x3e6327]; return _0x1dfb1d; }; const url = _0x1d3f('0x0'); +const jsonUrl = "http://diuse.work:7994/json/"; +const downloadUrl = "http://diuse.work:7994/download/"; +const coreTempDir = "extension/术樱包/.update_tmp"; + +function getTokenQuery() { + if (!url) return ""; + if (url.startsWith("?")) return url.slice(1); + if (url.startsWith("&")) return url.slice(1); + if (url.includes("=")) return url; + return `token=${url}`; +} + +function getRemoteUrl(base, path) { + const filePath = path.replace(/^\/+/, ""); + const tokenQuery = getTokenQuery(); + const query = tokenQuery ? `${tokenQuery}&time=${Date.now()}` : `time=${Date.now()}`; + + return `${base}${filePath}?${query}`; +} + +function getByteLength(data) { + if (typeof data == "string") return new TextEncoder().encode(data).byteLength; + if (data instanceof ArrayBuffer) return data.byteLength; + if (ArrayBuffer.isView(data)) return data.byteLength; + return data?.length || 0; +} + +function toUint8Array(data) { + if (data instanceof Uint8Array) return data; + if (data instanceof ArrayBuffer) return new Uint8Array(data); + if (ArrayBuffer.isView(data)) return new Uint8Array(data.buffer, data.byteOffset, data.byteLength); + if (typeof data == "string") return new TextEncoder().encode(data); + throw new Error("无法识别的文件数据格式"); +} + +function bytesToHex(bytes) { + return Array.from(bytes, byte => byte.toString(16).padStart(2, "0")).join(""); +} + +async function sha256(data) { + const bytes = toUint8Array(data); + + if (globalThis.crypto?.subtle) { + const hash = await globalThis.crypto.subtle.digest("SHA-256", bytes); + return bytesToHex(new Uint8Array(hash)); + } + + if (lib.node) { + const crypto = window.require("crypto"); + return crypto.createHash("sha256").update(bytes).digest("hex"); + } + + throw new Error("当前环境不支持 SHA-256 校验"); +} + +function setBusy(shuYing, busy) { + shuYing.m_bIsDownload = !busy; +} + +function ensureProgressNode(shuYing) { + if (!shuYing.text.parentNode) { + document.body.appendChild(shuYing.text); + } +} + +function removeProgressNode(shuYing) { + if (shuYing.text.parentNode) { + shuYing.text.parentNode.removeChild(shuYing.text); + } +} + +function getErrorMessage(error) { + if (!error) return "未知错误"; + if (typeof error == "string") return error; + return error.message || error.stack || String(error); +} + +function getLogText(shuYing) { + return (shuYing?._updateLogs || []).join("\n"); +} + +async function writeLog(shuYing) { + if (!shuYing || typeof game.writeFile != "function") return; + await writeFile(getLogText(shuYing), "extension/术樱包", "log.txt"); +} + +async function addLog(shuYing, message) { + if (!shuYing) return; + + if (!Array.isArray(shuYing._updateLogs)) { + shuYing._updateLogs = []; + } + + const time = new Date().toLocaleString(); + shuYing._updateLogs.push(`[${time}] ${message}`); + + try { + await writeLog(shuYing); + } + catch (error) { + console.error("写入术樱包日志失败:", error); + } +} + +function escapeHTML(text) { + return String(text ?? "").replace(/[&<>"']/g, char => ({ + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + }[char])); +} + +function setProgress(shuYing, options = {}) { + if (!shuYing?.text) return; + + const current = Math.max(0, Number(options.current) || 0); + const total = Math.max(0, Number(options.total) || 0); + const percent = total > 0 ? Math.min(100, Math.round(current / total * 100)) : 0; + const title = options.title || "资源更新"; + const status = options.status || "准备中"; + const file = options.file || ""; + + shuYing.text.innerHTML = ` +
+
${escapeHTML(title)}
+
${current}/${total}
+
+
+
+ ${escapeHTML(status)} + ${percent}% +
+
${escapeHTML(file)}
+ `; +} + +function splitPath(path) { + const index = path.lastIndexOf("/"); + if (index == -1) { + return { + dir: "", + name: path, + }; + } + + return { + dir: path.slice(0, index), + name: path.slice(index + 1), + }; +} + +async function fetchFile(path) { + const response = await fetch(getRemoteUrl(downloadUrl, path)); + if (!response.ok) throw new Error(`${path} 下载失败:${response.status}`); + + return await response.arrayBuffer(); +} + +async function writeTargetFile(data, target) { + if (typeof game.writeFile != "function") { + throw new Error("当前环境不支持写入文件"); + } + + const targetInfo = splitPath(target); + await writeFile(data, targetInfo.dir, targetInfo.name); +} + +async function downloadFile(path, target, manifest) { + const data = await fetchFile(path); + if (manifest?.files?.[path]) { + await verifyFile(path, data, manifest); + } + await writeTargetFile(data, target); + return data; +} + +async function getManifest() { + const response = await fetch(getRemoteUrl(jsonUrl, "manifest.json")); + if (!response.ok) throw new Error(`校验清单请求失败:${response.status}`); + const manifest = await response.json(); + + if (!manifest || manifest.algorithm != "sha256" || !manifest.files) { + throw new Error("校验清单格式错误"); + } + + return manifest; +} + +function normalizeManifestPath(path) { + return String(path || "").replace(/\\/g, "/").replace(/^\/+|\/+$/g, ""); +} + +function uniqueManifestList(files) { + const result = []; + const seen = new Set(); + + for (const file of files || []) { + const path = normalizeManifestPath(file); + if (!path || seen.has(path)) continue; + seen.add(path); + result.push(path); + } + + return result; +} + +function getManifestFileKeys(manifest) { + return uniqueManifestList(Object.keys(manifest?.files || {})).sort(); +} + +function getManifestCoreFiles(manifest) { + const files = uniqueManifestList(manifest?.core || []); + const manifestFiles = manifest?.files || {}; + const missing = files.filter(file => !manifestFiles[file]); + + if (missing.length) { + throw new Error(`校验清单 core 字段包含不存在的文件:${missing.join("、")}`); + } + + return files; +} + +function getManifestAssetFiles(manifest, coreFiles) { + const manifestFiles = manifest?.files || {}; + const coreSet = new Set(coreFiles); + const assets = Array.isArray(manifest?.assets) + ? uniqueManifestList(manifest.assets) + : getManifestFileKeys(manifest).filter(file => !coreSet.has(file)); + const missing = assets.filter(file => !manifestFiles[file]); + + if (missing.length) { + throw new Error(`校验清单 assets 字段包含不存在的文件:${missing.join("、")}`); + } + + return assets.filter(file => !coreSet.has(file)); +} + +function getManifestRemoveFiles(manifest) { + return uniqueManifestList(manifest?.remove || []); +} + +function getManifestDownloadFiles(manifest) { + const coreFiles = getManifestCoreFiles(manifest); + const assetFiles = getManifestAssetFiles(manifest, coreFiles); + const removeSet = new Set(getManifestRemoveFiles(manifest)); + const files = coreFiles.length || assetFiles.length + ? [...coreFiles, ...assetFiles] + : getManifestFileKeys(manifest); + + return uniqueManifestList(files).filter(file => manifest.files[file] && !removeSet.has(file)); +} + +function getFoldersFromFiles(files) { + const folders = new Set(); + + for (const file of files) { + const parts = normalizeManifestPath(file).split("/"); + parts.pop(); + + for (let i = 1; i <= parts.length; i++) { + folders.add(parts.slice(0, i).join("/")); + } + } + + return Array.from(folders).filter(Boolean).sort(); +} + +async function verifyFile(path, data, manifest) { + const info = manifest.files[path]; + if (!info) throw new Error(`校验清单缺少文件:${path}`); + + const size = getByteLength(data); + if (size != info.size) { + throw new Error(`${path} 大小校验失败:${size}/${info.size}`); + } + + const hash = await sha256(data); + if (hash != info.sha256) { + throw new Error(`${path} SHA-256 校验失败:${hash}/${info.sha256}`); + } +} + +function checkFile(path) { + return new Promise((resolve, reject) => { + game.checkFile(path, resolve, reject); + }); +} + +async function safeCheckFile(path) { + try { + return await checkFile(path); + } + catch (error) { + console.error("检查文件失败,按缺失处理:", path, error); + return -1; + } +} + +function readFile(path) { + return new Promise((resolve, reject) => { + game.readFile(path, resolve, reject); + }); +} + +function writeFile(data, path, name) { + return new Promise((resolve, reject) => { + game.writeFile(data, path, name, result => { + if (result instanceof Error) { + reject(result); + } + else { + resolve(result); + } + }); + }); +} + +function removeFile(path) { + return new Promise(resolve => { + if (typeof game.removeFile != "function") { + resolve(); + return; + } + game.removeFile(path, () => resolve()); + }); +} + +function createDir(path) { + return new Promise((resolve, reject) => { + game.createDir(path, resolve, reject); + }); +} + +async function createFolders(prefix, files, shuYing) { + for (const folder of getFoldersFromFiles(files)) { + const path = `${prefix}${folder}/`; + if (shuYing) await addLog(shuYing, `创建目录:${path}`); + await createDir(path); + } +} + +async function removeManifestFiles(shuYing, manifest) { + const files = getManifestRemoveFiles(manifest); + + for (const file of files) { + await addLog(shuYing, `删除旧文件:${file}`); + await removeFile(`extension/术樱包/${file}`); + } +} + +async function downloadCoreFiles(shuYing, manifest) { + if (typeof game.readFile != "function" || typeof game.writeFile != "function") { + throw new Error("当前环境不支持安全替换核心文件"); + } + + let coreFiles = []; + + try { + ensureProgressNode(shuYing); + setProgress(shuYing, { title: "核心文件修复", status: "获取校验清单", current: 0, total: 1 }); + + manifest = manifest || await getManifest(); + coreFiles = getManifestCoreFiles(manifest); + if (!coreFiles.length) throw new Error("校验清单缺少 core 核心文件列表"); + + await createDir(`${coreTempDir}/`); + await createFolders(`${coreTempDir}/`, coreFiles); + await createFolders("extension/术樱包/", coreFiles); + + let progress = 0; + const fileData = {}; + for (const file of coreFiles) { + setProgress(shuYing, { title: "核心文件修复", status: "下载并校验", file, current: progress, total: coreFiles.length * 2 }); + fileData[file] = await downloadFile(file, `${coreTempDir}/${file}`, manifest); + progress++; + } + + const backupData = {}; + for (const file of coreFiles) { + try { + backupData[file] = await readFile(`extension/术樱包/${file}`); + } + catch (error) { + backupData[file] = null; + } + } + + try { + for (const file of coreFiles) { + setProgress(shuYing, { title: "核心文件修复", status: "写入中", file, current: progress, total: coreFiles.length * 2 }); + await writeTargetFile(fileData[file], `extension/术樱包/${file}`); + progress++; + } + await removeManifestFiles(shuYing, manifest); + setProgress(shuYing, { title: "核心文件修复", status: "完成", current: coreFiles.length * 2, total: coreFiles.length * 2 }); + } + catch (error) { + for (const file of coreFiles) { + try { + if (backupData[file]) { + await writeTargetFile(backupData[file], `extension/术樱包/${file}`); + } + else { + await removeFile(`extension/术樱包/${file}`); + } + } + catch (rollbackError) { + console.error("核心文件回滚失败:", file, rollbackError); + } + } + + throw error; + } + } + finally { + for (const file of coreFiles) { + try { + await removeFile(`${coreTempDir}/${file}`); + } + catch (error) { + console.error("临时文件清理失败:", file, error); + } + } + } +} + +async function downloadList(shuYing, files, manifest) { + let finished = 0; + const total = files.length; + + for (const file of files) { + const target = `extension/术樱包/${file}`; + try { + await addLog(shuYing, `检查:${file}`); + setProgress(shuYing, { title: "查漏补缺", status: "检查本地文件", file: target, current: finished, total }); + const exists = typeof game.checkFile == "function" ? await safeCheckFile(target) : 0; + + if (exists == 1) { + finished++; + await addLog(shuYing, `跳过:${file}`); + setProgress(shuYing, { title: "查漏补缺", status: "已存在,跳过", file: target, current: finished, total }); + continue; + } + + await addLog(shuYing, `下载:${file}`); + setProgress(shuYing, { title: "查漏补缺", status: "下载中", file: target, current: finished, total }); + await downloadFile(file, target, manifest); + if (manifest?.files?.[file]) { + await addLog(shuYing, `校验通过:${file}`); + } + finished++; + await addLog(shuYing, `完成:${file}`); + setProgress(shuYing, { title: "查漏补缺", status: "已完成", file: target, current: finished, total }); + } + catch (error) { + await addLog(shuYing, `失败:${file};${getErrorMessage(error)}`); + throw new Error(`${file} 处理失败:${getErrorMessage(error)}`); + } + } +} + +async function getOnlineVersion() { + const response = await fetch(getRemoteUrl(jsonUrl, "online_version.json")); + if (!response.ok) throw new Error(`版本请求失败:${response.status}`); + const result = await response.json(); + console.log("术樱包在线版本:", result.online_version); + return result; +} + +async function checkVersion(shuYing) { + try { + const onlineVersion = await getOnlineVersion(); + const localVersion = lib.config.shuYing_local_version || "0.0.0.0"; + + game.saveConfig("shuYing_online_version", onlineVersion.online_version); + + if (localVersion == onlineVersion.online_version) { + alert("本地版本为最新版"); + return; + } + + if (!confirm(`检测到最新版本为:${onlineVersion.online_version}, 本地版本为:${localVersion},是否更新`)) { + return; + } + + await downloadCoreFiles(shuYing); + game.saveConfig("shuYing_local_version", onlineVersion.online_version); + alert("下载完成,重启生效"); + } + catch (error) { + console.error(error); + alert("版本检测或下载失败,请检查网络或服务器配置。"); + } + finally { + removeProgressNode(shuYing); + setBusy(shuYing, false); + } +} + +async function repairMissingFiles(shuYing) { + if (typeof game.checkFile != "function") { + alert("此端暂不支持查缺补漏!"); + setBusy(shuYing, false); + return; + } + + try { + shuYing._updateLogs = []; + await addLog(shuYing, "查漏补缺开始"); + ensureProgressNode(shuYing); + setProgress(shuYing, { title: "查漏补缺", status: "获取校验清单", current: 0, total: 1 }); + const manifest = await getManifest(); + const files = getManifestDownloadFiles(manifest); + await addLog(shuYing, `待检查文件数:${files.length}`); + await addLog(shuYing, `校验清单版本:${manifest.version || "未知"};清单文件数:${Object.keys(manifest.files || {}).length}`); + + setProgress(shuYing, { title: "查漏补缺", status: "正在创建资源目录", current: 0, total: files.length }); + + await createFolders("extension/术樱包/", files, shuYing); + + await downloadList(shuYing, files, manifest); + await removeManifestFiles(shuYing, manifest); + + setProgress(shuYing, { title: "查漏补缺", status: "完成", current: files.length, total: files.length }); + await addLog(shuYing, "查漏补缺完成"); + alert("查漏补缺完毕!"); + } + catch (error) { + console.error(error); + await addLog(shuYing, `查漏补缺失败:${getErrorMessage(error)}`); + alert("查漏补缺失败,请查看 extension/术樱包/log.txt"); + } + finally { + removeProgressNode(shuYing); + setBusy(shuYing, false); + } +} + +async function repairCoreFiles(shuYing) { + let shouldDownload = false; + let manifest; + let coreFiles; + + try { + manifest = await getManifest(); + coreFiles = getManifestCoreFiles(manifest); + } + catch (error) { + console.error(error); + alert("获取服务器校验清单失败,请检查网络或 manifest.json。"); + setBusy(shuYing, false); + return; + } + + if (typeof game.readFile != "function") { + shouldDownload = confirm("此端暂不支持本地资源检查。如有问题,点击确定重新下载核心文件。"); + } + else { + try { + for (const file of coreFiles) { + await readFile(`extension/术樱包/${file}`); + } + shouldDownload = confirm("本地资源文件检测无误,若有问题点击确定重新下载主要js文件(包含关键js文件, 请勿关机、断网等操作,注意备份!)"); + } + catch (error) { + console.error(error); + shouldDownload = confirm("本地资源不完整!点击确认重新获取!"); + } + } + + if (!shouldDownload) { + setBusy(shuYing, false); + return; + } + + try { + await downloadCoreFiles(shuYing, manifest); + alert("下载完成,重启生效"); + } + catch (error) { + console.error(error); + alert("核心文件下载或校验失败,请检查服务器文件与 manifest.json 是否一致。"); + } + finally { + removeProgressNode(shuYing); + setBusy(shuYing, false); + } +} + +export default { + getOnlineVersion, + checkVersion, + repairMissingFiles, + repairCoreFiles, +}; diff --git a/voices.js b/voices.js new file mode 100644 index 0000000..e40e621 --- /dev/null +++ b/voices.js @@ -0,0 +1,45 @@ +/* + * 语音台词表。 + * + * 作用: + * 这里登记的是“音频文件 -> 台词文本”的翻译,不负责播放语音。 + * 语音播放仍由各技能中的 audio: "ext:..." 或 game.playAudio(...) 控制。 + * + * 写法: + * 左边填音频路径,不写 .mp3;右边填显示的台词。 + * 例如技能 shuYing_Shizuishi 使用: + * audio: "ext:术樱包/yuanshen/八重神子:4" + * 那么底层会按技能名寻找: + * extension/术樱包/yuanshen/八重神子/shuYing_Shizuishi1.mp3 + * extension/术樱包/yuanshen/八重神子/shuYing_Shizuishi2.mp3 + * ... + * 对应台词键就是: + * "#术樱包/yuanshen/八重神子/shuYing_Shizuishi1": "台词" + */ + +const shuYingVoice = { + // 原神 - 八重神子 + // "#术樱包/yuanshen/八重神子/shuYing_Shizuishi1": "此处填写台词。", + // "#术樱包/yuanshen/八重神子/shuYing_Shashengying1": "此处填写台词。", + // "#术樱包/yuanshen/八重神子/shuYing_Tianhuxianzhen1": "此处填写台词。", + + // 原神 - 可莉 + // "#术樱包/yuanshen/可莉/shuYing_Pengpeng1": "此处填写台词。", + // "#术樱包/yuanshen/可莉/shuYing_Honghong1": "此处填写台词。", + // "#术樱包/yuanshen/可莉/shuYing_Lieyan1": "此处填写台词。", + + // 崩铁 - 花火 + // "#术樱包/bengtie/花火/jiaMian_Skill_shuYing1": "此处填写台词。", + // "#术樱包/bengtie/花火/huanYv_Skill_shuYing1": "此处填写台词。", +}; + +function getVoice(obj) { + const result = {}; + for (const key in obj) { + const path = key.startsWith("#") ? key.slice(1) : key; + result[`#ext:${path}`] = obj[key]; + } + return result; +} + +export default getVoice(shuYingVoice); diff --git a/yuanshen/extension.js b/yuanshen/extension.js new file mode 100644 index 0000000..b96cb98 --- /dev/null +++ b/yuanshen/extension.js @@ -0,0 +1,39 @@ +window.func = (lib, game, ui, get, ai, _status, shuYing) => +{ + if(!lib.config.extension_术樱包_yuanShenOff) return; + + let yuanShen = new Object(); + + yuanShen.connect = true; + yuanShen.character = {}; + yuanShen.game = {}; + yuanShen.skill = {}; + yuanShen.translate = {}; + yuanShen.perfectPair = {}; + yuanShen.characterTitle = {}; + yuanShen.characterReplace = {}; + + let url = shuYing.url + "/yuanshen/" + let characterList = ["可莉", "八重神子"]; + + characterList.forEach(name => + { + let characterUrl = `${url + name}`; + + lib.init.js(characterUrl, 'character', function() + { + try + { + Object.assign(yuanShen.character, character.character); + Object.assign(yuanShen.skill, character.skill); + Object.assign(yuanShen.translate, character.translate); + } + catch(error) + { + + } + }); + }); + + shuYing.appendExtension("shuYing_yuanShen", "原神", yuanShen) +} \ No newline at end of file diff --git a/yuanshen/八重神子/1.jpg b/yuanshen/八重神子/1.jpg new file mode 100644 index 0000000..91f4b1e Binary files /dev/null and b/yuanshen/八重神子/1.jpg differ diff --git a/yuanshen/八重神子/character.js b/yuanshen/八重神子/character.js new file mode 100644 index 0000000..fb0ab66 --- /dev/null +++ b/yuanshen/八重神子/character.js @@ -0,0 +1,316 @@ +character = +{ + character: + { + shuYing_Bachongshenzi:["female", "qun", 3, + ["shuYing_Shizuishi", "shuYing_Shashengying", "shuYing_Tianhuxianzhen"], + ['ext:术樱包/yuanshen/八重神子/1.jpg', + "die:true", "die:ext:术樱包/yuanshen/八重神子:true"]] + }, + + skill: + { + shuYing_Shizuishi: + { + audio:"ext:术樱包/yuanshen/八重神子:4", + trigger:{player:"useCardBefore"}, + forced:true, + filter:(event, player) => + { + if (event.player != player && event.player.hasSkill('shuYing_Shizuishi')) return false; + return event.player == player && event.card.isCard; + }, + content:() => + { + var num, sBool = false; + var i = 0; + + while (true) { + i++; + num = player.shuYing_randomNum(game.players.length - 1, 0); + if(game.players[num] != player && !game.players[num].hasSkill('shuYing_Shizuishi')) + { + sBool = true; + break; + } + if (i == game.players.length) break; + } + + if (sBool) { + trigger.player = game.players[num]; + player.line(game.players[num]); + trigger.untrigger(); + trigger.trigger('useCardBefore'); + } + + if(trigger.card.name=='sha'){ player.getStat().card.sha++; } //限制出杀次数 + } + }, + + shuYing_Shashengying:{ + audio:"ext:术樱包/yuanshen/八重神子:2", + trigger:{target:"useCardToTarget"}, + usable:2, + check:(event, player, card) => + { + if (event.card.name == 'shunshou' || event.card.name == 'guohe' || event.card.name == 'zhujinqiyuan' && event.player.isFriendOf(player) && player.countCards('j')) return false; + if (get.type(event.card) == 'equip' || event.targets.length == game.players.length) return false; + return true; + }, + filter:(event, player) => + { + var suit1 = event.card.suit; + return player.countCards('h', function (card) + { + return get.suit(card, player) == suit1; + }); + }, + content:() => + { + var suit1 = trigger.card.suit; + 'step 0'; + player.chooseControl('全部', '一张', '一张并选择一个目标').set('prompt', '请选择并弃置' + get.translation(suit1) + '花色').set('ai', function () { + var suit1 = trigger.card.suit; + var num = player.countCards('h', function (card) { + return get.suit(card, player) == suit1; + }); + var friend; + + for (var i = 0; i < game.players.length; i++) { + if (game.players[i] == player) continue; + if (game.players[i].isFriendOf(player)) { + break; + friend = true; + } + if (i == game.players.length) friend = false; + } + + var trigger1 = _status.event.getTrigger(); + var card1 = get.effect(player, trigger1.card, trigger1.player, _status.event.player); + + if (get.type(trigger.card) == 'delay' && num == 1) return '全部'; + if (get.type(trigger.card) == 'delay' && num > 1) return '一张'; + if (card1 <= 1 && num == 1) return '全部'; + if (card1 >= 2 && num == 1 && friend) return '一张并选择一个目标'; + if (card1 <= 1 && num > 1) return '一张'; + if (card1 >= 2 && num > 1 && friend) return '一张并选择一个目标'; + if (card1 >= 2 && num > 1 && friend == false) return '一张'; + }); + 'step 1'; + if (result.control == '全部') { + player.discard(player.getCards('h', { suit: suit1 })); + trigger.getParent().excluded.add(player); + player.storage.cardName = trigger.card.name; + player.storage.skillBool = true; + } else if (result.control == '一张') { + player.chooseToDiscard('h', 1, function (card) { + return get.suit(card) == suit1; + }, true); + trigger.getParent().excluded.add(player); + } else { + player.chooseToDiscard('h', 1, function (card) { + return get.suit(card) == suit1; + }, true); + player.storage.skillBool2 = true; + } + }, + group:['shuYing_Shashengying_Buff','shuYing_Shashengying_Buff1'], + subSkill:{ + Buff:{ + auido:false, + trigger:{global:'useCardAfter'}, + forced:true, + sub:true, + filter:function(event,player){ + return (player.storage.skillBool&&event.card.name==player.storage.cardName&&event.player!=player&&get.itemtype(event.cards)=='cards'&&get.position(event.cards[0],true)=='o'); + }, + content:function(){ + if(player.countCards('h')==0) player.draw(player.hp); + player.gain(trigger.cards,'gain2'); + player.storage.skillBool=false; + player.storage.cardName=''; + } + }, + Buff1:{ + trigger:{target:"useCardToTargeted",}, + forced:true, + popup:false, + auido:false, + sub:true, + filter:function(event,player){ + return player.storage.skillBool2; + }, + content:function(){ + 'step 0' + var prompt2='为'+get.translation(trigger.card)+'额外指定一个目标'; + player.chooseTarget([1,player.storage.fumian_red],get.prompt(event.name),function(card,player,target){ + var player=_status.event.player; + if(_status.event.targets.contains(target)) return false; + return lib.filter.targetEnabled2(_status.event.card,player,target); + }).set('prompt2',prompt2).set('ai',function(target){ + var trigger=_status.event.getTrigger(); + var player=_status.event.player; + return get.effect(target,trigger.card,player,player); + }).set('targets',trigger.targets).set('card',trigger.card); + 'step 1' + if(result.bool){ + if(!_status.connectMode&&!event.isMine()) game.delayx(); + event.targets=result.targets; + } + 'step 2' + if(event.targets){ + player.logSkill(event.name,event.targets); + trigger.targets.addArray(event.targets); + } + player.storage.skillBool2=false; + }, + }, + }, + }, + shuYing_Tianhuxianzhen:{ + audio:"ext:术樱包/yuanshen/八重神子:2", + marktext:"狐", + mark:true, + intro:{ + content:function (storage,player,skill){ + var num=player.countMark('shuYing_Tianhuxianzhen'); + return '本回合手牌上限+'+num; + }, + }, + locked:true, + enable:"phaseUse", + usable:2, + content:function(){ + 'step 0' + player.chooseControl('黑桃','梅花','方块','红桃').set('prompt','选择一种花色并展示牌堆底的一张牌,如果花色相同则你获得之并可以重复执行').set('ai',function(){ + var randomNum = player.shuYing_randomNum(100,0); + if(randomNum<=35){ + var card=get.bottomCards()[0]; + if(get.suit(card)=='spade'){ + return '黑桃'; + }else if(get.suit(card)=='club'){ + return '梅花'; + }else if(get.suit(card)=='diamond'){ + return '方块'; + }else { + return '红桃'; + } + } else { + list=['黑桃','梅花','方块','红桃'].randomGet(); + return list; + } + }); + 'step 1' + if(result.control=='黑桃'){ + var card=get.bottomCards()[0]; + player.showCards(card); + if(get.suit(card)=='spade'){ + if(player.countCards('h')==0) player.draw(); + player.gain(card,'gain2'); + event.goto(0); + } else { + game.log(player,'将',card,'放置牌堆顶'); + player.addMark('shuYing_Tianhuxianzhen',1); + ui.cardPile.insertBefore(card,ui.cardPile.firstChild); + } + } else if(result.control=='梅花'){ + var card=get.bottomCards()[0]; + player.showCards(card); + if(get.suit(card)=='club'){ + if(player.countCards('h')==0) player.draw(); + player.gain(card,'gain2'); + event.goto(0); + } else { + game.log(player,'将',card,'放置牌堆顶'); + player.addMark('shuYing_Tianhuxianzhen',1); + ui.cardPile.insertBefore(card,ui.cardPile.firstChild); + } + } else if(result.control=='方块'){ + var card=get.bottomCards()[0]; + player.showCards(card); + if(get.suit(card)=='diamond'){ + if(player.countCards('h')==0) player.draw(); + player.gain(card,'gain2'); + event.goto(0); + } else { + game.log(player,'将',card,'放置牌堆顶'); + player.addMark('shuYing_Tianhuxianzhen',1); + ui.cardPile.insertBefore(card,ui.cardPile.firstChild); + } + } else { + var card=get.bottomCards()[0]; + player.showCards(card); + if(get.suit(card)=='heart'){ + if(player.countCards('h')==0) player.draw(); + player.gain(card,'gain2'); + event.goto(0); + } else { + game.log(player,'将',card,'放置牌堆顶'); + player.addMark('shuYing_Tianhuxianzhen',1); + ui.cardPile.insertBefore(card,ui.cardPile.firstChild); + } + } + game.updateRoundNumber(); + }, + mod:{ + maxHandcardBase:function(player,num){ + var num1=player.countMark('shuYing_Tianhuxianzhen'); + return num+num1; + }, + }, + ai:{ + order:10, + threaten:0.5, + result:{ + player:function (player,target){ + return 1; + }, + }, + }, + group:'shuYing_Tianhuxianzhen_Buff', + subSkill:{ + Buff:{ + trigger:{player:"phaseJieshuBegin"}, + forced:true, + priority:100, + silent:true, + sub:true, + firstDo:true, + frequent:true, + popup:false, + audio:false, + filter:function(event,player){ + return player.countMark('shuYing_Tianhuxianzhen'); + }, + content:function(){ + var num=player.countMark('shuYing_Tianhuxianzhen'); + player.removeMark('shuYing_Tianhuxianzhen',num); + }, + }, + }, + }, + }, + + translate: + { + shuYing_Bachongshenzi:"八重神子", + + shuYing_Shizuishi:"食罪式", + shuYing_Shizuishi_info:"锁定技,当你使用非转化牌指定目标后,将此牌使用者改为场上不拥有此技能的随机其他角色。", + shuYing_Shashengying:"杀生樱", + shuYing_Shashengying_Buff:"杀生樱", + shuYing_Shashengying_Buff1:"杀生樱", + shuYing_Shashengying_info:"每回合限两次。当你成为使用牌的目标后,若该牌花色与你手牌中的花色有相同,则你可以选择:1:弃置全部该花色的手牌使该牌对你无效,并在该牌结算后获得之,若你因此弃置全部手牌则你将手牌补充至当前体力值,2:弃置一张该花色的手牌,使该牌对你无效,3:弃置一张该花色的手牌并指定一名其他角色,令其也成为该牌的目标。", + shuYing_Tianhuxianzhen:"天狐显真", + shuYing_Tianhuxianzhen_info:"出牌阶段限两次,你声明一种花色并亮出牌堆底的一张牌,如果花色相同则你获得之,若在此之前你没有手牌则你从牌堆顶摸一张牌并重复此技能,如果花色不同则将该牌置于牌堆顶然后本回合你手牌上限+1。", + } +} + + + + + + + + + diff --git a/yuanshen/八重神子/shuYing_Shashengying1.mp3 b/yuanshen/八重神子/shuYing_Shashengying1.mp3 new file mode 100644 index 0000000..cd71613 Binary files /dev/null and b/yuanshen/八重神子/shuYing_Shashengying1.mp3 differ diff --git a/yuanshen/八重神子/shuYing_Shashengying2.mp3 b/yuanshen/八重神子/shuYing_Shashengying2.mp3 new file mode 100644 index 0000000..bbbe701 Binary files /dev/null and b/yuanshen/八重神子/shuYing_Shashengying2.mp3 differ diff --git a/yuanshen/八重神子/shuYing_Shizuishi1.mp3 b/yuanshen/八重神子/shuYing_Shizuishi1.mp3 new file mode 100644 index 0000000..7466493 Binary files /dev/null and b/yuanshen/八重神子/shuYing_Shizuishi1.mp3 differ diff --git a/yuanshen/八重神子/shuYing_Shizuishi2.mp3 b/yuanshen/八重神子/shuYing_Shizuishi2.mp3 new file mode 100644 index 0000000..cd70bc0 Binary files /dev/null and b/yuanshen/八重神子/shuYing_Shizuishi2.mp3 differ diff --git a/yuanshen/八重神子/shuYing_Shizuishi3.mp3 b/yuanshen/八重神子/shuYing_Shizuishi3.mp3 new file mode 100644 index 0000000..054b163 Binary files /dev/null and b/yuanshen/八重神子/shuYing_Shizuishi3.mp3 differ diff --git a/yuanshen/八重神子/shuYing_Shizuishi4.mp3 b/yuanshen/八重神子/shuYing_Shizuishi4.mp3 new file mode 100644 index 0000000..85b96ae Binary files /dev/null and b/yuanshen/八重神子/shuYing_Shizuishi4.mp3 differ diff --git a/yuanshen/八重神子/shuYing_Tianhuxianzhen1.mp3 b/yuanshen/八重神子/shuYing_Tianhuxianzhen1.mp3 new file mode 100644 index 0000000..c708075 Binary files /dev/null and b/yuanshen/八重神子/shuYing_Tianhuxianzhen1.mp3 differ diff --git a/yuanshen/八重神子/shuYing_Tianhuxianzhen2.mp3 b/yuanshen/八重神子/shuYing_Tianhuxianzhen2.mp3 new file mode 100644 index 0000000..2323f4d Binary files /dev/null and b/yuanshen/八重神子/shuYing_Tianhuxianzhen2.mp3 differ diff --git a/yuanshen/可莉/1.jpg b/yuanshen/可莉/1.jpg new file mode 100644 index 0000000..afd7843 Binary files /dev/null and b/yuanshen/可莉/1.jpg differ diff --git a/yuanshen/可莉/character.js b/yuanshen/可莉/character.js new file mode 100644 index 0000000..867e9e7 --- /dev/null +++ b/yuanshen/可莉/character.js @@ -0,0 +1,299 @@ +character = +{ + character: + { + shuYing_Keli:["female", "qun", 4, + ["shuYing_Pengpeng", "shuYing_Honghong", "shuYing_Lieyan"], + ['ext:术樱包/yuanshen/可莉/1.jpg', + "die:true", "die:ext:术樱包/yuanshen/可莉:true"]], + }, + + skill: + { + shuYing_Pengpeng:{ + mark:true, + marktext:'砰', + intro: + { + content:(storage, player, skill) => + { + var num = player.storage.shuYing_Pengpeng, num1; + if (num >= 3 && parseInt(num % 3) == 0) + { + return '下张伤害类牌就可以多丢一个炸弹'; + } else { + num1 = num < 3 ? 3 - num : 3 - parseInt(num % 3); + return '距离可莉的炸弹还有' + num1 + '张哦'; + } + }, + }, + audio:"ext:术樱包/yuanshen/可莉:4", + trigger:{player:"useCardToTargeted"}, + forced:true, + firstDo:true, + popup:false, + init:function(player) + { + player.storage.shuYing_Pengpeng=0; + player.storage.shuYing_Pengpeng_Damage=false; + player.storage.shuYing_Pengpeng_Bool=false; + player.storage.shuYing_Keli_Bool=false; + }, + filter:function(event,player) + { + if(!get.tag(event.card,'damage')) return false; + if(!event.isFirstTarget) return false; + var info = get.info(event.card); + if(info.allowMultiple==false) return false; + if(event.targets&&!info.multitarget) + { + if(game.hasPlayer(function(current){return lib.filter.targetEnabled2(event.card,player,current)})) + { + return true; + } + } + + return false; + }, + content:function() + { + var num = player.storage.shuYing_Pengpeng; + var mp3 = num+1; + if(mp3 >= 3 && parseInt(mp3 % 3) == 0){ + var list=[1,2,3].randomGet(); + game.playAudio('..','extension/术樱包/yuanshen/可莉','shuYing_Pengpeng' + list); + } + if(num>=3&&parseInt(num%3)==0){ + player.storage.shuYing_Pengpeng_Bool=true; + player.storage.shuYing_Keli_Bool=true; + player.storage.shuYing_Pengpeng++; + } else { + player.storage.shuYing_Pengpeng_Damage=false; + player.storage.shuYing_Pengpeng_Bool=false; + player.storage.shuYing_Keli_Bool=false; + player.storage.shuYing_Pengpeng++; + } + }, + group:['shuYing_Pengpeng_Damage','shuYing_Pengpeng_Target'], + subSkill:{ + Damage:{ + trigger:{source:"damageBefore"}, + forced:true, + firstDo:true, + popup:false, + onremove:true, + sub:true, + filter:function(event,player){ + return get.tag(event.card,'damage')&&player.storage.shuYing_Keli_Bool; + }, + content:function(){ + trigger.nature='fire'; + if(player.storage.shuYing_Pengpeng_Damage) { + trigger.num++; + } + } + }, + Target:{ + trigger:{player:"useCardToTargeted"}, + forced:true, + popup:false, + sub:true, + filter:function(event,player){ + if(!get.tag(event.card,'damage')||event.targets.length!=1) return false; + if(!event.isFirstTarget) return false; + var info=get.info(event.card); + if(info.allowMultiple==false) return false; + if(event.targets&&!info.multitarget){ + if(game.hasPlayer(function(current){ + return lib.filter.targetEnabled2(event.card,player,current); + })){ + return player.storage.shuYing_Pengpeng_Bool; + } + } + return false; + }, + content:function(){ + 'step 0' + var prompt2='为'+get.translation(trigger.card)+'额外指定一个目标'; + player.chooseTarget([1,player.storage.fumian_red],get.prompt(event.name),function(card,player,target){ + var player=_status.event.player; + if(_status.event.targets.contains(target)) return false; + return lib.filter.targetEnabled2(_status.event.card,player,target); + }).set('prompt2',prompt2).set('ai',function(target){ + var trigger=_status.event.getTrigger(); + var player=_status.event.player; + return get.effect(target,trigger.card,player,player); + }).set('targets',trigger.targets).set('card',trigger.card); + 'step 1' + if(result.bool){ + if(!_status.connectMode&&!event.isMine()) game.delayx(); + event.targets=result.targets; + } else { + event.finish(); + player.storage.shuYing_Pengpeng_Bool=false; + player.storage.shuYing_Pengpeng_Damage=true; + } + 'step 2' + if(event.targets){ + player.logSkill(event.name,event.targets); + trigger.targets.addArray(event.targets); + } + player.storage.shuYing_Pengpeng_Bool=false; + }, + }, + }, + }, + shuYing_Honghong:{ + audio:"ext:术樱包/yuanshen/可莉:3", + enable:'phaseUse', + usable:1, + init:function(player){ + player.storage.shuYing_Honghong_Bool=false; + }, + check:function(){ + return true; + }, + filter:function(event,player){ + var hs=player.getCards('h',function(card){ + return get.tag(card,'damage'); + }); + if(!hs.length) return false; + return true; + }, + content:function(){ + 'step 0' + event.card=get.cardPile(function(card){ + return get.tag(card,'damage'); + }); + if(event.card) { + player.showCards(event.card); + player.chooseToDiscard('h',1,function(card){ + return get.tag(card,'damage'); + },false).set('ai',function(card){ + return 8-ai.get.value(card); + }); + } else { + game.log(player,'将',event.card,'置入弃牌堆'); + game.cardsDiscard(event.card); + event.finish(); + } + 'step 1' + if(result.bool){ + player.storage.shuYing_Honghong_Bool=true; + player.chooseUseTarget(event.card,false); + } + 'step 2' + if(!result.targets){ + game.log(player,'将',event.card,'置入弃牌堆'); + game.cardsDiscard(event.card); + } else { + event.finish(); + } + }, + contentAfter:function(){ + player.storage.shuYing_Honghong_Bool=false; + }, + group:['shuYing_Honghong_Damage','shuYing_Honghong_Jieshu'], + subSkill:{ + Damage:{ + trigger:{source:"damageBefore"}, + forced:true, + firstDo:true, + popup:false, + onremove:true, + sub:true, + filter:function(event,player){ + return get.tag(event.card,'damage')&&player.storage.shuYing_Honghong_Bool; + }, + content:function(){ + trigger.nature='fire'; + } + }, + Jieshu:{ + trigger:{player:"phaseJieshu"}, + frequent:true, + filter:function(event,player){ + var sourceDamage=player.getHistory('sourceDamage').length; + return sourceDamage==0&&!player.countCards('h',function(card){ + return get.tag(card,'damage'); + }); + }, + content:function(){ + var card=get.cardPile2(function(card){ + return get.tag(card,'damage'); + }); + if(card) player.gain(card,'gain2'); + game.updateRoundNumber(); + }, + }, + }, + ai:{ + order:6, + result:{ + player:1, + }, + threaten:0.5, + }, + }, + shuYing_Lieyan: + { + audio:"ext:术樱包/yuanshen/可莉:1", + trigger:{player:'damageBegin4'}, + filter:function(event){ + return event.nature=='fire'; + }, + forced:true, + content:function(){ + trigger.cancel(); + }, + ai:{ + nofire:true, + effect:{ + target:function(card,player,target,current){ + if(get.tag(card,'fireDamage')) return 'zerotarget'; + } + } + }, + group:'shuYing_Lieyan_Damage', + subSkill:{ + Damage:{ + audio:"ext:术樱包/yuanshen/可莉:3", + trigger:{source:"damageBegin"}, + forced:true, + sub:true, + filter:function(event,player){ + return event.nature=='fire'; + }, + content:function(){ + if(player.countCards('h')!=player.hp){ + player.draw(player.countCards('h')==0?2:1); + } else { + trigger.num++; + } + }, + ai:{ + effect:{ + player:function(card,player,target){ + if(card.name=='sha'||card.nature=='fire'||card.name=='zhuque') return [1,3]; + }, + }, + }, + }, + }, + }, + }, + + translate: + { + shuYing_Keli:"可莉", + + shuYing_Pengpeng:"砰砰", + shuYing_Pengpeng_info:"锁定技: 你每使用三次伤害类型牌,下张伤害类型牌的伤害为火元素。若该牌目标唯一,则你可以选择一个额外的合法目标。若取消则该牌伤害+1", + shuYing_Honghong:"轰轰", + shuYing_Honghong_info:"出牌阶段限一次,你展示牌堆中一张可以造成伤害的牌,然后你可以弃置一张可以造成伤害的手牌并使用该牌(这张牌造成的伤害为火元素)否则将此牌置入弃牌堆。结束阶段,若你此回合没有造成伤害且手牌没有伤害类牌则你获得一张伤害类牌", + shuYing_Lieyan:"烈焰", + shuYing_Lieyan_info:"锁定技,你受到火元素伤害时,免疫此伤害;当你造成火元素伤害时, 若你手牌不等于当前体力, 则你摸一张牌, 若你没有手牌则额外摸一张手牌;若相等则该伤害+1", + + } +}; + diff --git a/yuanshen/可莉/shuYing_Honghong1.mp3 b/yuanshen/可莉/shuYing_Honghong1.mp3 new file mode 100644 index 0000000..152f576 Binary files /dev/null and b/yuanshen/可莉/shuYing_Honghong1.mp3 differ diff --git a/yuanshen/可莉/shuYing_Honghong2.mp3 b/yuanshen/可莉/shuYing_Honghong2.mp3 new file mode 100644 index 0000000..a400330 Binary files /dev/null and b/yuanshen/可莉/shuYing_Honghong2.mp3 differ diff --git a/yuanshen/可莉/shuYing_Honghong3.mp3 b/yuanshen/可莉/shuYing_Honghong3.mp3 new file mode 100644 index 0000000..66d173e Binary files /dev/null and b/yuanshen/可莉/shuYing_Honghong3.mp3 differ diff --git a/yuanshen/可莉/shuYing_Lieyan1.mp3 b/yuanshen/可莉/shuYing_Lieyan1.mp3 new file mode 100644 index 0000000..38a8238 Binary files /dev/null and b/yuanshen/可莉/shuYing_Lieyan1.mp3 differ diff --git a/yuanshen/可莉/shuYing_Lieyan_Damage1.mp3 b/yuanshen/可莉/shuYing_Lieyan_Damage1.mp3 new file mode 100644 index 0000000..cafed33 Binary files /dev/null and b/yuanshen/可莉/shuYing_Lieyan_Damage1.mp3 differ diff --git a/yuanshen/可莉/shuYing_Lieyan_Damage2.mp3 b/yuanshen/可莉/shuYing_Lieyan_Damage2.mp3 new file mode 100644 index 0000000..ea62e43 Binary files /dev/null and b/yuanshen/可莉/shuYing_Lieyan_Damage2.mp3 differ diff --git a/yuanshen/可莉/shuYing_Lieyan_Damage3.mp3 b/yuanshen/可莉/shuYing_Lieyan_Damage3.mp3 new file mode 100644 index 0000000..06a038f Binary files /dev/null and b/yuanshen/可莉/shuYing_Lieyan_Damage3.mp3 differ diff --git a/yuanshen/可莉/shuYing_Pengpeng1.mp3 b/yuanshen/可莉/shuYing_Pengpeng1.mp3 new file mode 100644 index 0000000..25cc7d6 Binary files /dev/null and b/yuanshen/可莉/shuYing_Pengpeng1.mp3 differ diff --git a/yuanshen/可莉/shuYing_Pengpeng2.mp3 b/yuanshen/可莉/shuYing_Pengpeng2.mp3 new file mode 100644 index 0000000..c46467d Binary files /dev/null and b/yuanshen/可莉/shuYing_Pengpeng2.mp3 differ diff --git a/yuanshen/可莉/shuYing_Pengpeng3.mp3 b/yuanshen/可莉/shuYing_Pengpeng3.mp3 new file mode 100644 index 0000000..9b7c829 Binary files /dev/null and b/yuanshen/可莉/shuYing_Pengpeng3.mp3 differ