├── .gitignore ├── .vscode ├── launch.json └── settings.json ├── LICENSE ├── README.md ├── README.txt ├── _DE前置八炮.py ├── _FE二十二炮.py ├── _ME十三炮.py ├── _NE十五炮.py ├── _PE二十四炮.py ├── _PE半场十二炮.py ├── _PE最后之作.py ├── _PE经典十二炮.py ├── _PE经典四炮.py ├── _PE裸奔十六炮.py ├── _RE十六炮.py ├── _RE椭盘十四炮.py ├── _信息读取.py ├── pvz ├── __init__.py ├── core.py └── extra.py ├── pypi.txt ├── setup.cfg ├── setup.py ├── userdata ├── DE前置八炮 │ └── game1_13.dat ├── FE二十二炮 │ └── game1_13.dat ├── ME十三炮 │ └── game1_13.dat ├── NE十五炮 │ └── game1_13.dat ├── PE二十四炮 │ └── game1_13.dat ├── PE半场十二炮 │ └── game1_13.dat ├── PE最后之作 │ └── game1_13.dat ├── PE经典十二炮 │ └── game1_13.dat ├── PE经典四炮 │ └── game1_13.dat ├── PE裸奔十六炮 │ └── game1_13.dat ├── RE十六炮 │ └── game1_13.dat └── RE椭盘十四炮 │ └── game1_13.dat └── 在线文档.url /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Debug", 6 | "type": "python", 7 | "request": "launch", 8 | "program": "${file}", 9 | "console": "integratedTerminal", 10 | "env": { 11 | "PYTHONIOENCODING": "gbk" 12 | } 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.pythonPath": "C:\\Python34\\python.exe", 3 | "python.formatting.provider": "yapf", 4 | "python.formatting.yapfArgs": [ 5 | "--style", 6 | "{column_limit: 128}" 7 | ], 8 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # 植物大战僵尸脚本框架 3 | 4 | `pip install -U pvz` 5 | 6 | https://pvz.lmintlcx.com/scripts/ 7 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | 2 | # 植物大战僵尸脚本框架 3 | 4 | `pip install -U pvz` 5 | 6 | https://pvz.lmintlcx.com/scripts/ 7 | -------------------------------------------------------------------------------- /_DE前置八炮.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | 阵名: DE前置八炮 4 | 出处: https://tieba.baidu.com/p/3943536673 5 | 节奏: ch5: PP|I-PP|IPP-PP, (601|1437|1437) 6 | """ 7 | 8 | from pvz import * 9 | 10 | SetZombies(["普僵", "撑杆", "舞王", "冰车", "气球", "矿工", "小丑", "跳跳", "蹦极", "扶梯", "篮球", "白眼", "红眼"]) 11 | 12 | SelectCards(["咖啡豆", "寒冰菇", "复制冰", "樱桃", "窝瓜", "坚果", "花盆", "胆小菇", "阳光菇", "小喷菇"]) 13 | 14 | UpdatePaoList([(1, 1), (1, 5), (3, 1), (3, 5), (2, 5), (4, 5), (5, 1), (5, 5)]) 15 | 16 | AutoCollect() # 自动收集资源 17 | IceSpots([(2, 1), (4, 1), (3, 7)], 14 - 1) 18 | 19 | for wave in range(1, 21): 20 | print("当前操作波次: " + str(wave)) 21 | Prejudge(-195, wave) 22 | 23 | # PP 24 | if wave in (1, 4, 7, 10, 13, 16, 19): 25 | Until(-40) 26 | Pao((2, 9), (4, 9)) 27 | Until(601 + 10 - 298) 28 | Coffee() 29 | if wave in (19, ): 30 | Until(601 + 1437 - 200 - 373) 31 | Pao((2, 8.7), (4, 8.7)) 32 | # Until(601 + 1437 - 150) 33 | Until(4500 - 200 - 373) 34 | Pao((2, 8.4), (4, 8.4)) 35 | 36 | # I-PP 37 | elif wave in (2, 5, 8, 11, 14, 17): 38 | if wave == 2: 39 | Until(10 + 400) 40 | Card("倭瓜", (3, 9)) # 压冰车护存冰 41 | if wave == 11: 42 | Until(10 + 400 - 100) 43 | Card("樱桃", (3, 8)) # 炸冰车小偷护存冰 44 | if wave == 2: 45 | Until(750) 46 | Card("小喷菇", (3, 8)) # 垫撑杆 47 | Delay(100) 48 | Shovel((3, 8)) 49 | Until(1437 - 200 - 373) 50 | Pao((2, 8.7), (4, 8.7)) 51 | Until(1437 + 20 - 298) 52 | Coffee() 53 | 54 | # IPP-PP 55 | elif wave in (3, 6, 9, 12, 15, 18): 56 | Until(-150) 57 | Pao((2, 8.5), (4, 8.5)) 58 | Until(1437 - 200 - 373) 59 | Pao((2, 8.7), (4, 8.7)) 60 | if wave in (9, ): 61 | Until(1437 - 40) 62 | Pao((2, 8.7), (4, 8.7)) 63 | # Until(1437 + 601 + 1437 - 200 - 373) 64 | Until(4500 - 200 - 373) 65 | Pao((2, 8.4), (4, 8.4)) 66 | 67 | elif wave == 20: 68 | Until(-60) 69 | Pao((1, 9), (2, 9), (4, 9), (5, 9)) 70 | Delay(108) 71 | Pao((1, 8.8), (4, 8.8)) 72 | Until(300) 73 | Coffee() # 冰杀小偷 74 | Until(999) 75 | Card("复制冰", (4, 1)) # 最后一个存冰 76 | print("第 %s 波手动收尾." % wave) 77 | -------------------------------------------------------------------------------- /_FE二十二炮.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | 阵名: FE二十二炮 4 | 出处: None 5 | 节奏: ch9-57s: IPP-PPDD|PSD/PDC|IPP-PPDD|PSD/PDC|N+AD/DC|PD/PDC|PSD/PDC, (13.5|6|13.5|6|6|6|6) 6 | """ 7 | 8 | from pvz import * 9 | 10 | WriteMemory("int", 0x00679300, 0x0040FCED) # 取消点炮限制 11 | WriteMemory("unsigned short", 0xd231, 0x0041a68d) # 浓雾透视 12 | 13 | 14 | # Cannon Fodder 15 | # 下标 6/7 8/9 16 | # 垫材 花盆/胆小菇 阳光菇/小喷菇 17 | # 根据小喷是否可用来决定用哪一组垫材 18 | @RunningInThread 19 | def DianCai(): 20 | if ReadMemory("bool", 0x6A9EC0, 0x768, 0x144, 0x70 + 9 * 0x50): 21 | Card("阳光菇", (5, 9)) 22 | Card("小喷菇", (6, 9)) 23 | else: 24 | Card("花盆", (5, 9)) 25 | Card("胆小菇", (6, 9)) 26 | Delay(30) 27 | Shovel((5, 9), (6, 9)) 28 | 29 | 30 | SetZombies(["普僵", "撑杆", "舞王", "冰车", "海豚", "矿工", "跳跳", "蹦极", "扶梯", "篮球", "白眼", "红眼"]) 31 | 32 | SelectCards(["寒冰菇", "模仿冰", "毁灭菇", "睡莲", "樱桃", "坚果", "花盆", "胆小", "阳光", "小喷"]) 33 | 34 | UpdatePaoList([ 35 | (1, 1), 36 | (2, 1), 37 | (3, 1), 38 | (4, 1), 39 | (5, 1), 40 | (6, 1), 41 | (1, 3), 42 | (2, 3), 43 | (3, 3), 44 | (4, 3), 45 | (5, 3), 46 | (6, 3), 47 | (1, 5), 48 | (2, 5), 49 | (3, 5), 50 | (4, 5), 51 | (5, 5), 52 | (6, 5), 53 | (1, 7), 54 | (2, 7), 55 | # (3, 7), 56 | # (4, 7), 57 | (5, 7), 58 | (6, 7), 59 | ]) 60 | 61 | AutoCollect() # 自动收集资源 62 | 63 | # IPP-PPDD 64 | Prejudge(5 - 100, 1) # 本波 5cs 预判冰 65 | Card("寒冰菇", (2, 9)) 66 | Until(-15) 67 | Pao((1, 8.8)) # 上半场热过渡, 炸 1 路收跳跳 68 | Until(444 - 373) 69 | Pao((5, 7.4)) # 下半场热过渡, 右极限大概 7.43 70 | Until(1350 - 200 - 373) 71 | Pao((2, 9), (5, 8.7)) # 激活炮, 下半场落点左移收跳跳 72 | Delay(220) 73 | Pao((1, 8.6), (5, 8.6)) # 连续拦截之一, 落点左移 74 | 75 | # PSD/PDC 76 | Prejudge(-133, 2) # 133 预判对应上波过 220 继续拦截 77 | Pao((1, 9), (5, 9)) # 连续拦截之二 78 | Until(-95) 79 | Pao((2, 9)) # 上半场 S 80 | Until(-133 + 110) 81 | Pao((5, 7.8)) # 下半场 D 82 | Until(-95 + 110) 83 | Pao((1, 8.8)) # 上半场 D 84 | Until(601 + 5 - 100 - 320) # 下一波 5cs 预判冰 85 | Card("模仿者寒冰菇", (2, 9)) 86 | 87 | # IPP-PPDD 88 | # 相比 wave1 多了垫材操作 89 | Prejudge(-180, 3) 90 | DianCai() # 垫上一波撑杆 91 | Until(-15) 92 | Pao((1, 8.8)) 93 | Until(444 - 373) 94 | Pao((5, 7.4)) 95 | Until(5 + 600) # 全部解冻 96 | DianCai() # 垫红眼 97 | Until(1350 - 200 - 373) 98 | Pao((2, 9), (5, 8.7)) 99 | Delay(220) 100 | Pao((1, 8.6), (5, 8.6)) 101 | 102 | # PSD/PDC 103 | # 炸法同 wave2 104 | Prejudge(-133, 4) 105 | Pao((1, 9), (5, 9)) 106 | Until(-95) 107 | Pao((2, 9)) 108 | Until(-133 + 110) 109 | Pao((5, 7.8)) 110 | Until(-95 + 110) 111 | Pao((1, 8.8)) 112 | 113 | # N+AD/DC 114 | # 连续加速波下半场对应垫材 24 炮打法, 因此激活炮要尽早生效 115 | # 最早为 226 可全炸巨人, 相当于 147 预判炮 116 | # 这里 N 相当于激活炮, 上半场 A 相当于 S 117 | Prejudge(-145 + 83, 5) # 下半场使撑杆不啃炮的最早放垫材时间 118 | DianCai() # 垫上一波撑杆 119 | Until(-145 + 110) 120 | Pao((5, 7.8)) # 下半场 D 121 | Until(-95 + 110) 122 | Pao((1, 8.8)) # 上半场 D 123 | Until(-145 + 373 - 100) # 等效 145 预判炮 124 | Card("睡莲", (3, 9)) 125 | Card("毁灭菇", (3, 9)) 126 | Until(-95 + 373 - 100) # 等效 95 预判炮 127 | Card("樱桃炸弹", (2, 9)) 128 | 129 | # PD/PDC 130 | # 下半场对应垫材 24 炮打法, 上半场精准之舞 131 | Prejudge(-145, 6) 132 | Pao((5, 9)) # 下半场 P 133 | Delay(83) 134 | DianCai() # 垫上一波撑杆 135 | Until(-145 + 110) 136 | Pao((5, 7.8)) # 下半场 D 137 | Until(-14) 138 | Pao((2, 9)) # 上半场 P 139 | Delay(107) 140 | Pao((1, 7.8)) # 上半场 D 141 | 142 | # PSD/PDC 143 | Prejudge(-145, 7) 144 | Pao((5, 9)) # 下半场 P 145 | Until(-95) 146 | Pao((2, 9), (2, 9)) # 上半场 PS 147 | Until(-145 + 83) 148 | DianCai() # 垫上一波撑杆 149 | Until(-145 + 110) 150 | Pao((5, 7.8)) # 下半场 D 151 | Until(-95 + 110) 152 | Pao((1, 8.8)) # 上半场 D 153 | 154 | # IPP-PPDD 155 | Prejudge(-180, 8) 156 | DianCai() 157 | Until(5 - 100) 158 | Card("寒冰菇", (2, 9)) 159 | Until(-15) 160 | Pao((1, 8.8)) 161 | Until(444 - 373) 162 | Pao((5, 7.4)) 163 | Until(1350 + 15 - 100 - 320 - 373 - 1) # 571 164 | Pao((2, 8.2)) 165 | Until(5 + 600) # 全部解冻 166 | DianCai() 167 | Until(1350 - 200 - 373) # 777 168 | Pao((2, 9), (5, 8.7)) 169 | Until(1350 + 15 - 100 - 320) # 945 170 | Card("模仿者寒冰菇", (2, 9)) 171 | Until(1350 - 200 - 373 + 220) # 997 172 | Pao((1, 8.8), (5, 8.6)) # 上半场炸撑杆 173 | 174 | # 收尾波 175 | Prejudge(-133, 9) 176 | Pao((1, 9), (5, 9)) 177 | Until(-15) 178 | Pao((1, 9), (5, 9)) 179 | Until(1300 - 200 - 373) # 1350->1300 180 | Pao((2, 9), (5, 9)) 181 | Delay(220) 182 | Pao((1, 9), (5, 9)) 183 | Delay(220) 184 | Pao((1, 9), (5, 9)) 185 | Delay(600) # 等冰菇 CD 186 | Pao((2, 9), (5, 9)) 187 | Delay(700) # 清伴舞 188 | Pao((2, 9), (5, 9)) 189 | 190 | # PSD/PDC 191 | # 上半场 PSD, 下半场收撑杆省垫材 192 | Prejudge(-83, 10) # -83 193 | Pao((1, 9)) 194 | Until(-14) # -14 195 | Pao((5, 9)) 196 | Until(-83 + 104) # 394-373=21 197 | Pao((2, 9)) 198 | Until(-14 + 110) # 96 199 | Pao((5, 7.8)) 200 | Until(-83 + 104 + 110) # 131 201 | Pao((1, 8.8)) 202 | 203 | # IPP-PPDD 204 | Prejudge(5 - 100, 11) 205 | # 相比 wave1 多了垫红眼操作 206 | Card("寒冰菇", (2, 9)) 207 | Until(-15) 208 | Pao((1, 8.8)) 209 | Until(444 - 373) 210 | Pao((5, 7.4)) 211 | Until(5 + 600) # 全部解冻 212 | DianCai() 213 | Until(1350 - 200 - 373) 214 | Pao((2, 9), (5, 8.7)) 215 | Delay(220) 216 | Pao((1, 8.6), (5, 8.6)) 217 | 218 | # PSD/PDC 219 | Prejudge(-133, 12) # 133 预判对应上波过 220 继续拦截 220 | Pao((1, 9), (5, 9)) # 连续拦截之二 221 | Until(-95) 222 | Pao((2, 9)) # 上半场 S 223 | Until(-133 + 110) 224 | Pao((5, 7.8)) # 下半场 D 225 | Until(-95 + 110) 226 | Pao((1, 8.8)) # 上半场 D 227 | Until(601 + 5 - 100 - 320) # 下一波 5cs 预判冰 228 | Card("模仿者寒冰菇", (2, 9)) 229 | 230 | # IPP-PPDD 231 | # 相比 wave11 多了垫材操作 232 | Prejudge(-180, 13) 233 | DianCai() # 垫上一波撑杆 234 | Until(-15) 235 | Pao((1, 8.8)) 236 | Until(444 - 373) 237 | Pao((5, 7.4)) 238 | Until(5 + 600) # 全部解冻 239 | DianCai() # 垫红眼 240 | Until(1350 - 200 - 373) 241 | Pao((2, 9), (5, 8.7)) 242 | Delay(220) 243 | Pao((1, 8.6), (5, 8.6)) 244 | 245 | # PSD/PDC 246 | # 炸法同 wave12 247 | Prejudge(-133, 14) 248 | Pao((1, 9), (5, 9)) 249 | Until(-95) 250 | Pao((2, 9)) 251 | Until(-133 + 110) 252 | Pao((5, 7.8)) 253 | Until(-95 + 110) 254 | Pao((1, 8.8)) 255 | 256 | # N+AD/DC 257 | # 操作同 wave5, 弹坑改为 4-9 258 | Prejudge(-145 + 83, 15) 259 | DianCai() 260 | Until(-145 + 110) 261 | Pao((5, 7.8)) 262 | Until(-95 + 110) 263 | Pao((1, 8.8)) 264 | Until(-145 + 373 - 100) 265 | Card("睡莲", (4, 9)) 266 | Card("毁灭菇", (4, 9)) 267 | Until(-95 + 373 - 100) 268 | Card("樱桃炸弹", (2, 9)) 269 | 270 | # PD/PDC 271 | # 下半场对应垫材 24 炮打法, 上半场精准之舞 272 | Prejudge(-145, 16) 273 | Pao((5, 9)) # 下半场 P 274 | Delay(83) 275 | DianCai() # 垫上一波撑杆 276 | Until(-145 + 110) 277 | Pao((5, 7.8)) # 下半场 D 278 | Until(-14) 279 | Pao((2, 9)) # 上半场 P 280 | Delay(107) 281 | Pao((1, 7.8)) # 上半场 D 282 | 283 | # PSD/PDC 284 | Prejudge(-145, 17) 285 | Pao((5, 9)) # 下半场 P 286 | Until(-95) 287 | Pao((2, 9), (2, 9)) # 上半场 PS 288 | Until(-145 + 83) 289 | DianCai() # 垫上一波撑杆 290 | Until(-145 + 110) 291 | Pao((5, 7.8)) # 下半场 D 292 | Until(-95 + 110) 293 | Pao((1, 8.8)) # 上半场 D 294 | 295 | # IPP-PPDD 296 | Prejudge(-180, 18) 297 | DianCai() 298 | Until(5 - 100) 299 | Card("寒冰菇", (2, 9)) 300 | Until(-15) 301 | Pao((1, 8.8)) 302 | Until(444 - 373) 303 | Pao((5, 7.4)) 304 | Until(1350 + 15 - 100 - 320 - 373 - 1) # 571 305 | Pao((2, 8.2)) 306 | Until(5 + 600) # 全部解冻 307 | DianCai() 308 | Until(1350 - 200 - 373) # 777 309 | Pao((2, 9), (5, 8.7)) 310 | Until(1350 + 15 - 100 - 320) # 945 311 | Card("模仿者寒冰菇", (2, 9)) 312 | Until(1350 - 200 - 373 + 220) # 997 313 | Pao((1, 8.8), (5, 8.6)) # 上半场炸撑杆 314 | 315 | # 收尾波 316 | Prejudge(-133, 19) 317 | Pao((1, 9), (5, 9)) 318 | Until(-15) 319 | Pao((1, 9), (5, 9)) 320 | Until(1300 - 200 - 373) # 1350->1300 321 | Pao((2, 9), (5, 9)) 322 | Delay(220) 323 | Pao((1, 9), (5, 9)) 324 | Delay(220) 325 | Pao((1, 9), (5, 9)) 326 | Delay(600) # 等冰菇 CD 327 | Pao((2, 9), (5, 9)) 328 | Delay(700) # 清伴舞 329 | Pao((2, 9), (5, 9)) 330 | 331 | # PP-PPPPPPPP 332 | Prejudge(-150, 20) 333 | Pao((4, 7)) # 炮炸珊瑚 334 | Until(-60) # 等到刷新前 60cs 335 | Pao((2, 9), (5, 9)) 336 | Delay(108) 337 | Pao((1, 8.8), (5, 8.8)) 338 | Delay(108) 339 | Pao((1, 8.6), (5, 8.6)) 340 | Delay(108) 341 | Pao((2, 8.4), (5, 8.4)) # 炸小偷 342 | print("最后一大波手动收尾.") 343 | -------------------------------------------------------------------------------- /_ME十三炮.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | 阵名: ME十三炮 4 | 出处: https://tieba.baidu.com/p/5288033944 5 | 节奏: C5u-35s: PPD|PPD|PPD|IP-PPD, (6|6|6|17) 6 | """ 7 | 8 | from pvz import * 9 | 10 | 11 | @RunningInThread 12 | def I(): 13 | Card("花盆", (3, 7)) 14 | Card("寒冰菇", (3, 7)) 15 | Delay(100 + 1) 16 | Shovel((3, 7)) 17 | 18 | 19 | @RunningInThread 20 | def II(): 21 | Card("花盆", (3, 7)) 22 | Card("复制冰", (3, 7)) 23 | Delay(320 + 100 + 1) 24 | Shovel((3, 7)) 25 | 26 | 27 | SetZombies(["普僵", "撑杆", "橄榄", "冰车", "小丑", "气球", "跳跳", "蹦极", "扶梯", "篮球", "白眼", "红眼"]) 28 | 29 | SelectCards(["玉米", "玉米炮", "三叶草", "保护伞", "樱桃", "倭瓜", "坚果", "花盆", "寒冰菇", "复制冰"]) 30 | 31 | UpdatePaoList([ 32 | (1, 3), (1, 5), (1, 1), \ 33 | (2, 3), (2, 5), (2, 1), \ 34 | (3, 3), (3, 5), (3, 1), \ 35 | (4, 6), \ 36 | (4, 1), (5, 6), (5, 1) \ 37 | ]) 38 | 39 | AutoCollect() # 自动收集资源 40 | 41 | for wave in range(1, 21): 42 | print("当前操作波次: " + str(wave)) 43 | 44 | if wave in (20, ): 45 | Prejudge(10 - 320, wave) 46 | II() # 冰消空降 47 | Until(100) 48 | RoofPao((5, 8)) 49 | Until(800) 50 | RoofPao((2, 9), (2, 9), (2, 9), (2, 9)) 51 | Until(1000) 52 | RoofPao((4, 9), (4, 9), (4, 9), (4, 9)) 53 | print("第 %s 波手动收尾." % wave) 54 | 55 | # IP-PPD 56 | elif wave in (4, 8, 10, 14, 18): 57 | Prejudge(-150, wave) 58 | if wave in (4, 10, 18): # 本波原版冰 59 | Until(5 - 100) 60 | I() 61 | Until(100) 62 | RoofPao((5, 8)) 63 | Until(1700 - 200 - 373) 64 | RoofPao((2, 8.5), (4, 8.5)) 65 | Delay(230) # Until(1700 - 200 - 373 + 230) # 减速延迟 230 炸小鬼 66 | RoofPao((2, 7)) 67 | 68 | # PPD 69 | else: # elif wave in (1, 2, 3, 5, 6, 7, 9, 11, 12, 13, 15, 16, 17, 19): 70 | Prejudge(10, wave) # 刷新后 71 | RoofPao((2, 8.5), (4, 8.5)) 72 | Delay(130) # Until(10 + 130) # 原速延迟 130 炸小鬼 73 | RoofPao((2, 7.7)) 74 | if wave in (7, 13): # 下一波的复制冰 75 | Until(601 + 5 - 100 - 320) 76 | II() 77 | if wave in (9, 19): # 收尾 78 | Until(601) 79 | RoofPao((2, 8.5), (4, 8.5)) 80 | Delay(130) 81 | RoofPao((2, 7.5)) 82 | # 自动操作收尾 83 | Until(601 + 601) 84 | RoofPao((2, 8.5)) 85 | Delay(300) 86 | RoofPao((5, 8)) 87 | Delay(500) 88 | RoofPao((5, 8)) 89 | -------------------------------------------------------------------------------- /_NE十五炮.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | 阵名: NE十五炮 4 | 出处: https://tieba.baidu.com/p/1067040250 5 | 节奏: C8u: IPP-PP|PADC|PPDD|IPP-PP|NDC|PPDD, (13|6|6|13|6|6) 6 | """ 7 | 8 | from pvz import * 9 | 10 | 11 | @RunningInThread 12 | def DianCai(): 13 | Card("小喷", (4, 9)) 14 | Card("阳光", (5, 9)) 15 | Delay(120) 16 | Shovel((4, 9)) 17 | Shovel((5, 9)) 18 | 19 | 20 | SetZombies(["普僵", "撑杆", "舞王", "小丑", "气球", "矿工", "跳跳", "蹦极", "扶梯", "篮球", "白眼", "红眼"]) 21 | 22 | SelectCards(["复制冰", "原版冰", "核蘑菇", "樱桃", "倭瓜", "墓碑", "南瓜", "三叶草", "阳光菇", "小喷菇"]) 23 | 24 | # UpdatePaoList([ 25 | # (1, 1), (2, 1), (3, 1), (4, 1), (5, 1), 26 | # (1, 5), (2, 5), (3, 5), (4, 5), (5, 5), 27 | # (1, 7), (2, 7), (3, 7), (4, 7), (5, 7), 28 | # ]) 29 | 30 | AutoCollect() # 自动收集资源 31 | 32 | for wave in range(1, 21): 33 | print("当前操作波次: " + str(wave)) 34 | Prejudge(-195, wave) 35 | 36 | # PPD 37 | if wave in (10, ): 38 | Until(-56) 39 | Pao((2, 9), (4, 9)) 40 | Until(0) 41 | Pao((2, 9)) 42 | 43 | # IPP-PP 44 | elif wave in (1, 7, 11, 17): 45 | Until(-150) 46 | Pao((2, 8.5), (4, 8.5)) 47 | Until(5 - 100) 48 | Card("寒冰菇", (1, 9)) 49 | if wave == 11: 50 | Until(-150 + 83) 51 | DianCai() 52 | Until(1300 - 200 - 373) 53 | Pao((2, 9), (4, 9)) 54 | 55 | # PADC 56 | elif wave in (2, 8, 12, 18): 57 | Until(-95) 58 | Pao((2, 9)) 59 | Until(-12) 60 | Pao((2, 9)) 61 | Until(-95 + 373 - 100) 62 | Card("樱桃", (5, 9)) 63 | 64 | # PPDD 65 | elif wave in (3, 9, 13, 19): 66 | Until(-95) 67 | Pao((2, 9), (5, 9)) 68 | Until(-15) 69 | Pao((1, 9), (4, 9)) 70 | Until(0) 71 | DianCai() 72 | Until(601 + 44 - 100 - 320) # 44cs 预判冰 73 | Card("模仿者寒冰菇", (1, 9)) 74 | 75 | if wave in (9, 19): 76 | Until(601 - 150) 77 | Pao((4, 9)) 78 | Delay(450) 79 | Pao((1, 9)) 80 | Until(601 + 1300 - 200 - 373) 81 | Delay(300) 82 | Pao((2, 9), (5, 9)) 83 | 84 | # IPP-PP 85 | elif wave in (4, 14): 86 | Until(-150) 87 | Pao((2, 8.5), (4, 8.5)) 88 | Until(1300 - 200 - 373) 89 | Pao((2, 9), (4, 9)) 90 | 91 | # NDC 92 | elif wave in (5, 15): 93 | Until(-12) 94 | Pao((2, 9)) 95 | Until(-95 + 373 - 100) 96 | Card("核蘑菇", (3, 9) if wave == 5 else (2, 9)) 97 | 98 | # PPDD 99 | elif wave in (6, 16): 100 | Until(-95) 101 | Pao((2, 9), (5, 9)) 102 | Until(-12) 103 | Pao((1, 9), (4, 9)) 104 | Until(0) 105 | DianCai() 106 | 107 | elif wave == 20: 108 | Until(-56) 109 | Pao((1, 9), (4, 9)) 110 | Until(-35) 111 | Pao((2, 9), (5, 9)) # 炸墓碑冒出的僵尸 112 | Until(601 - 100 - 83) 113 | Pao((1, 8.3), (4, 8.3)) 114 | Until(601 - 100) 115 | # 冰杀小偷 116 | with MouseLock(): 117 | SafeClick() 118 | ClickSeed("寒冰菇") 119 | ClickGrid((1, 9)) 120 | ClickGrid((2, 9)) 121 | ClickGrid((3, 9)) 122 | ClickGrid((4, 9)) 123 | ClickGrid((5, 9)) 124 | SafeClick() 125 | Delay(100) 126 | Pao((2, 8.2), (5, 8.2)) 127 | print("第 %s 波手动收尾." % wave) 128 | -------------------------------------------------------------------------------- /_PE二十四炮.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | 阵名: PE二十四炮 4 | 出处: https://tieba.baidu.com/p/991306518 5 | 节奏: P6: PPDD|PPI|PPSSDD|PPDD|PPI|PPSSDD 6 | """ 7 | 8 | from pvz import * 9 | 10 | WriteMemory("int", 0x00679300, 0x0040FCED) # 取消点炮限制 11 | 12 | SetZombies(["普僵", "撑杆", "舞王", "冰车", "海豚", "矿工", "跳跳", "蹦极", "扶梯", "篮球", "白眼", "红眼"]) 13 | 14 | SelectCards(["复制冰", "寒冰菇", "咖啡豆", "南瓜", "坚果", "窝瓜", "花盆", "胆小", "阳光", "小喷"]) 15 | 16 | # UpdatePaoList( 17 | # [ 18 | # (1, 1), (1, 3), (1, 5), (1, 7), 19 | # (2, 1), (2, 3), (2, 5), (2, 7), 20 | # (3, 1), (3, 3), (3, 5), (3, 7), 21 | # (4, 1), (4, 3), (4, 5), (4, 7), 22 | # (5, 1), (5, 3), (5, 5), (5, 7), 23 | # (6, 1), (6, 3), (6, 5), (6, 7), 24 | # ] 25 | # ) 26 | # UpdatePaoList([(r, c) for r in range(1, 7) for c in range(1, 8, 2)]) 27 | 28 | AutoCollect() # 自动收集资源 29 | IceSpots([(4, 9)], 6) 30 | 31 | for wave in range(1, 21): 32 | print("当前操作波次: " + str(wave)) 33 | 34 | # 精准之舞 PPDD 35 | if wave in (1, 4, 7, 11, 14, 17): 36 | Prejudge(-14, wave) 37 | Pao((2, 9), (5, 9)) 38 | Delay(107) 39 | Pao((1, 7.7), (5, 7.7)) 40 | 41 | # 冰之旋舞 PPI 42 | elif wave in (2, 5, 8, 12, 15, 18): 43 | Prejudge(-95, wave) 44 | Pao((2, 9), (5, 9)) 45 | Delay(373 - 100 - 198) # 冰同步于炮生效 46 | Coffee() 47 | 48 | # 六神乱舞 PPSSDD 49 | elif wave in (3, 6, 9, 13, 16, 19): 50 | Prejudge(-95, wave) 51 | Pao((2, 9), (5, 9), (2, 9), (5, 9)) 52 | Delay(108) 53 | Pao((1, 8.8), (5, 8.8)) 54 | if wave in (9, 19): # 收尾 55 | Until(601 - 15) 56 | Pao((2, 9), (5, 9)) 57 | 58 | # 大波推迟 PPSSDD 59 | elif wave == 10: 60 | Prejudge(-56, wave) 61 | Pao((2, 9), (5, 9), (2, 9), (5, 9)) 62 | Delay(108) 63 | Pao((1, 8.8), (5, 8.8)) 64 | 65 | elif wave == 20: 66 | Prejudge(-150, wave) 67 | Pao((4, 6), (4, 8)) # 炮炸珊瑚 68 | Delay(90) # Until(-60) 69 | Pao((1, 9), (2, 9), (5, 9), (6, 9)) 70 | Delay(108) 71 | Pao((1, 9), (2, 9), (5, 9), (6, 9)) 72 | print("第 %s 波手动收尾." % wave) 73 | -------------------------------------------------------------------------------- /_PE半场十二炮.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | 阵名: PE半场十二炮 4 | 出处: https://tieba.baidu.com/p/1801759994 5 | 节奏: ch4: I+BC/d-PDD/P|I+BC/d-PDD/P, (18|18) 6 | """ 7 | 8 | from pvz import * 9 | 10 | WriteMemory("int", 0x00679300, 0x0040FCED) # 取消点炮限制 11 | 12 | 13 | # 种垫铲垫 14 | @RunningInThread 15 | def DianCai(): 16 | Card("小喷", (1, 9)) 17 | Card("阳光", (2, 9)) 18 | Delay(100) 19 | Shovel((1, 9)) 20 | Shovel((2, 9)) 21 | 22 | 23 | # 烧小偷 24 | @RunningInThread 25 | def 口吐金蛇(): 26 | # 等第 10 波刷新 27 | while ReadMemory("int", 0x6A9EC0, 0x768, 0x557C) < 10: 28 | Sleep(1) 29 | Delay(400) 30 | Card("睡莲", (4, 9)) 31 | Card("辣椒", (4, 9)) 32 | Delay(100 + 1) 33 | Shovel((4, 9)) 34 | # 等第 20 波刷新 35 | while ReadMemory("int", 0x6A9EC0, 0x768, 0x557C) < 20: 36 | Sleep(1) 37 | Delay(400) 38 | Card("睡莲", (4, 9)) 39 | Card("辣椒", (4, 9)) 40 | Delay(100 + 1) 41 | Shovel((4, 9)) 42 | 43 | 44 | 45 | @RunningInThread 46 | def NutsFixer(spots, seed): 47 | """ 48 | 坚果类植物修复. 在单独的子线程运行. 49 | @参数 spots(list): 位置, 包括若干个 (行, 列) 元组. 50 | @参数 seed(str): 卡片名称, 可选值 ["坚果", "高坚果", "南瓜头"]. 51 | @示例: 52 | >>> NutsFixer([(3, 8), (4, 8)], "高坚果") 53 | >>> NutsFixer([(4, 5),(4, 6),(4, 7),(4, 8)], "南瓜头") 54 | """ 55 | 56 | # 1.草地 2.裸地 3.泳池 57 | # 16.睡莲 33.花盆 58 | # 3.坚果 23.高坚果 30.南瓜头 59 | 60 | from pvz.core import debug 61 | from pvz.core import info 62 | from pvz.core import warning 63 | from pvz.core import error 64 | from pvz.core import read_memory 65 | from pvz.core import thread_sleep_for 66 | from pvz.extra import get_seed_by_name 67 | from pvz.extra import get_index_by_name 68 | from pvz.extra import get_block_type 69 | from pvz.extra import get_plants_croods 70 | from pvz.extra import use_seed 71 | from pvz.extra import game_scene 72 | from pvz.extra import game_delay_for 73 | 74 | while read_memory("int", 0x6A9EC0, 0x7FC) != 3: 75 | thread_sleep_for(1) 76 | 77 | info("启动坚果类植物修复线程.") 78 | 79 | seed_type = get_seed_by_name(seed) # 根据名称得到卡片代号 80 | if seed_type not in (3, 23, 30, 3 + 48, 23 + 48, 30 + 48): 81 | error("自动修复只支持 坚果/高坚果/南瓜头.") 82 | seed_index = get_index_by_name(seed) # 获取卡片的位置, 数组下标需要 -1 83 | if seed_index is None: 84 | error("卡槽没有 %s 卡片, 退出坚果类植物修复线程." % seed) 85 | 86 | seed_cost = read_memory("int", 0x69F2C0 + seed_type * 0x24) # 卡片价格 87 | seed_recharge = read_memory("int", 0x69F2C4 + seed_type * 0x24) # 卡片冷却 88 | # HP_MAX = 4000 if seed_type in (3, 30) else 8000 89 | if seed_type == 3: # Wall-nut 90 | HP_MAX = read_memory("int", 0x45E1A7) 91 | elif seed_type == 23: # Tall-nut 92 | HP_MAX = read_memory("int", 0x45E215) 93 | else: # 30 Pumpkin 94 | HP_MAX = read_memory("int", 0x45E445) 95 | LINIT = int(HP_MAX * 0.1) if len(spots) < 2 else int(HP_MAX * 0.3) # TODO 96 | 97 | # 补种函数 98 | def fix(spot): 99 | slots_offset = read_memory("unsigned int", 0x6A9EC0, 0x768, 0x144) 100 | seed_usable = read_memory("bool", slots_offset + 0x70 + (seed_index - 1) * 0x50) # 该卡片是否可用 101 | sun = read_memory("int", 0x6A9EC0, 0x768, 0x5560) # 当前阳光 102 | if seed_usable and sun >= seed_cost: 103 | while read_memory("bool", 0x6A9EC0, 0x768, 0x164): # 处于暂停 104 | thread_sleep_for(1) 105 | else: 106 | return False 107 | success = False 108 | if get_block_type(spot) == 3 and (16, spot[0], spot[1]) not in get_plants_croods(): 109 | seed_lilypad_index = get_index_by_name("睡莲") 110 | if seed_lilypad_index is None: 111 | warning("卡片 睡莲 不在卡槽中.") 112 | else: 113 | seed_lilypad_usable = read_memory("bool", slots_offset + 0x70 + (seed_lilypad_index - 1) * 0x50) 114 | if seed_lilypad_usable: 115 | use_seed("睡莲", spot) 116 | use_seed(seed, spot) 117 | success = True 118 | elif game_scene in (4, 5) and (33, spot[0], spot[1]) not in get_plants_croods(): 119 | seed_flowerpot_index = get_index_by_name("花盆") 120 | if seed_flowerpot_index is None: 121 | warning("卡片 花盆 不在卡槽中.") 122 | else: 123 | seed_flowerpot_usable = read_memory("bool", slots_offset + 0x70 + (seed_flowerpot_index - 1) * 0x50) 124 | if seed_flowerpot_usable: 125 | use_seed("花盆", spot) 126 | use_seed(seed, spot) 127 | success = True 128 | else: 129 | use_seed(seed, spot) 130 | success = True 131 | thread_sleep_for(1) 132 | return success 133 | 134 | while read_memory("int", 0x6A9EC0, 0x7FC) == 3 and read_memory("int", 0x6A9EC0, 0x768, 0x557C) < 20: 135 | # while read_memory("int", 0x6A9EC0, 0x7FC) == 3: 136 | 137 | croods_which_has_plant = [] 138 | plants = get_plants_croods() 139 | for plant_type, plant_row, plant_col in plants: 140 | # 需要修复的植物是 南瓜 时, 只有南瓜才算占位 141 | # 需要修复的植物是 坚果/高坚果 时, 不是 睡莲/花盆/南瓜 就算占位 142 | if (seed_type in (30, 30 + 48) and plant_type in (30, 30 + 48)) or (seed_type not in (30, 30 + 48) 143 | and plant_type not in (16, 30, 33)): 144 | croods_which_has_plant.append((plant_row, plant_col)) 145 | if plant_type == 47: # 玉米炮占两格 146 | croods_which_has_plant.append((plant_row, plant_col + 1)) 147 | spot_which_has_plant = [i for i in spots if i in croods_which_has_plant] 148 | 149 | for spot in spots: 150 | # 位置有植物但不是坚果/高坚果/南瓜 151 | if spot in spot_which_has_plant and (seed_type, spot[0], spot[1]) not in plants: 152 | thread_sleep_for(1) 153 | continue 154 | # 位置有植物而且是坚果/高坚果/南瓜 155 | elif spot in spot_which_has_plant and (seed_type, spot[0], spot[1]) in plants: 156 | plants_count_max = read_memory("unsigned int", 0x6A9EC0, 0x768, 0xB0) 157 | plants_offset = read_memory("unsigned int", 0x6A9EC0, 0x768, 0xAC) 158 | plants_index = None 159 | for i in range(plants_count_max): 160 | plants_disappeared = read_memory("bool", plants_offset + 0x141 + 0x14C * i) 161 | plants_crushed = read_memory("bool", plants_offset + 0x142 + 0x14C * i) 162 | plants_type = read_memory("int", plants_offset + 0x24 + 0x14C * i) 163 | plants_row = read_memory("int", plants_offset + 0x1C + 0x14C * i) 164 | plants_col = read_memory("int", plants_offset + 0x28 + 0x14C * i) 165 | if (not plants_disappeared and not plants_crushed and plants_type == seed_type and plants_row == spot[0] - 1 166 | and plants_col == spot[1] - 1): # 特定位置 167 | plants_index = i 168 | debug("位置 %s 的植物 %s 下标为 %d." % (str(spot), seed, plants_index)) 169 | plant_hp = read_memory("int", plants_offset + 0x40 + 0x14C * plants_index) 170 | debug("位置 %s 的植物 %s 血量为 %d." % (str(spot), seed, plant_hp)) 171 | if plant_hp < LINIT: 172 | if fix(spot): # 种植 173 | game_delay_for(seed_recharge + 1) 174 | break 175 | # 位置没有植物 176 | elif spot not in spot_which_has_plant: 177 | if fix(spot): # 种植 178 | game_delay_for(seed_recharge + 1) 179 | break 180 | 181 | game_delay_for(10) 182 | 183 | info("停止坚果类植物修复线程.") 184 | 185 | 186 | 187 | ### 188 | 189 | SetZombies(["普僵", "撑杆", "舞王", "冰车", "海豚", "矿工", "跳跳", "蹦极", "扶梯", "篮球", "白眼", "红眼"]) 190 | 191 | SelectCards(["白冰", "冰菇", "咖啡", "荷叶", "南瓜", "樱桃", "辣椒", "倭瓜", "阳光", "小喷"]) 192 | 193 | UpdatePaoList([ 194 | (1, 3), (2, 3), (3, 3), \ 195 | (1, 5), (2, 5), (3, 5), \ 196 | (1, 7), (2, 7), (3, 7), \ 197 | (1, 1), (2, 1), (3, 1), \ 198 | ]) 199 | 200 | 201 | while ReadMemory("int", 0x6A9EC0, 0x7FC) != 3: # 还没进入战斗界面 202 | Sleep(1) 203 | while ReadMemory("bool", 0x6A9EC0, 0x768, 0x164): # 处于暂停状态 204 | Sleep(1) 205 | Card("寒冰菇", (5, 5)) # 临时存冰 206 | Card("睡莲", (3, 9)) # 临时存冰位 207 | Card("南瓜头", (3, 9)) # 其实不需要 208 | 209 | 210 | AutoCollect() # 自动收集资源 211 | IceSpots([(4, 5), (4, 6), (4, 7), (4, 8), (3, 9)], 17 - 1) 212 | NutsFixer([(4, 5), (4, 6), (4, 7), (4, 8)], "南瓜头") 213 | 口吐金蛇() 214 | 215 | 216 | for wave in range(1, 21): 217 | print("当前操作波次: " + str(wave)) 218 | Prejudge(-190, wave) 219 | 220 | if wave in (1, 10): 221 | Until(-95) 222 | Pao((1, 9)) 223 | Until(-15) 224 | Pao((2, 9), (5, 9)) 225 | Until(-15 + 110) 226 | Pao((5, 7.7)) 227 | Until(-15 + 110 + 373 - 100) # 368 228 | Card("樱桃", (1, 9)) 229 | 230 | elif wave == 20: 231 | Until(-150) 232 | Pao((4, 7)) 233 | Until(-60) # 等到刷新前 60cs 234 | Pao((2, 9), (5, 9), (2, 9), (5, 9)) 235 | Until(-60 + 110) 236 | Pao((1, 8.8), (2, 8.8)) # 炮不够 == 237 | Until(-60 + 110 + 373 - 100) 238 | Card("樱桃", (5, 9)) 239 | print("第 %s 波手动收尾." % wave) 240 | # Pao((5, 8)) 241 | Until(5500 + 100) 242 | Shovel((3, 9), (3, 9)) # 跳白字后铲掉 243 | 244 | else: 245 | Until(-133) 246 | Pao((1, 8.0)) # 拦截上波红眼, 分离部分快速僵尸 247 | Until(360 - 373) 248 | Pao((2, 8.15)) # 无冰分离 249 | Until(360 - 298) # 360cs 反应冰 250 | Coffee() if wave not in (2,) else Card("咖啡豆", (5, 5)) 251 | Until(360 + 500 - 373) 252 | # WZ_PNT = (5, 2.7) if wave in (3, 12) else (5, 3) # 尾炸落点 253 | WZ_PNT = (5, 3) 254 | Pao(WZ_PNT) if wave not in (2, 11) else None # 下半场尾炸 255 | Until(1800 - 200 - 373) 256 | Pao((2, 9), (5, 8.1)) # 激活炸 257 | Delay(10) 258 | DianCai() # 垫撑杆 259 | Until(1800 - 200 - 373 + 220) 260 | Pao((1, 8.2)) # 秒白眼, 触发红眼投掷 261 | 262 | if wave in (9, 19): # 收尾波次 263 | Until(1800 - 133) 264 | Pao((1, 8.0)) 265 | Until(1800 + 360 - 373) 266 | Pao((2, 9)) 267 | Until(1800 + 360 + 500 - 373) 268 | Pao((5, 2.5)) 269 | Until(1800 + 1800 - 200 - 373) 270 | Pao((5, 6)) 271 | Delay(110) 272 | Pao((5, 6)) 273 | Delay(110) 274 | Pao((5, 3)) 275 | Until(4500 - 200 - 373) 276 | Pao((5, 5)) 277 | Skip(2) if wave == 9 else None # 中场调整炮序 278 | -------------------------------------------------------------------------------- /_PE最后之作.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | 阵名: PE最后之作 4 | 出处: https://tieba.baidu.com/p/5102612180 5 | 节奏: ch5u-35.62s: PPDD|I-PPdd|IPP-PPDDCC, (6|13|16.62) 6 | """ 7 | 8 | from pvz import * 9 | 10 | WriteMemory("int", 0x00679300, 0x0040FCED) # 取消点炮限制 11 | 12 | # @RunningInThread 13 | # def DianCai(): 14 | # diancai_list = ["保护伞", "胆小", "阳光", "小喷"] 15 | # diancai_spot = [(1, 8), (2, 8), (5, 8), (6, 8)] 16 | # import random 17 | # random.shuffle(diancai_list) 18 | # for i in range(4): 19 | # Card(diancai_list[i], diancai_spot[i]) 20 | # Delay(10) 21 | # for i in range(4): 22 | # Shovel(diancai_spot[i]) 23 | 24 | 25 | @RunningInThread 26 | def DianCai(): 27 | Card("保护伞", (1, 8)) 28 | Card("胆小", (2, 8)) 29 | Card("阳光", (5, 8)) 30 | Card("小喷", (6, 8)) 31 | Delay(10) 32 | Shovel((1, 8)) 33 | Shovel((2, 8)) 34 | Shovel((5, 8)) 35 | Shovel((6, 8)) 36 | 37 | 38 | @RunningInThread 39 | def TallNutKeeper(spots): 40 | """ 41 | 泳池水路 7/8 列临时高坚果阻挡海豚. 42 | 残血或被偷后自动补, 两行均有时中场种伞保护, 关底大波刷出后停止运行. 43 | @参数 spots(list[(int, int)]): 坐标. 44 | 示例: 45 | >>> TallNutKeeper([(3, 7)]) 46 | >>> TallNutKeeper([(3, 8), (4, 8)]) 47 | """ 48 | 49 | slots_offset = ReadMemory("unsigned int", 0x6A9EC0, 0x768, 0x144) 50 | slots_count = ReadMemory("unsigned int", 0x6A9EC0, 0x768, 0x144, 0x24) 51 | 52 | # 睡莲/高坚果/保护伞的卡槽数组下标 53 | lilypad_seed = None 54 | tallnut_seed = None 55 | umbrella_seed = None 56 | for i in range(slots_count): 57 | seed_type = ReadMemory("int", slots_offset + 0x5C + i * 0x50) 58 | if seed_type == 16: 59 | lilypad_seed = i 60 | elif seed_type == 23: 61 | tallnut_seed = i 62 | elif seed_type == 37: 63 | umbrella_seed = i 64 | 65 | # 返回值 (bool): 当前游戏是否暂停 66 | def GamePaused(): 67 | return ReadMemory("bool", 0x6A9EC0, 0x768, 0x164) 68 | 69 | # 返回值 (int): 游戏界面 70 | def GameUI(): 71 | return ReadMemory("int", 0x6A9EC0, 0x7FC) 72 | 73 | # 返回值 (int): 已刷新波数 74 | def CurrentWave(): 75 | return ReadMemory("int", 0x6A9EC0, 0x768, 0x557C) 76 | 77 | # 返回值 (int): 下一波刷新倒计时 78 | def WaveCountdown(): 79 | return ReadMemory("int", 0x6A9EC0, 0x768, 0x559C) 80 | 81 | # 获取指定位置的高坚果下标, 没有返回 None 82 | def GetTheTallnutIndex(r, c): 83 | plants_count_max = ReadMemory("int", 0x6A9EC0, 0x768, 0xB0) 84 | plants_offset = ReadMemory("unsigned int", 0x6A9EC0, 0x768, 0xAC) 85 | for i in range(plants_count_max): 86 | plant_disappeared = ReadMemory("bool", plants_offset + 0x141 + 0x14C * i) 87 | plant_crushed = ReadMemory("bool", plants_offset + 0x142 + 0x14C * i) 88 | plant_type = ReadMemory("int", plants_offset + 0x24 + 0x14C * i) 89 | plant_row = ReadMemory("int", plants_offset + 0x1C + 0x14C * i) 90 | plant_col = ReadMemory("int", plants_offset + 0x28 + 0x14C * i) 91 | if (not plant_disappeared and not plant_crushed \ 92 | and plant_type == 23 and plant_row == (r - 1) and plant_col == (c - 1)): 93 | return i 94 | 95 | # 更新高坚果 96 | def UpdateTallnut(r, c): 97 | slots_offset = ReadMemory("unsigned int", 0x6A9EC0, 0x768, 0x144) 98 | seed_usable = ReadMemory("bool", slots_offset + 0x70 + tallnut_seed * 0x50) # 该卡片是否可用 99 | seed_cost = ReadMemory("int", 0x69F2C0 + 23 * 0x24) # 卡片价格 100 | sun = ReadMemory("int", 0x6A9EC0, 0x768, 0x5560) # 当前阳光 101 | if seed_usable and sun >= seed_cost: 102 | while GamePaused(): 103 | Delay(1) 104 | Card("高坚果", (r, c)) 105 | 106 | # 开场种 107 | Delay(800) # TODO 让给存冰位先 108 | for spot in spots: 109 | while GamePaused(): 110 | Delay(1) 111 | Card("睡莲", spot) 112 | Card("高坚果", spot) 113 | if spot != spots[-1]: # 不是最后一个 114 | Delay(3000 + 1) 115 | Delay(1) 116 | 117 | # 保护伞状态, 种植于第一个高坚果前一列 118 | umbrella_planted = False # 已经种植 119 | umbrella_shoveled = False # 已经铲除 120 | umbrella_row, umbrella_col = spots[0][0], spots[0][1] + 1 121 | 122 | # 主循环, 第 20 波刷新前持续运行 123 | while GameUI() == 3 and CurrentWave() < 20: 124 | 125 | # 两列均有高坚果时, 第 10 波刷新前种伞, 第 11 波铲掉 126 | if len(spots) == 2 and umbrella_seed is not None: 127 | if not umbrella_planted and 9 <= CurrentWave() <= 10 and WaveCountdown() <= 600: 128 | while GamePaused(): 129 | Delay(1) 130 | # print("Planting Umbrella Leaf to protect 2 Tall-nuts.") 131 | Card("睡莲", (umbrella_row, umbrella_col)) 132 | Card("伞叶", (umbrella_row, umbrella_col)) 133 | umbrella_planted = True 134 | elif not umbrella_shoveled and CurrentWave() >= 11: 135 | while GamePaused(): 136 | Delay(1) 137 | # print("Shovel Umbrella Leaf.") 138 | Shovel((umbrella_row, umbrella_col)) 139 | Shovel((umbrella_row, umbrella_col)) 140 | umbrella_shoveled = True 141 | 142 | # 遍历指定要种植高坚果的格点 143 | for spot in spots: 144 | row, col = spot 145 | index = GetTheTallnutIndex(row, col) # 获取该格点的高坚果下标 146 | if index is None: 147 | # 没有则补种高坚果 148 | UpdateTallnut(row, col) 149 | Delay(3000 + 1) 150 | else: 151 | plants_offset = ReadMemory("unsigned int", 0x6A9EC0, 0x768, 0xAC) 152 | plant_hp = ReadMemory("int", plants_offset + 0x40 + 0x14C * index) 153 | if plant_hp < 2000: 154 | # 血量低于一定值则修复高坚果 155 | UpdateTallnut(row, col) 156 | Delay(3000 + 1) 157 | 158 | Sleep(100) # 每 1s 检测一次 159 | 160 | 161 | ### 162 | ### 163 | ### 164 | 165 | SetZombies(["普僵", "撑杆", "舞王", "冰车", "海豚", "矿工", "跳跳", "蹦极", "扶梯", "篮球", "白眼", "红眼"]) 166 | 167 | SelectCards(["咖啡豆", "寒冰菇", "复制冰", "睡莲", "高坚果", "樱桃", "保护伞", "胆小", "阳光", "小喷"]) 168 | 169 | # UpdatePaoList([ 170 | # (1, 1), (2, 1), (5, 1), (6, 1), \ 171 | # (1, 3), (2, 3), (5, 3), (6, 3), \ 172 | # (1, 6), (2, 6), (3, 6), (4, 6), (5, 6), (6, 6), \ 173 | # ]) 174 | 175 | while ReadMemory("int", 0x6A9EC0, 0x7FC) != 3: # 还没进入战斗界面 176 | Sleep(1) 177 | while ReadMemory("bool", 0x6A9EC0, 0x768, 0x164): # 处于暂停状态 178 | Sleep(1) 179 | Card("睡莲", (3, 3)) # 临时存冰位 180 | 181 | AutoCollect() # 自动收集资源 182 | IceSpots([(1, 5), (6, 5), (3, 3)], 13) 183 | TallNutKeeper([(3, 8), (4, 8)]) 184 | 185 | for wave in range(1, 21): 186 | print("当前操作波次: " + str(wave)) 187 | 188 | if wave in (1, ): 189 | Prejudge(-95, wave) 190 | Pao((2, 9), (5, 9)) 191 | Delay(110) 192 | Pao((1, 7.7), (5, 7.7)) 193 | 194 | elif wave in (2, 10): 195 | Prejudge(-15, wave) 196 | Pao((2, 9), (5, 9)) 197 | Until(-15 + 107) 198 | Pao((1, 7.625), (5, 7.625)) 199 | if wave == 10: 200 | Until(-15 + 373 - 100) 201 | Card("樱桃", (2, 9)) # A 202 | Until(601 + 20 - 298) # 20cs 预判冰 203 | Coffee() 204 | 205 | elif wave in (3, 6, 9, 11, 14, 17): # I-PPdd 206 | Prejudge(1300 - 200 - 373, wave) 207 | Pao((2, 8.8), (5, 8.8)) 208 | Until(1300 + 20 - 298) # 20cs 预判冰 209 | Coffee() 210 | Until(1300 - 200 - 373 + 350) # 减速尾炸 211 | Pao((1, 2.4), (5, 2.4)) 212 | if wave == 9: 213 | Until(1300 + 180) 214 | Pao((1, 7.2), (5, 7.2)) # 可省略 215 | Until(1300 + 1662 - 200 - 373) 216 | Pao((2, 8.8), (5, 8.8)) 217 | Delay(81) 218 | DianCai() 219 | Delay(220 - 81) 220 | Pao((1, 7.8), (5, 7.8)) 221 | Skip(4) 222 | 223 | elif wave in (4, 7, 12, 15, 18): # IPP-PPDDC 224 | Prejudge(180, wave) 225 | Pao((1, 7.2), (5, 7.2)) 226 | Until(1662 - 200 - 373) 227 | Pao((2, 8.8), (5, 8.8)) 228 | Delay(81) 229 | DianCai() 230 | Delay(220 - 81) 231 | Pao((1, 7.4), (5, 7.4)) # 左移 232 | 233 | elif wave in (5, 8, 13, 16, 19): # PPDD 234 | Prejudge(-15, wave) 235 | Pao((2, 9), (5, 9)) 236 | Until(-15 + 107) 237 | Pao((1, 7.625), (5, 7.625)) 238 | Until(601 + 20 - 298) # 20cs 预判冰 239 | Coffee() 240 | if wave == 19: 241 | Until(601 + 1300 - 200 - 373) 242 | Delay(100) # 尾炸炮时机微调 243 | Pao((2, 8.8), (5, 8.8)) 244 | Delay(220) 245 | Pao((1, 7.8), (5, 7.8)) 246 | Skip(3) 247 | 248 | elif wave in (20, ): 249 | Prejudge(-150, wave) 250 | Pao((4, 7)) 251 | Until(-60) 252 | Pao((1, 9), (2, 9), (5, 9), (6, 9)) 253 | Delay(108) 254 | Pao((1, 9), (2, 9), (5, 9), (6, 9)) 255 | -------------------------------------------------------------------------------- /_PE经典十二炮.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | 阵名: PE经典十二炮 4 | 出处: https://tieba.baidu.com/f?kz=675626485 5 | 节奏: P6: PP|PP|PP|PP|PP|PP 6 | """ 7 | 8 | from pvz import * 9 | 10 | WriteMemory("int", 0x00679300, 0x0040FCED) # 取消点炮限制 11 | 12 | SelectCards(["樱桃", "小喷"]) 13 | 14 | AutoCollect() # 自动收集资源 15 | 16 | for wave in range(1, 21): 17 | print("当前操作波次: " + str(wave)) 18 | Prejudge(-199, wave) 19 | 20 | # 关底炮炸珊瑚 21 | if wave in (20, ): 22 | Until(-150) 23 | Pao((4, 7)) 24 | 25 | # 每波预判炸 26 | Until(-95) 27 | if wave in (10, 20): 28 | Until(-30) 29 | Pao((2, 9), (5, 9)) 30 | 31 | # 旗帜波加樱桃消延迟 32 | if wave in (10, ): 33 | Until(-30 + 373 - 100) 34 | Card("樱桃", (2, 9)) 35 | 36 | # 收尾额外多炸两轮 37 | if wave in (9, 19, 20): 38 | for _ in range(2): 39 | Delay(601) 40 | Pao((2, 9), (5, 9)) 41 | -------------------------------------------------------------------------------- /_PE经典四炮.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | 阵名: PE经典四炮 4 | 出处: https://tieba.baidu.com/p/664115150 5 | 节奏: C7i: PP|I-PP|I-PP|I-N, (6|18|18|11.5) 6 | """ 7 | 8 | from pvz import * 9 | 10 | SetZombies(["普僵", "撑杆", "舞王", "冰车", "海豚", "矿工", "跳跳", "蹦极", "扶梯", "篮球", "白眼", "红眼"]) 11 | 12 | SelectCards(["寒冰菇", "复制冰", "核蘑菇", "睡莲", "咖啡豆", "南瓜", "樱桃", "窝瓜", "阳光菇", "小喷"]) 13 | 14 | UpdatePaoList([(3, 1), (4, 1), (3, 3), (4, 3)]) 15 | 16 | AutoCollect() # 自动收集资源 17 | IceSpots([(3, 5), (1, 4), (6, 4), (1, 5), (6, 5)], 15) 18 | 19 | for wave in range(1, 21): 20 | print("当前操作波次: " + str(wave)) 21 | 22 | Prejudge(-199, wave) 23 | 24 | # PP 25 | if wave in (1, 5, 9, 10, 14, 18): 26 | Until(601 - 200 - 373) 27 | Pao((2, 9), (5, 9)) 28 | if wave == 10: 29 | Until(601 - 200 - 100) 30 | Card("樱桃", (2, 9)) 31 | Until(601 + 20 - 298) # 20cs 预判冰 32 | Coffee() 33 | if wave == 9: # 第 9 波收尾 34 | Until(601 + 1800 - 200 - 373) 35 | Pao((2, 8.3), (5, 8.3)) 36 | print("第 %s 波手动收尾." % wave) # 倭瓜/垫材 37 | 38 | # I-PP 39 | elif wave in (2, 6, 11, 15, 19): 40 | Until(1800 - 200 - 373) 41 | Pao((2, 8.3), (5, 8.3)) 42 | if wave != 19: 43 | Until(1800 + 20 - 298) # 20cs 预判冰 44 | Coffee() 45 | if wave == 19: # 第 19 波收尾 46 | Until(1800 + 1800 - 200 - 373) 47 | Pao((2, 8.3), (5, 8.3)) 48 | print("第 %s 波手动收尾." % wave) # 倭瓜/垫材/樱桃 49 | 50 | # I-PP 51 | elif wave in (3, 7, 12, 16): 52 | Until(1800 - 200 - 373) 53 | Pao((2, 8.3), (5, 8.3)) 54 | Until(1800 + 50 - 298) # 50cs 预判冰 55 | Coffee() 56 | 57 | # I-N 58 | elif wave in (4, 8, 13, 17): 59 | if wave == 4: 60 | row, col = (3, 8) 61 | elif wave == 8: 62 | row, col = (3, 9) 63 | elif wave == 13: 64 | row, col = (4, 8) 65 | elif wave == 17: 66 | row, col = (4, 9) 67 | Until(1150 - 200 - 298) 68 | Card("睡莲", (row, col)) 69 | Card("核蘑菇", (row, col)) 70 | Card("咖啡豆", (row, col)) 71 | 72 | elif wave in (20, ): 73 | # 不管珊瑚 74 | Until(-60) 75 | Pao((2, 9), (5, 9)) 76 | Until(300) 77 | Coffee() 78 | print("第 %s 波手动收尾." % wave) # 樱桃/窝瓜/垫材/炮 79 | -------------------------------------------------------------------------------- /_PE裸奔十六炮.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | 阵名: PE裸奔十六炮 4 | 出处: https://tieba.baidu.com/p/1289540813 5 | 节奏: ch6: PPDC|IPd-PPD|PPDC|IPd-PPD, (6|12|6|12) 6 | """ 7 | 8 | from pvz import * 9 | 10 | WriteMemory("int", 0x00679300, 0x0040FCED) # 取消点炮限制 11 | 12 | 13 | # 种垫铲垫 14 | @RunningInThread 15 | def DianCai(): 16 | Card("小喷菇", (5, 9)) 17 | Card("阳光菇", (6, 9)) 18 | Delay(100) 19 | Shovel((5, 9)) 20 | Shovel((6, 9)) 21 | 22 | 23 | # 偷菜 24 | @RunningInThread 25 | def Sunflower(): 26 | sunflower_spots = [(1, 2), (1, 5), (1, 6), (2, 2), (2, 5), (2, 6)] 27 | # 开局种 28 | for spot in sunflower_spots: 29 | Card("向日葵", spot) 30 | Delay(751 + 1) 31 | # 等第 20 波刷新 32 | while ReadMemory("int", 0x6A9EC0, 0x768, 0x557C) < 20: 33 | Sleep(100) 34 | # 等白字出现 35 | while ReadMemory("int", 0x6A9EC0, 0x768, 0x140, 0x8C) != 12: 36 | Sleep(100) 37 | # 结尾铲 38 | for spot in sunflower_spots: 39 | Shovel(spot) 40 | 41 | 42 | ### 43 | 44 | SetZombies(["普僵", "撑杆", "舞王", "冰车", "海豚", "矿工", "跳跳", "蹦极", "扶梯", "篮球", "白眼", "红眼"]) 45 | 46 | SelectCards(["咖啡豆", "寒冰菇", "复制冰", "樱桃", "窝瓜", "南瓜头", "向日葵", "胆小菇", "阳光菇", "小喷菇"]) 47 | 48 | # UpdatePaoList([ 49 | # (3, 1), (3, 3), (3, 5), (3, 7), 50 | # (4, 1), (4, 3), (4, 5), (4, 7), 51 | # (5, 1), (5, 3), (5, 5), (5, 7), 52 | # (6, 1), (6, 3), (6, 5), (6, 7), 53 | # ]) 54 | 55 | AutoCollect() # 自动收集资源 56 | IceSpots([(3, 9), (4, 9), (1, 4), (2, 4)], 10) 57 | Sunflower() # 偷菜线程 58 | 59 | for wave in range(1, 21): 60 | print("当前操作波次: " + str(wave)) 61 | 62 | Prejudge(-198, wave) # 每波均用 198 预判 63 | 64 | # PPD|I- 65 | if wave in (1, 3, 5, 7, 9, 10, 12, 14, 16, 18): 66 | if wave == 10: 67 | Until(-56) 68 | Pao((2, 9), (5, 9)) 69 | Until(-56 + 110) 70 | Pao((5, 8)) 71 | Until(601 - 200 - 100) # 301 72 | Card("樱桃", (2, 9)) # 消延迟 炸小偷 73 | else: 74 | Until(-133) 75 | Pao((2, 9), (5, 9)) 76 | Until(-133 + 110) 77 | Pao((5, 8)) 78 | Until(601 + 50 - 298) # 353 79 | Coffee() # 50cs 预判冰 80 | 81 | if wave == 9: # 第 9 波收尾 82 | Until(601 - 135) 83 | DianCai() 84 | Until(601 - 100) 85 | Pao((1, 2.4)) 86 | Until(601 + 444 - 373) 87 | Pao((5, 7.4)) 88 | Until(601 + 1200 - 200 - 373) 89 | Pao((2, 9), (5, 9)) 90 | Delay(220) 91 | Pao((5, 8.5)) 92 | Until(601 + 1200 - 133) 93 | Pao((1, 2.4), (5, 9)) 94 | Until(601 + 1200 - 133 + 110) 95 | Pao((2, 9)) 96 | Until(601 + 1200 + 601 - 100) 97 | Delay(600) 98 | Pao((2, 8), (5, 9)) 99 | Card("小喷菇", (1, 7)) 100 | Card("阳光菇", (2, 7)) 101 | Delay(400) 102 | Shovel((1, 7), (2, 7)) 103 | 104 | # C|Pd-PPD 105 | elif wave in (2, 4, 6, 8, 11, 13, 15, 17, 19): 106 | Until(-135) 107 | DianCai() # -135 放垫, 撑杆跳跃用时 180, 落地后 5 冰生效 108 | Until(-100) 109 | if wave == 11: 110 | Pao((1, 4)) # 炸小鬼和小偷 111 | else: 112 | Pao((1, 2.4)) 113 | Until(444 - 373) 114 | Pao((5, 7.4)) 115 | Until(1200 - 200 - 373) 116 | Pao((2, 9), (5, 9)) 117 | Delay(220) 118 | Pao((5, 8.5)) 119 | 120 | if wave == 19: # 第 19 波收尾 121 | Until(1200 - 133) 122 | Pao((2, 9), (5, 9)) 123 | Delay(350) 124 | Pao((1, 2.4)) 125 | Delay(300) 126 | Pao((5, 9)) 127 | Delay(400) 128 | Pao((2, 9)) 129 | Delay(500) 130 | Pao((5, 9)) 131 | Delay(400) 132 | Pao((2, 8)) 133 | Card("小喷菇", (1, 7)) 134 | Card("阳光菇", (2, 7)) 135 | Delay(400) 136 | Shovel((1, 7), (2, 7)) 137 | 138 | elif wave == 20: 139 | Until(-150) 140 | Pao((4, 7)) 141 | Until(-60) # 等到刷新前 60cs 142 | Pao((1, 9), (2, 9), (5, 9), (6, 9)) 143 | Delay(108) 144 | Pao((1, 9), (2, 9), (5, 9), (6, 9)) 145 | Delay(180) 146 | Pao((1, 4)) # 尾炸小偷 147 | print("第 %s 波手动收尾." % wave) 148 | -------------------------------------------------------------------------------- /_RE十六炮.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | 阵名: RE十六炮 4 | 出处: https://tieba.baidu.com/p/1410367512 5 | 节奏: ch6: PSD/P|IP-PPD|PSD/P|IP-PPD, (6|12|6|12) 6 | """ 7 | 8 | from pvz import * 9 | 10 | WriteMemory("int", 0x00679300, 0x0040FCED) # 取消点炮限制 11 | 12 | SetZombies(["普僵", "撑杆", "橄榄", "冰车", "小丑", "气球", "跳跳", "蹦极", "扶梯", "篮球", "白眼", "红眼"]) 13 | 14 | SelectCards(["玉米", "玉米炮", "樱桃", "倭瓜", "坚果", "核蘑菇", "冰蘑菇", "模仿者寒冰菇", "咖啡豆", "花盆"]) 15 | 16 | UpdatePaoList([ 17 | (1, 3), # P 18 | (1, 5), # S 19 | (1, 7), # P 20 | (1, 1), # D 21 | (2, 3), # P 22 | (2, 5), # P 23 | (2, 7), # P 24 | (2, 1), # D 25 | (3, 3), # P 26 | (3, 5), # S 27 | (3, 7), # P 28 | (3, 1), # D 29 | (4, 6), # P 30 | (4, 1), # P 31 | (5, 6), # P 32 | (5, 1), # D 33 | ]) 34 | 35 | AutoCollect() # 自动收集资源 36 | IceSpots([(5, 3), (4, 3)], 11) 37 | 38 | for wave in range(1, 21): 39 | print("当前操作波次: " + str(wave)) 40 | 41 | # PPSD 42 | if wave in (1, 3, 5, 7, 9, 10, 12, 14, 16, 18): 43 | Prejudge(-10, wave) # -10+373 < 377 44 | RoofPao((2, 9), (2, 9), (4, 9)) 45 | Delay(110) # 110 拦截 46 | RoofPao((2, 8.8)) 47 | Until(601 + 50 - 298) # 50cs 预判冰 48 | Coffee() 49 | if wave == 9: 50 | Until(601 - 150) 51 | RoofPao((2, 9)) 52 | Until(601 + 1200 - 200 - 373) 53 | RoofPao((5, 9), (5, 9)) 54 | Delay(1100) # 等会儿 55 | RoofPao((5, 9)) 56 | 57 | # IP-PPD 58 | elif wave in (2, 4, 6, 8, 11, 13, 15, 17, 19): 59 | Prejudge(-150, wave) 60 | RoofPao((2, 9)) 61 | Until(1200 - 200 - 373) # 1200cs 波长 62 | RoofPao((2, 9), (4, 9)) # 激活炸 63 | Delay(220) # 220 拦截 64 | RoofPao((2, 7.8)) 65 | if wave == 19: 66 | Until(1200 - 10) 67 | RoofPao((2, 9), (2, 9), (4, 9)) 68 | Delay(110) # 110 拦截 69 | RoofPao((2, 8.8)) 70 | Until(1200 + 601 - 150) 71 | RoofPao((5, 9)) 72 | Until(1200 + 601 + 1200 - 200 - 373) 73 | Delay(50) # 等会儿 74 | RoofPao((5, 9)) 75 | 76 | elif wave == 20: 77 | Prejudge(-200, wave) 78 | Coffee() # 冰消空降 79 | Until(-100) 80 | RoofPao((2, 8.5), (5, 8.5)) # 炸冰车 81 | Until(50) 82 | RoofPao((4, 2.5), (4, 6.7)) # 炸小偷 83 | Until(800) 84 | RoofPao((2, 9), (2, 9), (2, 9), (2, 9)) 85 | Until(1000) 86 | RoofPao((4, 9), (4, 9), (4, 9), (4, 9)) 87 | print("第 %s 波手动收尾." % wave) 88 | -------------------------------------------------------------------------------- /_RE椭盘十四炮.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | 阵名: RE椭盘十四炮 4 | 出处: https://tieba.baidu.com/p/5029428684 5 | 节奏: ch4: ICE3+PPDD+B-PP|ICE3+PPDD+B-PP, (1780|1780) 6 | """ 7 | 8 | from pvz import * 9 | 10 | 11 | # 冰三修正函数 12 | @RunningInThread 13 | def ICE3(t): 14 | clock = ReadMemory("int", 0x6A9EC0, 0x768, 0x5568) # 基准时间 15 | while (ReadMemory("int", 0x6A9EC0, 0x768, 0x5568) - clock) < (t - 50): 16 | Sleep(0.1) 17 | ice_index = 0 18 | plants_count_max = ReadMemory("unsigned int", 0x6A9EC0, 0x768, 0xB0) 19 | plants_offset = ReadMemory("unsigned int", 0x6A9EC0, 0x768, 0xAC) 20 | for i in range(plants_count_max): 21 | plant_dead = ReadMemory("bool", plants_offset + 0x141 + 0x14C * i) 22 | plant_crushed = ReadMemory("bool", plants_offset + 0x142 + 0x14C * i) 23 | plant_type = ReadMemory("int", plants_offset + 0x24 + 0x14C * i) 24 | plant_countdown = ReadMemory("int", plants_offset + 0x50 + 0x14C * i) 25 | if not plant_dead and not plant_crushed and plant_type == 14 and 45 < plant_countdown < 55: 26 | ice_index = i 27 | break 28 | while (ReadMemory("int", 0x6A9EC0, 0x768, 0x5568) - clock) < (t - 10): 29 | Sleep(0.1) 30 | WriteMemory("int", 11, plants_offset + 0x50 + 0x14C * ice_index) 31 | 32 | 33 | WriteMemory("int", 0x00679300, 0x0040FCED) # 取消点炮限制 34 | 35 | SetZombies(["普僵", "撑杆", "橄榄", "冰车", "小丑", "气球", "跳跳", "蹦极", "扶梯", "篮球", "白眼", "红眼"]) 36 | 37 | SelectCards(["花盆", "寒冰菇", "模仿者寒冰菇", "毁灭菇", "咖啡豆", "樱桃炸弹", "火爆辣椒", "倭瓜", "寒冰射手", "坚果墙"]) 38 | 39 | UpdatePaoList([ 40 | (4, 2), # P 41 | (4, 4), # P 42 | (1, 4), # D 43 | (5, 4), # D 44 | (5, 6), # B 45 | (3, 1), # P 46 | (4, 7), # P 47 | ### 48 | (1, 2), # P 49 | (2, 4), # P 50 | (3, 3), # D 51 | (3, 5), # D 52 | (2, 6), # B 53 | (2, 1), # P 54 | (3, 7), # P 55 | ]) 56 | # IPPDDP-PP IPPDDP-PP 14 57 | # PPDDDD IP-PP 9 58 | # PPSSDD IAA'aP-PP 9 59 | Skip(5) # 调整炮序 60 | # while ReadMemory("int", 0x6A9EC0, 0x7FC) != 3: 61 | # Sleep(1) 62 | # while ReadMemory("bool", 0x6A9EC0, 0x768, 0x164): 63 | # Sleep(1) 64 | Card("花盆", (1, 7)) 65 | Card("寒冰菇", (1, 7)) 66 | 67 | AutoCollect([1, 2, 3, 4, 5, 6, 17], 15) 68 | IceSpots([(4, 6), (2, 3), (1, 1), (1, 6)], 18 - 1) 69 | 70 | for wave in range(1, 21): 71 | print("当前操作波次: " + str(wave)) 72 | 73 | if wave in (1, ): 74 | Prejudge(-190, wave) 75 | Until(377 - 373) 76 | RoofPao((2, 8.8), (4, 8.8)) 77 | Until(506 - 373) 78 | RoofPao((2, 8.8), (4, 8.8)) 79 | Until(601 + 34 - 373) 80 | RoofPao((2, 8.8), (4, 8.8)) 81 | Until(601 + 34 - 298) 82 | Card("咖啡豆", (1, 7)) # Coffee() 83 | ICE3(298) 84 | 85 | elif wave in (2, ): 86 | Prejudge(-190, wave) 87 | Until(50) 88 | Shovel((1, 7)) # 铲 89 | Until(1300 - 200 - 373) # 727 90 | RoofPao((4, 8.2)) 91 | Until(1780 - 200 - 373) # 1207 92 | RoofPao((2, 9), (4, 9)) 93 | Until(1780 + 10 - 298) # 1492 94 | Coffee() 95 | ICE3(298) 96 | 97 | elif wave in (10, ): 98 | Prejudge(-15, wave) 99 | RoofPao((2, 9), (4, 9), (2, 9), (4, 9)) 100 | Until(-15 + 110) # 95 101 | RoofPao((4, 7.7)) # 空炸小鬼兼小偷 102 | Until(-15 + 190) # 175 103 | RoofPao((1, 5)) # 2-5? 尾炸小鬼兼小偷 104 | Until(601 + 10 - 298) # 313 105 | Coffee() 106 | ICE3(298) 107 | 108 | elif wave in (11, ): 109 | Prejudge(-190, wave) 110 | Until(10 + 400 - 100) 111 | Card("辣椒", (1, 7)) 112 | Card("花盆", (4, 9)) 113 | Card("樱桃", (4, 9)) 114 | Until(10 + 400 + 10) 115 | Shovel((1, 7)) # 铲 116 | Shovel((4, 9)) # 铲 117 | Until(1250 - 200 - 373) # 1300->1250 118 | RoofPao((3, 8.21)) # 落点改为 3 路炸掉 2 路冰车 119 | Until(1780 - 200 - 373) 120 | RoofPao((2, 9), (4, 9)) 121 | Until(1780 + 10 - 298) 122 | Coffee() 123 | ICE3(298) 124 | 125 | elif wave in (3, 12): 126 | Prejudge(-190, wave) 127 | Until(10 + 400 - 373) 128 | RoofPao((2, 9), (4, 9)) 129 | Until(10 + 400 - 373 + 220) 130 | RoofPao((4, 8.5)) # 空炸 131 | Until(10 + 400 - 373 + 300) 132 | RoofPao((2, 4.7)) # 尾炸小鬼跳跳 133 | Until(1300 - 200 - 373) 134 | RoofPao((4, 8.2)) 135 | Until(1780 - 200 - 373) 136 | RoofPao((2, 9), (4, 9)) 137 | Until(1780 + 10 - 298) 138 | Coffee() 139 | ICE3(298) 140 | 141 | elif wave in (9, 19): 142 | Prejudge(-190, wave) 143 | Until(10 + 400 - 373) 144 | RoofPao((2, 9), (4, 9)) 145 | Until(10 + 400 - 373 + 220) 146 | RoofPao((2, 8.5), (4, 8.5)) 147 | Until(1300 - 200 - 373) 148 | RoofPao((3, 8.22)) # 落点改为 3 路减少小丑炸核机率 149 | # 收尾 150 | Until(1705 - 200 - 298) 151 | Card("花盆", (3, 9)) 152 | Card("核蘑菇", (3, 9)) 153 | Card("咖啡豆", (3, 9)) 154 | Until(1705 - 200 + 230 - 373) 155 | RoofPao((2, 8.5), (4, 8.5)) # 拦截 156 | Until(1705 - 200 + 230 + 230 - 373) 157 | RoofPao((2, 8.5), (4, 8.5)) # 拦截 158 | Until(1705 - 200 + 230 + 230 + 230 - 373) 159 | RoofPao((3, 9), (5, 9)) # 留下 1 路 160 | Delay(50) 161 | Card("寒冰射手", (1, 6)) 162 | # 清场 163 | if wave == 9: 164 | Skip(7 - 4 - 1 + 5) # 调整炮序 165 | Until(2700) 166 | Card("花盆", (1, 8)) # 垫一下 167 | Until(4500 - 200 - 373) # Until(4500 - 5) # 出红字时 168 | Delay(400) # 等那一门炮 169 | RoofPao((1, 8)) # 清场 170 | Until(4500 - 200 + 100) 171 | Shovel((1, 6)) # 铲掉冰豆 172 | Until(4500 - 5 + 750 - 599) # 第 10 波刷新前 599 173 | Card("花盆", (1, 7)) 174 | else: # 19 175 | Until(4500 - 200 - 373) 176 | RoofPao((1, 8)) # 清场 177 | Delay(200) 178 | Shovel((1, 6)) # 铲掉冰豆 179 | 180 | elif wave in (20, ): 181 | Prejudge(50 - 298, wave) 182 | Coffee() # 冰消空降 183 | Until(75) 184 | RoofPao((2, 3), (4, 8), (2, 8)) # 炸冰车小偷 185 | Until(1250 - 200 - 373) 186 | RoofPao((1, 9), (2, 9), (4, 9), (5, 9)) 187 | Until(1250 - 200 - 373 + 220) 188 | RoofPao((1, 9), (2, 9), (4, 9), (5, 9)) 189 | # 收尾 190 | print("第 %d 波手动收尾." % wave) 191 | # Delay(1000) 192 | # Pao((3, 9), (4, 9)) 193 | # Card("花盆", (1, 7)) 194 | # Card("坚果", (1, 7)) 195 | # Until(5500 - 182) 196 | # Card("倭瓜", (1, 6)) 197 | # Until(5500 + 100) 198 | # Shovel((1, 7)) 199 | # Shovel((1, 7)) 200 | 201 | else: # wave in (4, 5, 6, 7, 8, 13, 14, 15, 16, 17, 18): 202 | # 收尾波前一波延长波长 203 | WL = 1925 if wave in (8, 18) else 1780 204 | Prejudge(-190, wave) 205 | Until(10 + 400 - 373) 206 | RoofPao((2, 9), (4, 9)) 207 | Until(10 + 400 - 373 + 220) 208 | RoofPao((2, 8.5), (4, 8.5)) 209 | Until(1300 - 200 - 373) 210 | RoofPao((4, 8.2)) 211 | Until(WL - 200 - 373) # WL-573 212 | RoofPao((2, 9), (4, 9)) 213 | if wave in (8, 18): 214 | Until(WL - 200 - 373 + 83) # WL-490 215 | Card("花盆", (2, 8)) # 垫 2 路梯子 216 | Until(WL + 10 - 298) # WL-288 217 | Coffee() 218 | ICE3(298) 219 | if wave in (8, 18): 220 | Until(WL - 200) # WL-200 221 | Shovel((2, 8)) # 炮落地铲 222 | -------------------------------------------------------------------------------- /_信息读取.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | from pvz import ReadMemory 4 | from pvz.core import seeds_string 5 | from pvz.core import zombies_string 6 | 7 | sun = ReadMemory("int", 0x6A9EC0, 0x768, 0x5560) 8 | print("当前阳光数: " + str(sun)) 9 | 10 | zombies_count_max = ReadMemory("unsigned int", 0x6A9EC0, 0x768, 0x94) 11 | zombies_offset = ReadMemory("unsigned int", 0x6A9EC0, 0x768, 0x90) 12 | for i in range(zombies_count_max): 13 | zombie_dead = ReadMemory("bool", zombies_offset + 0xec + i * 0x15c) 14 | if not zombie_dead: 15 | zombie_type = ReadMemory("int", zombies_offset + 0x24 + i * 0x15c) 16 | zombie_row = ReadMemory("int", zombies_offset + 0x1c + i * 0x15c) 17 | zombie_x = ReadMemory("int", zombies_offset + 0x8 + i * 0x15c) 18 | zombie_hp = ReadMemory("int", zombies_offset + 0xc8 + i * 0x15c) 19 | print("%s僵尸 位于 %d 路 横坐标 %d 本体血量 %d" 20 | % (zombies_string[zombie_type][1], zombie_row + 1, zombie_x, zombie_hp)) 21 | 22 | plants_count_max = ReadMemory("unsigned int", 0x6A9EC0, 0x768, 0xB0) 23 | plants_offset = ReadMemory("unsigned int", 0x6A9EC0, 0x768, 0xAC) 24 | for i in range(plants_count_max): 25 | plant_dead = ReadMemory("bool", plants_offset + 0x141 + i * 0x14c) 26 | plant_squished = ReadMemory("bool", plants_offset + 0x142 + i * 0x14c) 27 | if not plant_dead and not plant_squished: 28 | plant_type = ReadMemory("int", plants_offset + 0x24 + i * 0x14c) 29 | plant_row = ReadMemory("int", plants_offset + 0x1c + i * 0x14c) 30 | plant_col = ReadMemory("int", plants_offset + 0x28 + i * 0x14c) 31 | plant_hp = ReadMemory("int", plants_offset + 0x40 + i * 0x14c) 32 | plant_hp_max = ReadMemory("int", plants_offset + 0x44 + i * 0x14c) 33 | print("植物序号 %d 位于 %d 路 %d 列 血量 %d/%d 类型 %s" 34 | % (i, plant_row + 1, plant_col + 1, plant_hp, plant_hp_max, seeds_string[plant_type][1])) 35 | -------------------------------------------------------------------------------- /pvz/__init__.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | """ 3 | Python vs. Zombies 4 | """ 5 | 6 | __name__ = "pvz" 7 | __version__ = "4.0.2" 8 | __description__ = "Python vs. Zombies" 9 | __date__ = "2020-09-13" 10 | __status__ = "Production" 11 | __author__ = "lmintlcx" 12 | __copyright__ = "Copyright 2018-2020, lmintlcx" 13 | __credits__ = ["no_doudle", "a418569882"] 14 | __license__ = "GPL" 15 | __maintainer__ = "lmintlcx" 16 | __email__ = "lmintlcx@gmail.com" 17 | 18 | import platform 19 | import sys 20 | import gc 21 | import atexit 22 | 23 | ### 检查操作系统和 Python 版本 24 | 25 | if platform.system() != "Windows": 26 | raise Exception("本项目 (pvz) 只支持在 Windows 系统上使用.") 27 | 28 | if sys.hexversion < 0x030400f0: 29 | raise Exception("本项目 (pvz) 要求版本号 >=3.4.0 的 Python 运行环境.") 30 | 31 | print("当前版本: %s" % __version__) 32 | print("在线教程: https://pvz.lmintlcx.com/scripts/") 33 | 34 | from .core import * 35 | from .extra import * 36 | 37 | ### documented api 38 | 39 | ## 读写内存 40 | from .core import read_memory as ReadMemory 41 | from .core import write_memory as WriteMemory 42 | 43 | ## 模拟鼠标点击 44 | from .core import left_click as LeftClick 45 | from .core import right_click as RightClick 46 | from .core import special_button_click as ButtonClick 47 | 48 | ## 模拟键盘敲击 49 | from .core import press_esc as PressEsc 50 | from .core import press_space as PressSpace 51 | from .core import press_enter as PressEnter 52 | from .core import press_keys as PressKeys 53 | 54 | ## 功能修改 55 | from .extra import set_zombies as SetZombies 56 | 57 | ## 选卡/更新炮列表 58 | from .extra import select_seeds_and_lets_rock as SelectCards 59 | from .extra import update_cob_cannon_list as UpdatePaoList 60 | 61 | ## 阻塞延时 62 | from .core import thread_sleep_for as Sleep 63 | from .extra import game_delay_for as Delay 64 | from .extra import until_relative_time_after_refresh as Prejudge 65 | from .extra import until_relative_time as Until 66 | 67 | ## 场地点击 68 | from .extra import get_mouse_lock as MouseLock 69 | from .extra import safe_click as SafeClick 70 | from .extra import click_seed as ClickSeed 71 | from .extra import click_shovel as ClickShovel 72 | from .extra import click_grid as ClickGrid 73 | 74 | ## 主要操作 75 | from .extra import use_seed as Card 76 | from .extra import use_shovel as Shovel 77 | from .extra import fire_cob as Pao 78 | from .extra import fire_cob_on_roof as RoofPao 79 | from .extra import skip_cob_index as Skip 80 | 81 | ## 子线程操作 82 | from .extra import running_in_thread as RunningInThread 83 | from .extra import auto_collect as AutoCollect 84 | from .extra import auto_fill_ice as IceSpots 85 | from .extra import activate_ice as Coffee 86 | 87 | __all__ = [ 88 | # 读写内存 89 | "ReadMemory", 90 | "WriteMemory", 91 | # 模拟鼠标点击 92 | "LeftClick", 93 | "RightClick", 94 | "ButtonClick", 95 | # 模拟键盘敲击 96 | "PressEsc", 97 | "PressSpace", 98 | "PressEnter", 99 | "PressKeys", 100 | # 功能修改 101 | "SetZombies", 102 | # 选卡/更新炮列表 103 | "SelectCards", 104 | "UpdatePaoList", 105 | # 阻塞延时 106 | "Sleep", 107 | "Delay", 108 | "Prejudge", 109 | "Until", 110 | # 场地点击 111 | "MouseLock", 112 | "SafeClick", 113 | "ClickSeed", 114 | "ClickShovel", 115 | "ClickGrid", 116 | # 主要操作 117 | "Card", 118 | "Shovel", 119 | "Pao", 120 | "RoofPao", 121 | "Skip", 122 | # 子线程操作 123 | "RunningInThread", 124 | "AutoCollect", 125 | "IceSpots", 126 | "Coffee", 127 | ] 128 | 129 | ### 启动和退出时的特殊处理 130 | 131 | 132 | def _on_start(): 133 | # 其实不需要, 游戏启动后会变成 0.5ms 134 | timeBeginPeriod(1) # res == TIMERR_NOERROR 135 | 136 | # 时间敏感 137 | gc.disable() 138 | sys.setswitchinterval(0.001) 139 | 140 | SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS) 141 | 142 | get_dpi_scale() # 自动获取缩放率 143 | # set_dpi_scale(1.25) # 出错则手动设置 144 | 145 | if find_pvz(): 146 | ui = game_ui() 147 | if ui in (2, 3): 148 | set_pvz_foreground() 149 | set_pvz_high_priority() 150 | update_game_scene() 151 | update_seeds_list() if ui == 3 else None 152 | update_cob_cannon_list() 153 | else: 154 | critical("游戏未开启或者游戏版本不受支持!") 155 | 156 | enable_logging(False) # 是否输出调试日志 157 | set_logging_level("INFO") 158 | 159 | 160 | def _on_exit(): 161 | timeEndPeriod(1) # res == TIMERR_NOERROR 162 | 163 | if is_valid(): 164 | CloseHandle(pvz_handle) 165 | 166 | 167 | _on_start() 168 | atexit.register(_on_exit) 169 | -------------------------------------------------------------------------------- /pvz/core.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | import logging 4 | import ctypes 5 | import struct 6 | import threading 7 | import time 8 | import functools 9 | import random 10 | import gc 11 | 12 | ### 调试日志 13 | 14 | fmt_str = "%(asctime)s.%(msecs)03d - %(name)s - %(levelname)s - %(message)s" 15 | log_formatter = logging.Formatter(fmt=fmt_str, datefmt='%H:%M:%S') 16 | 17 | console_handler = logging.StreamHandler() 18 | console_handler.setFormatter(log_formatter) 19 | console_handler.setLevel(logging.INFO) 20 | 21 | pvz_logger = logging.getLogger("pvz") 22 | pvz_logger.addHandler(console_handler) 23 | pvz_logger.setLevel(logging.INFO) 24 | 25 | 26 | def enable_logging(on=True): 27 | """ 28 | 启用日志. 29 | 30 | 输出调试信息开销较大, 会影响操作精度, 建议在调试完成后正式运行的时候关闭. 31 | 32 | @参数 on(bool): 是否启用, 默认启用. 33 | """ 34 | pvz_logger.disabled = not on 35 | 36 | 37 | def set_logging_level(level="INFO"): 38 | """ 39 | 设置日志级别. 40 | 41 | @参数 level(str): 可选值 ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]. 42 | """ 43 | logging_level = { 44 | "DEBUG": logging.DEBUG, 45 | "INFO": logging.INFO, 46 | "WARNING": logging.WARNING, 47 | "ERROR": logging.ERROR, 48 | "CRITICAL": logging.CRITICAL, 49 | } 50 | pvz_logger.setLevel(logging_level[level]) 51 | 52 | 53 | # 频繁出现的调试信息 54 | def debug(txt): 55 | pvz_logger.debug(txt) 56 | 57 | 58 | # 常规调试信息 59 | def info(txt): 60 | pvz_logger.info(txt) 61 | 62 | 63 | # 不影响运行的警告 64 | def warning(txt): 65 | pvz_logger.warning(txt) 66 | 67 | 68 | # 用户操作出错 69 | def error(txt): 70 | pvz_logger.error(txt) 71 | raise Exception(txt) 72 | 73 | 74 | # 内部严重错误 75 | def critical(txt): 76 | pvz_logger.critical(txt) 77 | raise Exception(txt) 78 | 79 | 80 | ### win32 动态库 81 | 82 | user32 = ctypes.windll.user32 83 | kernel32 = ctypes.windll.kernel32 84 | winmm = ctypes.windll.winmm 85 | gdi32 = ctypes.windll.gdi32 86 | 87 | ### win32 类型 88 | 89 | from ctypes import c_size_t as SIZE_T 90 | from ctypes.wintypes import BOOL 91 | from ctypes.wintypes import DWORD 92 | from ctypes.wintypes import INT 93 | from ctypes.wintypes import UINT 94 | from ctypes.wintypes import LONG 95 | from ctypes.wintypes import HWND 96 | from ctypes.wintypes import HANDLE 97 | from ctypes.wintypes import HDC 98 | from ctypes.wintypes import LPVOID 99 | from ctypes.wintypes import LPCVOID 100 | from ctypes.wintypes import LPCWSTR 101 | from ctypes.wintypes import LPDWORD 102 | from ctypes.wintypes import POINT 103 | from ctypes.wintypes import LPPOINT 104 | from ctypes.wintypes import RECT 105 | from ctypes.wintypes import LPRECT 106 | from ctypes.wintypes import WPARAM 107 | from ctypes.wintypes import LPARAM 108 | 109 | 110 | # typedef struct SECURITY_ATTRIBUTES { 111 | # DWORD nLength; 112 | # LPVOID lpSecurityDescriptor; 113 | # BOOL bInheritHandle; 114 | # } SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES; 115 | class SECURITY_ATTRIBUTES(ctypes.Structure): 116 | _fields_ = [("nLength", DWORD), ("lpSecurityDescriptor", LPVOID), ("bInheritHandle", BOOL)] 117 | 118 | 119 | LPSECURITY_ATTRIBUTES = ctypes.POINTER(SECURITY_ATTRIBUTES) 120 | 121 | # typedef DWORD (__stdcall *LPTHREAD_START_ROUTINE) ( 122 | # [in] LPVOID lpThreadParameter 123 | # ); 124 | LPTHREAD_START_ROUTINE = ctypes.WINFUNCTYPE(DWORD, LPVOID) 125 | 126 | ### win32 函数接口 和 参数常量 127 | 128 | # int MessageBoxW( 129 | # HWND hWnd, 130 | # LPCWSTR lpText, 131 | # LPCWSTR lpCaption, 132 | # UINT uType 133 | # ); 134 | MessageBoxW = user32.MessageBoxW 135 | MessageBoxW.argtypes = [HWND, LPCWSTR, LPCWSTR, UINT] 136 | MessageBoxW.restype = INT 137 | 138 | # HWND FindWindowW( 139 | # LPCWSTR lpClassName, 140 | # LPCWSTR lpWindowName 141 | # ); 142 | FindWindowW = user32.FindWindowW 143 | FindWindowW.argtypes = [LPCWSTR, LPCWSTR] 144 | FindWindowW.restype = HWND 145 | 146 | # DWORD GetWindowThreadProcessId( 147 | # HWND hWnd, 148 | # LPDWORD lpdwProcessId 149 | # ); 150 | GetWindowThreadProcessId = user32.GetWindowThreadProcessId 151 | GetWindowThreadProcessId.argtypes = [HWND, LPDWORD] 152 | GetWindowThreadProcessId.restype = DWORD 153 | 154 | # HANDLE OpenProcess( 155 | # DWORD dwDesiredAccess, 156 | # BOOL bInheritHandle, 157 | # DWORD dwProcessId 158 | # ); 159 | OpenProcess = kernel32.OpenProcess 160 | OpenProcess.argtypes = [DWORD, BOOL, DWORD] 161 | OpenProcess.restype = HANDLE 162 | 163 | PROCESS_ALL_ACCESS = 0x001F0FFF 164 | 165 | # BOOL GetExitCodeProcess( 166 | # HANDLE hProcess, 167 | # LPDWORD lpExitCode 168 | # ); 169 | GetExitCodeProcess = kernel32.GetExitCodeProcess 170 | GetExitCodeProcess.argtypes = [HANDLE, LPDWORD] 171 | GetExitCodeProcess.restype = BOOL 172 | 173 | STILL_ACTIVE = 0x00000103 174 | 175 | # BOOL WINAPI CloseHandle( 176 | # _In_ HANDLE hObject 177 | # ); 178 | CloseHandle = kernel32.CloseHandle 179 | CloseHandle.argtypes = [HANDLE] 180 | CloseHandle.restype = BOOL 181 | 182 | # BOOL WINAPI ReadProcessMemory( 183 | # _In_ HANDLE hProcess, 184 | # _In_ LPCVOID lpBaseAddress, 185 | # _Out_ LPVOID lpBuffer, 186 | # _In_ SIZE_T nSize, 187 | # _Out_ SIZE_T *lpNumberOfBytesRead 188 | # ); 189 | ReadProcessMemory = kernel32.ReadProcessMemory 190 | ReadProcessMemory.argtypes = [HANDLE, LPCVOID, LPVOID, SIZE_T, LPDWORD] 191 | ReadProcessMemory.restype = BOOL 192 | 193 | # BOOL WINAPI WriteProcessMemory( 194 | # _In_ HANDLE hProcess, 195 | # _In_ LPVOID lpBaseAddress, 196 | # _In_ LPCVOID lpBuffer, 197 | # _In_ SIZE_T nSize, 198 | # _Out_ SIZE_T *lpNumberOfBytesWritten 199 | # ); 200 | WriteProcessMemory = kernel32.WriteProcessMemory 201 | WriteProcessMemory.argtypes = [HANDLE, LPVOID, LPCVOID, SIZE_T, LPDWORD] 202 | WriteProcessMemory.restype = BOOL 203 | 204 | # DWORD WINAPI GetLastError(void); 205 | GetLastError = kernel32.GetLastError 206 | GetLastError.argtypes = [] 207 | GetLastError.restype = DWORD 208 | 209 | # HWND SetActiveWindow( 210 | # HWND hWnd 211 | # ); 212 | SetActiveWindow = user32.SetActiveWindow 213 | SetActiveWindow.argtypes = [HWND] 214 | SetActiveWindow.restype = HWND 215 | 216 | # BOOL SetForegroundWindow( 217 | # HWND hWnd 218 | # ); 219 | SetForegroundWindow = user32.SetForegroundWindow 220 | SetForegroundWindow.argtypes = [HWND] 221 | SetForegroundWindow.restype = BOOL 222 | 223 | # BOOL SetWindowPos( 224 | # HWND hWnd, 225 | # HWND hWndInsertAfter, 226 | # int X, 227 | # int Y, 228 | # int cx, 229 | # int cy, 230 | # UINT uFlags 231 | # ); 232 | SetWindowPos = user32.SetWindowPos 233 | SetWindowPos.argtypes = [HWND, HWND, INT, INT, INT, INT, UINT] 234 | SetWindowPos.restype = BOOL 235 | 236 | HWND_NOTOPMOST = -2 237 | HWND_TOPMOST = -1 238 | SWP_NOMOVE = 0x0002 239 | SWP_NOSIZE = 0x0001 240 | SWP_SHOWWINDOW = 0x0040 241 | 242 | # LPVOID WINAPI VirtualAllocEx( 243 | # _In_ HANDLE hProcess, 244 | # _In_opt_ LPVOID lpAddress, 245 | # _In_ SIZE_T dwSize, 246 | # _In_ DWORD flAllocationType, 247 | # _In_ DWORD flProtect 248 | # ); 249 | VirtualAllocEx = kernel32.VirtualAllocEx 250 | VirtualAllocEx.argtypes = [HANDLE, LPVOID, SIZE_T, DWORD, DWORD] 251 | VirtualAllocEx.restype = LPVOID 252 | 253 | MEM_COMMIT = 0x00001000 254 | PAGE_EXECUTE_READWRITE = 0x40 255 | 256 | # BOOL WINAPI VirtualFreeEx( 257 | # _In_ HANDLE hProcess, 258 | # _In_ LPVOID lpAddress, 259 | # _In_ SIZE_T dwSize, 260 | # _In_ DWORD dwFreeType 261 | # ); 262 | VirtualFreeEx = kernel32.VirtualFreeEx 263 | VirtualFreeEx.argtypes = [HANDLE, LPVOID, SIZE_T, DWORD] 264 | VirtualFreeEx.restype = BOOL 265 | 266 | MEM_RELEASE = 0x00008000 267 | 268 | # HANDLE CreateRemoteThread( 269 | # HANDLE hProcess, 270 | # LPSECURITY_ATTRIBUTES lpThreadAttributes, 271 | # SIZE_T dwStackSize, 272 | # LPTHREAD_START_ROUTINE lpStartAddress, 273 | # LPVOID lpParameter, 274 | # DWORD dwCreationFlags, 275 | # LPDWORD lpThreadId 276 | # ); 277 | CreateRemoteThread = kernel32.CreateRemoteThread 278 | CreateRemoteThread.argtypes = [HANDLE, LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD] 279 | CreateRemoteThread.restype = HANDLE 280 | 281 | # DWORD WaitForSingleObject( 282 | # HANDLE hHandle, 283 | # DWORD dwMilliseconds 284 | # ); 285 | WaitForSingleObject = kernel32.WaitForSingleObject 286 | WaitForSingleObject.argtypes = [HANDLE, DWORD] 287 | WaitForSingleObject.restype = DWORD 288 | 289 | INFINITE = 0xFFFFFFFF 290 | WAIT_FAILED = 0xFFFFFFFF 291 | 292 | # LRESULT SendMessageW( 293 | # HWND hWnd, 294 | # UINT Msg, 295 | # WPARAM wParam, 296 | # LPARAM lParam 297 | # ); 298 | SendMessageW = user32.SendMessageW 299 | SendMessageW.argtypes = [HWND, UINT, WPARAM, LPARAM] 300 | SendMessageW.restype = LONG 301 | 302 | # BOOL PostMessageW( 303 | # HWND hWnd, 304 | # UINT Msg, 305 | # WPARAM wParam, 306 | # LPARAM lParam 307 | # ); 308 | PostMessageW = user32.PostMessageW 309 | PostMessageW.argtypes = [HWND, UINT, WPARAM, LPARAM] 310 | PostMessageW.restype = BOOL 311 | 312 | WM_KEYDOWN = 0x0100 313 | WM_KEYUP = 0x0101 314 | VK_ESCAPE = 0x1B 315 | VK_SPACE = 0x20 316 | VK_RETURN = 0x0D 317 | VK_LEFT = 0x25 318 | VK_UP = 0x26 319 | VK_RIGHT = 0x27 320 | VK_DOWN = 0x28 321 | 322 | WM_LBUTTONDOWN = 0x0201 323 | WM_LBUTTONUP = 0x0202 324 | WM_RBUTTONDOWN = 0x0204 325 | WM_RBUTTONUP = 0x0205 326 | 327 | MK_LBUTTON = 0x0001 328 | MK_RBUTTON = 0x0002 329 | 330 | # HDC GetDC( 331 | # HWND hWnd 332 | # ); 333 | GetDC = user32.GetDC 334 | GetDC.argtypes = [HWND] 335 | GetDC.restype = HDC 336 | 337 | # int GetDeviceCaps( 338 | # HDC hdc, 339 | # int index 340 | # ); 341 | GetDeviceCaps = gdi32.GetDeviceCaps 342 | GetDeviceCaps.argtypes = [HDC, INT] 343 | GetDeviceCaps.restype = INT 344 | 345 | HORZRES = 8 346 | DESKTOPHORZRES = 118 347 | 348 | # int ReleaseDC( 349 | # HWND hWnd, 350 | # HDC hDC 351 | # ); 352 | ReleaseDC = user32.ReleaseDC 353 | ReleaseDC.argtypes = [HWND, HDC] 354 | ReleaseDC.restype = INT 355 | 356 | # MMRESULT timeBeginPeriod( 357 | # UINT uPeriod 358 | # ); 359 | timeBeginPeriod = winmm.timeBeginPeriod 360 | timeBeginPeriod.argtypes = [UINT] 361 | timeBeginPeriod.restype = UINT 362 | 363 | # MMRESULT timeEndPeriod( 364 | # UINT uPeriod 365 | # ); 366 | timeEndPeriod = winmm.timeEndPeriod 367 | timeEndPeriod.argtypes = [UINT] 368 | timeEndPeriod.restype = UINT 369 | 370 | # HANDLE GetCurrentProcess( 371 | # ); 372 | GetCurrentProcess = kernel32.GetCurrentProcess 373 | GetCurrentProcess.argtypes = [] 374 | GetCurrentProcess.restype = HANDLE 375 | 376 | # DWORD GetPriorityClass( 377 | # HANDLE hProcess 378 | # ); 379 | GetPriorityClass = kernel32.GetPriorityClass 380 | GetPriorityClass.argtypes = [HANDLE] 381 | GetPriorityClass.restype = DWORD 382 | 383 | # BOOL SetPriorityClass( 384 | # HANDLE hProcess, 385 | # DWORD dwPriorityClass 386 | # ); 387 | SetPriorityClass = kernel32.SetPriorityClass 388 | SetPriorityClass.argtypes = [HANDLE, DWORD] 389 | SetPriorityClass.restype = BOOL 390 | 391 | HIGH_PRIORITY_CLASS = 0x00000080 392 | REALTIME_PRIORITY_CLASS = 0x00000100 393 | 394 | ### 进程操作 395 | 396 | # 窗口句柄 397 | pvz_hwnd = HWND() 398 | 399 | # 进程标识 400 | pvz_pid = DWORD() 401 | 402 | # 进程句柄 403 | pvz_handle = HANDLE() 404 | 405 | # 游戏版本 406 | pvz_version = None 407 | 408 | 409 | def is_valid(): 410 | """ 411 | 检查目标进程是否可用. 412 | 413 | @返回值 (bool): 未找到或者已经退出则返回 False. 414 | """ 415 | 416 | if pvz_handle.value is None: 417 | return False 418 | 419 | exit_code = DWORD() 420 | GetExitCodeProcess(pvz_handle, ctypes.byref(exit_code)) 421 | return exit_code.value == STILL_ACTIVE 422 | 423 | 424 | def open_process_by_window(class_name, window_name): 425 | """ 426 | 根据窗口的类名和标题打开进程. 427 | 428 | @参数 class_name(str): 窗口类名, 可省略为 None. 429 | 430 | @参数 window_name(str): 窗口标题, 可省略为 None. 431 | 432 | @返回值 (bool): 成功打开目标进程则返回 True. 433 | """ 434 | 435 | global pvz_hwnd, pvz_pid, pvz_handle 436 | 437 | # 关闭之前已经打开的句柄 438 | if is_valid(): 439 | CloseHandle(pvz_handle) 440 | 441 | pvz_hwnd.value = None 442 | pvz_pid.value = 0 443 | pvz_handle.value = None 444 | 445 | pvz_hwnd.value = FindWindowW(class_name, window_name) 446 | if pvz_hwnd.value is not None: 447 | GetWindowThreadProcessId(pvz_hwnd, ctypes.byref(pvz_pid)) 448 | if pvz_pid.value != 0: 449 | pvz_handle.value = OpenProcess(PROCESS_ALL_ACCESS, False, pvz_pid) 450 | 451 | result = pvz_handle.value is not None 452 | info("查找游戏窗口, 类名 '%s', 标题 '%s', 结果 %s." % (class_name, window_name, result)) 453 | return result 454 | 455 | 456 | def find_pvz(): 457 | """ 458 | 查找原版植物大战僵尸游戏进程. 459 | 460 | @返回值 (bool): 查找成功返回 True, 没找到或是版本不符则返回 False. 461 | """ 462 | 463 | global pvz_version 464 | 465 | # 不建议省略窗口类名, 因为可能存在其他标题相同的窗口从而引起查找失误. 466 | # 已知所有的植物大战僵尸一代电脑版的窗口类名均为 "MainWindow". 467 | # 原版英文版的窗口标题为 "Plants vs. Zombies". 468 | if not open_process_by_window("MainWindow", "Plants vs. Zombies"): 469 | open_process_by_window("MainWindow", None) 470 | 471 | if is_valid(): 472 | if read_memory("unsigned int", 0x004140C5) == 0x0019B337: 473 | pvz_version = "1.0.0.1051" 474 | info("已找到游戏 1.0.0.1051 !!!") 475 | return True 476 | elif read_memory("unsigned int", 0x004140D5) == 0x0019B827: 477 | pvz_version = "1.2.0.1065" 478 | info("已找到游戏 1.2.0.1065 !!!") 479 | return True 480 | else: 481 | pvz_version = None 482 | warning("不支持的游戏版本 !!!") 483 | return False 484 | else: 485 | pvz_version = None 486 | warning("未找到游戏 !!!") 487 | return False 488 | 489 | 490 | def pvz_ver(): 491 | return pvz_version 492 | 493 | 494 | # C/C++ 数据类型 495 | cpp_typename = { 496 | "char": "b", 497 | "signed char": "b", 498 | "int8_t": "b", 499 | "unsigned char": "B", 500 | "byte": "B", 501 | "uint8_t": "B", 502 | "bool": "?", 503 | "short": "h", 504 | "int16_t": "h", 505 | "unsigned short": "H", 506 | "uint16_t": "H", 507 | "int": "i", 508 | "int32_t": "i", 509 | "intptr_t": "i", 510 | "unsigned int": "I", 511 | "uint32_t": "I", 512 | "uintptr_t": "I", 513 | "size_t": "I", 514 | "long": "l", 515 | "unsigned long": "L", 516 | "long long": "q", 517 | "int64_t": "q", 518 | "intmax_t": "q", 519 | "unsigned long long": "Q", 520 | "uint64_t": "Q", 521 | "uintmax_t": "Q", 522 | "float": "f", 523 | "double": "d", 524 | } 525 | 526 | # 读写内存时加锁 527 | memory_lock = threading.Lock() 528 | 529 | ### 读写内存 530 | 531 | 532 | def read_memory(data_type, *address, array=1): 533 | """ 534 | 读取内存数据. 535 | 536 | @参数 data_type(str): 数据类型, 取自 C/C++ 语言关键字, 可选值 ["char", "bool", "unsigned char", "short", "unsigned short", "int", "unsigned int", "long", "unsigned long", "long long", "unsigned long long", "float", "double"]. 537 | 538 | @参数 address(int): 地址, 可为多级偏移. 539 | 540 | @参数 array(int): 数量. 默认一个, 大于一个时需要显式指定关键字参数. 541 | 542 | @返回值 (int/float/bool/tuple): 默认情况下返回单个数值, 获取多个数据则返回一个长度为指定数量的元组. 543 | 544 | @示例: 545 | 546 | >>> ReadMemory("int", 0x6a9ec0, 0x768, 0x5560) 547 | 8000 548 | 549 | >>> ReadMemory("byte", 0x0041d7d0, array=3) 550 | (81, 131, 248) 551 | """ 552 | 553 | if not is_valid(): 554 | critical("目标进程不可用, 读内存失败.") 555 | 556 | memory_lock.acquire() 557 | 558 | level = len(address) # 偏移级数 559 | offset = ctypes.c_void_p() # 内存地址 560 | buffer = ctypes.c_uint() # 中间数据缓冲 561 | bytes_read = ctypes.c_ulong() # 已读字节数 562 | 563 | for i in range(level): 564 | offset.value = buffer.value + address[i] 565 | 566 | if i != level - 1: 567 | size = ctypes.sizeof(buffer) 568 | success = ReadProcessMemory(pvz_handle, offset, ctypes.byref(buffer), size, ctypes.byref(bytes_read)) 569 | if success == 0 or bytes_read.value != size: 570 | critical("读取内存失败, 错误代码 %d." % GetLastError()) 571 | 572 | else: 573 | fmt_str = "<" + str(array) + cpp_typename[data_type] 574 | size = struct.calcsize(fmt_str) # 目标数据大小 575 | buff = ctypes.create_string_buffer(size) # 目标数据缓冲 576 | success = ReadProcessMemory(pvz_handle, offset, ctypes.byref(buff), size, ctypes.byref(bytes_read)) 577 | if success == 0 or bytes_read.value != size: 578 | critical("读取内存失败, 错误代码 %d." % GetLastError()) 579 | 580 | result = struct.unpack(fmt_str, buff.raw) 581 | 582 | memory_lock.release() 583 | 584 | debug("读取内存, 类型 %s, 地址 %s, 数量 %d, 结果 %s." % (data_type, str(address), array, str(result))) 585 | if array == 1: 586 | return result[0] 587 | else: 588 | return result 589 | 590 | 591 | def write_memory(data_type, values, *address): 592 | """ 593 | 写入内存数据. 594 | 595 | @参数 data_type(str): 数据类型, 取自 C/C++ 语言关键字, 可选值 ["char", "bool", "unsigned char", "short", "unsigned short", "int", "unsigned int", "long", "unsigned long", "long long", "unsigned long long", "float", "double"]. 596 | 597 | @参数 values(int/float/bool/list/tuple): 需要写入的数据, 多个数据采用列表或者元组形式. 598 | 599 | @参数 address(int): 地址, 可为多级偏移. 600 | 601 | @示例: 602 | 603 | >>> WriteMemory("int", 8000, 0x6a9ec0, 0x768, 0x5560) 604 | 605 | >>> WriteMemory("unsigned char", [0xb0, 0x01, 0xc3], 0x0041d7d0) 606 | """ 607 | 608 | if not is_valid(): 609 | critical("目标进程不可用, 写内存失败.") 610 | 611 | # 将单个数据转换为列表方便统一处理 612 | if not isinstance(values, (tuple, list)): 613 | values = [values] 614 | 615 | memory_lock.acquire() 616 | 617 | level = len(address) # 偏移级数 618 | offset = ctypes.c_void_p() # 内存地址 619 | buffer = ctypes.c_uint() # 中间数据缓冲 620 | bytes_read = ctypes.c_ulong() # 已读字节数 621 | bytes_written = ctypes.c_ulong() # 已写字节数 622 | 623 | for i in range(level): 624 | offset.value = buffer.value + address[i] 625 | 626 | if i != level - 1: 627 | size = ctypes.sizeof(buffer) 628 | success = ReadProcessMemory(pvz_handle, offset, ctypes.byref(buffer), size, ctypes.byref(bytes_read)) 629 | if success == 0 or bytes_read.value != size: 630 | critical("读取内存失败, 错误代码 %d." % GetLastError()) 631 | 632 | else: 633 | array = len(values) # 目标数据的数量 634 | fmt_str = "<" + str(array) + cpp_typename[data_type] 635 | size = struct.calcsize(fmt_str) # 目标数据大小 636 | buff = ctypes.create_string_buffer(size) # 创建目标数据缓冲 637 | buff.value = struct.pack(fmt_str, *values) # 将目标数据载入缓冲区 638 | success = WriteProcessMemory(pvz_handle, offset, ctypes.byref(buff), size, ctypes.byref(bytes_written)) 639 | if success == 0 or bytes_written.value != size: 640 | critical("写入内存失败, 错误代码 %d." % GetLastError()) 641 | 642 | memory_lock.release() 643 | 644 | debug("写入内存, 类型 %s, 数值 %s, 地址 %s." % (data_type, str(values), str(address))) 645 | 646 | 647 | ### 窗口置顶 648 | 649 | 650 | def active_pvz(): 651 | """ 652 | 激活 PvZ 窗口. (后台时不激活.) 653 | """ 654 | if pvz_hwnd.value is not None: 655 | SetActiveWindow(pvz_hwnd) 656 | 657 | 658 | def set_pvz_foreground(): 659 | """ 660 | 激活并前台显示 PvZ 窗口. 661 | """ 662 | if pvz_hwnd.value is not None: 663 | SetForegroundWindow(pvz_hwnd) 664 | 665 | 666 | def set_pvz_top_most(on=True): 667 | """ 668 | 置顶显示游戏窗口. 669 | 670 | @参数 on(bool): 是否开启. 671 | """ 672 | if pvz_hwnd.value is not None: 673 | SetWindowPos( 674 | pvz_hwnd, # 675 | HWND_TOPMOST if on else HWND_NOTOPMOST, # 676 | 0, # 677 | 0, # 678 | 0, # 679 | 0, # 680 | SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW, # 681 | ) 682 | 683 | 684 | ### 进程优先级 685 | 686 | 687 | def set_pvz_high_priority(): 688 | """ 689 | 游戏进程以高优先级运行. 可在一定程度上提高帧率. 690 | """ 691 | if GetPriorityClass(pvz_handle) != REALTIME_PRIORITY_CLASS: 692 | SetPriorityClass(pvz_handle, HIGH_PRIORITY_CLASS) 693 | 694 | 695 | # 汇编代码 696 | 697 | asm_code = bytes() 698 | 699 | 700 | # 初始化 701 | def asm_init(): 702 | global asm_code 703 | asm_code = bytes() 704 | 705 | 706 | # unsigned char 707 | def asm_add_byte(code): 708 | global asm_code 709 | asm_code += struct.pack("<1B", code) 710 | 711 | 712 | # unsigned short 713 | def asm_add_word(code): 714 | global asm_code 715 | asm_code += struct.pack("<1H", code) 716 | 717 | 718 | # unsigned int 719 | def asm_add_dword(code): 720 | global asm_code 721 | asm_code += struct.pack("<1I", code) 722 | 723 | 724 | # bytes from list/tuple 725 | def asm_add_bytes(codes): 726 | for code in codes: 727 | asm_add_byte(code) 728 | 729 | 730 | # push 0x12345678 731 | def asm_push(code): 732 | asm_add_byte(0x68) 733 | asm_add_dword(code) 734 | 735 | 736 | # mov exx, 0x12345678 737 | asm_mov_exx_code = { 738 | "eax": [0xB8], 739 | "ebx": [0xBB], 740 | "ecx": [0xB9], 741 | "edx": [0xBA], 742 | "esi": [0xBE], 743 | "edi": [0xBF], 744 | "ebp": [0xBD], 745 | "esp": [0xBC] 746 | } 747 | 748 | 749 | def asm_mov_exx(register, code): 750 | asm_add_bytes(asm_mov_exx_code[register]) 751 | asm_add_dword(code) 752 | 753 | 754 | # add exx, 0x12345678 755 | asm_add_exx_code = { 756 | "eax": [0x05], 757 | "ebx": [0x81, 0xC3], 758 | "ecx": [0x81, 0xC1], 759 | "edx": [0x81, 0xC2], 760 | "esi": [0x81, 0xC6], 761 | "edi": [0x81, 0xC7], 762 | "ebp": [0x81, 0xC5], 763 | "esp": [0x81, 0xC4], 764 | } 765 | 766 | 767 | def asm_add_exx(register, code): 768 | asm_add_bytes(asm_add_exx_code[register]) 769 | asm_add_dword(code) 770 | 771 | 772 | # mov exx, ds:[0x12345678] 773 | asm_mov_exx_dword_ptr_code = { 774 | "eax": [0x3E, 0xA1], 775 | "ebx": [0x3E, 0x8B, 0x1D], 776 | "ecx": [0x3E, 0x8B, 0x0D], 777 | "edx": [0x3E, 0x8B, 0x15], 778 | "esi": [0x3E, 0x8B, 0x35], 779 | "edi": [0x3E, 0x8B, 0x3D], 780 | "ebp": [0x3E, 0x8B, 0x2D], 781 | "esp": [0x3E, 0x8B, 0x25], 782 | } 783 | 784 | 785 | def asm_mov_exx_dword_ptr(register, code): 786 | asm_add_bytes(asm_mov_exx_dword_ptr_code[register]) 787 | asm_add_dword(code) 788 | 789 | 790 | # mov exx, [exx + 0x12345678] 791 | asm_mov_exx_dword_ptr_exx_add_code = { 792 | "eax": [0x8B, 0x80], 793 | "ebx": [0x8B, 0x9B], 794 | "ecx": [0x8B, 0x89], 795 | "edx": [0x8B, 0x92], 796 | "esi": [0x8B, 0xB6], 797 | "edi": [0x8B, 0xBF], 798 | "ebp": [0x8B, 0xAD], 799 | "esp": [0x8B, 0xA4, 0x24], 800 | } 801 | 802 | 803 | def asm_mov_exx_dword_ptr_exx_add(register, code): 804 | asm_add_bytes(asm_mov_exx_dword_ptr_exx_add_code[register]) 805 | asm_add_dword(code) 806 | 807 | 808 | # push exx 809 | asm_push_exx_code = { 810 | "eax": [0x50], 811 | "ebx": [0x53], 812 | "ecx": [0x51], 813 | "edx": [0x52], 814 | "esi": [0x56], 815 | "edi": [0x57], 816 | "ebp": [0x55], 817 | "esp": [0x54] 818 | } 819 | 820 | 821 | def asm_push_exx(register): 822 | asm_add_bytes(asm_push_exx_code[register]) 823 | 824 | 825 | # pop exx 826 | asm_pop_exx_code = { 827 | "eax": [0x58], 828 | "ebx": [0x5B], 829 | "ecx": [0x59], 830 | "edx": [0x5A], 831 | "esi": [0x5E], 832 | "edi": [0x5F], 833 | "ebp": [0x5D], 834 | "esp": [0x5C] 835 | } 836 | 837 | 838 | def asm_pop_exx(register): 839 | asm_add_bytes(asm_pop_exx_code[register]) 840 | 841 | 842 | # ret 843 | def asm_ret(): 844 | asm_add_byte(0xC3) 845 | 846 | 847 | # call 0x12345678 848 | # call $ + 7 849 | # jmp short $ + 8 850 | # push 0x12345678 851 | # ret 852 | def asm_call(address): 853 | asm_add_bytes([0xE8, 0x02, 0x00, 0x00, 0x00]) 854 | asm_add_bytes([0xEB, 0x06]) 855 | asm_push(address) 856 | asm_ret() 857 | 858 | 859 | # inject 860 | def asm_code_inject(): 861 | """ 862 | 远程注入汇编代码. 863 | """ 864 | 865 | size = len(asm_code) 866 | 867 | # thread_addr = LPVOID() 868 | thread_addr = VirtualAllocEx(pvz_handle, None, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE) 869 | 870 | if thread_addr is not None: 871 | 872 | bytes_written = ctypes.c_ulong() 873 | success = WriteProcessMemory(pvz_handle, thread_addr, asm_code, size, ctypes.byref(bytes_written)) 874 | if success == 0 or bytes_written.value != size: 875 | critical("写入汇编代码失败, 错误代码 %d." % GetLastError()) 876 | 877 | # thread_handle = HANDLE() 878 | start = LPTHREAD_START_ROUTINE(thread_addr) 879 | thread_handle = CreateRemoteThread(pvz_handle, None, 0, start, None, 0, None) 880 | 881 | if thread_handle is not None: 882 | success = WaitForSingleObject(thread_handle, INFINITE) 883 | if success == WAIT_FAILED: 884 | critical("等待对象返回失败, 错误代码 %d." % GetLastError()) 885 | success = CloseHandle(thread_handle) 886 | if success == 0: 887 | critical("关闭远程线程句柄失败, 错误代码 %d." % GetLastError()) 888 | else: 889 | critical("创建远程线程失败, 错误代码 %d." % GetLastError()) 890 | 891 | success = VirtualFreeEx(pvz_handle, thread_addr, 0, MEM_RELEASE) 892 | if success == 0: 893 | critical("释放内存失败, 错误代码 %d." % GetLastError()) 894 | 895 | else: 896 | critical("分配内存失败, 错误代码 %d." % GetLastError()) 897 | 898 | info("远程注入代码: %s." % str([hex(x) for x in asm_code])) 899 | 900 | 901 | # 避免崩溃 902 | def asm_code_inject_safely(): 903 | if pvz_ver() == "1.0.0.1051": 904 | write_memory("unsigned char", 0xFE, 0x00552014) 905 | else: 906 | write_memory("unsigned char", 0xFE, 0x00552244) 907 | time.sleep(0.01) 908 | if is_valid(): 909 | asm_code_inject() 910 | if pvz_ver() == "1.0.0.1051": 911 | write_memory("unsigned char", 0xDB, 0x00552014) 912 | else: 913 | write_memory("unsigned char", 0xDB, 0x00552244) 914 | 915 | 916 | ### 键盘操作 917 | 918 | 919 | def press_esc(): 920 | """ 921 | 敲击 退出 键. 922 | """ 923 | PostMessageW(pvz_hwnd, WM_KEYDOWN, VK_ESCAPE, 0) 924 | PostMessageW(pvz_hwnd, WM_KEYUP, VK_ESCAPE, 0) 925 | 926 | 927 | def press_space(): 928 | """ 929 | 敲击 空格 键. 930 | """ 931 | PostMessageW(pvz_hwnd, WM_KEYDOWN, VK_SPACE, 0) 932 | PostMessageW(pvz_hwnd, WM_KEYUP, VK_SPACE, 0) 933 | 934 | 935 | def press_enter(): 936 | """ 937 | 敲击 回车 键. 938 | """ 939 | PostMessageW(pvz_hwnd, WM_KEYDOWN, VK_RETURN, 0) 940 | PostMessageW(pvz_hwnd, WM_KEYUP, VK_RETURN, 0) 941 | 942 | 943 | def press_left(): 944 | """ 945 | 敲击 左方向 键. 946 | """ 947 | PostMessageW(pvz_hwnd, WM_KEYDOWN, VK_LEFT, 0) 948 | PostMessageW(pvz_hwnd, WM_KEYUP, VK_LEFT, 0) 949 | 950 | 951 | def press_up(): 952 | """ 953 | 敲击 上方向 键. 954 | """ 955 | PostMessageW(pvz_hwnd, WM_KEYDOWN, VK_UP, 0) 956 | PostMessageW(pvz_hwnd, WM_KEYUP, VK_UP, 0) 957 | 958 | 959 | def press_right(): 960 | """ 961 | 敲击 右方向 键. 962 | """ 963 | PostMessageW(pvz_hwnd, WM_KEYDOWN, VK_RIGHT, 0) 964 | PostMessageW(pvz_hwnd, WM_KEYUP, VK_RIGHT, 0) 965 | 966 | 967 | def press_down(): 968 | """ 969 | 敲击 下方向 键. 970 | """ 971 | PostMessageW(pvz_hwnd, WM_KEYDOWN, VK_DOWN, 0) 972 | PostMessageW(pvz_hwnd, WM_KEYUP, VK_DOWN, 0) 973 | 974 | 975 | def press_key(key): 976 | """ 977 | 敲击按键. 可选值 '0' - '9' 'A' - 'Z'. 978 | """ 979 | code = ord(key) 980 | PostMessageW(pvz_hwnd, WM_KEYDOWN, code, 0) 981 | PostMessageW(pvz_hwnd, WM_KEYUP, code, 0) 982 | 983 | 984 | def press_keys(keys): 985 | """ 986 | 敲击一系列按键. 987 | 988 | @参数 keys(str): 按键字符串, 由 '0' - '9' 'A' - 'Z' 组成. 989 | 990 | @示例: 991 | 992 | >>> PressKeys("FUTURE") # 智慧树指令, 使僵尸带上眼镜 993 | """ 994 | for k in keys: 995 | press_key(k.upper()) 996 | 997 | 998 | ### 鼠标操作 999 | 1000 | dpi_scale = 1.0 1001 | 1002 | 1003 | def get_dpi_scale(): 1004 | """ 1005 | 获取 DPI 缩放比例. 1006 | """ 1007 | screen = GetDC(None) 1008 | if screen is not None: 1009 | virtual_width = GetDeviceCaps(screen, HORZRES) 1010 | physical_width = GetDeviceCaps(screen, DESKTOPHORZRES) 1011 | ReleaseDC(None, screen) 1012 | scale = physical_width / virtual_width 1013 | else: 1014 | scale = 1.0 1015 | 1016 | global dpi_scale 1017 | dpi_scale = scale 1018 | info("获取 DPI 缩放比例: %s." % dpi_scale) 1019 | 1020 | 1021 | def set_dpi_scale(scale): 1022 | """ 1023 | 设置 DPI 缩放比例. 1024 | 1025 | @参数 scale(float): 比例系数. 1026 | """ 1027 | global dpi_scale 1028 | dpi_scale = scale 1029 | info("设置 DPI 缩放比例: %s." % dpi_scale) 1030 | 1031 | 1032 | def MAKELONG(low, high): 1033 | # low += 0 # 加上画面横坐标偏移 [[[6a9ec0]+768]+30] 1034 | if dpi_scale != 1.0: 1035 | low, high = int(low / dpi_scale), int(high / dpi_scale) 1036 | else: 1037 | low, high = int(low), int(high) 1038 | return ((high & 0xFFFF) << 16) | (low & 0xFFFF) 1039 | 1040 | 1041 | def left_down(x, y): 1042 | """ 1043 | 鼠标左键按下. 1044 | """ 1045 | coord = MAKELONG(x, y) 1046 | PostMessageW(pvz_hwnd, WM_LBUTTONDOWN, MK_LBUTTON, coord) 1047 | 1048 | 1049 | def left_up(x, y): 1050 | """ 1051 | 鼠标左键弹起. 1052 | """ 1053 | coord = MAKELONG(x, y) 1054 | PostMessageW(pvz_hwnd, WM_LBUTTONUP, MK_LBUTTON, coord) 1055 | 1056 | 1057 | def left_click(x, y): 1058 | """ 1059 | 鼠标左键单击. 1060 | 1061 | @参数 x(int): 横坐标, 单位像素. 建议范围 [0, 799]. 1062 | 1063 | @参数 y(int): 纵坐标, 单位像素. 建议范围 [0, 599]. 1064 | 1065 | @示例: 1066 | 1067 | >>> LeftClick(108, 42) # 左键单击卡槽第一张卡片的位置 1068 | """ 1069 | coord = MAKELONG(x, y) 1070 | PostMessageW(pvz_hwnd, WM_LBUTTONDOWN, MK_LBUTTON, coord) 1071 | PostMessageW(pvz_hwnd, WM_LBUTTONUP, MK_LBUTTON, coord) 1072 | 1073 | 1074 | def right_down(x, y): 1075 | """ 1076 | 鼠标右键按下. 1077 | """ 1078 | coord = MAKELONG(x, y) 1079 | PostMessageW(pvz_hwnd, WM_RBUTTONDOWN, MK_RBUTTON, coord) 1080 | 1081 | 1082 | def right_up(x, y): 1083 | """ 1084 | 鼠标右键弹起. 1085 | """ 1086 | coord = MAKELONG(x, y) 1087 | PostMessageW(pvz_hwnd, WM_RBUTTONUP, MK_RBUTTON, coord) 1088 | 1089 | 1090 | def right_click(x, y): 1091 | """ 1092 | 鼠标右键单击. 1093 | 1094 | @参数 x(int): 横坐标, 单位像素. 建议范围 [0, 799]. 1095 | 1096 | @参数 y(int): 纵坐标, 单位像素. 建议范围 [0, 599]. 1097 | 1098 | @示例: 1099 | 1100 | >>> RightClick(399, 299) # 右键单击场地中间位置 1101 | """ 1102 | coord = MAKELONG(x, y) 1103 | PostMessageW(pvz_hwnd, WM_RBUTTONDOWN, MK_RBUTTON, coord) 1104 | PostMessageW(pvz_hwnd, WM_RBUTTONUP, MK_RBUTTON, coord) 1105 | 1106 | 1107 | def special_button_click(x, y): 1108 | """ 1109 | 适用于模仿者按钮和菜单按钮的特殊点击. 1110 | 1111 | 调用的时候不要把鼠标光标放在游戏窗口内. 1112 | 1113 | @参数 x(int): 横坐标, 单位像素. 建议范围 [0, 799]. 1114 | 1115 | @参数 y(int): 纵坐标, 单位像素. 建议范围 [0, 599]. 1116 | 1117 | @示例: 1118 | 1119 | >>> ButtonClick(490, 550) # 选卡界面点击模仿者卡片 1120 | 1121 | >>> ButtonClick(740, 10) # 点击菜单按钮 1122 | """ 1123 | left_down(x, y) 1124 | right_down(x, y) 1125 | left_up(x, y) 1126 | right_up(x, y) 1127 | time.sleep(0.01) 1128 | 1129 | 1130 | ### 卡片和僵尸名称 1131 | 1132 | seeds_string = [ 1133 | ["Peashooter", "豌豆射手", "豌豆", "单发"], 1134 | ["Sunflower", "向日葵", "小向", "太阳花", "花"], 1135 | ["Cherry Bomb", "樱桃炸弹", "樱桃", "炸弹", "爆炸", "草莓", "樱"], 1136 | ["Wall-nut", "坚果", "坚果墙", "墙果", "建国", "柠檬圆"], 1137 | ["Potato Mine", "土豆地雷", "土豆", "地雷", "土豆雷"], 1138 | ["Snow Pea", "寒冰射手", "冰豆", "冰冻豌豆", "冰豌豆", "雪花豌豆", "雪花"], 1139 | ["Chomper", "大嘴花", "大嘴", "食人花", "咀嚼者", "食"], 1140 | ["Repeater", "双重射手", "双发射手", "双重", "双发", "双发豌豆"], 1141 | ["Puff-shroom", "小喷菇", "小喷", "喷汽蘑菇", "烟雾蘑菇", "免费蘑菇", "炮灰菇", "小蘑菇", "免费货", "免费", "紫蘑菇"], 1142 | ["Sun-shroom", "阳光菇", "阳光", "阳光蘑菇"], 1143 | ["Fume-shroom", "大喷菇", "大喷", "烟雾喷菇", "大蘑菇", "喷子", "喷"], 1144 | ["Grave Buster", "咬咬碑", "墓碑吞噬者", "墓碑破坏者", "噬碑藤", "墓碑", "墓碑苔藓", "苔藓"], 1145 | ["Hypno-shroom", "迷糊菇", "魅惑菇", "魅惑", "迷惑菇", "迷蘑菇", "催眠蘑菇", "催眠", "花蘑菇", "毒蘑菇"], 1146 | ["Scaredy-shroom", "胆小菇", "胆小", "胆怯蘑菇", "胆小鬼蘑菇", "杠子蘑菇"], 1147 | ["Ice-shroom", "冰川菇", "寒冰菇", "冰菇", "冷冻蘑菇", "冰蘑菇", "冰莲菇", "面瘫", "蓝冰", "原版冰", "冰"], 1148 | ["Doom-shroom", "末日菇", "毁灭菇", "核蘑菇", "核弹", "核武", "毁灭", "末日蘑菇", "末日", "黑核", "原版核", "核"], 1149 | ["Lily Pad", "莲叶", "睡莲", "荷叶", "莲"], 1150 | ["Squash", "窝瓜", "倭瓜", "窝瓜大叔", "倭瓜大叔", "镇压者"], 1151 | ["Threepeater", "三重射手", "三线射手", "三头豌豆", "三联装豌豆", "三重", "三线", "三头", "三管", "管"], 1152 | ["Tangle Kelp", "缠绕水草", "缠绕海草", "缠绕海藻", "缠绕海带", "水草", "海草", "海藻", "海带", "马尾藻", "绿毛线", "毛线"], 1153 | ["Jalapeno", "火爆辣椒", "辣椒", "墨西哥胡椒", "墨西哥辣椒", "辣", "椒"], 1154 | ["Spikeweed", "地刺", "刺", "尖刺", "尖刺杂草", "棘草", "荆棘"], 1155 | ["Torchwood", "火炬树桩", "火树桩", "火树", "火炬", "树桩", "火炬木", "火"], 1156 | ["Tall-nut", "高坚果", "搞基果", "高建国", "巨大墙果", "巨大", "高墙果", "大墙果", "大土豆"], 1157 | ["Sea-shroom", "水兵菇", "海蘑菇"], 1158 | ["Plantern", "路灯花", "灯笼", "路灯", "灯笼草", "灯笼花", "吐槽灯", "灯"], 1159 | ["Cactus", "仙人掌", "小仙", "掌"], 1160 | ["Blover", "三叶草", "三叶", "风扇", "吹风", "愤青"], 1161 | ["Split Pea", "双向射手", "裂荚射手", "裂荚", "双头", "分裂豌豆", "双头豌豆"], 1162 | ["Starfruit", "星星果", "杨桃", "星星", "五角星", "五星黄果", "1437", "大帝", "桃"], 1163 | ["Pumpkin", "南瓜头", "南瓜", "南瓜罩", "南瓜壳", "套"], 1164 | ["Magnet-shroom", "磁力菇", "磁铁", "磁力蘑菇", "磁"], 1165 | ["Cabbage-pult", "卷心菜投手", "包菜", "卷心菜", "卷心菜投抛者", "包菜投掷手"], 1166 | ["Flower Pot", "花盆", "盆"], 1167 | ["Kernel-pult", "玉米投手", "玉米", "黄油投手", "玉米投抛者", "玉米投掷手"], 1168 | ["Coffee Bean", "咖啡豆", "咖啡", "兴奋剂", "春药"], 1169 | ["Garlic", "大蒜", "蒜"], 1170 | ["Umbrella Leaf", "萝卜伞", "叶子保护伞", "伞型保护叶", "莴苣", "萝卜", "白菜", "保护伞", "叶子伞", "伞叶", "叶子", "伞", "叶"], 1171 | ["Marigold", "金盏花", "金盏草", "金盏菊", "吐钱花"], 1172 | ["Melon-pult", "西瓜投手", "西瓜", "绿皮瓜", "瓜", "西瓜投抛者", "西瓜投掷手"], 1173 | ["Gatling Pea", "机枪射手", "加特林豌豆", "格林豌豆", "加特林", "机枪", "枪"], 1174 | ["Twin Sunflower", "双胞向日葵", "双子向日葵", "双头葵花", "双胞", "双子", "双向", "双花"], 1175 | ["Gloom-shroom", "多嘴小蘑菇", "忧郁蘑菇", "忧郁", "忧郁菇", "章鱼", "曾哥", "曾哥蘑菇", "曾"], 1176 | ["Cattail", "猫尾草", "香蒲", "猫尾", "猫尾香蒲", "小猫香蒲", "小猫", "猫"], 1177 | ["Winter Melon", "冰西瓜", "'冰'瓜", '"冰"瓜', "冰瓜", "冰冻西瓜", "冬季西瓜"], 1178 | ["Gold Magnet", "吸金菇", "吸金磁", "吸金草", "金磁铁", "吸金", "磁力金钱菇"], 1179 | ["Spikerock", "钢地刺", "钢刺", "地刺王", "尖刺岩石", "尖刺石", "石荆棘"], 1180 | ["Cob Cannon", "玉米加农炮", "玉米炮", "加农炮", "春哥", "春哥炮", "炮", "春", "神"], 1181 | ] 1182 | 1183 | # # 确保没有重复项, 发布时注释掉 1184 | # seeds_string_all = [] 1185 | # for items in seeds_string: 1186 | # seeds_string_all += items 1187 | # print(len(seeds_string_all)) 1188 | # assert len(seeds_string_all) == len(set(seeds_string_all)) 1189 | 1190 | # 模仿者卡片前缀 1191 | seeds_imitater_string = ["Imitater", "imitater", "变身茄子", "模仿者", "模仿", "复制", "白", "小白", "克隆"] 1192 | 1193 | # 整理成字典方便快速查找 1194 | # key: 卡片名称 1195 | # value: 卡片代号 0~47 (模仿者 +48) 1196 | seeds_string_dict = {} 1197 | for i, items in enumerate(seeds_string): 1198 | for item in items: 1199 | # 绿卡 紫卡 1200 | seeds_string_dict[item] = i 1201 | # 白卡 1202 | for j, im in enumerate(seeds_imitater_string): 1203 | seeds_string_dict[im + item] = i + 48 1204 | seeds_string_dict[im + " " + item] = i + 48 1205 | # print(len(seeds_string_dict)) # it's huge!!! 1206 | 1207 | zombies_string = [ 1208 | ["Zombie", "普僵", "普通", "领带"], 1209 | ["Flag Zombie", "旗帜", "摇旗", "旗子"], 1210 | ["Conehead Zombie", "路障"], 1211 | ["Pole Vaulting Zombie", "撑杆", "撑杆跳"], 1212 | ["Buckethead Zombie", "铁桶"], 1213 | ["Newspaper Zombie", "读报", "报纸"], 1214 | ["Screen Door Zombie", "铁门", "铁栅门", "门板"], 1215 | ["Football Zombie", "橄榄", "橄榄球"], 1216 | ["Dancing Zombie", "舞王", "MJ"], 1217 | ["Backup Dancer", "伴舞", "舞伴"], 1218 | ["Ducky Tube Zombie", "鸭子", "救生圈"], 1219 | ["Snorkel Zombie", "潜水"], 1220 | ["Zomboni", "冰车", "制冰车"], 1221 | ["Zombie Bobsled Team", "雪橇", "雪橇队", "雪橇小队"], 1222 | ["Dolphin Rider Zombie", "海豚", "海豚骑士"], 1223 | ["Jack-in-the-Box Zombie", "小丑", "玩偶匣"], 1224 | ["Balloon Zombie", "气球"], 1225 | ["Digger Zombie", "矿工", "挖地"], 1226 | ["Pogo Zombie", "跳跳", "弹跳"], 1227 | ["Zombie Yeti", "雪人"], 1228 | ["Bungee Zombie", "蹦极", "小偷"], 1229 | ["Ladder Zombie", "扶梯", "梯子"], 1230 | ["Catapult Zombie", "投篮", "投篮车", "篮球"], 1231 | ["Gargantuar", "白眼", "伽刚特尔", "巨人"], 1232 | ["Imp", "小鬼", "小恶魔", "IMP"], 1233 | ["Dr. Zomboss", "僵王", "僵博"], 1234 | ["Peashooter Zombie", "豌豆"], 1235 | ["Wall-nut Zombie", "坚果"], 1236 | ["Jalapeno Zombie", "辣椒"], 1237 | ["Gatling Pea Zombie", "机枪", "加特林"], 1238 | ["Squash Zombie", "倭瓜", "窝瓜"], 1239 | ["Tall-nut Zombie", "高坚果"], 1240 | ["GigaGargantuar", "红眼", "暴走伽刚特尔", "红眼巨人"], 1241 | ] 1242 | 1243 | zombies_string_dict = {} 1244 | for i, zombies in enumerate(zombies_string): 1245 | for j, z in enumerate(zombies): 1246 | if j == 0: # 第一个英文名称 1247 | zombies_string_dict[z] = i 1248 | else: 1249 | zombies_string_dict[z] = i 1250 | zombies_string_dict[z + "僵尸"] = i 1251 | # print(len(zombies_string_dict)) 1252 | 1253 | ### 延时 1254 | 1255 | 1256 | def thread_sleep_for(time_cs): 1257 | """ 1258 | 线程睡眠延时. 1259 | 1260 | 实际睡眠时间依赖于操作系统线程切换时间片精度. 1261 | 1262 | @参数 time_cs(float): 时间, 单位 cs, 精度 0.1. 1263 | """ 1264 | 1265 | if time_cs > 0.0: 1266 | time.sleep(time_cs / 100) 1267 | elif time_cs == 0.0: 1268 | pass 1269 | else: 1270 | error("线程睡眠时间不能小于零.") 1271 | 1272 | 1273 | # 细微延时 1274 | def delay_a_little_time(): 1275 | thread_sleep_for(0.1) # 1ms 1276 | 1277 | 1278 | ### 子线程装饰器 1279 | 1280 | 1281 | def running_in_thread(func): 1282 | """ 1283 | 将此装饰器应用到需要在子线程运行的函数上. 1284 | 1285 | 定义一个函数, 应用该装饰器, 则函数在调用的时候会运行在单独的线程中. 1286 | 1287 | @示例: 1288 | 1289 | >>> @RunningInThread 1290 | >>> def func(): 1291 | >>> pass 1292 | >>> # ... 1293 | >>> func() 1294 | """ 1295 | @functools.wraps(func) # 复制原函数元信息 1296 | def wrapper(*args, **kwargs): 1297 | thread = threading.Thread(target=func, args=args, kwargs=kwargs) 1298 | # thread.setDaemon(True) # 守护线程 1299 | thread.start() 1300 | 1301 | return wrapper 1302 | -------------------------------------------------------------------------------- /pvz/extra.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | from .core import * 4 | 5 | ### 读取常用信息 6 | 7 | 8 | def game_on(): 9 | """ 10 | @返回值 (bool): 游戏是否开启, 没开则会尝试重新查找一次. 11 | """ 12 | if is_valid(): 13 | return True 14 | else: 15 | return find_pvz() 16 | 17 | 18 | def game_ui(): 19 | """ 20 | @返回值 (int): 游戏界面. 21 | 22 | 1: 主界面, 2: 选卡, 3: 正常游戏/战斗, 4: 僵尸进屋, 7: 模式选择. 23 | """ 24 | return read_memory("int", 0x6A9EC0, 0x7FC) 25 | 26 | 27 | def game_mode(): 28 | """ 29 | @返回值 (int): 游戏模式, 13 为生存无尽. 30 | """ 31 | return read_memory("int", 0x6A9EC0, 0x7F8) 32 | 33 | 34 | def game_scene(): 35 | """ 36 | @返回值 (int): 游戏场景/场地/地图. 37 | 38 | 0: 白天, 1: 黑夜, 2: 泳池, 3: 浓雾, 4: 屋顶, 5: 月夜, 6: 蘑菇园, 7: 禅境花园, 8: 水族馆, 9: 智慧树. 39 | """ 40 | return read_memory("int", 0x6A9EC0, 0x768, 0x554C) 41 | 42 | 43 | def game_paused(): 44 | """ 45 | @返回值 (bool): 当前游戏是否暂停. 46 | """ 47 | return read_memory("bool", 0x6A9EC0, 0x768, 0x164) 48 | 49 | 50 | def mouse_in_game(): 51 | """ 52 | @返回值 (bool): 鼠标是否在游戏窗口内部. 53 | """ 54 | return read_memory("bool", 0x6A9EC0, 0x768, 0x138, 0x18) # 0x6A9EC0, 0x768, 0x59 55 | 56 | 57 | def mouse_have_something(): 58 | """ 59 | @返回值 (bool): 鼠标是否选中卡炮或铲子. 60 | """ 61 | return read_memory("int", 0x6A9EC0, 0x768, 0x138, 0x30) in (1, 6, 8) 62 | 63 | 64 | def game_clock(): 65 | """ 66 | @返回值 (int): 内部时钟, 游戏暂停和选卡时会暂停计时. 67 | """ 68 | return read_memory("int", 0x6A9EC0, 0x768, 0x5568) 69 | 70 | 71 | def wave_init_countdown(): 72 | """ 73 | @返回值 (int): 刷新倒计时初始值. 74 | """ 75 | return read_memory("int", 0x6A9EC0, 0x768, 0x55A0) 76 | 77 | 78 | def wave_countdown(): 79 | """ 80 | @返回值 (int): 下一波刷新倒计时, 触发刷新时重置为 200, 减少至 1 后刷出下一波. 81 | """ 82 | return read_memory("int", 0x6A9EC0, 0x768, 0x559C) 83 | 84 | 85 | def huge_wave_countdown(): 86 | """ 87 | @返回值 (int): 大波刷新倒计时, 对于旗帜波, 刷新倒计时减少至 4 后停滞, 由该值代替减少. 88 | """ 89 | return read_memory("int", 0x6A9EC0, 0x768, 0x55A4) 90 | 91 | 92 | def current_wave(): 93 | """ 94 | @返回值 (int): 已刷新波数. 95 | """ 96 | return read_memory("int", 0x6A9EC0, 0x768, 0x557C) 97 | 98 | 99 | ### 修改出怪 100 | 101 | 102 | # 从出怪种子生成出怪类型 103 | def update_zombies_type(): 104 | write_memory("bool", [False] * 33, 0x6A9EC0, 0x768, 0x54D4) 105 | asm_init() 106 | asm_mov_exx_dword_ptr("esi", 0x6A9EC0) 107 | asm_mov_exx_dword_ptr_exx_add("esi", 0x768) 108 | asm_mov_exx_dword_ptr_exx_add("esi", 0x160) 109 | asm_call(0x00425840) if pvz_ver() == "1.0.0.1051" else asm_call(0x004258A0) 110 | asm_ret() 111 | asm_code_inject_safely() 112 | 113 | 114 | # 从出怪类型生成出怪列表 115 | def update_zombies_list(): 116 | asm_init() 117 | asm_mov_exx_dword_ptr("edi", 0x6A9EC0) 118 | asm_mov_exx_dword_ptr_exx_add("edi", 0x768) 119 | asm_call(0x004092E0) if pvz_ver() == "1.0.0.1051" else asm_call(0x004092F0) 120 | asm_ret() 121 | asm_code_inject_safely() 122 | 123 | 124 | # 更新选卡界面出怪预览 125 | def update_zombies_preview(): 126 | write_memory("byte", 0x80, 0x0043A153) if pvz_ver() == "1.0.0.1051" else write_memory("byte", 0x80, 0x0043A1C3) 127 | asm_init() 128 | asm_mov_exx_dword_ptr("ebx", 0x6A9EC0) 129 | asm_mov_exx_dword_ptr_exx_add("ebx", 0x768) 130 | asm_call(0x0040DF70) if pvz_ver() == "1.0.0.1051" else asm_call(0x0040DF80) 131 | asm_mov_exx_dword_ptr("eax", 0x6A9EC0) 132 | asm_mov_exx_dword_ptr_exx_add("eax", 0x768) 133 | asm_mov_exx_dword_ptr_exx_add("eax", 0x15C) 134 | asm_push_exx("eax") 135 | asm_call(0x0043A140) if pvz_ver() == "1.0.0.1051" else asm_call(0x0043A1B0) 136 | asm_ret() 137 | asm_code_inject_safely() 138 | write_memory("byte", 0x85, 0x0043A153) if pvz_ver() == "1.0.0.1051" else write_memory("byte", 0x85, 0x0043A1C3) 139 | 140 | 141 | # 僵尸名称统一转换为代号表示 142 | def zombie_name_to_index(zombie): 143 | if isinstance(zombie, str): 144 | if zombie in zombies_string_dict: 145 | return zombies_string_dict[zombie] 146 | else: 147 | error("未知僵尸名称: %s." % zombie) 148 | else: # int 149 | if zombie in range(33): 150 | return zombie 151 | else: 152 | error("未知僵尸类型代号: %s." % zombie) 153 | 154 | 155 | def set_internal_spawn(zombies=None): 156 | """ 157 | 内置刷怪, 由游戏自带函数生成出怪列表. 普僵不管有没有选都是必出. 158 | 159 | @参数 zombies(list[str/int]): 包含僵尸名称或代号的列表. 160 | """ 161 | 162 | if not (game_on() and game_ui() in (2, 3)): 163 | return 164 | 165 | if zombies is None: 166 | zombies = [] 167 | zombies += [0] # 普僵必出 168 | zombies = [zombie_name_to_index(z) for z in zombies] 169 | zombies = list(set(zombies)) 170 | 171 | zombies_type_offset = read_memory("unsigned int", 0x6A9EC0, 0x768) + 0x54D4 172 | write_memory("bool", [True if i in zombies else False for i in range(33)], zombies_type_offset) 173 | update_zombies_list() 174 | 175 | if game_ui() == 2: 176 | update_zombies_preview() 177 | 178 | 179 | def set_customize_spawn(zombies=None): 180 | """ 181 | 自定义刷怪, 由脚本生成并填充出怪列表. 182 | 183 | @参数 zombies(list[str/int]): 包含僵尸名称或代号的列表. 184 | """ 185 | 186 | if not (game_on() and game_ui() in (2, 3)): 187 | return 188 | 189 | if zombies is None: 190 | zombies = [0] # 默认普僵 191 | zombies = [zombie_name_to_index(z) for z in zombies] 192 | zombies = list(set(zombies)) 193 | 194 | zombies_list = [0] * 1000 195 | 196 | has_flag = 1 in zombies 197 | has_yeti = 19 in zombies 198 | has_bungee = 20 in zombies 199 | limit_flag = True 200 | limit_yeti = True 201 | limit_bungee = True 202 | 203 | count = 0 204 | for i in range(33): 205 | if i in zombies: 206 | count += 1 207 | 208 | if count > 0: 209 | 210 | zombie_type = 0 211 | for i in range(1000): 212 | while True: 213 | zombie_type += 1 214 | zombie_type %= 33 215 | if not ( # 216 | (zombie_type not in zombies) # 217 | or (has_flag and limit_flag and zombie_type == 1) # 218 | or (has_yeti and limit_yeti and zombie_type == 19) # 219 | or (has_bungee and limit_bungee and zombie_type == 20) # 220 | ): # 221 | break 222 | zombies_list[i] = zombie_type 223 | 224 | index_flag = [450, 950] 225 | index_bungee = [451, 452, 453, 454, 951, 952, 953, 954] 226 | 227 | if has_flag and limit_flag: 228 | for i in index_flag: 229 | zombies_list[i] = 1 230 | 231 | if has_bungee and limit_bungee: 232 | for i in index_bungee: 233 | zombies_list[i] = 20 234 | 235 | if has_yeti and limit_yeti: 236 | i = 0 237 | while True: 238 | i = random.randint(0, 999) 239 | if not ((i in index_flag) or (i in index_bungee)): 240 | break 241 | zombies_list[i] = 19 242 | 243 | write_memory("unsigned int", zombies_list, 0x6A9EC0, 0x768, 0x6B4) 244 | 245 | if game_ui() == 2: 246 | update_zombies_preview() 247 | 248 | 249 | def set_zombies(zombies=None, mode="极限刷怪"): 250 | """ 251 | 设置出怪. 252 | 253 | 旗帜只会在每个大波出现一只, 雪人只会出现一只, 蹦极只会在大波出现. 254 | 255 | @参数 zombies(list[str/int]): 包含僵尸名称或代号的列表, 建议 8~12 种. 256 | 257 | @参数 mode(str): 刷怪模式, 默认使用极限刷怪. 可选值 "自然刷怪" "极限刷怪". 258 | 259 | 自然刷怪只改变出怪种类, 再由游戏内置的函数生成出怪列表. 260 | 261 | 极限刷怪是把所选僵尸种类按顺序均匀地填充到出怪列表. 262 | 263 | @示例: 264 | 265 | >>> SetZombies(["撑杆", "舞王", "冰车", "海豚", "气球", "矿工", "跳跳", "扶梯", "白眼", "红眼"]) 266 | """ 267 | 268 | if mode in ("自然", "内置", "内置生成", "自然出怪", "自然刷怪"): 269 | set_internal_spawn(zombies + ["普僵"]) 270 | elif mode in ("极限", "填充", "均匀填充", "极限出怪", "极限刷怪"): 271 | set_customize_spawn(zombies + ["旗帜"]) 272 | else: 273 | error("未知刷怪模式: %s." % mode) 274 | 275 | 276 | ### 选卡 277 | 278 | # (50, 160) 为左上角卡片中心坐标 279 | # (215, 160) 为模仿者选卡界面左上角卡片中心坐标 280 | # 单张卡片宽度约 50px 高度约 70px 281 | # 模仿者卡片位置 (490, 550), 成功点击后等待界面出现再选卡 282 | # 每次选完卡均等待一定时间 283 | 284 | SEED_0_0_X = 50 285 | SEED_0_0_Y = 160 286 | IMITATER_SEED_0_0_X = 215 287 | IMITATER_SEED_0_0_Y = 160 288 | SEED_WIDTH = 50 289 | SEED_HEIGHT = 70 290 | IMITATER_X = 490 291 | IMITATER_Y = 550 292 | IMITATER_SHOW_UP = 0 293 | SEED_DELAY_TIME = 5 294 | 295 | 296 | def select_seed_by_crood(row, col, imitater=False): 297 | """ 298 | 选择单张卡片. 299 | 300 | @参数 row(int): 行 301 | 302 | @参数 col(int): 列 303 | 304 | @参数 imitater(bool): 是否为模仿者 305 | """ 306 | 307 | if imitater: 308 | if row not in (1, 2, 3, 4, 5): 309 | critical("卡片行数 %d 超出有效范围." % row) 310 | if col not in (1, 2, 3, 4, 5, 6, 7, 8): 311 | critical("卡片列数 %d 超出有效范围." % col) 312 | else: 313 | if row not in (1, 2, 3, 4, 5, 6): 314 | critical("卡片行数 %d 超出有效范围." % row) 315 | if col not in (1, 2, 3, 4, 5, 6, 7, 8): 316 | critical("卡片列数 %d 超出有效范围." % col) 317 | 318 | if imitater: 319 | special_button_click(IMITATER_X, IMITATER_Y) 320 | thread_sleep_for(IMITATER_SHOW_UP) 321 | x = IMITATER_SEED_0_0_X + (col - 1) * (SEED_WIDTH + 1) 322 | y = IMITATER_SEED_0_0_Y + (row - 1) * (SEED_HEIGHT + 2) 323 | else: 324 | x = SEED_0_0_X + (col - 1) * (SEED_WIDTH + 3) 325 | y = SEED_0_0_Y + (row - 1) * (SEED_HEIGHT + 0) 326 | 327 | # 不小心点进了图鉴或者商店 328 | window_type = read_memory("int", 0x6A9EC0, 0x320, 0x88, 0xC) 329 | if window_type == 1: 330 | left_click(720, 580) 331 | thread_sleep_for(5) 332 | elif window_type == 4: 333 | left_click(430, 550) 334 | thread_sleep_for(5) 335 | 336 | special_button_click(x, y) 337 | thread_sleep_for(SEED_DELAY_TIME) 338 | 339 | if imitater: 340 | im_str = seeds_imitater_string[0] + " " 341 | else: 342 | im_str = "" 343 | seed_str = seeds_string[(row - 1) * 8 + (col - 1)][0] 344 | info("选择单张卡片 " + im_str + seed_str + ".") 345 | 346 | 347 | @functools.singledispatch 348 | def seed_to_crood(seed): 349 | """ 350 | 卡片转换为 (行, 列, 模仿者) 的标准形式. 351 | 352 | 根据参数类型选择不同的实现. 353 | 354 | @参数 seed(int/tuple/str): 卡片 355 | 356 | @示例: 357 | 358 | >>> seed_to_crood(14 + 48) 359 | (2, 7, True) 360 | 361 | >>> seed_to_crood((2, 7, True)) 362 | (2, 7, True) 363 | 364 | >>> seed_to_crood("复制冰") 365 | (2, 7, True) 366 | """ 367 | error("卡片参数不支持 %s 类型." % type(seed)) 368 | 369 | 370 | @seed_to_crood.register(int) 371 | def _(seed): 372 | if seed == 1437: 373 | row = 4 374 | col = 6 375 | imitater = False 376 | else: 377 | imitater = seed >= 48 378 | index = seed % 48 379 | row, col = divmod(index, 8) 380 | return row + 1, col + 1, imitater 381 | 382 | 383 | @seed_to_crood.register(tuple) 384 | def _(seed): 385 | if len(seed) == 2: 386 | row, col = seed 387 | imitater = False 388 | elif len(seed) == 3: 389 | row, col, im = seed 390 | imitater = im not in (False, 0) 391 | return row, col, imitater 392 | 393 | 394 | @seed_to_crood.register(str) 395 | def _(seed): 396 | if not seed in seeds_string_dict: 397 | error("未知卡片名称: %s." % seed) 398 | seed_index = seeds_string_dict[seed] # 卡片代号(+48) 399 | imitater = seed_index >= 48 400 | index = seed_index % 48 401 | row, col = divmod(index, 8) 402 | return row + 1, col + 1, imitater 403 | 404 | 405 | # 检查已选卡片是否完全相符 406 | def slots_exact_match(seeds_selected): 407 | slots_count = read_memory("int", 0x6A9EC0, 0x768, 0x144, 0x24) 408 | slots_selected = read_memory("int", 0x6A9EC0, 0x774, 0xD24) 409 | if slots_selected < slots_count: 410 | return False 411 | match = True 412 | for i in range(slots_count): 413 | row, col, imitater = seeds_selected[i] 414 | seed_index = (row - 1) * 8 + (col - 1) 415 | if imitater: 416 | seed_index = 48 417 | seed_plant = read_memory("int", 0x6A9EC0, 0x774, 0xC4 + seed_index * 0x3C) 418 | seed_status = read_memory("int", 0x6A9EC0, 0x774, 0xC8 + seed_index * 0x3C) 419 | # !卡片对应的植物正确并且位于卡槽中 420 | if not (seed_plant == seed_index and seed_status == 1): 421 | match = False 422 | break 423 | return match 424 | 425 | 426 | # 清空卡槽所有卡片 427 | def clear_slots(): 428 | slots_count = read_memory("int", 0x6A9EC0, 0x768, 0x144, 0x24) 429 | for slot_index in reversed(range(slots_count)): # 逆序 430 | slot_index += 1 431 | if slots_count == 10: 432 | x = 63 + 51 * slot_index 433 | elif slots_count == 9: 434 | x = 63 + 52 * slot_index 435 | elif slots_count == 8: 436 | x = 61 + 54 * slot_index 437 | elif slots_count == 7: 438 | x = 61 + 59 * slot_index 439 | else: 440 | x = 61 + 59 * slot_index 441 | x -= 10 442 | y = 12 443 | special_button_click(x, y) 444 | thread_sleep_for(5) 445 | thread_sleep_for(5) 446 | 447 | 448 | def select_all_seeds(seeds_selected=None): 449 | """ 450 | 选择所有卡片. 451 | """ 452 | 453 | # 不小心点进了图鉴或者商店 454 | window_type = read_memory("int", 0x6A9EC0, 0x320, 0x88, 0xC) 455 | if window_type == 1: 456 | left_click(720, 580) 457 | thread_sleep_for(5) 458 | elif window_type == 4: 459 | left_click(430, 550) 460 | thread_sleep_for(5) 461 | 462 | default_seeds = [40, 41, 42, 43, 44, 45, 46, 47, 8, 8 + 48] 463 | slots_count = read_memory("int", 0x6A9EC0, 0x768, 0x144, 0x24) 464 | 465 | # 默认八张紫卡和两张免费卡 466 | if seeds_selected is None: 467 | seeds_selected = default_seeds[0:slots_count] 468 | 469 | # 参数个数小于卡槽数则用默认卡片填充 470 | while len(seeds_selected) < slots_count: 471 | for seed in default_seeds: 472 | if seed_to_crood(seed) not in [seed_to_crood(s) for s in seeds_selected]: 473 | seeds_selected += [seed] 474 | break 475 | 476 | if len(seeds_selected) > slots_count: 477 | critical("卡片数量 %d 超过卡槽格数 %d." % (len(seeds_selected), slots_count)) 478 | 479 | # 卡片列表转换为标准形式 480 | seeds_selected = [seed_to_crood(seed) for seed in seeds_selected] 481 | info("所选卡片转换为标准形式 %s." % seeds_selected) 482 | 483 | clear_slots() # 清空卡槽已选卡片 484 | retry_count = 0 485 | 486 | while not slots_exact_match(seeds_selected): 487 | 488 | if retry_count > 3: 489 | critical("选卡重试多次失败.") 490 | retry_count += 1 491 | info("选卡过程未完成, 正在重试.") 492 | 493 | # 清空卡槽并选择所有卡片 494 | clear_slots() 495 | for seed in seeds_selected: 496 | row, col, imitater = seed 497 | select_seed_by_crood(row, col, imitater) 498 | thread_sleep_for(5) # 等内存变化 499 | 500 | thread_sleep_for(5) # 等内存变化 501 | 502 | 503 | def lets_rock(): 504 | while read_memory("bool", 0x6A9EC0, 0x768, 0x15C, 0x2C): # 位于选卡界面 505 | left_down(234, 567) 506 | thread_sleep_for(1) 507 | left_up(234, 567) 508 | thread_sleep_for(10) 509 | while read_memory("int", 0x6A9EC0, 0x320, 0x94) != 0: # 出现了对话框 510 | left_click(320, 400) 511 | thread_sleep_for(10) 512 | 513 | 514 | def select_seeds_and_lets_rock(seeds_selected=None): 515 | """ 516 | 选卡并开始游戏. 517 | 518 | 选择所有卡片, 点击开始游戏, 更新场景数据, 更新卡片列表, 更新加农炮列表, 等待开场红字消失. 519 | 520 | 建议把鼠标光标移出窗口外以避免可能出现的模仿者选卡失败. 521 | 522 | @参数 seeds_selected(list): 卡片列表, 长度不大于卡槽格数. 523 | 524 | 列表为空默认选择八张紫卡和两张免费卡, 卡片个数小于卡槽数则用默认卡片填充. 525 | 526 | 单张卡片 seed 可用 int/tuple/str 表示, 不同的表示方法可混用. 527 | 528 | seed(int): 卡片序号, 0 为豌豆射手, 47 为玉米加农炮, 对于模仿者这个数字再加上 48. 529 | 530 | seed(tuple): 卡片位置, 用 (行, 列, 是否模仿者) 表示, 第三项可省略, 默认非模仿者. 531 | 532 | seed(str): 卡片名称, 参考 seeds_string, 包含了一些常用名字. 533 | 534 | @示例: 535 | 536 | >>> SelectCards() 537 | 538 | >>> SelectCards([14, 14 + 48, 17, 2, 3, 30, 33, 13, 9, 8]) 539 | 540 | >>> SelectCards([(2, 7), (2, 7, True), (3, 2), (1, 3, False), (1, 4, False), (4, 7), (5, 2), (2, 6), (2, 2), (2, 1),]) 541 | 542 | >>> SelectCards(["寒冰菇", "复制冰", "窝瓜", "樱桃", "坚果", "南瓜", "花盆", "胆小", "阳光", "小喷"]) 543 | 544 | >>> SelectCards(["小喷菇", "模仿者小喷菇"]) 545 | """ 546 | gc.collect() 547 | 548 | # 激活并提高运行优先级 549 | set_pvz_foreground() 550 | set_pvz_high_priority() 551 | 552 | # 等待战斗结束进入选卡界面 553 | while game_ui() != 2: 554 | thread_sleep_for(1) 555 | 556 | # 选卡 557 | select_all_seeds(seeds_selected) 558 | lets_rock() 559 | 560 | # 更新相关数据 561 | update_game_scene() 562 | update_seeds_list() 563 | update_cob_cannon_list() 564 | 565 | # 选卡后等待直至正式开始战斗 566 | while game_ui() != 3: 567 | thread_sleep_for(1) 568 | 569 | 570 | ### 获取卡槽信息 571 | 572 | # 每张卡片在卡槽里的位置, 用于根据卡片代号找卡槽位置 573 | seeds_in_slot = [None] * (48 * 2) 574 | 575 | # 卡槽中每张卡片的代号, 用于根据卡槽位置找卡片代号 576 | slot_seeds = [None] * 10 577 | 578 | 579 | def update_seeds_list(): 580 | """ 581 | 更新卡片相关数据. 该函数须在点击 "Let's Rock!" 后调用. 582 | """ 583 | global seeds_in_slot, slot_seeds 584 | seeds_in_slot = [None] * (48 * 2) 585 | slot_seeds = [None] * 10 586 | 587 | slots_count = read_memory("int", 0x6A9EC0, 0x768, 0x144, 0x24) 588 | slots_offset = read_memory("unsigned int", 0x6A9EC0, 0x768, 0x144) 589 | for i in range(slots_count): 590 | seed_type = read_memory("int", slots_offset + 0x5C + i * 0x50) 591 | seed_imitater_type = read_memory("int", slots_offset + 0x60 + i * 0x50) 592 | if seed_type == 48: 593 | seed = seed_imitater_type + 48 594 | else: 595 | seed = seed_type 596 | seeds_in_slot[seed] = i + 1 597 | slot_seeds[i] = seed 598 | 599 | # info("更新卡片位置 %s." % str(seeds_in_slot)) 600 | info("更新卡槽代号 %s." % str(slot_seeds)) 601 | 602 | 603 | # 卡片名字 name 604 | # 卡片代号 seed 605 | # 卡槽位置 index 606 | 607 | 608 | # (name: str) -> int 609 | def get_seed_by_name(name): 610 | """ 611 | 根据卡片名字得到卡片代号. (模仿者 +48) 612 | """ 613 | if name not in seeds_string_dict: 614 | error("未知卡片名称: %s." % name) 615 | return seeds_string_dict[name] 616 | 617 | 618 | # (seed: int) -> int 619 | def get_index_by_seed(seed): 620 | """ 621 | 根据卡片代号得到卡槽位置. 不在返回 None. 622 | """ 623 | if seed not in range(48 * 2): 624 | error("卡片代号 %d 超出有效范围." % seed) 625 | return seeds_in_slot[seed] 626 | 627 | 628 | # (name: str) -> int 629 | def get_index_by_name(name): 630 | """ 631 | 根据卡片名字得到卡槽位置. 不在返回 None. 632 | """ 633 | if name not in seeds_string_dict: 634 | error("未知卡片名称: %s." % name) 635 | return seeds_in_slot[seeds_string_dict[name]] 636 | 637 | 638 | # (index: int) -> int 639 | def get_seed_by_index(index): 640 | """ 641 | 根据卡槽位置得到卡片代号. 642 | """ 643 | if index not in range(1, 11): 644 | error("卡槽位置 %d 超出有效范围." % index) 645 | return slot_seeds[index - 1] 646 | 647 | 648 | ### 场景相关信息 649 | 650 | # 卡槽格数, 选卡和用卡函数需要 651 | slots_count = 10 652 | 653 | # 场景地图, 点击场上格子相关函数需要 654 | game_scene = 2 655 | 656 | scenes = { 657 | 0: "Day", 658 | 1: "Night", 659 | 2: "Pool", 660 | 3: "Fog", 661 | 4: "Roof", 662 | 5: "Moon", 663 | 6: "Mushroom Garden", 664 | 7: "Zen Garden", 665 | 8: "Aquarium Garden", 666 | 9: "Tree of Wisdom", 667 | } 668 | 669 | 670 | # 更新卡槽格数和场景地图 671 | def update_game_scene(): 672 | global slots_count, game_scene 673 | slots_count = read_memory("int", 0x6A9EC0, 0x768, 0x144, 0x24) 674 | game_scene = read_memory("int", 0x6A9EC0, 0x768, 0x554C) 675 | info("更新卡槽格数 %d." % slots_count) 676 | info("更新场景地图 %s." % scenes[game_scene]) 677 | 678 | 679 | ### 场景点击操作 680 | 681 | # 唯一内置鼠标锁 682 | mouse_lock = threading.Lock() 683 | 684 | 685 | def get_mouse_lock(): 686 | """ 687 | 获取鼠标锁, 进行完整的 (不可分割的) 鼠标操作前加锁, 操作完毕后释放. 688 | 689 | @返回值 (object): 唯一内置鼠标锁. 690 | 691 | @示例: 692 | 693 | >>> MouseLock().acquire() # 获取鼠标操作权 694 | >>> SafeClick() # 安全右键避免冲突 695 | >>> pass # 干点什么 696 | >>> MouseLock().release() # 释放鼠标操作权 697 | 698 | >>> with MouseLock(): # 获取鼠标操作权, 代码块结束后自动释放 699 | >>> SafeClick() # 安全右键避免冲突 700 | >>> pass # 干点什么 701 | """ 702 | 703 | return mouse_lock 704 | 705 | 706 | def safe_click(): 707 | """ 708 | 安全右键. 709 | 710 | 即右键单击左上角, 用于取消之前的 (可能未完成的) 操作以避免冲突. 711 | """ 712 | right_click(0, 0) 713 | 714 | 715 | def click_seed(seed): 716 | """ 717 | 点击卡槽中的卡片. 718 | 719 | @参数 seed(int/str): 卡槽第几格或者卡片名称. 720 | 721 | @示例: 722 | 723 | >>> ClickSeed(5) # 点击第 5 格卡槽 724 | 725 | >>> ClickSeed("樱桃") # 点击卡槽中的樱桃卡片 726 | """ 727 | 728 | if isinstance(seed, str): 729 | slot_index = get_index_by_name(seed) 730 | if slot_index is None: 731 | error("卡槽当中没有 %s 卡片, 操作失败." % seed) 732 | else: # int 733 | slot_index = seed 734 | if slot_index not in range(1, 11): 735 | error("卡槽格数 %d 超出有效范围, 操作失败." % slot_index) 736 | 737 | if slots_count == 10: 738 | x = 63 + 51 * slot_index 739 | elif slots_count == 9: 740 | x = 63 + 52 * slot_index 741 | elif slots_count == 8: 742 | x = 61 + 54 * slot_index 743 | elif slots_count == 7: 744 | x = 61 + 59 * slot_index 745 | else: 746 | x = 61 + 59 * slot_index 747 | y = 12 748 | left_click(x, y) 749 | 750 | 751 | def click_shovel(): 752 | """ 753 | 点击铲子. 754 | """ 755 | if slots_count == 10: 756 | x = 640 757 | elif slots_count == 9: 758 | x = 600 759 | elif slots_count == 8: 760 | x = 570 761 | elif slots_count == 7: 762 | x = 550 763 | else: 764 | x = 490 765 | y = 36 766 | left_click(x, y) 767 | 768 | 769 | # 坐标转换 770 | def rc2xy(*crood): 771 | """ 772 | row, col -> x, y 773 | """ 774 | 775 | if isinstance(crood[0], tuple): 776 | row, col = crood[0] 777 | else: 778 | row, col = crood 779 | 780 | x = 80 * col 781 | if game_scene in (2, 3): 782 | y = 55 + 85 * row 783 | elif game_scene in (4, 5): 784 | if col >= 6: 785 | y = 45 + 85 * row 786 | else: 787 | y = 45 + 85 * row + 20 * (6 - col) 788 | else: 789 | y = 40 + 100 * row 790 | 791 | return int(x), int(y) # 取整 792 | 793 | 794 | def click_grid(*crood): 795 | """ 796 | 点击场上格点. 797 | 798 | @参数 crood(float/tuple): 坐标, 两个分别表示 行/列 的数字或者一个 (行, 列) 元组, 数字可为小数. 799 | 800 | @示例: 801 | 802 | >>> ClickGrid((2, 9)) # 点击 2 行 9 列 803 | 804 | >>> ClickGrid(2, 9) # 同上 805 | """ 806 | x, y = rc2xy(*crood) 807 | left_click(x, y) 808 | 809 | 810 | ### 更新炮列表 811 | 812 | # 炮列表 (row, col) x n 813 | cob_list = [] 814 | 815 | # 用炮序号 816 | cob_index = 0 817 | 818 | # 炮列表锁 819 | cob_lock = threading.Lock() 820 | 821 | 822 | def update_cob_cannon_list(cobs=None): 823 | """ 824 | 更新玉米加农炮列表. 825 | 826 | 选卡时会自动调用, 空参数则自动找炮. 若需要自定义顺序请在选卡函数后面使用. 827 | 828 | @参数 cobs(list): 加农炮列表, 包括若干个 (行, 列) 元组, 以后轮坐标为准. 829 | 830 | @示例: 831 | 832 | >>> UpdatePaoList() 833 | 834 | >>> UpdatePaoList([(3, 1), (4, 1), (3, 3), (4, 3), (1, 5), (2, 5), (3, 5), (4, 5), (5, 5), (6, 5)]) 835 | 836 | >>> UpdatePaoList( 837 | >>> [ 838 | >>> (r, c) 839 | >>> for r in (1, 2, 3, 4, 5, 6) 840 | >>> for c in (1, 3, 5, 7) 841 | >>> if not (r in (3, 4) and c == 7) 842 | >>> ] 843 | >>> ) 844 | 845 | >>> UpdatePaoList([ 846 | >>> (1, 5), (1, 7), 847 | >>> (2, 1), (2, 5), (2, 7), 848 | >>> (3, 1), (3, 3), (3, 5), (3, 7), 849 | >>> (4, 1), (4, 3), (4, 5), (4, 7), 850 | >>> (5, 1), (5, 5), (5, 7), 851 | >>> (6, 5), (6, 7), 852 | >>> ]) 853 | """ 854 | 855 | global cob_list, cob_index 856 | 857 | cob_list_tmp = [] 858 | plants_count_max = read_memory("unsigned int", 0x6A9EC0, 0x768, 0xB0) 859 | plants_offset = read_memory("unsigned int", 0x6A9EC0, 0x768, 0xAC) 860 | for i in range(plants_count_max): 861 | plant_dead = read_memory("bool", plants_offset + 0x141 + 0x14C * i) 862 | plant_crushed = read_memory("bool", plants_offset + 0x142 + 0x14C * i) 863 | plant_type = read_memory("int", plants_offset + 0x24 + 0x14C * i) 864 | if not plant_dead and not plant_crushed and plant_type == 47: 865 | cob_row = read_memory("int", plants_offset + 0x1C + 0x14C * i) 866 | cob_col = read_memory("int", plants_offset + 0x28 + 0x14C * i) 867 | cob_list_tmp.append((cob_col + 1, cob_row + 1)) # 优先按列排 868 | cob_list_tmp.sort() # 排序 869 | cob_list_tmp = [(r, c) for c, r in cob_list_tmp] # 再反过来 870 | 871 | cob_lock.acquire() # 加锁 872 | 873 | cob_list = [] 874 | cob_index = 0 875 | 876 | # 自动查找 877 | if cobs is None: 878 | cob_list = cob_list_tmp 879 | info("查找场上玉米炮 %s." % str(cob_list)) 880 | 881 | # 手动更新 882 | else: 883 | cobs_not_exist = [] 884 | for cob in cobs: 885 | if cob not in cob_list_tmp: 886 | cobs_not_exist.append(cob) 887 | if len(cobs_not_exist) > 0: 888 | error("玉米炮 %s 不存在." % str(cobs_not_exist)) 889 | cob_list = cobs 890 | info("手动更新炮列表 %s." % str(cob_list)) 891 | 892 | cob_lock.release() # 解锁 893 | 894 | 895 | ### 用卡操作 896 | 897 | 898 | def use_seed(seed, *crood): 899 | """ 900 | 用卡操作. 901 | 902 | @参数 seed(int/str): 卡槽第几格或者卡片名称. 903 | 904 | @参数 crood(int/tuple): 坐标, 两个分别表示 行/列 的数字或者一个 (行, 列) 元组, 数字均为整数. 905 | 906 | @示例: 907 | 908 | >>> Card(1, (2, 3)) # 将卡槽中的第 1 张卡片种在 2 行 3 列 909 | 910 | >>> Card(1, 2, 3) # 同上 911 | 912 | >>> Card("樱桃", (5, 9)) # 将樱桃种在 5 行 9 列 913 | 914 | >>> Card("樱桃", 5, 9) # 同上 915 | """ 916 | 917 | if isinstance(seed, str): 918 | seed_type = get_seed_by_name(seed) 919 | slot_index = get_index_by_seed(seed_type) 920 | else: # int 921 | seed_type = get_seed_by_index(seed) 922 | slot_index = seed 923 | 924 | # 墓碑/咖啡豆 理想种植坐标偏上约 30px 925 | if seed_type in (11, 35, 11 + 48, 35 + 48): 926 | row_fix = -0.3 927 | else: 928 | row_fix = 0 929 | if isinstance(crood[0], tuple): 930 | row, col = crood[0] 931 | else: 932 | row, col = crood 933 | row += row_fix 934 | 935 | mouse_lock.acquire() 936 | safe_click() 937 | click_seed(slot_index) 938 | click_grid((row, col)) 939 | safe_click() 940 | mouse_lock.release() 941 | 942 | if isinstance(seed, str): 943 | info("向 %s 种植 %s 卡片." % (str(crood), str(seed))) 944 | else: # int 945 | info("向 %s 种植卡槽第 %s 张卡片." % (str(crood), str(seed))) 946 | 947 | 948 | ### 用铲子操作 949 | 950 | 951 | def use_shovel(*croods): 952 | """ 953 | 用铲子操作. 954 | 955 | @参数 croods(float/tuple): 坐标, 两个分别表示 行/列 的数字或者一至多个 (行, 列) 元组, 数字可为小数. 956 | 957 | @示例: 958 | 959 | >>> Shovel((3, 4)) # 铲掉 3 行 4 列的普通植物 960 | 961 | >>> Shovel(3, 4) # 同上 962 | 963 | >>> Shovel((5 + 0.1, 6)) # 铲掉 5 行 6 列的南瓜头 964 | 965 | >>> Shovel((1, 9), (2, 9), (5, 9), (6, 9)) # 铲掉所有 9 列垫材 966 | """ 967 | 968 | mouse_lock.acquire() 969 | safe_click() 970 | 971 | if isinstance(croods[0], tuple): 972 | for crood in croods: 973 | click_shovel() 974 | click_grid(crood) 975 | else: # float/int 976 | click_shovel() 977 | click_grid(*croods) 978 | 979 | safe_click() 980 | mouse_lock.release() 981 | 982 | info("对格子 %s 使用铲子." % str(croods)) 983 | 984 | 985 | ### 用炮操作 986 | 987 | 988 | @functools.singledispatch 989 | def fire_cob(*croods): 990 | """ 991 | 用炮操作. 992 | 993 | @参数 croods(float/tuple/list): 落点, 一至多个格式为 (行, 列) 的元组, 或者一个包含了这些元组的列表. 994 | 995 | @示例: 996 | 997 | >>> Pao((2, 9)) 998 | 999 | >>> Pao(2, 9) 1000 | 1001 | >>> Pao((2, 9), (5, 9)) 1002 | 1003 | >>> Pao((2, 9), (5, 9), (2, 9), (5, 9)) 1004 | 1005 | >>> Pao([(2, 9), (5, 9), (2, 9), (5, 9)]) 1006 | """ 1007 | error("炮落点参数格式不正确.") 1008 | 1009 | 1010 | @fire_cob.register(int) 1011 | def _(fall_row, fall_col): 1012 | global cob_list, cob_index 1013 | cob_count = len(cob_list) 1014 | if cob_count == 0: 1015 | error("炮列表为空.") 1016 | 1017 | cob_lock.acquire() 1018 | cob_row, cob_col = cob_list[cob_index] 1019 | fire_cob_by_crood(cob_row, cob_col, fall_row, fall_col) 1020 | cob_index += 1 1021 | cob_index %= cob_count 1022 | cob_lock.release() 1023 | 1024 | 1025 | @fire_cob.register(tuple) 1026 | def _(*fall_grids): 1027 | global cob_list, cob_index 1028 | cob_count = len(cob_list) 1029 | if cob_count == 0: 1030 | error("炮列表为空.") 1031 | 1032 | cob_lock.acquire() 1033 | for grid in fall_grids: 1034 | cob_row, cob_col = cob_list[cob_index] 1035 | fall_row, fall_col = grid 1036 | fire_cob_by_crood(cob_row, cob_col, fall_row, fall_col) 1037 | cob_index += 1 1038 | cob_index %= cob_count 1039 | cob_lock.release() 1040 | 1041 | 1042 | @fire_cob.register(list) 1043 | def _(fall_grids): 1044 | fire_cob(*fall_grids) 1045 | 1046 | 1047 | ### 屋顶用炮操作 1048 | 1049 | # 屋顶玉米炮飞行时间, 只考虑落点前场 7~9 列的情况 1050 | flying_time = {1: 359, 2: 362, 3: 364, 4: 367, 5: 369, 6: 372, 7: 373} 1051 | 1052 | 1053 | def get_cob_flying_time(cob_col, fall_col): 1054 | # 暂不考虑 fall_col 1055 | if cob_col in flying_time: 1056 | return flying_time[cob_col] 1057 | else: 1058 | return 373 1059 | 1060 | 1061 | FLYING_TIME = 373 1062 | 1063 | 1064 | @running_in_thread 1065 | @functools.singledispatch 1066 | def fire_cob_on_roof(*croods): 1067 | """ 1068 | 屋顶修正飞行时间发炮. 参数格式与 `Pao()` 相同. 1069 | 1070 | 此函数开新线程开销较大不适合精确键控, 只适用于前场落点 (约 7~9 列). 1071 | """ 1072 | error("参数格式不正确.") 1073 | 1074 | 1075 | @fire_cob_on_roof.register(int) 1076 | def _(fall_row, fall_col): 1077 | clock = game_clock() # 参照时钟 1078 | global cob_list, cob_index 1079 | cob_count = len(cob_list) 1080 | if cob_count == 0: 1081 | error("炮列表为空.") 1082 | 1083 | cob_lock.acquire() 1084 | cob_row, cob_col = cob_list[cob_index] 1085 | cob_index += 1 1086 | cob_index %= cob_count 1087 | cob_lock.release() 1088 | 1089 | flying_time = get_cob_flying_time(cob_col, fall_col) 1090 | while (game_clock() - clock) < (FLYING_TIME - flying_time): 1091 | delay_a_little_time() 1092 | fire_cob_by_crood(cob_row, cob_col, fall_row, fall_col) 1093 | 1094 | 1095 | @fire_cob_on_roof.register(tuple) 1096 | def _(*fall_grids): 1097 | clock = game_clock() # 参照时钟 1098 | global cob_list, cob_index 1099 | cob_count = len(cob_list) 1100 | if cob_count == 0: 1101 | error("炮列表为空.") 1102 | 1103 | # (flying_time, cob_row, cob_col, fall_row, fall_col) x n 1104 | operate_list = [] 1105 | 1106 | cob_lock.acquire() 1107 | for grid in fall_grids: 1108 | cob_row, cob_col = cob_list[cob_index] 1109 | fall_row, fall_col = grid 1110 | flying_time = get_cob_flying_time(cob_col, fall_col) 1111 | operate_list.append((flying_time, cob_row, cob_col, fall_row, fall_col)) 1112 | cob_index += 1 1113 | cob_index %= cob_count 1114 | cob_lock.release() 1115 | 1116 | operate_list.sort() # 根据飞行时间排序 1117 | for op in reversed(operate_list): # 逆序发射 1118 | flying_time, cob_row, cob_col, fall_row, fall_col = op 1119 | while (game_clock() - clock) < (FLYING_TIME - flying_time): 1120 | delay_a_little_time() 1121 | fire_cob_by_crood(cob_row, cob_col, fall_row, fall_col) 1122 | 1123 | 1124 | @fire_cob_on_roof.register(list) 1125 | def _(fall_grids): 1126 | return fire_cob_on_roof(*fall_grids) 1127 | 1128 | 1129 | ### 跳炮 1130 | 1131 | 1132 | def skip_cob_index(num): 1133 | """ 1134 | 按炮列表顺序跳过即将发射的一定数量的玉米炮, 通常用于 wave9/19 手动收尾. 1135 | 1136 | @参数 num(int): 数量. 1137 | """ 1138 | global cob_list, cob_index 1139 | 1140 | cob_lock.acquire() 1141 | cob_index += num 1142 | cob_index %= len(cob_list) 1143 | cob_lock.release() 1144 | 1145 | info("跳过炮列表中的 %d 门炮." % num) 1146 | 1147 | 1148 | ### 直接发炮 1149 | 1150 | # 炮身点击次数 1151 | CLICK_COUNT = 3 1152 | 1153 | 1154 | # 无视内置炮列表直接指定炮位和落点 1155 | def fire_cob_by_crood(cob_row, cob_col, fall_row, fall_col): 1156 | 1157 | mouse_lock.acquire() 1158 | safe_click() 1159 | for _ in range(CLICK_COUNT): 1160 | # 点炮位置稍微偏离, 配合改内存解决炮粘手的问题 1161 | click_grid(cob_row + 2 / 85, cob_col - 2 / 80) 1162 | click_grid(fall_row, fall_col) 1163 | safe_click() 1164 | mouse_lock.release() 1165 | 1166 | info("从 (%d, %d) 向 (%d, %d) 发射玉米炮." % (cob_row, cob_col, fall_row, fall_col)) 1167 | 1168 | 1169 | ### 阻塞延时 1170 | 1171 | # 当前波次刷新时间点 1172 | refresh_time_point = 0 1173 | 1174 | 1175 | def game_delay_for(time_cs): 1176 | """ 1177 | 游戏内部时钟延时. 相对于线程睡眠更准确. 1178 | 1179 | 只能在战斗界面 `[[0x6A9EC0]+0x7FC] == 3` 使用, 游戏暂停时计时同样暂停. 1180 | 1181 | @参数 time_cs(int): 时间, 单位 cs, 精度 1. 1182 | """ 1183 | 1184 | if time_cs > 0: 1185 | clock = game_clock() 1186 | while (game_clock() - clock) < time_cs: 1187 | delay_a_little_time() 1188 | elif time_cs == 0: 1189 | pass 1190 | else: 1191 | error("游戏延时参数不能小于零.") 1192 | 1193 | 1194 | def until_countdown(time_cs, hugewave=False): 1195 | """ 1196 | 等待直至刷新倒计时数值达到指定值. 1197 | 1198 | 调用时需要保证上一波已经刷出. 该函数仅保留兼容旧式写法, 已不建议使用. 1199 | 1200 | @参数 time_cs(int): 倒计时数值, 单位 cs, 精度 1. 建议范围 [200, 1]. 1201 | 1202 | 第一波最早 599, 旗帜波最早 750. 1203 | 1204 | @参数 hugewave(bool): 是否为旗帜波, 默认不是. 可用 (波数 % 10 == 0) 判断. 1205 | 1206 | @示例: 1207 | 1208 | >>> Countdown(100) # 非旗帜波 100cs 预判 1209 | 1210 | >>> Countdown(55, True) # 旗帜波 55cs 预判 1211 | 1212 | >>> Countdown(95, wave % 10 == 0) # 第 wave 波 95cs 预判 1213 | """ 1214 | 1215 | if not hugewave: 1216 | while wave_countdown() > time_cs: 1217 | delay_a_little_time() 1218 | else: 1219 | while wave_countdown() > 5: # 这里用 5 而不用 4, 为了支持大波 750cs 预判 1220 | delay_a_little_time() 1221 | while huge_wave_countdown() > time_cs: 1222 | delay_a_little_time() 1223 | 1224 | 1225 | # 倒计时(大波倒计时/初始倒计时)小于等于 200(750/599) 才算激活刷新 1226 | refresh_trigger = [750 if wave % 10 == 0 else 599 if wave == 1 else 200 for wave in range(1, 21)] 1227 | 1228 | 1229 | def until_relative_time_after_refresh(time_relative_cs, wave): 1230 | """ 1231 | 读内存获取刷新状况, 等待直至与设定波次刷新时间点的差值达到指定值. 1232 | 1233 | 该函数须在每波操作开始时执行一次. 通常用于预判 (在设定波次刷新前调用), 也可以在设定波次刷新之后调用. 1234 | 1235 | @参数 time_relative_cs(int): 与刷新时间点的相对时间, 单位 cs, 精度 1. 建议范围 [-200, 400]. 1236 | 1237 | 第一波最早 -599, 旗帜波最早 -750. 为了方便可统一给每波设置类似 -180 等数值. 1238 | 1239 | 因为脚本语言的精度问题, 设置成 -599/-750/-200 等过早的边界值时可能会因为实际达到时间已经超过该值而引起报错. 1240 | 1241 | @参数 wave(int): 波数. 用于内部判断刷新状况以及本波是否为旗帜波. 1242 | 1243 | @示例: 1244 | 1245 | >>> Prejudge(-100, wave) # 第 wave 波刷新前 100cs 预判 1246 | 1247 | >>> Prejudge(-150, 20) # 第 20 波预判炮炸珊瑚时机 1248 | 1249 | >>> Prejudge(300, wave) # 第 wave 波刷新后 300cs 1250 | 1251 | >>> Prejudge(900 - 200 - 373, wave) # 第 wave 波 900cs 波长激活炸时机 1252 | """ 1253 | 1254 | # 设定波次的刷新时间点 1255 | global refresh_time_point 1256 | 1257 | _current_wave = current_wave() 1258 | 1259 | # 设定波次还未刷出 1260 | if _current_wave < wave: 1261 | 1262 | # 等待设定预判波次的上一波刷出 1263 | if _current_wave < wave - 1: 1264 | while current_wave() < (wave - 1): 1265 | delay_a_little_time() 1266 | 1267 | # 等到本波触发刷新 1268 | is_huge_wave = wave % 10 == 0 1269 | until_countdown(refresh_trigger[wave - 1], is_huge_wave) 1270 | 1271 | # 计算实际倒计时数值 1272 | _wave_countdown = wave_countdown() 1273 | _huge_wave_countdown = huge_wave_countdown() 1274 | if is_huge_wave: 1275 | if _wave_countdown in (4, 5): 1276 | countdown = _huge_wave_countdown 1277 | else: 1278 | countdown = _wave_countdown - 5 + 750 1279 | else: 1280 | countdown = _wave_countdown 1281 | 1282 | # 计算刷新时间点(倒计时变为下一波初始值时)的时钟数值 1283 | _game_clock = game_clock() 1284 | refresh_time_point = _game_clock + countdown 1285 | 1286 | # 等待 目标相对时间 和 当前相对时间(即倒计时数值负值) 的差值 1287 | time_to_wait = time_relative_cs + countdown 1288 | if time_to_wait >= 0: 1289 | game_delay_for(time_to_wait) 1290 | else: 1291 | error("第 %d 波设定时间 %d 已经过去, 当前相对时间 %d." % (wave, time_relative_cs, time_relative_cs - time_to_wait)) 1292 | 1293 | # 设定波次已经刷出 1294 | elif _current_wave == wave: 1295 | 1296 | # 获取当前时钟/倒计时数值/倒计时初始数值 1297 | _game_clock = game_clock() 1298 | _wave_countdown = wave_countdown() 1299 | _wave_init_countdown = wave_init_countdown() 1300 | 1301 | if _wave_countdown <= 200: 1302 | warning("设定波次 %d 的下一波即将刷新, 请调整脚本写法." % wave) 1303 | 1304 | # 计算刷新时间点(倒计时变为下一波初始值时)的时钟数值 1305 | refresh_time_point = _game_clock - (_wave_init_countdown - _wave_countdown) 1306 | 1307 | # 等到设定时间 1308 | time_to_wait = time_relative_cs - (_wave_init_countdown - _wave_countdown) 1309 | if time_to_wait >= 0: 1310 | game_delay_for(time_to_wait) 1311 | else: 1312 | error("第 %d 波设定时间 %d 已经过去, 当前相对时间 %d." % (wave, time_relative_cs, time_relative_cs - time_to_wait)) 1313 | 1314 | # 设定波次的下一波已经刷出 1315 | else: 1316 | error("设定波次 %d 的下一波已经刷新, 请调整脚本写法." % wave) 1317 | 1318 | 1319 | def until_relative_time(time_relative_cs): 1320 | """ 1321 | 等待直至当前时间戳与本波刷新时间点的差值达到指定值. 1322 | 1323 | 该函数需要配合 Prejudge() 使用. 多个 Until() 连用时注意调用顺序必须符合时间先后顺序. 1324 | 1325 | @参数 time_relative_cs(int): 相对时间, 单位 cs, 精度 1. 建议范围 [-200, 2300]. 1326 | 1327 | @示例: 1328 | 1329 | >>> Until(-95) # 刷新前 95cs 1330 | 1331 | >>> Until(180) # 刷新后 180cs 1332 | 1333 | >>> Until(-150) # 炮炸珊瑚可用时机 1334 | 1335 | >>> Until(444 - 373) # 444cs 生效炮发射时机 1336 | 1337 | >>> Until(601 + 20 - 298) # 加速波下一波 20cs 预判冰点咖啡豆时机 1338 | 1339 | >>> Until(601 + 5 - 100 - 320) # 加速波下一波 5cs 预判冰复制冰种植时机 1340 | 1341 | >>> Until(1200 - 200 - 373) # 1200cs 波长激活炸时机 1342 | 1343 | >>> Until(4500 - 5) # 收尾波拖满时红字出现时机 1344 | 1345 | >>> Until(5500) # 最后一大波白字出现时机 1346 | """ 1347 | 1348 | # while (game_clock() - refresh_time_point) < time_relative_cs: 1349 | # delay_a_little_time() 1350 | 1351 | time_to_wait = time_relative_cs - (game_clock() - refresh_time_point) 1352 | if time_to_wait >= 0: 1353 | game_delay_for(time_to_wait) 1354 | else: 1355 | error("设定时间 %d 已经过去, 当前相对时间 %d." % (time_relative_cs, time_relative_cs - time_to_wait)) 1356 | 1357 | 1358 | ### 自动收集线程 1359 | 1360 | collect_items_dict = { 1361 | "银币": 1, 1362 | "金币": 2, 1363 | "钻石": 3, 1364 | "阳光": 4, 1365 | "小阳光": 5, 1366 | "大阳光": 6, 1367 | "太阳": 4, 1368 | "小太阳": 5, 1369 | "大太阳": 6, 1370 | "幼苗": 17, 1371 | "花苗": 17, 1372 | "盆栽": 17, 1373 | "花盆": 17, 1374 | "礼盒": 17, 1375 | "礼品盒": 17, 1376 | 1: 1, 1377 | 2: 2, 1378 | 3: 3, 1379 | 4: 4, 1380 | 5: 5, 1381 | 6: 6, 1382 | 17: 17, 1383 | } 1384 | 1385 | item_type_names = {1: "银币", 2: "金币", 3: "钻石", 4: "阳光", 5: "小阳光", 6: "大阳光", 17: "幼苗"} 1386 | 1387 | 1388 | @running_in_thread 1389 | def auto_collect(collect_items=None, interval_cs=12): 1390 | """ 1391 | 自动收集场上资源, 在单独的子线程运行. 1392 | 1393 | 为了避免操作冲突, 当鼠标选中 卡片/铲子/玉米炮 时会暂停收集. 1394 | 1395 | 建议把鼠标光标移出窗口外以避免可能出现的游戏卡顿. 1396 | 1397 | @参数 collect_items(list[str/int]): 包含需要收集的资源类型的列表, 默认所有. 1398 | 1399 | 可选值物品名称 ["银币", "金币", "钻石", "阳光", "小阳光", "大阳光", "幼苗"] 或者代号 [1, 2, 3, 4, 5, 6, 17]. 1400 | 1401 | @参数 interval_cs(float/int): 点击间隔, 单位 cs, 默认 12. 1402 | 1403 | @示例: 1404 | 1405 | >>> AutoCollect() # 自动收集所有资源 1406 | 1407 | >>> AutoCollect(["钻石", "阳光", "小阳光", "大阳光"], 20) # 只收集钻石和各种阳光, 间隔 0.2s 1408 | """ 1409 | 1410 | if collect_items is None: 1411 | collect_items = ["银币", "金币", "钻石", "阳光", "小阳光", "大阳光", "幼苗"] 1412 | collect_items_list = [collect_items_dict[item] for item in collect_items] 1413 | 1414 | while game_ui() != 3: 1415 | thread_sleep_for(10) 1416 | 1417 | info("启动自动收集线程.") 1418 | 1419 | while game_ui() == 3: 1420 | items_count = read_memory("int", 0x6A9EC0, 0x768, 0xF4) 1421 | items_count_max = read_memory("int", 0x6A9EC0, 0x768, 0xE8) 1422 | items_offset = read_memory("int", 0x6A9EC0, 0x768, 0xE4) 1423 | 1424 | if items_count == 0: 1425 | thread_sleep_for(interval_cs) 1426 | continue 1427 | 1428 | uncollected_item_count = 0 1429 | for i in range(items_count_max): 1430 | disappeared = read_memory("bool", items_offset + 0x38 + 0xD8 * i) 1431 | collected = read_memory("bool", items_offset + 0x50 + 0xD8 * i) 1432 | item_type = read_memory("int", items_offset + 0x58 + 0xD8 * i) 1433 | if not disappeared and not collected and item_type in collect_items_list: 1434 | uncollected_item_count += 1 1435 | if uncollected_item_count == 0: 1436 | thread_sleep_for(interval_cs * 10) # 等久一点 1437 | continue 1438 | 1439 | for i in range(items_count_max): 1440 | if game_ui() != 3: 1441 | break 1442 | 1443 | # while game_paused(): # 一直收集 1444 | # while game_paused() or mouse_in_game(): # 鼠标移出时收集 1445 | while game_paused() or (mouse_in_game() and mouse_have_something()): # 没选中卡炮铲时收集 1446 | thread_sleep_for(interval_cs) 1447 | 1448 | disappeared = read_memory("bool", items_offset + 0x38 + 0xD8 * i) 1449 | collected = read_memory("bool", items_offset + 0x50 + 0xD8 * i) 1450 | item_type = read_memory("int", items_offset + 0x58 + 0xD8 * i) 1451 | if not disappeared and not collected and item_type in collect_items_list: 1452 | 1453 | item_x = read_memory("float", items_offset + 0x24 + 0xD8 * i) 1454 | item_y = read_memory("float", items_offset + 0x28 + 0xD8 * i) 1455 | if item_x >= 0.0 and item_y >= 70.0: 1456 | # write_memory("bool", True, items_offset + 0x50 + 0xd8 * i) 1457 | x, y = int(item_x + 30), int(item_y + 30) 1458 | mouse_lock.acquire() 1459 | safe_click() 1460 | left_click(x, y) 1461 | safe_click() 1462 | mouse_lock.release() 1463 | 1464 | debug("收集位于 (%d, %d) 的物品 %s." % (x, y, item_type_names[item_type])) 1465 | thread_sleep_for(interval_cs) 1466 | # thread_sleep_for(random.randint(int(interval_cs * 0.5), int(interval_cs * 1.5))) # 时间波动 1467 | 1468 | info("停止自动收集线程.") 1469 | 1470 | 1471 | ### 自动存冰线程 1472 | 1473 | 1474 | def get_seeds_index(seed): 1475 | """ 1476 | 获取某种卡片在卡槽里的所有位置. 1477 | 1478 | @参数 seed(str): 卡片名称. 1479 | 1480 | @返回值 (list[int]): 某种卡片 (包括其模仿者) 在卡槽的数组下标列表. 1481 | """ 1482 | seed = get_seed_by_name(seed) 1483 | seed %= 48 1484 | 1485 | seed_indexes = [] 1486 | slots_count = read_memory("int", 0x6A9EC0, 0x768, 0x144, 0x24) 1487 | slots_offset = read_memory("unsigned int", 0x6A9EC0, 0x768, 0x144) 1488 | for i in range(slots_count): 1489 | seed_type = read_memory("int", slots_offset + 0x5C + i * 0x50) 1490 | seed_imitater_type = read_memory("int", slots_offset + 0x60 + i * 0x50) 1491 | if seed_type == seed or (seed_type == 48 and seed_imitater_type == seed): 1492 | seed_indexes.append(i) 1493 | return seed_indexes 1494 | 1495 | 1496 | def get_plants_croods(): 1497 | """ 1498 | 获取场上植物坐标. 1499 | """ 1500 | croods = [] 1501 | plants_count_max = read_memory("unsigned int", 0x6A9EC0, 0x768, 0xB0) 1502 | plants_offset = read_memory("unsigned int", 0x6A9EC0, 0x768, 0xAC) 1503 | for i in range(plants_count_max): 1504 | plant_dead = read_memory("bool", plants_offset + 0x141 + 0x14C * i) 1505 | plant_crushed = read_memory("bool", plants_offset + 0x142 + 0x14C * i) 1506 | if not plant_dead and not plant_crushed: 1507 | plant_type = read_memory("int", plants_offset + 0x24 + 0x14C * i) 1508 | plant_row = read_memory("int", plants_offset + 0x1C + 0x14C * i) 1509 | plant_col = read_memory("int", plants_offset + 0x28 + 0x14C * i) 1510 | croods.append((plant_type, plant_row + 1, plant_col + 1)) 1511 | return croods 1512 | 1513 | 1514 | def get_block_type(*crood): 1515 | """ 1516 | 获取格子类型. 1.lawn 2.bare 3.pool 1517 | """ 1518 | if isinstance(crood[0], tuple): 1519 | row, col = crood[0] 1520 | else: 1521 | row, col = crood 1522 | row, col = row - 1, col - 1 1523 | return read_memory("int", 0x6a9ec0, 0x768, 0x168 + row * 0x04 + col * 0x18) 1524 | 1525 | 1526 | # 存冰位 1527 | ice_spots = [] 1528 | ice_total = 0 1529 | 1530 | 1531 | @running_in_thread 1532 | def auto_fill_ice(spots=None, total=0x7FFFFFFF): 1533 | """ 1534 | 设置存冰位置和要存的数量, 将会在单独的子线程运行自动存冰. 1535 | 1536 | @参数 spots(list): 存冰点, 包括若干个 (行, 列) 元组. 临时位在后. 默认为场上现有存冰的位置. 1537 | 1538 | @参数 total(int): 总个数, 默认无限. 1539 | 1540 | @示例: 1541 | 1542 | >>> IceSpots() 1543 | 1544 | >>> IceSpots([(6, 1), (5, 1), (2, 1), (1, 1)], 10) # 往指定位置总计存 10 个冰 1545 | """ 1546 | 1547 | while game_ui() != 3: 1548 | thread_sleep_for(1) 1549 | 1550 | info("启动自动存冰线程.") 1551 | 1552 | # 默认为场上现有存冰的位置 1553 | if spots is None: 1554 | spots = [] 1555 | plants = get_plants_croods() 1556 | for plant_type, plant_row, plant_col in plants: 1557 | if plant_type == get_seed_by_name("寒冰菇"): 1558 | spots.append((plant_row, plant_col)) 1559 | if spots == []: 1560 | error("场上没有寒冰菇, 退出自动存冰.") 1561 | return 1562 | 1563 | global ice_spots, ice_total 1564 | ice_spots = spots 1565 | ice_total = total 1566 | 1567 | slots_offset = read_memory("unsigned int", 0x6A9EC0, 0x768, 0x144) 1568 | 1569 | ice_seeds_index = get_seeds_index("寒冰菇") # 获取所有寒冰菇卡片的下标 1570 | if ice_seeds_index == []: 1571 | error("卡槽没有寒冰菇, 退出自动存冰.") 1572 | 1573 | filled = 0 # 已存数量 1574 | while game_ui() == 3 and filled < total: 1575 | 1576 | while game_paused(): 1577 | thread_sleep_for(1) # 等待暂停取消 1578 | if game_ui() != 3: 1579 | break 1580 | 1581 | croods_which_has_plant = [] 1582 | plants = get_plants_croods() 1583 | for plant_type, plant_row, plant_col in plants: 1584 | if plant_type not in (16, 30, 33): # 睡莲/南瓜/花盆 1585 | croods_which_has_plant.append((plant_row, plant_col)) 1586 | if plant_type == 47: # 玉米炮占两格 1587 | croods_which_has_plant.append((plant_row, plant_col + 1)) 1588 | ice_spot_which_has_plant = [i for i in ice_spots if i in croods_which_has_plant] 1589 | 1590 | # 这句忘了有什么作用了 ... 1591 | if game_ui() != 3 and ice_spot_which_has_plant == []: 1592 | break 1593 | 1594 | if set(ice_spot_which_has_plant) >= set(spots): # 存冰位植物满了 1595 | ice_seeds_cd_left = [] 1596 | for i in ice_seeds_index: 1597 | seed_usable = read_memory("bool", slots_offset + 0x70 + i * 0x50) 1598 | seed_cd_past = read_memory("int", slots_offset + 0x4C + i * 0x50) 1599 | seed_cd_total = read_memory("int", slots_offset + 0x50 + i * 0x50) 1600 | ice_seeds_cd_left.append(0 if seed_usable else (seed_cd_total - seed_cd_past)) 1601 | if min(ice_seeds_cd_left) > 0: # 冰卡都在冷却时等待最小的卡片 CD 1602 | info("寒冰菇卡片冷却中, 等待 %d." % (min(ice_seeds_cd_left) + 1)) 1603 | game_delay_for(min(ice_seeds_cd_left) + 1) 1604 | continue 1605 | else: # TODO 冰卡可用时等待正在用的咖啡豆 1606 | thread_sleep_for(2) # 延时以减小遍历植物的 CPU 消耗 1607 | continue 1608 | 1609 | # 遍历指定的存冰位 1610 | for spot in spots: 1611 | if game_ui() != 3: 1612 | break 1613 | 1614 | # 如果该位置无植物则尝试存冰 1615 | if pvz_ver() == "1.0.0.1051": 1616 | seed_ice_cost = read_memory("int", 0x69F2C0 + 14 * 0x24) 1617 | else: 1618 | seed_ice_cost = read_memory("int", 0x69F2D0 + 14 * 0x24) 1619 | sun = read_memory("int", 0x6A9EC0, 0x768, 0x5560) 1620 | if sun < seed_ice_cost: 1621 | thread_sleep_for(1) 1622 | continue 1623 | block_type = get_block_type(spot) 1624 | # 1.草地 2.裸地 3.泳池 16.睡莲 33.花盆 1625 | if (spot not in ice_spot_which_has_plant \ 1626 | and sun >= seed_ice_cost \ 1627 | and ((block_type == 1 and game_scene not in (4, 5)) \ 1628 | or (block_type == 1 and game_scene in (4, 5) and (33, spot[0], spot[1]) in plants) \ 1629 | or (block_type == 3 and (16, spot[0], spot[1]) in plants))): 1630 | # 遍历寒冰菇卡片, 通常为 原版冰 x 1 + 复制冰 x 1 1631 | for i in ice_seeds_index: 1632 | seed_usable = read_memory("bool", slots_offset + 0x70 + i * 0x50) 1633 | if seed_usable: 1634 | while game_paused(): 1635 | thread_sleep_for(1) # 等待暂停取消 1636 | mouse_lock.acquire() 1637 | safe_click() 1638 | click_seed(i + 1) 1639 | click_grid(spot) 1640 | safe_click() 1641 | mouse_lock.release() 1642 | filled += 1 1643 | info("往 %s 存冰 (第 %d 张卡)." % (str(spot), i + 1)) 1644 | game_delay_for(1) # 等待内存数据更新 1645 | break 1646 | else: 1647 | thread_sleep_for(1) 1648 | break # 不管有没有成功都重新遍历存冰位以保证顺序(先永久位后临时位) 1649 | else: 1650 | pass 1651 | 1652 | info("停止自动存冰线程.") 1653 | 1654 | 1655 | def activate_ice(): 1656 | """ 1657 | 点冰. 使用咖啡豆激活存冰, 优先点临时位. 1658 | 1659 | 该函数需要配合自动存冰线程 IceSpots() 使用. 1660 | """ 1661 | coffee_index = get_index_by_seed(35) # 咖啡豆位置 1662 | if coffee_index is None: 1663 | error("卡槽没有咖啡豆, 点冰失败.") 1664 | 1665 | mouse_lock.acquire() 1666 | safe_click() 1667 | click_seed(coffee_index) 1668 | for spot in reversed(ice_spots): # 优先点临时位 1669 | row, col = spot 1670 | row -= 0.3 # 咖啡豆 理想种植坐标偏上约 30px 1671 | click_grid((row, col)) 1672 | safe_click() 1673 | mouse_lock.release() 1674 | -------------------------------------------------------------------------------- /pypi.txt: -------------------------------------------------------------------------------- 1 | 2 | python setup.py check 3 | python setup.py sdist 4 | twine upload dist/* 5 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | 3 | from setuptools import setup 4 | 5 | setup( 6 | name="pvz", 7 | version="4.0.2", 8 | author="lmintlcx", 9 | author_email="lmintlcx@gmail.com", 10 | url="https://github.com/lmintlcx/pvzscripts", 11 | license="GPL", 12 | description="Python vs. Zombies", 13 | long_description="Plants vs. Zombies TAS Framework", 14 | platforms=["Windows"], 15 | python_requires=">=3.4.0", 16 | packages=["pvz"], 17 | ) 18 | -------------------------------------------------------------------------------- /userdata/DE前置八炮/game1_13.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmintlcx/pvzscripts/22f09d18d3bdcc16b1f6d9e4f84eb7029d64ffa0/userdata/DE前置八炮/game1_13.dat -------------------------------------------------------------------------------- /userdata/FE二十二炮/game1_13.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmintlcx/pvzscripts/22f09d18d3bdcc16b1f6d9e4f84eb7029d64ffa0/userdata/FE二十二炮/game1_13.dat -------------------------------------------------------------------------------- /userdata/ME十三炮/game1_13.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmintlcx/pvzscripts/22f09d18d3bdcc16b1f6d9e4f84eb7029d64ffa0/userdata/ME十三炮/game1_13.dat -------------------------------------------------------------------------------- /userdata/NE十五炮/game1_13.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmintlcx/pvzscripts/22f09d18d3bdcc16b1f6d9e4f84eb7029d64ffa0/userdata/NE十五炮/game1_13.dat -------------------------------------------------------------------------------- /userdata/PE二十四炮/game1_13.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmintlcx/pvzscripts/22f09d18d3bdcc16b1f6d9e4f84eb7029d64ffa0/userdata/PE二十四炮/game1_13.dat -------------------------------------------------------------------------------- /userdata/PE半场十二炮/game1_13.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmintlcx/pvzscripts/22f09d18d3bdcc16b1f6d9e4f84eb7029d64ffa0/userdata/PE半场十二炮/game1_13.dat -------------------------------------------------------------------------------- /userdata/PE最后之作/game1_13.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmintlcx/pvzscripts/22f09d18d3bdcc16b1f6d9e4f84eb7029d64ffa0/userdata/PE最后之作/game1_13.dat -------------------------------------------------------------------------------- /userdata/PE经典十二炮/game1_13.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmintlcx/pvzscripts/22f09d18d3bdcc16b1f6d9e4f84eb7029d64ffa0/userdata/PE经典十二炮/game1_13.dat -------------------------------------------------------------------------------- /userdata/PE经典四炮/game1_13.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmintlcx/pvzscripts/22f09d18d3bdcc16b1f6d9e4f84eb7029d64ffa0/userdata/PE经典四炮/game1_13.dat -------------------------------------------------------------------------------- /userdata/PE裸奔十六炮/game1_13.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmintlcx/pvzscripts/22f09d18d3bdcc16b1f6d9e4f84eb7029d64ffa0/userdata/PE裸奔十六炮/game1_13.dat -------------------------------------------------------------------------------- /userdata/RE十六炮/game1_13.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmintlcx/pvzscripts/22f09d18d3bdcc16b1f6d9e4f84eb7029d64ffa0/userdata/RE十六炮/game1_13.dat -------------------------------------------------------------------------------- /userdata/RE椭盘十四炮/game1_13.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lmintlcx/pvzscripts/22f09d18d3bdcc16b1f6d9e4f84eb7029d64ffa0/userdata/RE椭盘十四炮/game1_13.dat -------------------------------------------------------------------------------- /在线文档.url: -------------------------------------------------------------------------------- 1 | [InternetShortcut] 2 | URL=https://pvz.lmintlcx.com/scripts/ 3 | --------------------------------------------------------------------------------