├── .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 |
--------------------------------------------------------------------------------