Codebase list pangzero / upstream/latest
Imported Upstream version 1.4.1+git20121103 Markus Koschany 7 years ago
134 changed file(s) with 5961 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 UPi <upi at sourceforge.net>
1 SAdam <sadam at apocalypse.rulez.org>
2 DaniGM <danigm at gmail.com>
3 Elio Blanca <eblanca76 at users.sourceforge.net>
0 use strict;
1 use warnings;
2 use Module::Build;
3
4 my $build = Module::Build->new(
5 module_name => 'Games::PangZero',
6 all_from => 'lib/Games/PangZero.pm',
7 dist_abstract => 'a fast-paced action game about popping balloons with a harpoon',
8 dist_author => [
9 'UPi <upi@sourceforge.net>',
10 'SAdam <sadam@apocalypse.rulez.org>',
11 'DaniGM <danigm@gmail.com>',
12 'Elio Blanca <eblanca76@users.sourceforge.net>',
13 ],
14 license => 'gpl',
15 requires => {
16 'File::ShareDir' => '0',
17 'File::Spec' => '0',
18 'Time::HiRes' => '0',
19 'SDL' => '2.536',
20 },
21 configure_requires => {
22 'Module::Build' => '0.38',
23 },
24 meta_merge => {
25 resources => {
26 bugtracker => 'http://github.com/kthakore/pangzero/issues',
27 repository => 'http://github.com/kthakore/pangzero'
28 }
29 },
30 share_dir => 'data',
31 )->create_build_script();
0 GNU GENERAL PUBLIC LICENSE
1 Version 2, June 1991
2
3 Copyright (C) 1989, 1991 Free Software Foundation, Inc.
4 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
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 licenses for most software are designed to take away your
11 freedom to share and change it. By contrast, the GNU General Public
12 License is intended to guarantee your freedom to share and change free
13 software--to make sure the software is free for all its users. This
14 General Public License applies to most of the Free Software
15 Foundation's software and to any other program whose authors commit to
16 using it. (Some other Free Software Foundation software is covered by
17 the GNU Library General Public License instead.) You can apply it to
18 your programs, too.
19
20 When we speak of free software, we are referring to freedom, not
21 price. Our General Public Licenses are designed to make sure that you
22 have the freedom to distribute copies of free software (and charge for
23 this service if you wish), that you receive source code or can get it
24 if you want it, that you can change the software or use pieces of it
25 in new free programs; and that you know you can do these things.
26
27 To protect your rights, we need to make restrictions that forbid
28 anyone to deny you these rights or to ask you to surrender the rights.
29 These restrictions translate to certain responsibilities for you if you
30 distribute copies of the software, or if you modify it.
31
32 For example, if you distribute copies of such a program, whether
33 gratis or for a fee, you must give the recipients all the rights that
34 you have. You must make sure that they, too, receive or can get the
35 source code. And you must show them these terms so they know their
36 rights.
37
38 We protect your rights with two steps: (1) copyright the software, and
39 (2) offer you this license which gives you legal permission to copy,
40 distribute and/or modify the software.
41
42 Also, for each author's protection and ours, we want to make certain
43 that everyone understands that there is no warranty for this free
44 software. If the software is modified by someone else and passed on, we
45 want its recipients to know that what they have is not the original, so
46 that any problems introduced by others will not reflect on the original
47 authors' reputations.
48
49 Finally, any free program is threatened constantly by software
50 patents. We wish to avoid the danger that redistributors of a free
51 program will individually obtain patent licenses, in effect making the
52 program proprietary. To prevent this, we have made it clear that any
53 patent must be licensed for everyone's free use or not licensed at all.
54
55 The precise terms and conditions for copying, distribution and
56 modification follow.
57
58 GNU GENERAL PUBLIC LICENSE
59 TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
60
61 0. This License applies to any program or other work which contains
62 a notice placed by the copyright holder saying it may be distributed
63 under the terms of this General Public License. The "Program", below,
64 refers to any such program or work, and a "work based on the Program"
65 means either the Program or any derivative work under copyright law:
66 that is to say, a work containing the Program or a portion of it,
67 either verbatim or with modifications and/or translated into another
68 language. (Hereinafter, translation is included without limitation in
69 the term "modification".) Each licensee is addressed as "you".
70
71 Activities other than copying, distribution and modification are not
72 covered by this License; they are outside its scope. The act of
73 running the Program is not restricted, and the output from the Program
74 is covered only if its contents constitute a work based on the
75 Program (independent of having been made by running the Program).
76 Whether that is true depends on what the Program does.
77
78 1. You may copy and distribute verbatim copies of the Program's
79 source code as you receive it, in any medium, provided that you
80 conspicuously and appropriately publish on each copy an appropriate
81 copyright notice and disclaimer of warranty; keep intact all the
82 notices that refer to this License and to the absence of any warranty;
83 and give any other recipients of the Program a copy of this License
84 along with the Program.
85
86 You may charge a fee for the physical act of transferring a copy, and
87 you may at your option offer warranty protection in exchange for a fee.
88
89 2. You may modify your copy or copies of the Program or any portion
90 of it, thus forming a work based on the Program, and copy and
91 distribute such modifications or work under the terms of Section 1
92 above, provided that you also meet all of these conditions:
93
94 a) You must cause the modified files to carry prominent notices
95 stating that you changed the files and the date of any change.
96
97 b) You must cause any work that you distribute or publish, that in
98 whole or in part contains or is derived from the Program or any
99 part thereof, to be licensed as a whole at no charge to all third
100 parties under the terms of this License.
101
102 c) If the modified program normally reads commands interactively
103 when run, you must cause it, when started running for such
104 interactive use in the most ordinary way, to print or display an
105 announcement including an appropriate copyright notice and a
106 notice that there is no warranty (or else, saying that you provide
107 a warranty) and that users may redistribute the program under
108 these conditions, and telling the user how to view a copy of this
109 License. (Exception: if the Program itself is interactive but
110 does not normally print such an announcement, your work based on
111 the Program is not required to print an announcement.)
112
113 These requirements apply to the modified work as a whole. If
114 identifiable sections of that work are not derived from the Program,
115 and can be reasonably considered independent and separate works in
116 themselves, then this License, and its terms, do not apply to those
117 sections when you distribute them as separate works. But when you
118 distribute the same sections as part of a whole which is a work based
119 on the Program, the distribution of the whole must be on the terms of
120 this License, whose permissions for other licensees extend to the
121 entire whole, and thus to each and every part regardless of who wrote it.
122
123 Thus, it is not the intent of this section to claim rights or contest
124 your rights to work written entirely by you; rather, the intent is to
125 exercise the right to control the distribution of derivative or
126 collective works based on the Program.
127
128 In addition, mere aggregation of another work not based on the Program
129 with the Program (or with a work based on the Program) on a volume of
130 a storage or distribution medium does not bring the other work under
131 the scope of this License.
132
133 3. You may copy and distribute the Program (or a work based on it,
134 under Section 2) in object code or executable form under the terms of
135 Sections 1 and 2 above provided that you also do one of the following:
136
137 a) Accompany it with the complete corresponding machine-readable
138 source code, which must be distributed under the terms of Sections
139 1 and 2 above on a medium customarily used for software interchange; or,
140
141 b) Accompany it with a written offer, valid for at least three
142 years, to give any third party, for a charge no more than your
143 cost of physically performing source distribution, a complete
144 machine-readable copy of the corresponding source code, to be
145 distributed under the terms of Sections 1 and 2 above on a medium
146 customarily used for software interchange; or,
147
148 c) Accompany it with the information you received as to the offer
149 to distribute corresponding source code. (This alternative is
150 allowed only for noncommercial distribution and only if you
151 received the program in object code or executable form with such
152 an offer, in accord with Subsection b above.)
153
154 The source code for a work means the preferred form of the work for
155 making modifications to it. For an executable work, complete source
156 code means all the source code for all modules it contains, plus any
157 associated interface definition files, plus the scripts used to
158 control compilation and installation of the executable. However, as a
159 special exception, the source code distributed need not include
160 anything that is normally distributed (in either source or binary
161 form) with the major components (compiler, kernel, and so on) of the
162 operating system on which the executable runs, unless that component
163 itself accompanies the executable.
164
165 If distribution of executable or object code is made by offering
166 access to copy from a designated place, then offering equivalent
167 access to copy the source code from the same place counts as
168 distribution of the source code, even though third parties are not
169 compelled to copy the source along with the object code.
170
171 4. You may not copy, modify, sublicense, or distribute the Program
172 except as expressly provided under this License. Any attempt
173 otherwise to copy, modify, sublicense or distribute the Program is
174 void, and will automatically terminate your rights under this License.
175 However, parties who have received copies, or rights, from you under
176 this License will not have their licenses terminated so long as such
177 parties remain in full compliance.
178
179 5. You are not required to accept this License, since you have not
180 signed it. However, nothing else grants you permission to modify or
181 distribute the Program or its derivative works. These actions are
182 prohibited by law if you do not accept this License. Therefore, by
183 modifying or distributing the Program (or any work based on the
184 Program), you indicate your acceptance of this License to do so, and
185 all its terms and conditions for copying, distributing or modifying
186 the Program or works based on it.
187
188 6. Each time you redistribute the Program (or any work based on the
189 Program), the recipient automatically receives a license from the
190 original licensor to copy, distribute or modify the Program subject to
191 these terms and conditions. You may not impose any further
192 restrictions on the recipients' exercise of the rights granted herein.
193 You are not responsible for enforcing compliance by third parties to
194 this License.
195
196 7. If, as a consequence of a court judgment or allegation of patent
197 infringement or for any other reason (not limited to patent issues),
198 conditions are imposed on you (whether by court order, agreement or
199 otherwise) that contradict the conditions of this License, they do not
200 excuse you from the conditions of this License. If you cannot
201 distribute so as to satisfy simultaneously your obligations under this
202 License and any other pertinent obligations, then as a consequence you
203 may not distribute the Program at all. For example, if a patent
204 license would not permit royalty-free redistribution of the Program by
205 all those who receive copies directly or indirectly through you, then
206 the only way you could satisfy both it and this License would be to
207 refrain entirely from distribution of the Program.
208
209 If any portion of this section is held invalid or unenforceable under
210 any particular circumstance, the balance of the section is intended to
211 apply and the section as a whole is intended to apply in other
212 circumstances.
213
214 It is not the purpose of this section to induce you to infringe any
215 patents or other property right claims or to contest validity of any
216 such claims; this section has the sole purpose of protecting the
217 integrity of the free software distribution system, which is
218 implemented by public license practices. Many people have made
219 generous contributions to the wide range of software distributed
220 through that system in reliance on consistent application of that
221 system; it is up to the author/donor to decide if he or she is willing
222 to distribute software through any other system and a licensee cannot
223 impose that choice.
224
225 This section is intended to make thoroughly clear what is believed to
226 be a consequence of the rest of this License.
227
228 8. If the distribution and/or use of the Program is restricted in
229 certain countries either by patents or by copyrighted interfaces, the
230 original copyright holder who places the Program under this License
231 may add an explicit geographical distribution limitation excluding
232 those countries, so that distribution is permitted only in or among
233 countries not thus excluded. In such case, this License incorporates
234 the limitation as if written in the body of this License.
235
236 9. The Free Software Foundation may publish revised and/or new versions
237 of the General Public License from time to time. Such new versions will
238 be similar in spirit to the present version, but may differ in detail to
239 address new problems or concerns.
240
241 Each version is given a distinguishing version number. If the Program
242 specifies a version number of this License which applies to it and "any
243 later version", you have the option of following the terms and conditions
244 either of that version or of any later version published by the Free
245 Software Foundation. If the Program does not specify a version number of
246 this License, you may choose any version ever published by the Free Software
247 Foundation.
248
249 10. If you wish to incorporate parts of the Program into other free
250 programs whose distribution conditions are different, write to the author
251 to ask for permission. For software which is copyrighted by the Free
252 Software Foundation, write to the Free Software Foundation; we sometimes
253 make exceptions for this. Our decision will be guided by the two goals
254 of preserving the free status of all derivatives of our free software and
255 of promoting the sharing and reuse of software generally.
256
257 NO WARRANTY
258
259 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
260 FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
261 OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
262 PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
263 OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
264 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
265 TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
266 PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
267 REPAIR OR CORRECTION.
268
269 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
270 WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
271 REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
272 INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
273 OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
274 TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
275 YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
276 PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
277 POSSIBILITY OF SUCH DAMAGES.
278
279 END OF TERMS AND CONDITIONS
280
281 How to Apply These Terms to Your New Programs
282
283 If you develop a new program, and you want it to be of the greatest
284 possible use to the public, the best way to achieve this is to make it
285 free software which everyone can redistribute and change under these terms.
286
287 To do so, attach the following notices to the program. It is safest
288 to attach them to the start of each source file to most effectively
289 convey the exclusion of warranty; and each file should have at least
290 the "copyright" line and a pointer to where the full notice is found.
291
292 <one line to give the program's name and a brief idea of what it does.>
293 Copyright (C) <year> <name of author>
294
295 This program is free software; you can redistribute it and/or modify
296 it under the terms of the GNU General Public License as published by
297 the Free Software Foundation; either version 2 of the License, or
298 (at your option) any later version.
299
300 This program is distributed in the hope that it will be useful,
301 but WITHOUT ANY WARRANTY; without even the implied warranty of
302 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
303 GNU General Public License for more details.
304
305 You should have received a copy of the GNU General Public License
306 along with this program; if not, write to the Free Software
307 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
308
309
310 Also add information on how to contact you by electronic and paper mail.
311
312 If the program is interactive, make it output a short notice like this
313 when it starts in an interactive mode:
314
315 Gnomovision version 69, Copyright (C) year name of author
316 Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 This is free software, and you are welcome to redistribute it
318 under certain conditions; type `show c' for details.
319
320 The hypothetical commands `show w' and `show c' should show the appropriate
321 parts of the General Public License. Of course, the commands you use may
322 be called something other than `show w' and `show c'; they could even be
323 mouse-clicks or menu items--whatever suits your program.
324
325 You should also get your employer (if you work as a programmer) or your
326 school, if any, to sign a "copyright disclaimer" for the program, if
327 necessary. Here is a sample; alter the names:
328
329 Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 `Gnomovision' (which makes passes at compilers) written by James Hacker.
331
332 <signature of Ty Coon>, 1 April 1989
333 Ty Coon, President of Vice
334
335 This General Public License does not permit incorporating your program into
336 proprietary programs. If your program is a subroutine library, you may
337 consider it more useful to permit linking proprietary applications with the
338 library. If this is what you want to do, use the GNU Library General
339 Public License instead of this License.
0
1 2012-03-12 19:42 FROGGS
2 * 1.4.1, fix for SDL's Joystick API
3
4 2012-02-21 22:27 kthakore, FROGGS
5 * ChangeLog, VERSION, *.p[l/m]: Bumped version to 1.4, making pangzero
6 work with upstream release of SDL. Many fixes.
7 See http://cpansearch.perl.org/src/FROGGS/SDL-2.536/CHANGELOG
8
9 2007-07-15 10:12 upi
10
11 * bin/upipang.pl: Proper website launching for Windows. High level
12 table is now saved properly.
13
14 2007-06-28 09:10 upi
15
16 * ChangeLog, VERSION, bin/upipang.pl: Bumped version to 1.2.
17
18 2007-06-28 09:10 upi
19
20 * INSTALL: Basic installation instructions.
21
22 2007-06-28 08:56 upi
23
24 * bin/upipang.pl: Some minor tweaks. Balls smaller than level 3 no
25 longer have magic in them. Fixed a bug related to multiple slow
26 effects. Slow effect no longer survives in Challenge Mode.
27 Eliminated some SDL warning messages. Added TutorialGame (one on
28 one with a specific kind of ball).
29
30 2007-06-28 08:47 upi
31
32 * data/: Balls-Upside128.png, Balls-Upside64.png: Fixed background
33 green
34
35 2007-06-28 08:46 upi
36
37 * data/bonus.png: New bonus graphics
38
39 2007-04-03 21:35 upi
40
41 * bin/upipang.pl: Added UpsideDownBall, minor fixes.
42
43 2007-04-03 21:33 upi
44
45 * data/: Balls-Upside128.png, Balls-Upside16.png,
46 Balls-Upside32.png, Balls-Upside64.png, Balls-Upside96.png: Added
47 UpsideDownBall
48
49 2007-01-04 10:38 upi
50
51 * VERSION, bin/upipang.pl: Separate high score tables for Panic and
52 Challenge modes. More challenge levels.
53
54 2007-01-03 23:31 upi
55
56 * bin/upipang.pl: Show website at exit.
57
58 2007-01-03 19:51 upi
59
60 * bin/upipang.pl, data/bonus.png: Added "Matrix effect": slows the
61 game down for 15 sec.
62
63 2007-01-01 09:52 upi
64
65 * ChangeLog, VERSION: Bumped version number to 1.0
66
67 2007-01-01 09:50 upi
68
69 * bin/upipang.pl: Quick fix for the Demo before 1.0
70
71 2006-12-31 16:32 upi
72
73 * bin/upipang.pl: Added option for weapon durations.
74
75 2006-12-31 15:49 upi
76
77 * bin/upipang.pl: Some more fixes for Challenge mode.
78
79 2006-12-31 10:54 upi
80
81 * bin/upipang.pl: Fixes for challenge mode. Better scoreboard
82 layout for very small screen heights.
83
84 2006-12-30 19:45 upi
85
86 * bin/upipang.pl: Improved High Score table. Menu items now show
87 tool tips at the bottom of the screen.
88
89 2006-12-30 12:33 upi
90
91 * bin/upipang.pl: Player can choose Panic or Challenge game from
92 the menu.
93
94 2006-12-30 10:38 upi
95
96 * bin/upipang.pl: Bugfix for machine gun.
97
98 2006-12-30 10:21 upi
99
100 * bin/upipang.pl: Some speed improvements. Removed the Christmas
101 decor.
102
103 2006-12-23 10:40 upi
104
105 * bin/upipang.pl: Improved ball positioning in challenge mode.
106
107 2006-12-20 14:16 upi
108
109 * bin/upipang.pl, data/santa.png: Added flying Santa.
110
111 2006-12-20 13:29 upi
112
113 * VERSION, bin/upipang.pl, data/christmas_candle.jpg,
114 data/christmas_house.jpg, data/christmas_houses.jpg,
115 data/christmas_tree.jpg, data/christmas_trees.jpg,
116 data/guyChristmas.png, data/guy_r2.png: Graphics update for 0.19
117
118 2006-12-20 10:39 upi
119
120 * bin/upipang.pl, data/glossyfont.png: Number of players moved to
121 "Player setup" menu. Several bugfixes (santa pixel bug, credits
122 bug, 200.000 points bonus life bug) Level display more prominent
123 in Challenge Game mode. Error is better reported if the program
124 dies.
125
126 2006-12-16 21:41 upi
127
128 * data/guyChristmas.png: Fix of minor graphics glitch.
129
130 2006-12-16 21:23 upi
131
132 * bin/upipang.pl: Minor bugfixes
133
134 2006-12-16 14:42 upi
135
136 * bin/upipang.pl: Bugfixing for credits screen.
137
138 2006-12-16 09:43 upi
139
140 * ChangeLog, VERSION, bin/upipang.pl, data/fireplace.jpg,
141 data/xmas-dawn.jpg, data/xmas-night.jpg: Updated version number.
142 Updated demo. New Christmas backgrounds.
143
144 2006-12-15 23:33 upi
145
146 * bin/upipang.pl, data/bonus.png: XmasBall now drops random weapon
147 or powerup. Hexa balls rotation is improved.
148
149 2006-12-15 11:11 upi
150
151 * bin/upipang.pl: Slippery floor can be toggled in the menu
152
153 2006-12-15 09:55 upi
154
155 * bin/upipang.pl: Added XmasBall.
156
157 2006-12-15 09:54 upi
158
159 * data/: Balls-Snow128.png, Balls-Snow16.png, Balls-Snow32.png,
160 Balls-Snow64.png, Balls-Snow96.png, Balls-XMAS128.png,
161 border-lighted-theme.png, border-xmas-theme.png: Updated
162 christmas graphics.
163
164 2006-12-15 02:23 upi
165
166 * bin/upipang.pl, data/Balls-Snow128.png, data/Balls-Snow16.png,
167 data/Balls-Snow32.png, data/Balls-Snow64.png,
168 data/Balls-Snow96.png, data/Balls-XMAS128.png,
169 data/border-lighted-theme.png, data/border-xmas-theme.png,
170 data/guyChristmas.png: Christmas edition graphics. Challenge
171 version added.
172
173 2006-12-15 01:32 upi
174
175 * ChangeLog, bin/upipang.pl, data/Balls-Fragile128.png,
176 data/Balls-Fragile16.png, data/Balls-Fragile32.png,
177 data/Balls-Fragile64.png, data/Balls-Fragile96.png: Minor code
178 refactoring. Added FragileBall.
179
180 2006-12-04 14:00 upi
181
182 * VERSION, bin/upipang.pl, data/bonus.png, data/border.png:
183 Improved border for 0.17
184
185 2006-12-01 15:06 upi
186
187 * AUTHORS, ChangeLog, VERSION: Update for version 0.16
188
189 2006-12-01 15:06 upi
190
191 * bin/upipang.pl: Better handling of music files.
192
193 2006-12-01 15:05 upi
194
195 * data/: Hexa-16.png, Hexa-32.png, Hexa-64.png: Background fixes
196 for Hexas.
197
198 2006-12-01 11:16 upi
199
200 * data/: Balls-Bouncy16.png, Balls-Bouncy32.png,
201 Balls-Bouncy64.png, Balls-Death64.png, Balls-EarthQ16.png,
202 Balls-EarthQ32.png, Balls-EarthQ64.png, Balls-Red128.png,
203 Balls-Red16.png, Balls-Red32.png, Balls-Red64.png,
204 Balls-Red96.png, Balls-Seeker32.png, Balls-Seeker64.png,
205 Balls-SuperClock64.png, Balls-SuperClock96.png,
206 Balls-SuperStar64.png, Balls-SuperStar96.png, Balls-Water16.png,
207 Balls-Water32.png, Balls-Water64.png, Balls-Water96.png,
208 Hexa-16.png, Hexa-32.png, Hexa-64.png, guy_danigm.png,
209 guy_pix.png, guy_pux.png, guy_r2.png, guy_sonic.png, l1.jpg,
210 l2.jpg, l3.jpg, l4.jpg, l5.jpg, l6.jpg, l7.jpg, l8.jpg, l9.jpg:
211 Data files updated
212
213 2006-11-30 19:22 upi
214
215 * bin/upipang.pl: New graphics, hopefully faster, too. High score
216 table.
217
218 2006-10-08 00:39 upi
219
220 * AUTHORS, ChangeLog, VERSION, bin/upipang.pl: Updated version and
221 credits for 0.15
222
223 2006-10-08 00:26 upi
224
225 * bin/upipang.pl: Added FPS indicator.
226
227 2006-10-07 23:47 upi
228
229 * bin/upipang.pl: Players can now select character and color.
230 AlterPalette now uses display_format (hopefully faster).
231
232 2006-10-07 23:43 upi
233
234 * data/ball.png: New and improved ball
235
236 2006-10-07 23:18 upi
237
238 * data/: guy_danigm.png, guy_pix.png, guy_pux.png: Added selectable
239 characters.
240
241 2006-10-05 15:07 upi
242
243 * bin/upipang.pl: Fixed for the menu changes.
244
245 2006-10-05 13:11 upi
246
247 * bin/upipang.pl: Nifty menu animations.
248
249 2006-10-05 08:47 upi
250
251 * bin/upipang.pl: Added "ball mixer" to the menu.
252
253 2006-09-06 10:57 upi
254
255 * bin/upipang.pl, data/guy.png: More appealing death effect.
256
257 2006-09-06 10:09 upi
258
259 * data/harpoon.png: Added straight harpoon by eblanca76.
260
261 2006-09-06 00:38 upi
262
263 * bin/upipang.pl: More forgiving collision detection.
264
265 2006-09-05 23:17 upi
266
267 * bin/upipang.pl, data/harpoon.png: New harpoon graphics by
268 eblanca76 at sourceforge.net
269
270 2006-09-05 18:16 upi
271
272 * bin/upipang.pl, data/meltdown.png: Deathball Meltdown at 30+
273 death balls.
274
275 2006-09-05 15:46 upi
276
277 * bin/upipang.pl: DeadGuy uses RotoZoom (funny effect).
278
279 2006-09-05 13:45 upi
280
281 * bin/upipang.pl: Fix for previous checkin.
282
283 2006-09-05 13:44 upi
284
285 * bin/upipang.pl: Workaround for SDL_perl 1.2.20
286
287 2006-09-04 22:21 upi
288
289 * bin/upipang.pl, data/guy.png, data/level.png,
290 data/level_empty.png: Widescreen mode improvements, better
291 scoreboard.
292
293 2006-08-21 14:10 upi
294
295 * VERSION, ChangeLog: Updated for version 0.13
296
297 2006-08-21 14:08 upi
298
299 * bin/upipang.pl: Minor graphics changes, max players set to 6
300
301 2006-08-21 14:05 upi
302
303 * data/guy.png: Replaced graphics with free version by "DaniGM"
304 (panreyes@panreyes.com)
305
306 2006-08-14 20:40 upi
307
308 * bin/upipang.pl: Bugfix in background image zooming
309
310 2006-08-14 19:01 upi
311
312 * bin/upipang.pl: Widescreen mode. Some code cleanup.
313
314 2006-08-07 11:38 upi
315
316 * bin/upipang.pl: * Fixed SDL_perl 2.x compatibility issue * Added
317 Rewind capability to RecordGame
318
319 2006-08-05 16:21 upi
320
321 * bin/upipang.pl: New Demo (includes SeekerBall). Fix for SDL_perl
322 2.x
323
324 2006-08-04 20:53 upi
325
326 * ChangeLog, VERSION: Updated for version 0.12
327
328 2006-08-04 20:34 upi
329
330 * data/: bonus.png, harpoon.png: Adjusted for new weapons
331
332 2006-08-04 20:30 upi
333
334 * bin/upipang.pl: * Joysticks now work in the menu. * Added
335 "Credits" screen. * New ball type: "Seeker" ball. * Fullscreen
336 / window can be toggled in the menu * Refactoring: Rolled package
337 SpawningBall into Ball. * Refactoring: Removed individual "anim"
338 attributes and moved them to Game->{anim} * Made sure that the
339 GamePause is always on top. * Added Replay capability to normal
340 games. * Randomized bonus collection. * Visual enhancements *
341 Improved scoreboard layout * Added new difficulty level: "Miki" *
342 Split off "Options" menu * Controls menu now displays key names
343
344 2006-07-28 06:17 upi
345
346 * bin/upipang.pl: * Added new ball type: WaterBall * Added weapons
347 and weapon drops: MachineGun, PowerWire, HalfCutter * Code
348 cleanup * Uses Carp module * SuperBall now has its own BallDesc
349 (no extra parameter uglyness) * BallDesc now contains the package
350 name of the ball, so Ball::Create is cleaner * Fixed a bug which
351 caused GamePause to disappear sometimes. * Player lives
352 indicator now works for >3 lives. * Some minor fixes.
353
354 2006-07-20 11:52 upi
355
356 * bin/upipang.pl: Earthquake-ball related tweaks
357
358 2006-07-20 11:37 upi
359
360 * ChangeLog, VERSION: Updated for 0.11
361
362 2006-07-20 11:22 upi
363
364 * bin/upipang.pl: Better joystick support
365
366 2006-07-20 10:01 upi
367
368 * bin/upipang.pl, data/gameover.png, data/paused.png: Game Over and
369 Paused messages
370
371 2006-07-20 09:20 upi
372
373 * bin/upipang.pl: New background every 10 levels
374
375 2006-07-20 01:14 upi
376
377 * bin/upipang.pl: Color the border red during death sequence
378
379 2006-07-20 00:50 upi
380
381 * bin/upipang.pl, data/bonus.png: Added pictograms for Earthquake
382 Balls and Death Balls
383
384 2006-07-20 00:42 upi
385
386 * data/quake.voc: Added Earthquake Balls
387
388 2006-07-19 21:59 upi
389
390 * bin/upipang.pl: Added Earthquake Balls
391
392 2006-07-19 18:34 upi
393
394 * bin/upipang.pl: The number of death balls spawned is now limited
395 to 2
396
397 2006-07-19 18:20 upi
398
399 * bin/upipang.pl: Added demo / help
400
401 2006-07-19 14:09 upi
402
403 * bin/upipang.pl: Can take multiple screanshots with W key
404
405 2006-07-18 12:36 upi
406
407 * ChangeLog, VERSION: Updated for 0.10
408
409 2006-07-16 14:04 upi
410
411 * bin/upipang.pl: Sound and music can be toggled on/off in main
412 menu
413
414 2006-07-16 12:03 upi
415
416 * bin/upipang.pl: Added difficulty levels. Split controls menu
417 from main menu.
418
419 2006-07-15 13:34 upi
420
421 * bin/upipang.pl: Renamed package to SpawningBall
422
423 2006-07-15 13:06 upi
424
425 * bin/upipang.pl: Factored the menu-related subs into a package
426 (more OOP!)
427
428 2006-07-15 10:06 upi
429
430 * bin/upipang.pl: Esc key now goes back to the menu instead of
431 quitting during game
432
433 2006-07-14 23:16 upi
434
435 * bin/upipang.pl: Added configuration loading and saving
436
437 2006-07-13 22:53 upi
438
439 * bin/upipang.pl: Factored apart Game and GameBase packages.
440
441 2006-07-12 09:14 upi
442
443 * AUTHORS, COPYING, ChangeLog, NEWS, README, TODO, VERSION: Import
444 of usual documentation files.
445
446 2006-07-12 08:47 upi
447
448 * bin/upipang.pl: Automatically find data directory (good for
449 autoconf)
450
451 2006-07-12 08:14 upi
452
453 * bin/upipang.pl: Main game loop now in the Game package.
454
455 2006-07-11 13:31 upi
456
457 * bin/upipang.pl: Factored many global methods and variables into
458 package Game.
459
460 2006-07-10 05:55 upi
461
462 * bin/upipang.pl: Better game balance, respawning players.
463
464 2006-06-23 21:47 upi
465
466 * data/: UPiPang.mid, UPiPang.mp3, ball.png, bonus.png, border.png,
467 brandybun3.png, desert2.png, font2.png, gun.voc, guy.png,
468 harpoon.png, hexa.png, level.voc, magic.voc, meow.voc, pop.voc,
469 pop3.voc, shoot.voc, super.voc: Initial import.
470
471 2006-06-23 21:36 upi
472
473 * bin/upipang.pl: Initial import
474
475 2006-06-23 21:36 upi
476
477 * bin/upipang.pl: Initial revision
478
0 Installation instuctions for Pang Zero
1 ======================================
2
3 1. Download and unpack the archive
4
5 2. Go to the unpacked directory
6
7 3. ./configure
8
9 4. make install
10
0 #!include_default
1 # Avoid configuration metadata file
2 ^MYMETA\.
3
4 # Avoid Module::Build generated and utility files.
5 \bBuild$
6 \bBuild.bat$
7 \b_build
8 \bBuild.COM$
9 \bBUILD.COM$
10 \bbuild.com$
11 ^MANIFEST\.SKIP
12
13 # Avoid archives of this distribution
14 \bGames-PangZero-[\d\.\_]+
0 Please visit our website at
1 http://apocalypse.rulez.org/pangzero
2
3 To see what's new at the website, go to
4 http://apocalypse.rulez.org/pangzero/Recent_Changes
0 Pang Zero README
1 ================
2
3 Pang Zero is a clone of Super Pang, a fast-paced action game that involves
4 popping balloons with a harpoon. The intention of our effort is to create a
5 fun, open-source game that many (currently up to 6) people can play
6 together.
7
8 For more info, please visit our website at
9 http://apocalypse.rulez.org/pangzero
0 See pangzero.pl for current TODO list.
0 pangzero 1.3
0 #!/usr/bin/perl
1
2 use strict;
3 use warnings;
4
5 use lib 'lib';
6 use Games::PangZero;
7
8
9 eval {
10 Games::PangZero::Initialize();
11 #Games::PangZero::DoDemo() while 1;
12 #while (1) { Games::PangZero::DoRecordDemo(); $Games::PangZero::App->delay(2000); }
13 while (1) {
14 Games::PangZero::MainLoop();
15 }
16 };
17 if ($@) {
18 my $errorMessage = $@;
19 Games::PangZero::ShowErrorMessage($errorMessage);
20 die $errorMessage;
21 }
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
Binary diff not shown
0 ##########################################################################
1 package Games::PangZero::Ball;
2 ##########################################################################
3
4 use Games::PangZero::Globals;
5
6 @ISA = qw(Games::PangZero::GameObject);
7 $Gravity = 0.05;
8 $MagicBallRect = SDL::Rect->new(80, 0, 16, 15);
9
10 for (my $i = 0; $i <= $#Games::PangZero::BallDesc; ++$i) {
11 my $desc = $Games::PangZero::BallDesc[$i];
12 $desc->{speedY} = 0 unless $desc->{speedY};
13 $desc->{bounceY} = $desc->{speedY} * $desc->{speedY} / $Games::PangZero::Ball::Gravity / 2 unless $desc->{bounceY};
14 }
15
16 sub Create {
17 my ($description, $x, $y, $dir) = @_;
18 my ($retval);
19
20 my $code = sprintf('$retval = Games::PangZero::%s->new(@_);', $description->{class});
21 eval $code; die $@ if $@;
22 return $retval;
23 }
24
25 sub Spawn {
26 my ($description, $x, $dir, $hasBonus) = @_;
27 my ($retval);
28
29 $x = $Games::PangZero::Game->Rand( $Games::PangZero::ScreenWidth - $description->{width} ) if $x < 0;
30 $retval = Games::PangZero::Ball::Create( $description, $x, -$description->{height} - $Games::PangZero::ScreenMargin, $dir );
31 $retval->GiveMagic() if $retval->{w} > 32;
32 $retval->GiveBonus() if $hasBonus;
33
34 $retval->{spawning} = 1;
35 my $surfaceName = 'dark' . $description->{surface};
36 $retval->{surface} = $Games::PangZero::BallSurfaces{$surfaceName};
37 die "No surface: $surfaceName" unless $retval->{surface};
38 return $retval;
39 }
40
41 sub new {
42 my ($class, $description, $x, $y, $dir) = @_;
43 my $self = Games::PangZero::GameObject->new();
44 %{$self} = ( %{$self},
45 'x' => $x,
46 'y' => $y,
47 'w' => $description->{width},
48 'h' => $description->{height},
49 'surface' => $Games::PangZero::BallSurfaces{$description->{surface}},
50 'hexa' => $description->{hexa} ? 1 : 0,
51 'desc' => $description,
52 'hasmagic' => 0, # true if one of the ball's descendants is magic
53 'ismagic' => 0, # true if the ball IS magic
54 'spawning' => 0,
55 );
56 $self->{speedX} = $dir > 0 ? 1.3 : -1.3;
57 $self->SetupCollisions();
58 bless $self, $class;
59 }
60
61 sub NormalAdvance {
62 my $self = shift;
63
64 $self->{speedY} += $Games::PangZero::Ball::Gravity * $Games::PangZero::GameSpeed unless ($self->{hexa});
65 $self->{x} += $self->{speedX} * $Games::PangZero::GameSpeed;
66 $self->{y} += $self->{speedY} * $Games::PangZero::GameSpeed;
67 if ($self->{y} > $Games::PangZero::ScreenHeight - $self->{h}) {
68 $self->{y} = $Games::PangZero::ScreenHeight - $self->{h};
69 if ($self->{hexa}) {
70 $self->{speedY} = -abs($self->{speedY});
71 } else {
72 $self->{speedY} = -$self->{desc}->{speedY};
73 }
74 $self->Bounce;
75 }
76 if ($self->{y} < 0) {
77 $self->{y} = 0;
78 $self->{speedY} = abs($self->{speedY});
79 }
80 if ($self->{x} < 0) {
81 $self->{x} = 0;
82 $self->{speedX} = abs( $self->{speedX} );
83 }
84 if ($self->{x} > $Games::PangZero::ScreenWidth - $self->{w}) {
85 $self->{x} = $Games::PangZero::ScreenWidth - $self->{w};
86 $self->{speedX} = -abs( $self->{speedX} );
87 }
88 }
89
90 sub SpawningAdvance {
91 my $self = shift;
92
93 $self->{y} += 0.32;
94 if ($self->{y} >= 0) {
95 $self->{spawning} = 0;
96 $self->{surface} = $Games::PangZero::BallSurfaces{$self->{desc}->{surface}},
97 }
98 }
99
100 sub Advance {
101 my $self = shift;
102
103 unless( $Games::PangZero::GamePause > 0 ) {
104 if ($self->{spawning}) {
105 $self->SpawningAdvance();
106 } else {
107 $self->NormalAdvance();
108 }
109 }
110
111 $self->CheckCollisions() unless $Games::PangZero::Game->{nocollision} or $self->{spawning};
112 }
113
114 sub Bounce {
115 }
116
117 sub CheckCollisions {
118 my $self = shift;
119
120 foreach my $harpoon (values %Games::PangZero::Harpoon::Harpoons) {
121 if ($self->Collisions($harpoon)) {
122 $self->Pop($harpoon->{guy}, $harpoon->{popEffect});
123 $harpoon->Delete();
124 return;
125 }
126 }
127 foreach my $guy (values %Games::PangZero::Guy::Guys) {
128 if ($Games::PangZero::GamePause <= 0 and $self->Collisions($guy)) {
129 $guy->Kill();
130 }
131 }
132 }
133
134 sub Draw {
135 my ($self) = @_;
136
137 return if $Games::PangZero::GamePause > 0 and $Games::PangZero::GamePause < 100 and (int($Games::PangZero::GamePause / 3) % 4) < 2;
138
139 $self->TransferRect();
140 if ($self->{ismagic} and int($Games::PangZero::Game->{anim}/4) % 2) {
141 SDL::Video::blit_surface($Games::PangZero::BallSurfaces{ball4}, $Games::PangZero::Ball::MagicBallRect, $Games::PangZero::App, $self->{rect} );
142 } else {
143 SDL::Video::blit_surface($self->{surface}, $self->{desc}->{rect}, $Games::PangZero::App, $self->{rect} );
144 }
145 }
146
147 sub Collisions {
148 my ($self, $other) = @_;
149
150 # Bounding box detection
151
152 return unless $self->SUPER::Collisions($other);
153
154 # Circle vs rectangle collision
155
156 my ($centerX, $centerY, $boxAxisX, $boxAxisY, $boxCenterX, $boxCenterY, $distSquare, $distance);
157 $boxAxisX = ($other->{collisionw} or $other->{w}) / 2;
158 $boxAxisY = ($other->{collisionh} or $other->{h}) / 2;
159 $boxCenterX = $other->{x} + $other->{w} / 2;
160 $boxCenterY = $other->{y} + $other->{h} / 2;
161 $centerX = $self->{x} + $self->{w} / 2;
162 $centerY = $self->{y} + $self->{h} / 2;
163
164 # Translate coordinates to the box center
165 $centerX -= $boxCenterX;
166 $centerY -= $boxCenterY;
167 $centerX = abs($centerX);
168 $centerY = abs($centerY);
169
170 if ($centerX < $boxAxisX) {
171 return 1 if $centerY < $boxAxisY + $self->{h} / 2;
172 return 0;
173 }
174 if ($centerY < $boxAxisY) {
175 return 2 if $centerX < $boxAxisX + $self->{w} / 2;
176 return 0;
177 }
178 $distSquare = ($centerX-$boxAxisX) * ($centerX-$boxAxisX);
179 $distSquare += ($centerY-$boxAxisY) * ($centerY-$boxAxisY);
180 return 3 if $distSquare < $self->{h} * $self->{h} / 4;
181
182 return 0;
183 }
184
185 sub Pop {
186 my ($self, $guy, $popEffect) = @_;
187
188 Carp::confess "no $popEffect" unless defined $popEffect;
189 $Games::PangZero::GameEvents{'pop'} = 1;
190 $Games::PangZero::GameEvents{'magic'} = 1 if ($self->{ismagic});
191 $guy->GiveScore($self->{desc}->{score}) if $guy;
192 $self->Delete();
193
194 goto skipChildren if ($popEffect eq 'meltdown');
195
196 if ($self->{desc}->{nextgen}) {
197 die caller unless $self->{desc}->{nextgen}->{class};
198 my @children = $self->SpawnChildren();
199 if (scalar @children) {
200 $self->AdjustChildren(@children);
201 if ($popEffect eq 'HalfCutter') {
202 push @Games::PangZero::GameObjects, ($self->{speedX} > 0 ? $children[1] : $children[0]);
203 } else {
204 push @Games::PangZero::GameObjects, (@children);
205 }
206 }
207 }
208 if ($self->{bonus} and $popEffect ne 'superkill') {
209 push @Games::PangZero::GameObjects, Games::PangZero::BonusDrop->new($self);
210 }
211 $Games::PangZero::Game->OnBallPopped();
212
213 skipChildren:
214 push @Games::PangZero::GameObjects, Games::PangZero::Pop->new($self->{x}, $self->{y}, $self->{desc}->{popIndex}, $self->{surface});
215 }
216
217 sub SpawnChildren {
218 my $self = shift;
219 my $nextgen = $self->{desc}->{nextgen};
220 die caller unless $nextgen->{class};
221 my $x = $self->{x} + $self->{w} / 2;
222 my $y = $self->{y} + ( $self->{h} - $nextgen->{height} ) / 2;
223 my $child1 = Create($nextgen, $self->{x}, $y, 0);
224 my $child2 = Create($nextgen, $self->{x} + $self->{w} - $nextgen->{width}, $y, 1);
225
226 return ($child1, $child2);
227 }
228
229 sub AdjustChildren {
230 my ($self, @children) = @_;
231 my ($nextgen, $speedY, $altitude);
232
233 if ($self->{hasmagic}) {
234 $children[0]->GiveMagic();
235 }
236
237 $nextgen = $self->{desc}->{nextgen};
238 $altitude = $Games::PangZero::ScreenHeight - $self->{y} - $self->{h};
239 $speedY = 1.8;
240 unless ($altitude > $nextgen->{bounceY}) {
241 $speedY = 1.8;
242 while ($speedY * $speedY / $Games::PangZero::Ball::Gravity / 2 + $altitude < $nextgen->{bounceY}) {
243 ++$speedY;
244 }
245 }
246 foreach (@children) {
247 $_->{speedY} = -$speedY;
248 }
249 }
250
251 sub GiveMagic {
252 my $self = shift;
253
254 $self->{hasmagic} = 1;
255 $self->{ismagic} = 1 unless $self->{desc}->{nextgen};
256 }
257
258 sub GiveBonus {
259 my $self = shift;
260
261 $self->{bonus} = 1;
262 }
263
264 1;
0 package Games::PangZero::BonusDrop;
1
2 @ISA = qw(Games::PangZero::GameObject);
3 use strict;
4 use warnings;
5
6 use vars qw(@BonusDesc);
7
8 @BonusDesc = (
9 # MachineGun is disabled for now as it is broken (seems like invisible bullets keep flying around)
10 # { 'weaponClass' => 'MachineGun', 'bonusDelay' => 1500, 'srcRect' => SDL::Rect->new( 0, 64, 32, 32), },
11 { 'weaponClass' => 'HalfCutter', 'bonusDelay' => 1000, 'srcRect' => SDL::Rect->new(32, 64, 32, 32), },
12 { 'weaponClass' => 'PowerWire', 'bonusDelay' => 3000, 'srcRect' => SDL::Rect->new(32, 96, 32, 32), },
13 { 'onCollectedSub' => \&OnCollectedSlowEffect, 'srcRect' => SDL::Rect->new(32, 0, 32, 32), },
14 );
15
16
17 sub new {
18 my ($class, $ball) = @_;
19 my ($self);
20
21 $self = Games::PangZero::GameObject->new();
22
23 %{$self} = ( %{$self},
24 'x' => $ball->{x} + ($ball->{w} - 32) / 2,
25 'y' => $ball->{y} + ($ball->{h} - 32) / 2,
26 'w' => 32,
27 'h' => 32,
28 'speedY' => -3,
29 'speedX' => 0,
30 'bottomDelay' => 500,
31 'desc' => $BonusDesc[int $Games::PangZero::Game->Rand(scalar @BonusDesc)],
32 );
33 bless $self, $class;
34 }
35
36 sub Advance {
37 my $self = shift;
38
39 if ($self->{y} >= $Games::PangZero::ScreenHeight - $self->{h}) {
40 $self->{y} = $Games::PangZero::ScreenHeight - $self->{h};
41 if (--$self->{bottomDelay} < 0) {
42 $self->Delete();
43 }
44 } else {
45 $self->{speedY} += 0.1;
46 $self->{y} += $self->{speedY};
47 }
48
49 $self->CheckCollisions() if $self->{speedY} >= 0;
50 }
51
52 sub CheckCollisions {
53 my $self = shift;
54 my ($guy, @guysTouched);
55
56 foreach $guy (@Games::PangZero::GameObjects) {
57 next unless ref($guy) eq 'Games::PangZero::Guy';
58 next unless $self->Collisions($guy);
59 push @guysTouched, ($guy);
60 }
61 return unless @guysTouched;
62 $self->Collected($guysTouched[$Games::PangZero::Game->Rand( scalar @guysTouched )]);
63 }
64
65 sub SetOnCollectedSub {
66 my ($self, $onCollectedSub) = @_;
67 $self->{onCollectedSub} = $onCollectedSub;
68 }
69
70 sub Collected {
71 my ($self, $guy) = @_;
72
73 if ($self->{onCollectedSub}) {
74 $self->{onCollectedSub}->($self, $guy);
75 } elsif ($self->{desc}->{onCollectedSub}) {
76 $self->{desc}->{onCollectedSub}->($self, $guy);
77 } else {
78 $guy->{weapon} = $self->{desc}->{weaponClass};
79 $guy->{bonusDelay} = $self->{desc}->{bonusDelay} * $Games::PangZero::WeaponDuration->{durationmultiplier};
80 }
81 $self->Delete();
82 }
83
84 sub Draw {
85 my $self = shift;
86
87 return if $self->{bottomDelay} < 100 and (($Games::PangZero::Game->{anim} / 4) % 2 < 1);
88 $self->TransferRect();
89 SDL::Video::blit_surface($Games::PangZero::BonusSurface, $self->{desc}->{srcRect}, $Games::PangZero::App, $self->{rect});
90 }
91
92 sub OnCollectedSlowEffect {
93 my ($self, $guy) = @_;
94
95 Games::PangZero::SlowEffect::RemoveSlowEffects();
96 push @Games::PangZero::GameObjects, Games::PangZero::SlowEffect->new();
97 }
98
99 1;
0 ##########################################################################
1 package Games::PangZero::ChallengeGame;
2 ##########################################################################
3
4 @ISA = qw(Games::PangZero::PlayableGameBase);
5 use strict;
6 use warnings;
7
8 sub new {
9 my ($class) = @_;
10 my $self = Games::PangZero::PlayableGameBase->new();
11 %{$self} = (%{$self},
12 'challenge' => undef,
13 );
14 bless $self, $class;
15 }
16
17 sub SetGameLevel {
18 my ($self, $level) = @_;
19
20 Games::PangZero::SlowEffect::RemoveSlowEffects();
21 $self->SUPER::SetGameLevel($level);
22 $level = $#Games::PangZero::ChallengeLevels if $level > $#Games::PangZero::ChallengeLevels;
23 $self->{challenge} = $Games::PangZero::ChallengeLevels[$level];
24 die unless $self->{challenge};
25 $self->SpawnChallenge();
26 }
27
28 sub AdvanceGame {
29 my ($self) = @_;
30
31 if ($self->{nextlevel}) {
32 Games::PangZero::Music::PlaySound('level');
33 $self->SetGameLevel($self->{level} + 1);
34 delete $self->{nextlevel};
35 }
36 if ($self->{playerspawned}) {
37 $self->SpawnChallenge();
38 $self->{playerspawned} = 0;
39 }
40 $self->SUPER::AdvanceGame();
41 }
42
43 sub SpawnChallenge {
44 my $self = shift;
45 my ($challenge, @guys, $balldesc, $ball, $hasBonus, %balls, $numBalls, $ballsSpawned, @ballKeys, $x);
46
47 @guys = $self->PopEveryBall();
48 foreach (@guys) {
49 $_->{bonusDelay} = 1;
50 $_->{invincible} = 1;
51 }
52 $Games::PangZero::GamePause = 0;
53 delete $Games::PangZero::GameEvents{magic};
54 $challenge = $self->{challenge};
55 die unless $challenge;
56
57 while ($challenge =~ /(\w+)/g) {
58 $balldesc = $Games::PangZero::BallDesc{$1};
59 warn "Unknown ball in challenge: $1" unless $balldesc;
60 $balls{$1}++;
61 $numBalls++;
62 }
63 $ballsSpawned = 0;
64 while ($ballsSpawned < $numBalls) {
65 foreach (keys %balls) {
66 next unless $balls{$_};
67 --$balls{$_};
68 $balldesc = $Games::PangZero::BallDesc{$_};
69 $x = $Games::PangZero::ScreenWidth * ($ballsSpawned * 2 + 1) / ($numBalls * 2) - $balldesc->{width} / 2;
70 $x = $Games::PangZero::ScreenWidth - $balldesc->{width} if $x > $Games::PangZero::ScreenWidth - $balldesc->{width};
71 $hasBonus = (($balldesc->{width} >= 32) and ($self->Rand(1) < $Games::PangZero::DifficultyLevel->{bonusprobability}));
72 $ball = &Games::PangZero::Ball::Spawn($balldesc, $x, ($ballsSpawned % 2) ? 0 : 1, $hasBonus);
73 if ($ball->{w} <= 32) {
74 $ball->{ismagic} = $ball->{hasmagic} = 0;
75 }
76 push @Games::PangZero::GameObjects, ($ball) ;
77 ++$ballsSpawned;
78 }
79 }
80 }
81
82 sub OnBallPopped {
83 my $self = shift;
84 my ($i);
85
86 for ($i = $#Games::PangZero::GameObjects; $i >= 0; --$i) {
87 if ($Games::PangZero::GameObjects[$i]->isa('Games::PangZero::Ball')) {
88 return;
89 }
90 }
91 $self->{nextlevel} = 1;
92 }
93
94 1;
0 ##########################################################################
1 # CONFIG SAVE/LOAD
2 ##########################################################################
3
4 package Games::PangZero::Config;
5
6 use File::ShareDir qw(dist_dir);
7
8 sub IsMicrosoftWindows {
9 return $^O eq 'MSWin32';
10 }
11
12
13 sub TestDataDir {
14 return -f "$Games::PangZero::DataDir/glossyfont.png"; # Should be a file from the latest version.
15 }
16
17 sub FindDataDir {
18 return if $Games::PangZero::DataDir and TestDataDir();
19 my @guesses = ('.', dist_dir('Games-PangZero'));
20 foreach my $guess (@guesses) {
21 $Games::PangZero::DataDir = $guess;
22 return if TestDataDir();
23 $Games::PangZero::DataDir = "$guess/data";
24 return if TestDataDir();
25 }
26 die "Couldn't find the data directory. Please set it manually.";
27 }
28
29 sub GetConfigFilename {
30 if ( IsMicrosoftWindows() ) {
31 if ($ENV{USERPROFILE}) {
32 return "$ENV{USERPROFILE}\\pangzero.cfg";
33 }
34 return "$Games::PangZero::DataDir/pangzero.cfg";
35 }
36 if ($ENV{HOME}) {
37 return "$ENV{HOME}/.pangzerorc";
38 }
39 if (-w $Games::PangZero::DataDir) {
40 return "$Games::PangZero::DataDir/pangzero.cfg";
41 }
42 return "/tmp/pangzero.cfg";
43 }
44
45 sub GetConfigVars {
46 my ($i, $j);
47 my @result = qw(
48 Games::PangZero::NumGuys
49 Games::PangZero::DifficultyLevelIndex
50 Games::PangZero::WeaponDurationIndex
51 Games::PangZero::Slippery
52 Games::PangZero::MusicEnabled
53 Games::PangZero::SoundEnabled
54 Games::PangZero::FullScreen
55 Games::PangZero::ShowWebsite
56 Games::PangZero::DeathBallsEnabled
57 Games::PangZero::EarthquakeBallsEnabled
58 Games::PangZero::WaterBallsEnabled
59 Games::PangZero::SeekerBallsEnabled
60 );
61 for ($i=0; $i < scalar @Games::PangZero::Players; ++$i) {
62 for ($j=0; $j < 3; ++$j) {
63 push @result, ("Games::PangZero::Players[$i]->{keys}->[$j]");
64 }
65 push @result, ("Games::PangZero::Players[$i]->{colorindex}");
66 push @result, ("Games::PangZero::Players[$i]->{imagefileindex}");
67 }
68 my ($difficulty, $gameMode);
69 for ($difficulty=0; $difficulty < scalar @Games::PangZero::DifficultyLevels; ++$difficulty) {
70 foreach $gameMode ('highScoreTablePan', 'highLevelTablePan', 'highScoreTableCha', 'highLevelTableCha') {
71 next if ($Games::PangZero::DifficultyLevels[$difficulty]->{name} eq 'Miki' and $gameMode eq 'highScoreTableCha');
72 for ($i=0; $i < 5; ++$i) {
73 push @result, "Games::PangZero::DifficultyLevels[$difficulty]->{$gameMode}->[$i]->[0]", # Name of high score
74 "Games::PangZero::DifficultyLevels[$difficulty]->{$gameMode}->[$i]->[1]", # High score
75 }
76 }
77 }
78 return @result;
79 }
80
81 sub SaveConfig {
82 my ($filename, $varname, $value);
83 $filename = GetConfigFilename();
84
85 open CONFIG, "> $filename" or return;
86 foreach $varname (GetConfigVars()) {
87 eval("\$value = \$$varname"); die $@ if $@;
88 print CONFIG "\$$varname = $value\n";
89 }
90 close CONFIG;
91 }
92
93 sub LoadConfig {
94 my ($filename, $text, $varname);
95
96 $text = '';
97 $filename = GetConfigFilename();
98 if (open CONFIG, "$filename") {
99 read CONFIG, $text, 16384;
100 close CONFIG;
101 }
102
103 foreach $varname (GetConfigVars()) {
104 my $pattern = $varname;
105 $pattern =~ s/\[/\\[/g;
106 if ($text =~ /$pattern = (.+?)$/m) {
107 $val = $1;
108 if ($varname eq Games::PangZero::ShowWebsite) {
109 eval( "\$$varname = '$val'" );
110 }
111 elsif($val =~ /^SDLK_\w+$/) {
112 eval( "\$$varname = SDL::Events::$val()" );
113 }
114 elsif($val =~ /^[\d\.]+$/) {
115 eval( "\$$varname = $val" );
116 }
117 else {
118 eval( "\$$varname = '$val'" );
119 }
120 }
121 }
122
123 SetDifficultyLevel($Games::PangZero::DifficultyLevelIndex);
124 SetWeaponDuration($Games::PangZero::WeaponDurationIndex);
125 }
126
127 sub SetDifficultyLevel {
128 my $difficultyLevelIndex = shift;
129 if ($difficultyLevelIndex < 0 or $difficultyLevelIndex > $#Games::PangZero::DifficultyLevels) {
130 $difficultyLevelIndex = $Games::PangZero::DifficultyLevelIndex;
131 }
132 $Games::PangZero::DifficultyLevelIndex = $difficultyLevelIndex;
133 $Games::PangZero::DifficultyLevel = $Games::PangZero::DifficultyLevels[$difficultyLevelIndex];
134 }
135
136 sub SetWeaponDuration {
137 my $weaponDurationIndex = shift;
138 if ($weaponDurationIndex < 0 or $weaponDurationIndex > $#Games::PangZero::WeaponDurations) {
139 $weaponDurationIndex = $Games::PangZero::WeaponDurationIndex;
140 }
141 $Games::PangZero::WeaponDurationIndex = $weaponDurationIndex;
142 $Games::PangZero::WeaponDuration = $Games::PangZero::WeaponDurations[$Games::PangZero::WeaponDurationIndex];
143 }
144
145 1;
0 ##########################################################################
1 package Games::PangZero::DeadGuy;
2 ##########################################################################
3
4 @ISA = qw(Games::PangZero::GameObject);
5 use strict;
6 use warnings;
7
8 sub new {
9 my ($class, $guy, $dir) = @_;
10 my ($self, $player);
11
12 $self = Games::PangZero::GameObject->new();
13 $player = $guy->{player};
14
15 %{$self} = ( %{$self},
16 'x' => $guy->{x},
17 'y' => $guy->{y},
18 'w' => 64,
19 'h' => 64,
20 'speedY' => -7,
21 'surface' => $player->{guySurface},
22 'anim' => 0,
23 'bounce' => 0,
24 'bouncex' => 0,
25 );
26 $self->{'speedX'} = ($Games::PangZero::Game->Rand(2) + 1.5) * (($self->{x} > $Games::PangZero::ScreenWidth / 2) ? 1 : -1);
27 bless $self, $class;
28 }
29
30 sub Advance {
31 my $self = shift;
32
33 $self->{speedY} += 0.1;
34 $self->{x} += $self->{speedX};
35 $self->{y} += $self->{speedY};
36
37 unless ($self->{bouncex}) {
38 if ($self->{x} < -16) {
39 $self->{x} = -16;
40 $self->{speedX} = abs( $self->{speedX} );
41 $self->{speedY} = -3 if $self->{speedY} > -3;
42 $self->{bouncex} = 1;
43 }
44 if ($self->{x} > $Games::PangZero::ScreenWidth - $self->{w} +16) {
45 $self->{x} = $Games::PangZero::ScreenWidth - $self->{w} + 16;
46 $self->{speedX} = -abs( $self->{speedX} );
47 $self->{speedY} = -3 if $self->{speedY} > -3;
48 $self->{bouncex} = 1;
49 }
50 }
51 if ($self->{y} > $Games::PangZero::ScreenHeight - 64 and not $self->{bounce}) {
52 $self->{bounce} = 1;
53 $self->{speedY} = -3;
54 }
55
56 if ($self->{y} > $Games::PangZero::PhysicalScreenHeight) {
57 $self->Delete;
58 }
59 $self->{anim} += $self->{speedX} > 0 ? -1 : +1;
60 }
61
62 sub Draw {
63 my $self = shift;
64 my ($srcrect);
65
66 $srcrect = SDL::Rect->new(($self->{speedX} > 0 ? 0 : 64), 128, 64, 64 );
67 $self->TransferRect();
68 if(SDL::Config->has('SDL_gfx_rotozoom')) {
69 my $roto = SDL::Surface->new( SDL::Video::SDL_SWSURFACE(), 64, 64, 32, 0xFF000000, 0xFF0000, 0xFF00, 0xFF);
70 SDL::Video::blit_surface($self->{surface}, $srcrect, $roto, SDL::Rect->new(0, 0, $roto->w, $roto->h) );
71 $roto = SDL::GFX::Rotozoom::surface($roto, $self->{anim} * 5, 1, SDL::GFX::Rotozoom::SMOOTHING_OFF());
72 $self->{rect}->x( $self->{rect}->x - ($roto->w - 64) / 2 );
73 $self->{rect}->y( $self->{rect}->y - ($roto->h - 64) / 2 );
74 SDL::Video::blit_surface($roto, SDL::Rect->new(0, 0, 64, 64), $Games::PangZero::App, $self->{rect} );
75 return;
76 }
77 else {
78 SDL::Video::blit_surface($self->{surface}, $srcrect, $Games::PangZero::App, $self->{rect} );
79 }
80 }
81
82 1;
0 ##########################################################################
1 package Games::PangZero::DeathBall;
2 ##########################################################################
3
4 @ISA = qw(Games::PangZero::Ball);
5 use strict;
6 use warnings;
7
8 sub new {
9 my $class = shift;
10 my $self = Games::PangZero::Ball->new(@_);
11 $self->{expires} = 2000; # 20sec
12 $self->{speedX} *= 0.9;
13 bless $self, $class;
14 }
15
16 sub NormalAdvance {
17 my $self = shift;
18
19 $self->SUPER::NormalAdvance();
20 if (--$self->{expires} < 0) {
21 $self->{bonus} = 1 if $self->{hasmagic};
22 $self->Pop(undef, 'expire');
23 }
24
25 }
26
27 sub Pop {
28 my ($self, $guy, $popEffect) = @_;
29
30 $self->{dontspawn} = 1 if $popEffect eq 'expire' or $popEffect eq 'superkill';
31 $self->SUPER::Pop($guy, $popEffect);
32 if (CountDeathBalls() > 30) {
33 $Games::PangZero::GameEvents{'meltdown'} = 1;
34 }
35 }
36
37 sub SpawnChildren {
38 my $self = shift;
39
40 return if $self->{dontspawn};
41 $self->SUPER::SpawnChildren(@_);
42 }
43
44 sub CountDeathBalls {
45 my $count = 0;
46
47 foreach my $ball (@Games::PangZero::GameObjects) {
48 if (ref($ball) eq 'Games::PangZero::DeathBall') { ++$count; }
49 }
50 return $count;
51 }
52
53 1;
0 ##########################################################################
1 package Games::PangZero::DemoGame;
2 ##########################################################################
3
4 @ISA = qw(Games::PangZero::PanicGame);
5
6 sub ResetGame {
7 my $self = shift;
8 Games::PangZero::Config::SetDifficultyLevel(1);
9 Games::PangZero::Config::SetWeaponDuration(0);
10 $Games::PangZero::Slippery = 0;
11 $self->SUPER::ResetGame();
12
13 my $ball = Games::PangZero::Ball::Create($Games::PangZero::BallDesc[4], 400, 0, -10, 0);
14 $ball->GiveMagic();
15
16 push @Games::PangZero::GameObjects, (
17 Games::PangZero::Ball::Create($Games::PangZero::BallDesc[0], 100, 0, 1),
18 Games::PangZero::Ball::Create($Games::PangZero::BallDesc{super0}, 300, 0, 0),
19 Games::PangZero::Ball::Create($Games::PangZero::BallDesc{super1}, 500, 0, 1),
20 $ball,
21 );
22 $Games::PangZero::GamePause = 0;
23 $Games::PangZero::GameSpeed = 0.8;
24 $self->{spawndelay} = $self->{superballdelay} = 1000000;
25 $self->{ballcounter} = 0;
26 $self->{balls} = [ qw(b0 h0 w1 quake death seeker) ];
27 }
28
29 sub SetGameSpeed {
30 $Games::PangZero::GameSpeed = 0.8;
31 }
32
33 sub SpawnBalls {
34 my $self = shift;
35
36 return if (--$self->{spawndelay} > 0);
37 my $ballName = $self->{balls}->[$self->{ballcounter}];
38 return unless $ballName;
39 push @Games::PangZero::GameObjects, ( Games::PangZero::Ball::Spawn($Games::PangZero::BallDesc{$ballName}, 100, 1, 0) );
40 $self->{spawndelay} = 1000000;
41 ++$self->{ballcounter};
42 }
43
44 sub RespawnPlayers {}
45 sub OnBallPopped {}
46
47 1;
0 ##########################################################################
1 package Games::PangZero::DemoPlaybackGame;
2 ##########################################################################
3
4 @ISA = qw(Games::PangZero::DemoGame Games::PangZero::PlaybackGame);
5 use strict;
6 use warnings;
7
8 sub new {
9 my $class = shift;
10 my $self = Games::PangZero::PlaybackGame->new(@_);
11 bless $self, $class;
12 }
13
14 sub DrawScoreBoard {
15 my $self = shift;
16 my $x = 10;
17 my $y = $Games::PangZero::ScreenHeight + 2 * $Games::PangZero::ScreenMargin + 5;
18 if ($self->{anim} < 1) {
19 SDLx::SFont::print_text( $Games::PangZero::Background, $x, $y, "Press F to fast forward" );
20 SDLx::SFont::print_text( $Games::PangZero::App, $x, $y, "Press F to fast forward" );
21 } return;
22 SDL::Video::fill_rect($Games::PangZero::App, SDL::Rect->new(0, $y, $Games::PangZero::PhysicalScreenWidth, $Games::PangZero::PhysicalScreenHeight - $y), SDL::Video::map_RGB($Games::PangZero::App->format(), 0, 0, 0));
23 SDLx::SFont::print_text( $Games::PangZero::App, $x, $y, $self->{recordpointer} );
24
25 }
0 ##########################################################################
1 package Games::PangZero::DemoRecordGame;
2 ##########################################################################
3
4 @ISA = qw(Games::PangZero::DemoGame Games::PangZero::RecordGame);
5 use strict;
6 use warnings;
7
8 sub new {
9 my $class = shift;
10 my $self = Games::PangZero::RecordGame->new(@_);
11 bless $self, $class;
12 }
13
14 1;
0 ##########################################################################
1 package Games::PangZero::EarthquakeBall;
2 ##########################################################################
3
4 @ISA = qw(Games::PangZero::Ball);
5 use strict;
6 use warnings;
7
8 sub new {
9 my $class = shift;
10 my $self = Games::PangZero::Ball->new(@_);
11 bless $self, $class;
12 }
13
14 sub CountEarthquakeBalls {
15 my $count = 0;
16
17 foreach my $ball (@Games::PangZero::GameObjects) {
18 if (ref($ball) eq 'Games::PangZero::EarthquakeBall') { ++$count; }
19 }
20 return $count;
21 }
22
23 sub Bounce {
24 my $self = shift;
25
26 unless ($Games::PangZero::GameEvents{earthquake} and $Games::PangZero::GameEvents{earthquake} > $self->{desc}->{quake}) {
27 $Games::PangZero::GameEvents{earthquake} = [$self->{desc}->{quake}, $self->{x}];
28 }
29 }
30
31 1;
0 ##########################################################################
1 package Games::PangZero::FpsIndicator;
2 ##########################################################################
3
4 @ISA = qw(Games::PangZero::GameObject);
5
6 sub new {
7 my ($class) = @_;
8 my $self = Games::PangZero::GameObject->new();
9 my $width = Games::PangZero::Graphics::TextWidth("999");
10 %{$self} = ( %{$self},
11 'x' => $Games::PangZero::ScreenWidth - $width + $Games::PangZero::ScreenMargin,
12 'y' => -$Games::PangZero::ScreenMargin,
13 'w' => $width,
14 'h' => 32,
15 );
16
17 $self->TransferRect();
18 bless $self, $class;
19 }
20
21 sub Draw {
22 my $self = shift;
23
24 SDLx::SFont::print_text( $Games::PangZero::App, $self->{rect}->x, $self->{rect}->y, Games::PangZero::GameTimer::GetFramesPerSecond() );
25
26 }
27
28 1;
0 ##########################################################################
1 package Games::PangZero::FragileBall;
2 ##########################################################################
3
4 @ISA = qw( Games::PangZero::Ball );
5 use strict;
6 use warnings;
7
8 sub Bounce {
9 my $self = shift;
10 if ($self->{desc}->{nextgen}) {
11 $self->{bonus} = 0;
12 $self->Pop(undef, '');
13 }
14 $self->{speedX} = ($self->{speedX} > 0) ? 1.3 : -1.3;
15 }
16
17 sub SpawnChildren {
18 my $self = shift;
19 my (@children, $child, $i);
20
21 my $nextgen = $self->{desc}->{nextgen};
22 die caller unless $nextgen->{class};
23 my $numchildren = 2;
24 while ($nextgen->{nextgen}) {
25 $nextgen = $nextgen->{nextgen};
26 $numchildren *= 2;
27 }
28
29 my $y = $self->{y} + ($self->{h} - $nextgen->{height}) / 2;
30 for ($i = 0; $i < $numchildren; ++$i) {
31 $child = Games::PangZero::Ball::Create($nextgen, $self->{x}, $y, 0);
32 $child->{speedX} = -1.5 + ($i / ($numchildren-1) * 3);
33 $child->{x} = $self->{x} + ($self->{w} - $child->{w}) * ($i / ($numchildren-1));
34 push @children, $child;
35 }
36
37 return @children;
38 }
39
40 1;
0 ##########################################################################
1 package Games::PangZero::GameBase;
2 ##########################################################################
3
4 use SDL;
5 use SDL::Video;
6
7 sub new {
8 my ($class) = @_;
9 my $self = {
10 abortgame => 0,
11 anim => 0,
12 nocollision => 0,
13 backgrounds => [ 'desert2.png', ],
14 };
15 $Games::PangZero::GameSpeed = 1.0;
16 $Games::PangZero::GamePause = 0;
17 bless $self, $class;
18 }
19
20 sub Exit {
21 Games::PangZero::ShowWebPage("http://apocalypse.rulez.org/pangzero/Thanks_For_Playing_Pang_Zero_$Games::PangZero::VERSION" ) if $Games::PangZero::ShowWebsite ne $Games::PangZero::VERSION;
22 exit;
23 }
24
25 sub Rand {
26 shift;
27 return rand($_[0]);
28 }
29
30 sub Delay {
31 my ($self, $ticks) = @_;
32
33 while ($ticks > 0) {
34 my $advance = $self->CalculateAdvances();
35 %Games::PangZero::Events = ();
36 Games::PangZero::HandleEvents();
37 return if $self->{abortgame};
38 $ticks -= $advance;
39 $self->DrawGame();
40 }
41 }
42
43 sub SetGameSpeed {
44 }
45
46 sub SetBackground {
47 my ($self, $backgroundIndex) = @_;
48
49 return if $backgroundIndex >= scalar( @{$self->{backgrounds}} );
50 Games::PangZero::Graphics::LoadBackground($self->{backgrounds}->[$backgroundIndex]);
51 SDL::Video::blit_surface($Games::PangZero::Background, SDL::Rect->new(0, 0, $Games::PangZero::App->w, $Games::PangZero::App->h), $Games::PangZero::App, SDL::Rect->new(0, 0, $Games::PangZero::App->w, $Games::PangZero::App->h));
52 }
53
54 sub ShowTooltip {
55 }
56
57 sub ResetGame {
58 my $self = shift;
59 @Games::PangZero::GameObjects = ();
60 %Games::PangZero::Guy::Guys = ();
61 %Games::PangZero::Harpoon::Harpoons = ();
62 $Games::PangZero::GamePause = 0;
63 %Games::PangZero::GameEvents = ();
64 $self->SetBackground(0);
65 }
66
67 sub CalculateAdvances {
68 my $advance = Games::PangZero::GameTimer::GetAdvances();
69 while ($advance <= 0) {
70 SDL::delay(3); # Wait 3ms = 0.3 game ticks
71 $advance = Games::PangZero::GameTimer::GetAdvances();
72 }
73 if ($advance > 5) {
74 # print STDERR "advance = $advance!\n";
75 $advance = 5;
76 }
77 return $advance;
78 }
79
80 sub AdvanceGameObjects {
81 my ($self) = @_;
82
83 ++$self->{anim};
84 foreach my $gameObject (@Games::PangZero::GameObjects) {
85 $gameObject->Advance();
86 }
87 }
88
89 sub OnBallPopped {
90 }
91
92 sub DrawGame {
93 my ($self) = @_;
94
95 my ($gameObject);
96 foreach $gameObject (@Games::PangZero::GameObjects) {
97 $gameObject->Clear();
98 }
99 $self->DrawScoreBoard();
100 foreach $gameObject (@Games::PangZero::GameObjects) {
101 $gameObject->Draw();
102 }
103 SDL::Video::flip($Games::PangZero::App);
104 }
105
106 sub DrawScoreBoard {
107 }
108
109 1;
0 ##########################################################################
1 package Games::PangZero::GameObject;
2 ##########################################################################
3
4 sub new {
5 my ($class) = @_;
6 my $self = {
7 'rect' => SDL::Rect->new( 0, 0, 0, 0 ),
8 'speedX' => 0,
9 'speedY' => 0,
10 'x' => 0,
11 'y' => 0,
12 'w' => 10,
13 'h' => 10,
14 };
15 bless $self, $class;
16 }
17
18 sub Delete {
19 my $self = shift;
20
21 for (my $i = 0; $i < scalar @Games::PangZero::GameObjects; ++$i) {
22 if ($Games::PangZero::GameObjects[$i] eq $self) {
23 splice @Games::PangZero::GameObjects, $i, 1;
24 last;
25 }
26 }
27 $self->{deleted} = 1;
28 $self->Clear();
29 }
30
31 sub Advance {
32 my $self = shift;
33
34 $self->{advance}->($self) if $self->{advance};
35 }
36
37 sub Clear {
38 my ($self) = @_;
39 SDL::Video::blit_surface($Games::PangZero::Background, $self->{rect}, $Games::PangZero::App, $self->{rect});
40 }
41
42 sub TransferRect {
43 my ($self) = @_;
44
45 $self->{rect}->x($self->{x} + $Games::PangZero::ScreenMargin);
46 $self->{rect}->y($self->{y} + $Games::PangZero::ScreenMargin);
47 $self->{rect}->w($self->{w});
48 $self->{rect}->h($self->{h});
49 }
50
51 sub Draw {
52 my ($self) = @_;
53
54 $self->TransferRect();
55 if ($self->{draw}) {
56 $self->{draw}->($self);
57 } else {
58 SDL::Video::fill_rect($Games::PangZero::App, $self->{rect}, SDL::Video::map_RGB($Games::PangZero::App->format(), 0x80, 0, 0));
59 }
60 }
61
62 sub SetupCollisions {
63 my ($self) = @_;
64
65 $self->{collisionw} = ($self->{collisionw} or $self->{w});
66 $self->{collisionh} = ($self->{collisionh} or $self->{h});
67 $self->{collisionmarginw1} = ( $self->{w} - $self->{collisionw} ) / 2;
68 $self->{collisionmarginw2} = $self->{collisionmarginw1} + $self->{collisionw};
69 $self->{collisionmarginh1} = ( $self->{h} - $self->{collisionh} ) / 2;
70 $self->{collisionmarginh2} = $self->{collisionmarginh1} + $self->{collisionh};
71 $self->{centerx} = $self->{w} / 2;
72 $self->{centery} = $self->{y} / 2;
73 }
74
75 sub Collisions {
76 my ($self, $other) = @_;
77
78 # Bounding box detection
79
80 unless ($self->{collisionmarginw1} and $other->{collisionmarginw1}) {
81 return 0 if $self->{x} >= $other->{x} + $other->{w};
82 return 0 if $other->{x} >= $self->{x} + $self->{w};
83 return 0 if $self->{y} >= $other->{y} + $other->{h};
84 return 0 if $other->{y} >= $self->{y} + $self->{h};
85 return 1;
86 }
87
88 return 0 if $self->{x} + $self->{collisionmarginw1} >= $other->{x} + $other->{collisionmarginw2};
89 return 0 if $other->{x} + $other->{collisionmarginw1} >= $self->{x} + $self->{collisionmarginw2};
90 return 0 if $self->{y} + $self->{collisionmarginh1} >= $other->{y} + $other->{collisionmarginh2};
91 return 0 if $other->{y} + $other->{collisionmarginh1} >= $self->{y} + $self->{collisionmarginh2};
92 return 1;
93 }
94
95 1;
0 ##########################################################################
1 package Games::PangZero::GamePause;
2 ##########################################################################
3
4 @ISA = qw(Games::PangZero::GameObject);
5
6 sub Show {
7 foreach my $gameObject (@Games::PangZero::GameObjects) {
8 return if (ref $gameObject eq 'Games::PangZero::GamePause');
9 }
10 push @Games::PangZero::GameObjects, Games::PangZero::GamePause->new();
11 }
12
13 sub new {
14 my ($class) = @_;
15 my $self = Games::PangZero::GameObject->new();
16 my $width = Games::PangZero::Graphics::TextWidth("Time left: 9.999");
17 %{$self} = ( %{$self},
18 'x' => ($Games::PangZero::PhysicalScreenWidth - $width) / 2,
19 'y' => 100,
20 'w' => $width,
21 'h' => 32,
22 );
23 $self->TransferRect();
24 bless $self, $class;
25 }
26
27 sub BringToFront {
28 my $self = shift;
29
30 @Games::PangZero::GameObjects = grep { $_ ne $self } @Games::PangZero::GameObjects;
31 push @Games::PangZero::GameObjects, ($self);
32 }
33
34 sub Advance {
35 my $self = shift;
36
37 if ($Games::PangZero::GamePause <= 0) {
38 $self->Delete;
39 return;
40 }
41 unless ($Games::PangZero::GameObjects[$#Games::PangZero::GameObjects] eq $self) {
42 $self->BringToFront();
43 }
44 }
45
46 sub Draw {
47 my $self = shift;
48
49 SDLx::SFont::print_text( $Games::PangZero::App, $self->{rect}->x, $self->{rect}->y, "Time left: " . ($Games::PangZero::GamePause / 100) );
50
51 }
52
53 1;
0 ##########################################################################
1 package Games::PangZero::GameTimer;
2 ##########################################################################
3
4 use vars qw($FirstTick $LastTick $TotalAdvances $LastFpsTick $LastFps $Fps);
5
6 sub ResetTimer {
7 $FirstTick = SDL::get_ticks();
8 $LastTick = $LastFpsTick = $FirstTick;
9 $TotalAdvances = 0;
10 $Fps = $LastFps = 0;
11 }
12
13 sub GetAdvances {
14 my ($ticks, $advance);
15
16 $ticks = SDL::get_ticks();
17 $advance = int(($ticks - $FirstTick) / 10) - $TotalAdvances;
18 $TotalAdvances += $advance;
19
20 # Calculate frames per second;
21 ++$Fps if $advance > 0;
22 if ($ticks - $LastFpsTick > 1000) {
23 $LastFps = $Fps;
24 $LastFpsTick = $ticks;
25 $Fps = 0;
26 }
27
28 return $advance;
29 }
30
31 sub GetFramesPerSecond {
32 return $LastFps;
33 }
34
35 1;
0 ##########################################################################
1 # GLOBAL CONFIGURATION
2 ##########################################################################
3 package Games::PangZero::Globals;
4
5 use Games::PangZero::Config;
6 use SDL::Rect;
7
8 %Games::PangZero::Sounds = (
9 'pop' => 'pop.voc',
10 'shoot' => 'shoot.voc',
11 'death' => 'meow.voc',
12 'level' => 'level.voc',
13 'bonuslife' => 'magic.voc',
14 'pause' => 'pop3.voc',
15 'quake' => 'quake.voc',
16 );
17
18 @Games::PangZero::DifficultyLevels = (
19 { 'name' => 'Easy', 'spawnmultiplier' => 1.2, 'speed' => 0.8, 'harpoons' => 5, 'superball' => 0.8, 'bonusprobability' => 0.2, },
20 { 'name' => 'Normal', 'spawnmultiplier' => 1.0, 'speed' => 1.0, 'harpoons' => 3, 'superball' => 1.0, 'bonusprobability' => 0.1, },
21 { 'name' => 'Hard', 'spawnmultiplier' => 0.9, 'speed' => 1.2, 'harpoons' => 2, 'superball' => 1.1, 'bonusprobability' => 0.05, },
22 { 'name' => 'Nightmare','spawnmultiplier' => 0.8, 'speed' => 1.4, 'harpoons' => 2, 'superball' => 1.5, 'bonusprobability' => 0.02, },
23 { 'name' => 'Miki', 'spawnmultiplier' => 0.4, 'speed' => 1.0, 'harpoons' => 3, 'superball' => 1.0, 'bonusprobability' => 0.1, },
24 );
25 Games::PangZero::Config::SetDifficultyLevel(1);
26 @Games::PangZero::WeaponDurations = (
27 { 'name' => 'Short (Default)', 'durationmultiplier' => 1, },
28 { 'name' => 'Medium', 'durationmultiplier' => 3, },
29 { 'name' => 'Long', 'durationmultiplier' => 6, },
30 { 'name' => 'Very Long', 'durationmultiplier' => 12, },
31 { 'name' => 'Forever', 'durationmultiplier' => 10000, },
32 );
33 Games::PangZero::Config::SetWeaponDuration(0);
34
35 $Games::PangZero::NumGuys = 1;
36 @Games::PangZero::Players = (
37 { 'keys' => [SDLK_LEFT, SDLK_RIGHT, SDLK_UP], }, # blue
38 { 'keys' => [SDLK_a, SDLK_d, SDLK_s], }, # red
39 { 'keys' => [SDLK_j, SDLK_l, SDLK_k], }, # green
40 { 'keys' => [SDLK_KP6, SDLK_KP4, SDLK_KP5], }, # pink
41 { 'keys' => [SDLK_KP6, SDLK_KP4, SDLK_KP5], }, # yellow
42 { 'keys' => [SDLK_KP6, SDLK_KP4, SDLK_KP5], }, # cyan
43 { 'keys' => [SDLK_KP6, SDLK_KP4, SDLK_KP5], }, # gray
44 { 'keys' => [SDLK_KP6, SDLK_KP4, SDLK_KP5], }, # snot
45 { 'keys' => [SDLK_KP6, SDLK_KP4, SDLK_KP5], }, # purple
46 );
47 @Games::PangZero::GuyImageFiles = ( 'guyChristmas.png', 'guy_danigm.png', 'guy_pix.png', 'guy_pux.png', 'guy_r2.png', 'guy_sonic.png' );
48 @Games::PangZero::GuyColors = ( [170, 255, 'blue'], [ 0, 255, 'red'], [ 85, 255, 'green'], [212, 255, 'pink'],
49 [ 42, 255, 'yellow'], [128, 255, 'cyan'], [128, 0, 'gray'], [113, 128, 'snot'], [212, 64, 'purple'] );
50 for (my $i=0; $i<=$#Games::PangZero::Players; ++$i) {
51 $Games::PangZero::Players[$i]->{number} = $i;
52 $Games::PangZero::Players[$i]->{colorindex} = $i;
53 $Games::PangZero::Players[$i]->{imagefileindex} = $i % scalar(@Games::PangZero::GuyImageFiles);
54 }
55
56 my %n0 = ('popIndex' => 0, 'rect' => SDL::Rect->new(0, 0, 128, 106));
57 my %n1 = ('popIndex' => 1, 'rect' => SDL::Rect->new(0, 0, 96, 80));
58 my %n2 = ('popIndex' => 2, 'rect' => SDL::Rect->new(0, 0, 64, 53));
59 my %n3 = ('popIndex' => 3, 'rect' => SDL::Rect->new(0, 0, 32, 28));
60 my %n4 = ('popIndex' => 4, 'rect' => SDL::Rect->new(0, 0, 16, 15));
61
62 @Games::PangZero::BallDesc = (
63 # Normal balls (n0 .. n4)
64 { 'name' => 'n0', 'class' => 'Ball', 'score' => 2000, 'spawndelay' => 1, 'speedY' => 6.5, %n0, 'surface' => 'ball0', 'nextgen' => 'n1', },
65 { 'name' => 'n1', 'class' => 'Ball', 'score' => 1000, 'spawndelay' => 0.5, 'speedY' => 5.7, %n1, 'surface' => 'ball1', 'nextgen' => 'n2', },
66 { 'name' => 'n2', 'class' => 'Ball', 'score' => 800, 'spawndelay' => 0.25, 'speedY' => 5, %n2, 'surface' => 'ball2', 'nextgen' => 'n3', },
67 { 'name' => 'n3', 'class' => 'Ball', 'score' => 600, 'spawndelay' => 0.12, 'speedY' => 4, %n3, 'surface' => 'ball3', 'nextgen' => 'n4', },
68 { 'name' => 'n4', 'class' => 'Ball', 'score' => 500, 'spawndelay' => 0.05, 'speedY' => 3, %n4, 'surface' => 'ball4', },
69 # "Bouncy" balls (b0..b2)
70 { 'name' => 'b0', 'class' => 'Ball', 'score' => 1500, 'spawndelay' => 0.5, 'speedY' => 5.7, %n2, 'surface' => 'bouncy2', 'nextgen' => 'b1', },
71 { 'name' => 'b1', 'class' => 'Ball', 'score' => 750, 'spawndelay' => 0.2, 'speedY' => 5, %n3, 'surface' => 'bouncy3', 'nextgen' => 'b2', },
72 { 'name' => 'b2', 'class' => 'Ball', 'score' => 500, 'spawndelay' => 0.1, 'speedY' => 4.2, %n4, 'surface' => 'bouncy4' },
73 # Hexas (h0..h2)
74 { 'name' => 'h0', 'class' => 'Hexa', 'score' => 1500, 'spawndelay' => 0.5, 'popIndex' => 5, 'hexa' => 1,
75 'surface' => 'hexa0', 'rect' => SDL::Rect->new(0, 0, 64, 52), 'nextgen' => 'h1', },
76 { 'name' => 'h1', 'class' => 'Hexa', 'score' => 1000, 'spawndelay' => 0.2, 'popIndex' => 6, 'hexa' => 1,
77 'surface' => 'hexa1', 'rect' => SDL::Rect->new(0, 0, 32, 28), 'nextgen' => 'h2', },
78 { 'name' => 'h2', 'class' => 'Hexa', 'score' => 500, 'spawndelay' => 0.1, 'popIndex' => 7, 'hexa' => 1,
79 'surface' => 'hexa2', 'rect' => SDL::Rect->new(0, 0, 16, 14),
80 'magicrect' => SDL::Rect->new(48, 0, 16, 14), },
81 # Water ball
82 { 'name' => 'w1', 'class' => 'WaterBall', 'score' => 1500, 'spawndelay' => 0.4, 'speedY' => 5.7, %n1, 'surface' => 'blue1', 'nextgen' => 'w2', },
83 { 'name' => 'w2', 'class' => 'WaterBall', 'score' => 1000, 'spawndelay' => 0.2, 'speedY' => 5, %n2, 'surface' => 'blue2', 'nextgen' => 'w3', },
84 { 'name' => 'w3', 'class' => 'WaterBall', 'score' => 800, 'spawndelay' => 0.1, 'speedY' => 4, %n3, 'surface' => 'blue3', 'nextgen' => 'w4', },
85 { 'name' => 'w4', 'class' => 'WaterBall', 'score' => 600, 'spawndelay' => 0.05, 'speedY' => 3, %n4, 'surface' => 'blue4', },
86 # Fragile
87 { 'name' => 'f0', 'class' => 'FragileBall', 'score' => 1500, 'spawndelay' => 0.8, 'speedY' => 6.5, %n0, 'surface' => 'frag0', 'nextgen' => 'f1', },
88 { 'name' => 'f1', 'class' => 'FragileBall', 'score' => 1500, 'spawndelay' => 0.4, 'speedY' => 5.7, %n1, 'surface' => 'frag1', 'nextgen' => 'f2', },
89 { 'name' => 'f2', 'class' => 'FragileBall', 'score' => 1000, 'spawndelay' => 0.2, 'speedY' => 5, %n2, 'surface' => 'frag2', 'nextgen' => 'f3', },
90 { 'name' => 'f3', 'class' => 'FragileBall', 'score' => 800, 'spawndelay' => 0.1, 'speedY' => 4, %n3, 'surface' => 'frag3', 'nextgen' => 'f4', },
91 { 'name' => 'f4', 'class' => 'FragileBall', 'score' => 600, 'spawndelay' => 0.05, 'speedY' => 3, %n4, 'surface' => 'frag4', },
92 # Superball
93 { 'name' => 'super0', 'class' => 'SuperBall', 'score' => 1000, 'spawndelay' => 0.5, 'speedY' => 5.7, %n1, 'surface' => 'green1', },
94 { 'name' => 'super1', 'class' => 'SuperBall', 'score' => 800, 'spawndelay' => 0.25, 'speedY' => 5, %n2, 'surface' => 'green2', },
95 { 'name' => 'xmas', 'class' => 'XmasBall', 'score' => 1000, 'spawndelay' => 0.5, 'speedY' => 6.5, %n0, 'surface' => 'xmas', },
96 # Death
97 { 'name' => 'death', 'class' => 'DeathBall', 'score' => 0, 'spawndelay' => 0.5, 'speedY' => 5, %n2, 'surface' => 'death2', 'nextgen' => 'death', },
98 # Seeker
99 { 'name' => 'seeker', 'class' => 'SeekerBall', 'score' => 1200, 'spawndelay' => 0.2, 'speedY' => 5.7, %n2, 'surface' => 'white2', 'nextgen' => 'seeker1', },
100 { 'name' => 'seeker1', 'class' => 'SeekerBall', 'score' => 1200, 'spawndelay' => 0.1, 'speedY' => 5, %n3, 'surface' => 'white3', },
101 # Quake
102 { 'name' => 'quake', 'class' => 'EarthquakeBall', 'score' => 1600, 'spawndelay' => 0.7, 'speedY' => 5.7, %n2, 'surface' => 'quake2',
103 'quake' => 5, 'nextgen' => 'quake1', },
104 { 'name' => 'quake1', 'class' => 'EarthquakeBall', 'score' => 1200, 'spawndelay' => 0.2, 'speedY' => 5, %n3, 'surface' => 'quake3',
105 'quake' => 3, 'nextgen' => 'quake2', },
106 { 'name' => 'quake2', 'class' => 'EarthquakeBall', 'score' => 1000, 'spawndelay' => 0.1, 'speedY' => 4.2, %n4, 'surface' => 'quake4',
107 'quake' => 2, },
108 # Upside down ball
109 { 'name' => 'u0', 'class' => 'UpsideDownBall', 'score' => 2000, 'spawndelay' => 1, 'speedY' => 5.8, %n0, 'surface' => 'upside0', 'nextgen' => 'u1', },
110 { 'name' => 'u1', 'class' => 'UpsideDownBall', 'score' => 1000, 'spawndelay' => 0.5, 'speedY' => 5.8, %n1, 'surface' => 'upside1', 'nextgen' => 'u2', },
111 { 'name' => 'u2', 'class' => 'UpsideDownBall', 'score' => 800, 'spawndelay' => 0.25, 'speedY' =>5.8, %n2, 'surface' => 'upside2', 'nextgen' => 'u3', },
112 { 'name' => 'u3', 'class' => 'UpsideDownBall', 'score' => 600, 'spawndelay' => 0.12, 'speedY' =>5.9, %n3, 'surface' => 'upside3', 'nextgen' => 'u4', },
113 { 'name' => 'u4', 'class' => 'UpsideDownBall', 'score' => 500, 'spawndelay' => 0.05, 'speedY' =>5.9, %n4, 'surface' => 'upside4', },
114
115 { 'name' => 'credits1', 'class' => 'Ball', 'speedY' => 6.1, 'nextgen' => 'credits1', 'surface' => 'blue3', %n3 },
116 { 'name' => 'credits2', 'class' => 'Ball', 'speedY' => 6.1, 'nextgen' => 'credits2', 'surface' => 'ball3', %n3 },
117 );
118 {
119 foreach my $ballDesc (@Games::PangZero::BallDesc) {
120 $ballDesc->{width} = $ballDesc->{rect}->w();
121 $ballDesc->{height} = $ballDesc->{rect}->h();
122 $Games::PangZero::BallDesc{$ballDesc->{name}} = $ballDesc;
123 }
124 foreach my $ballDesc (@Games::PangZero::BallDesc) {
125 my $nextgen = $ballDesc->{nextgen};
126 $ballDesc->{nextgen} = $Games::PangZero::BallDesc{$nextgen} if $nextgen;
127 }
128 }
129
130 @Games::PangZero::ChallengeLevels = (
131 'n4 n4 n4 n4 xmas',
132 'n3 n3 n3',
133 'n2 n2',
134 'b0 b0',
135 'h2 h2 h2 h2 h2 h2',
136 'h0 h0',
137 'n1 f2',
138 'w1 n2',
139 'n0 b0 w1 h0',
140 # 10
141 'n1 quake',
142 'n1 b0 quake',
143 'w1 seeker u2',
144 'n0 seeker seeker',
145 'w1 w1',
146 'f1 quake h0',
147 'w1 seeker h0 h0',
148 'n0 w1 w1 b0 h0',
149 'u0 u0 quake',
150 'quake quake w1 b0 h0',
151 # 20
152 'death n1 b0',
153 'n4 ' x 24,
154 'w1 w1 w1 f0',
155 'death w1 h0',
156 'n0 n0 u0 seeker h2 h2 b0',
157 'n4 b2 h2 u4 ' x 6,
158 'quake quake quake b0',
159 'h0 h0 h0 h0 h0 h0 h0 h0',
160 'quake seeker f3 n1 b0 b0',
161 'death death w1 f0 n0 u2 h0',
162 # 30
163 'n0 n0 u0 u0',
164 'death quake n1',
165 'b0 h0 n2 ' x 3,
166 'w1 w1 w1 w1 f1 f1',
167 'n3 n3 n3 u3 ' x 4,
168 'quake quake seeker seeker n0 f0',
169 'seeker ' x 8,
170 'n0 n1 n2 n3 n4 b0 f2 h0 h1 h2 w1 seeker',
171 'quake quake quake h0 h0 h0 u2',
172 'death quake seeker w1 n0 b0 h0',
173 # 40
174 'n0 n1 n2 ' x 3,
175 'death quake seeker u2 ' x 3,
176 'f0 f0',
177 'death quake f0 n1 ' x 2,
178 'h0 ' x 8 . ' f0 f1 ',
179 'death ' x 10,
180 'quake b0 ' x 5,
181 'w1 w1 f0 f1 death',
182 'seeker ' x 13,
183 'n0 u0 w1 f0 quake death ' x 2,
184 );
185
186 for ( my $i = 0; $i < 10; ++$i) {
187 $Games::PangZero::ChallengeLevels[$i + 49] = $Games::PangZero::ChallengeLevels[$i + 9] . ' ' . $Games::PangZero::ChallengeLevels[$i + 29];
188 $Games::PangZero::ChallengeLevels[$i + 59] = $Games::PangZero::ChallengeLevels[$i + 19] . ' ' . $Games::PangZero::ChallengeLevels[$i + 39];
189 }
190 foreach (@Games::PangZero::ChallengeLevels) {
191 while (/(\w+)/g) {
192 die "Unknown ball '$1' in challenge '$_'" unless defined $Games::PangZero::BallDesc{$1};
193 }
194 }
195
196 my %BallMixes = (
197 'easy' => [ qw(n0 2 n1 20 n2 10 n3 3 n4 2 f0 3 f1 5 f2 5 b0 5 b1 2 b2 1 w1 10 h0 5 h1 3 h2 1 quake 1 seeker 2 u1 1 u2 2 u3 4 u4 1) ],
198 'medium' => [ qw(n0 10 n1 20 n2 10 n3 3 n4 2 f0 3 f1 3 b0 10 b1 2 b2 1 w1 15 h0 15 h1 5 h2 1 death 2 quake 5 seeker 10 u0 2 u1 5 u2 5 u3 5) ],
199 'bouncy' => [ qw(n0 20 n1 10 n2 5 n3 1 n4 1 f0 3 f1 3 b0 30 b1 9 b2 1 w1 10 h0 15 h1 5 death 5 quake 10 seeker 15 u0 5 u1 5 u2 1 u3 1) ],
200 'hard' => [ qw(n0 20 n1 10 n2 5 n3 1 f0 5 f1 1 b0 20 b1 2 w1 20 h0 20 h1 5 death 10 quake 15 seeker 20 u0 5 u1 5 u2 1 u3 1) ],
201 'watery' => [ qw(n0 20 n1 10 n2 5 n3 1 n4 1 f0 3 f1 1 b0 10 b1 5 w1 50 h0 15 h1 5 death 5 quake 10 seeker 15 u0 1 u1 5 u2 5 u3 1) ],
202 'hexas' => [ qw(n0 20 n1 10 n2 5 n3 1 f0 3 f1 1 b0 15 b1 2 w1 20 h0 40 h1 15 death 5 quake 10 seeker 15 u0 1 u1 8 u2 2 u3 1) ],
203 'quakes' => [ qw(n0 15 n1 10 n2 5 n3 1 f0 3 f1 1 b0 15 w1 15 h0 20 h1 5 death 5 quake 40 seeker 15 u0 8 u1 1 u2 2 u3 1) ],
204 );
205
206 sub AddLevels {
207 my ($num, $balls, $gamespeedStart, $gamespeedEnd, $spawndelayStart, $spawndelayEnd) = @_;
208 my ($i, $level);
209
210 for ($i = 0; $i < $num; ++$i) {
211 $level = {
212 'balls' => $balls,
213 'gamespeed' => $gamespeedStart + ($gamespeedEnd - $gamespeedStart) * ($i) / ($num),
214 'spawndelay' => $spawndelayStart + ($spawndelayEnd - $spawndelayStart) * ($i) / ($num),
215 };
216 push @Games::PangZero::PanicLevels, ( $level );
217 }
218 }
219
220 AddLevels( 9, $BallMixes{easy}, 0.75, 1.25, 20, 20 ); # 0-9
221 AddLevels( 10, $BallMixes{medium}, 0.7 , 1.3 , 20, 15 ); # 1x
222 AddLevels( 10, $BallMixes{hard}, 0.7 , 1.5 , 15, 15 ); # 2x
223 AddLevels( 10, $BallMixes{hexas}, 1.0 , 1.5 , 15, 12 ); # 3x
224 AddLevels( 10, $BallMixes{watery}, 0.7 , 1.7 , 15, 17 ); # 4x
225 AddLevels( 10, $BallMixes{bouncy}, 1.0 , 2.0 , 12, 12 ); # 5x
226 AddLevels( 10, $BallMixes{quakes}, 1.5 , 2.2 , 13, 8 ); # 6x
227 AddLevels( 10, $BallMixes{hard}, 1.0 , 2.2 , 13, 10 ); # 7x
228 AddLevels( 10, $BallMixes{hexas}, 1.3 , 2.4 , 12, 9 ); # 8x
229 AddLevels( 10, $BallMixes{hard}, 2.0 , 3.0 , 13, 10 ); # 9x
230
231 # Set defaults
232
233 $Games::PangZero::ScreenMargin = 16;
234 $Games::PangZero::ScreenWidth = 800 - $Games::PangZero::ScreenMargin * 2;
235 $Games::PangZero::ScreenHeight = 416;
236 $Games::PangZero::SoundEnabled = 1;
237 $Games::PangZero::MusicEnabled = 1;
238 $Games::PangZero::DeathBallsEnabled = 1;
239 $Games::PangZero::EarthquakeBallsEnabled = 1;
240 $Games::PangZero::WaterBallsEnabled = 1;
241 $Games::PangZero::SeekerBallsEnabled = 1;
242 $Games::PangZero::FullScreen = 1;
243 $Games::PangZero::UnicodeMode = 0;
244 $Games::PangZero::Slippery = 0;
245 $Games::PangZero::ShowWebsite = 0;
246
247 1;
0 package Games::PangZero::Graphics;
1
2 use strict;
3 use warnings;
4
5 use SDL;
6 use SDL::Surface;
7 use SDL::Palette;
8 use SDL::PixelFormat;
9 use SDL::Video;
10 use SDL::Event;
11 use SDL::Events;
12 use SDL::Color;
13 use SDL::Config;
14 use SDL::Cursor;
15 use SDL::GFX::Rotozoom;
16 use SDL::Mixer;
17 use SDL::Mixer::Samples;
18 use SDL::Mixer::Channels;
19 use SDL::Mixer::Music;
20 use SDL::Mixer::MixChunk;
21 use SDL::Mixer::MixMusic;
22 use SDL::Joystick;
23 use SDL::Mouse;
24 use SDL::Image;
25 use SDLx::SFont;
26
27 sub LoadSurfaces {
28 my ($i, $transparentColor);
29
30 my %balls = qw (
31 ball0 Balls-Red128.png ball1 Balls-Red96.png ball2 Balls-Red64.png ball3 Balls-Red32.png ball4 Balls-Red16.png
32 xmas Balls-XMAS128.png
33 ball4 Balls-Red16.png ball3 Balls-Red32.png
34 bouncy2 Balls-Bouncy64.png bouncy3 Balls-Bouncy32.png bouncy4 Balls-Bouncy16.png
35 hexa0 Hexa-64.png hexa1 Hexa-32.png hexa2 Hexa-16.png
36 blue1 Balls-Water96.png blue2 Balls-Water64.png blue3 Balls-Water32.png blue4 Balls-Water16.png
37 frag0 Balls-Fragile128.png frag1 Balls-Fragile96.png frag2 Balls-Fragile64.png frag3 Balls-Fragile32.png frag4 Balls-Fragile16.png
38 green1 Balls-SuperClock96.png green2 Balls-SuperClock64.png gold1 Balls-SuperStar96.png gold2 Balls-SuperStar64.png
39 death2 Balls-Death64.png
40 white2 Balls-Seeker64.png white3 Balls-Seeker32.png
41 quake2 Balls-EarthQ64.png quake3 Balls-EarthQ32.png quake4 Balls-EarthQ16.png
42 upside0 Balls-Upside128.png upside1 Balls-Upside96.png upside2 Balls-Upside64.png upside3 Balls-Upside32.png upside4 Balls-Upside16.png
43 );
44
45 foreach (sort keys %balls) {
46 $Games::PangZero::BallSurfaces{$_} = SDL::Image::load("$Games::PangZero::DataDir/$balls{$_}");
47 $Games::PangZero::BallSurfaces{$_} = SDL::Video::display_format($Games::PangZero::BallSurfaces{$_});
48 $transparentColor = $Games::PangZero::BallSurfaces{$_}->get_pixel(0);
49 SDL::Video::set_color_key($Games::PangZero::BallSurfaces{$_}, SDL_SRCCOLORKEY, $transparentColor );
50 $Games::PangZero::BallSurfaces{"dark$_"} = SDL::Image::load( "$Games::PangZero::DataDir/$balls{$_}");
51 $Games::PangZero::BallSurfaces{"dark$_"} = SDL::Video::display_format($Games::PangZero::BallSurfaces{"dark$_"});
52 SDL::Video::set_color_key($Games::PangZero::BallSurfaces{"dark$_"}, SDL_SRCCOLORKEY, $Games::PangZero::BallSurfaces{"dark$_"}->get_pixel(0) );
53 SDL::Video::set_alpha($Games::PangZero::BallSurfaces{"dark$_"}, SDL_SRCALPHA, 128);
54 }
55
56 $Games::PangZero::BorderSurface = SDL::Image::load("$Games::PangZero::DataDir/border.png");
57 $Games::PangZero::RedBorderSurface = SDL::Image::load("$Games::PangZero::DataDir/border.png");
58 $Games::PangZero::WhiteBorderSurface = SDL::Image::load("$Games::PangZero::DataDir/border.png");
59 $Games::PangZero::BonusSurface = SDL::Image::load("$Games::PangZero::DataDir/bonus.png");
60 $Games::PangZero::LevelIndicatorSurface = SDL::Image::load("$Games::PangZero::DataDir/level.png");
61 $Games::PangZero::LevelIndicatorSurface2 = SDL::Image::load("$Games::PangZero::DataDir/level_empty.png");
62
63 AlterPalette( $Games::PangZero::RedBorderSurface, sub { 1; },
64 sub { shift @_; my ($h, $s, $i) = Games::PangZero::Palette::RgbToHsi(@_);
65 return Games::PangZero::Palette::HsiToRgb( $h - 30, $s, $i * 0.75 + 63); } );
66 AlterPalette( $Games::PangZero::WhiteBorderSurface, sub { 1; },
67 sub { shift @_; my ($h, $s, $i) = Games::PangZero::Palette::RgbToHsi(@_);
68 return Games::PangZero::Palette::HsiToRgb( 0, 0, $i*0.25 + 191 ); } );
69
70 MakeGuySurfaces();
71 }
72
73 sub MakeGuySurface {
74 my ($player) = @_;
75 my ($guySurfaceFile, $guySurface, $whiteGuySurface, $harpoonSurface);
76
77 $guySurfaceFile = $Games::PangZero::DataDir . '/' . $Games::PangZero::GuyImageFiles[ $player->{imagefileindex} % scalar(@Games::PangZero::GuyImageFiles) ];
78 $guySurface = SDL::Image::load($guySurfaceFile);
79 $whiteGuySurface = SDL::Image::load($guySurfaceFile);
80 $harpoonSurface = SDL::Image::load("$Games::PangZero::DataDir/harpoon.png");
81 $player->{hue} = $Games::PangZero::GuyColors[$player->{colorindex}]->[0];
82 $player->{saturation} = $Games::PangZero::GuyColors[$player->{colorindex}]->[1];
83
84 AlterPalette($whiteGuySurface, sub {1;}, sub { return (255, 255, 255); } );
85 AlterPalette( $guySurface, sub { $_[3] > $_[2] and $_[3] > $_[1]; },
86 sub {
87 shift @_;
88 my ($h, $s, $i) = Games::PangZero::Palette::RgbToHsi(@_);
89 return Games::PangZero::Palette::HsiToRgb($player->{hue}, $player->{saturation}, $i); }
90 );
91 AlterPalette( $harpoonSurface, sub { 1; },
92 sub {
93 shift @_;
94 my ($h, $s, $i) = Games::PangZero::Palette::RgbToHsi(@_);
95 return Games::PangZero::Palette::HsiToRgb($player->{hue}, $player->{saturation} * $s / 256, $i); }
96 );
97 $player->{guySurface} = $guySurface;
98 $player->{whiteGuySurface} = $whiteGuySurface;
99 $player->{harpoonSurface} = $harpoonSurface;
100 }
101
102 sub MakeGuySurfaces {
103 foreach my $player (@Games::PangZero::Players) {
104 MakeGuySurface($player);
105 }
106
107 $Games::PangZero::WhiteHarpoonSurface = SDL::Image::load("$Games::PangZero::DataDir/harpoon.png");
108 AlterPalette($Games::PangZero::WhiteHarpoonSurface, sub {1;}, sub { return (255, 255, 255); } );
109 }
110
111 sub AlterPalette {
112 my ($surface, $filterSub, $alterSub) = @_;
113 my ($r, $g, $b);
114 my ($palette, $numColors, $n, $color);
115
116 $palette = $surface->format->palette();
117 $numColors = ($surface->format->BytesPerPixel == 1) ? $palette->ncolors() : -1;
118 for ($n = 0; $n < $numColors; $n++) {
119 $color = $palette->color_index($n);
120 ($r, $g, $b) = ( $color->r, $color->g, $color->b );
121
122 next unless $filterSub->($n, $r, $g, $b);
123 ($r, $g, $b) = $alterSub->($n, $r, $g, $b);
124 $r = $g = $b = 4 if ($r == 0 and $g == 0 and $b == 0);
125
126 $color->r($r);
127 $color->g($g);
128 $color->b($b);
129 SDL::Video::set_colors($surface, $n, $color);
130 }
131 $surface = SDL::Video::display_format($surface);
132 }
133
134 sub RenderBorder {
135 my ($borderSurface, $targetSurface) = @_;
136 my ($dstrect, $srcrect1, $srcrect2, $xpos, $ypos, $width, $height);
137
138 $width = $Games::PangZero::ScreenWidth + 2 * $Games::PangZero::ScreenMargin;
139 $height = $Games::PangZero::ScreenHeight + 2 * $Games::PangZero::ScreenMargin;
140
141 # Draw the corners
142 $dstrect = SDL::Rect->new(0, 0, 16, 16);
143 $srcrect1 = SDL::Rect->new(0, 0, 16, 16);
144 SDL::Video::blit_surface($borderSurface, $srcrect1, $targetSurface, $dstrect);
145 $dstrect->x($width - 16); $srcrect1->x(144);
146 SDL::Video::blit_surface($borderSurface, $srcrect1, $targetSurface, $dstrect);
147 $dstrect->y($height - 16); $srcrect1->y(144);
148 SDL::Video::blit_surface($borderSurface, $srcrect1, $targetSurface, $dstrect);
149 $dstrect->x(0); $srcrect1->x(0);
150 SDL::Video::blit_surface($borderSurface, $srcrect1, $targetSurface, $dstrect);
151
152 if(SDL::Config->has('SDL_gfx_rotozoom')) {
153 # Top border
154 my $zoom = SDL::Surface->new(SDL_SWSURFACE(), 128, 16, 32);
155 $srcrect1->x(16); $srcrect1->y(0); $srcrect1->w(128); $srcrect1->h(16);
156 SDL::Video::blit_surface($borderSurface, $srcrect1, $zoom, SDL::Rect->new(0, 0, $srcrect1->w, $srcrect1->h) );
157 $zoom = SDL::GFX::Rotozoom::zoom_surface($zoom, $Games::PangZero::ScreenWidth / 128, 1, SDL::GFX::Rotozoom::SMOOTHING_OFF());
158 $dstrect->x(16); $dstrect->y(0);
159 SDL::Video::blit_surface($zoom, SDL::Rect->new(0, 0, $zoom->w, $zoom->h), $targetSurface, $dstrect );
160
161 # Left border
162 $zoom = SDL::Surface->new(SDL_SWSURFACE(), 16, 128, 32);
163 $srcrect1->x(0); $srcrect1->y(16); $srcrect1->h(128); $srcrect1->w(16);
164 SDL::Video::blit_surface($borderSurface, $srcrect1, $zoom, SDL::Rect->new(0, 0, $srcrect1->w, $srcrect1->h) );
165 $zoom = SDL::GFX::Rotozoom::zoom_surface($zoom, 1, $Games::PangZero::ScreenHeight / 128, SDL::GFX::Rotozoom::SMOOTHING_OFF());
166 $dstrect->x(0); $dstrect->y(16);
167 SDL::Video::blit_surface($zoom, SDL::Rect->new(0, 0, $zoom->w, $zoom->h), $targetSurface, $dstrect );
168 }
169
170 # Draw top and bottom border
171
172 $srcrect1->w(128); $srcrect1->x(16); $srcrect1->y(0);
173 $srcrect2 = SDL::Rect->new( 16, 144, 128, 16 );
174 for ($xpos = 16; $xpos < $width-16; ) {
175 $dstrect->x($xpos);
176 $dstrect->y(0);
177 SDL::Video::blit_surface($borderSurface, $srcrect1, $targetSurface, $dstrect);
178 $dstrect->y($height - 16);
179 SDL::Video::blit_surface($borderSurface, $srcrect2, $targetSurface, $dstrect);
180 $xpos += $srcrect1->w();
181 $srcrect1->w(16); $srcrect1->x(128);
182 $srcrect2->w(16); $srcrect2->x(128);
183 }
184
185 # Draw left and right border
186
187 $srcrect1->h(128); $srcrect1->y(16); $srcrect1->x(0);
188 $srcrect2->h(128); $srcrect2->y(16); $srcrect2->x(144);
189 for ($ypos = 16; $ypos < $height-16; ) {
190 $dstrect->x(0);
191 $dstrect->y($ypos);
192 SDL::Video::blit_surface($borderSurface, $srcrect1, $targetSurface, $dstrect);
193 $dstrect->x($width - 16);
194 SDL::Video::blit_surface($borderSurface, $srcrect2, $targetSurface, $dstrect);
195 $ypos += $srcrect1->h();
196 $srcrect1->h(16); $srcrect1->y(128);
197 $srcrect2->h(16); $srcrect2->y(128);
198 }
199
200 if(SDL::Config->has('SDL_gfx_rotozoom')) {
201 # Top border
202 my $zoom = SDL::Surface->new(SDL::Video::SDL_SWSURFACE(), 128, 16, 32);
203 $srcrect1->x(16); $srcrect1->y(0); $srcrect1->w(128); $srcrect1->h(16);
204 SDL::Video::blit_surface($borderSurface, $srcrect1, $zoom, SDL::Rect->new(0, 0, $srcrect1->w, $srcrect1->h) );
205 $zoom = SDL::GFX::Rotozoom::zoom_surface($zoom, $Games::PangZero::ScreenWidth / 128, 1, SDL::GFX::Rotozoom::SMOOTHING_OFF());
206 $dstrect->x(16); $dstrect->y(0);
207 SDL::Video::blit_surface($zoom, SDL::Rect->new(0, 0, $zoom->w, $zoom->h), $targetSurface, $dstrect );
208
209 # Left border
210 $zoom = SDL::Surface->new( SDL_SWSURFACE(), 16, 128, 32);
211 $srcrect1->x(0); $srcrect1->y(16); $srcrect1->h(128); $srcrect1->w(16);
212 SDL::Video::blit_surface($borderSurface, $srcrect1, $zoom, SDL::Rect->new(0, 0, $srcrect1->w, $srcrect1->h) );
213 $zoom = SDL::GFX::Rotozoom::zoom_surface($zoom, 1, $Games::PangZero::ScreenHeight / 128, SDL::GFX::Rotozoom::SMOOTHING_OFF());
214 $dstrect->x(0); $dstrect->y(16);
215 SDL::Video::blit_surface($zoom, SDL::Rect->new(0, 0, $zoom->w, $zoom->h), $targetSurface, $dstrect );
216 }
217 }
218
219 sub LoadBackground {
220 my $filename = shift;
221
222 SDL::Video::fill_rect($Games::PangZero::Background, SDL::Rect->new(0, 0, $Games::PangZero::PhysicalScreenWidth, $Games::PangZero::PhysicalScreenHeight), SDL::Video::map_RGB($Games::PangZero::Background->format(), 0, 0, 0));
223 my $backgroundImage = SDL::Image::load("$Games::PangZero::DataDir/$filename");
224 my $dstrect = SDL::Rect->new($Games::PangZero::ScreenMargin, $Games::PangZero::ScreenMargin, 0, 0);
225 my $srcrect = SDL::Rect->new(0, 0, $Games::PangZero::ScreenWidth, $Games::PangZero::ScreenHeight);
226 if ($Games::PangZero::ScreenWidth != $backgroundImage->w() or $Games::PangZero::ScreenHeight != $backgroundImage->h()) {
227 if (SDL::Config->has('SDL_gfx_rotozoom')) {
228 my $zoomX = $Games::PangZero::ScreenWidth / $backgroundImage->w(); # $zoomX = 1.0 if $zoomX < 1.0;
229 my $zoomY = $Games::PangZero::ScreenHeight / $backgroundImage->h(); # $zoomY = 1.0 if $zoomY < 1.0;
230 $backgroundImage = SDL::GFX::Rotozoom::zoom_surface($backgroundImage, $zoomX, $zoomY, SDL::GFX::Rotozoom::SMOOTHING_OFF());
231 }
232 }
233 SDL::Video::blit_surface($backgroundImage, $srcrect, $Games::PangZero::Background, $dstrect);
234
235 RenderBorder($Games::PangZero::BorderSurface, $Games::PangZero::Background);
236 }
237
238 sub TextWidth {
239 SDLx::SFont::SDL_TEXTWIDTH(@_); # perl-sdl-2.x
240 }
241
242 sub FindVideoMode {
243 if ($Games::PangZero::FullScreen < 2) {
244 return (800, 600);
245 }
246
247 # Find a suitable widescreen mode
248 # One native resolution: 1680 x 1050 => 1.6 : 1
249 # Which could translate to: 840 x 525 => 1.6 : 1
250 # Some adapters have: 848 x 480 => 1.76 : 1
251 # 720 x 480 => 1.5 : 1
252 # 800 x 512 => 1.56 : 1
253 # Conclusion: Any resolution where w in [800,900], h > 480 and r in [1.5, 1.8] is good
254
255 my ($modes, $mode, @goodModes, $w, $h, $ratio);
256 $modes = SDL::Video::list_modes( 0, SDL_HWSURFACE ); #add back fullscreen
257 foreach $mode (@{$modes}) {
258 $w = $mode->w;
259 $h = $mode->h;
260 $ratio = $w / $h;
261 warn sprintf( "%4d x %4d => %0.3f\n", $w, $h, $ratio );
262 next if $w < 800 or $w > 900;
263 next if $h < 480;
264 next if $ratio < 1.5 or $ratio > 1.8;
265 push @goodModes, ( { -w => $w, -h => $h, -score => abs($ratio - 1.6) * 1000 + abs($w - 800) } );
266 }
267 @goodModes = sort { $a->{-score} <=> $b->{-score} } @goodModes;
268 return (800, 600) unless @goodModes;
269 foreach $mode (@goodModes) {
270 print sprintf( '%d x %d => %0.3f (score %d)', $mode->{-w}, $mode->{-h}, $mode->{-w} / $mode->{-h}, $mode->{-score} ), "\n";
271 }
272 return ($goodModes[0]->{-w}, $goodModes[0]->{-h});
273 }
274
275 1;
0 ##########################################################################
1 package Games::PangZero::Guy;
2 ##########################################################################
3
4 @ISA = qw(Games::PangZero::GameObject);
5 use vars qw(%Guys $GuyId);
6
7 sub new {
8 my ($class, $player) = @_;
9 my $self = Games::PangZero::GameObject->new();
10 my $number = $player->{number};
11 %{$self} = ( %{$self},
12 'player' => $player,
13 'number' => $number,
14 'x' => $player->{startX},
15 'y' => $Games::PangZero::ScreenHeight - 64,
16 'w' => 64,
17 'h' => 64,
18 'collisionw' => '28',
19 'collisionh' => '48',
20 'delay' => 0,
21 'speedY' => 0,
22 'speedX' => 0,
23 'dir' => $number % 2,
24 'state' => 'idle',
25 'killed' => 0,
26 'harpoons' => 0,
27 'invincible' => 0,
28 'surface' => $player->{guySurface},
29 'whiteSurface' => $player->{whiteGuySurface},
30 'weapon' => 'Harpoon',
31 'bonusDelay' => 0,
32 'id' => ++$GuyId,
33 );
34 bless $self, $class;
35 $self->SetupCollisions();
36 $self->CalculateAnimPhases();
37 $Guys{$self->{id}} = $self;
38 return $self;
39 }
40
41 sub Delete {
42 my $self = shift;
43
44 $self->SUPER::Delete;
45 delete $Guys{$self->{id}};
46 }
47
48 sub CalculateAnimPhases {
49 my $self = shift;
50
51 $self->{animPhases} = $self->{player}->{guySurface}->w / 128,
52 }
53
54 sub DemoMode {
55 my ($self) = shift;
56 $self->{state} = 'demo';
57 $self->{dir} = 1;
58 }
59
60 sub Fire {
61 my ($self) = @_;
62
63 if ($self->{harpoons} < $Games::PangZero::DifficultyLevel->{harpoons}) {
64 ++$self->{harpoons};
65 eval("unshift \@Games::PangZero::GameObjects, (Games::PangZero::$self->{weapon}::Create(\$self));");
66 $self->{state} = 'shoot';
67 $self->{delay} = 7;
68 Games::PangZero::Music::PlaySound('shoot');
69 return 1;
70 }
71 return 0;
72 }
73
74 sub AdvanceWhileFlying {
75 my $self = shift;
76
77 $self->{speedY} += $Games::PangZero::Ball::Gravity * 2;
78 $self->{y} += $self->{speedY};
79 $self->{x} += $self->{dir} > 0 ? 1 : -1;
80 if ($self->{x} < -16) {
81 $self->{x} = 0;
82 $self->{dir} = 1;
83 }
84 if ($self->{x} > $Games::PangZero::ScreenWidth - $self->{w} + 16) {
85 $self->{x} = $Games::PangZero::ScreenWidth - $self->{w}; $self->{dir} = 0;
86 }
87 if ($self->{y} >= $Games::PangZero::ScreenHeight - $self->{h}) {
88 $self->{state} = 'idle';
89 $self->{y} = $Games::PangZero::ScreenHeight - $self->{h};
90 $self->{speedX} = $self->{dir} ? 1 : -1;
91 }
92 }
93
94 sub Advance {
95 my ($self) = @_;
96 my ($slippery, $keys);
97
98 $slippery = $Games::PangZero::Slippery ? 0.0625 : 0;
99
100 return if $self->{killed};
101 return if $self->{state} eq 'demo';
102 --$self->{invincible};
103
104 if ($self->{bonusDelay} > 0) {
105 --$self->{bonusDelay};
106 $self->{weapon} = 'Harpoon' if $self->{bonusDelay} <= 0;
107 }
108
109 if ($self->{state} eq 'fly') {
110 $self->AdvanceWhileFlying();
111 return;
112 }
113
114 if ($self->{delay} > 0) {
115 --$self->{delay};
116 $keys = [ 0, 0, 0 ];
117 } else {
118 $keys = $self->{player}->{keys};
119 }
120
121 $self->{speedX} = 0 unless $slippery;
122 $self->{state} = 'idle';
123
124 if ( $Games::PangZero::Events{$keys->[2]} ) {
125 return if $self->Fire();
126 }
127 if ( $Games::PangZero::Keys{$keys->[0]} ) {
128 if ($slippery) {
129 $self->{speedX} -= $slippery * 2 if $self->{speedX} > -3;
130 } else {
131 $self->{speedX} = -3;
132 }
133 $self->{dir} = 0;
134 $self->{state} = 'walk';
135 } elsif ( $Games::PangZero::Keys{$keys->[1]} ) {
136 if ($slippery) {
137 $self->{speedX} += $slippery * 2 if $self->{speedX} < 3;
138 } else {
139 $self->{speedX} = 3;
140 }
141 $self->{dir} = 1;
142 $self->{state} = 'walk';
143 } else {
144 if ($slippery) {
145 $self->{speedX} += $slippery if $self->{speedX} < 0;
146 $self->{speedX} -= $slippery if $self->{speedX} > 0;
147 }
148 }
149 $self->{x} += $self->{speedX};
150
151 if ($self->{x} < -16) {
152 $self->{x} = -16; $self->{speedX} = 0;
153 }
154 if ($self->{x} > $Games::PangZero::ScreenWidth - $self->{w} + 16) {
155 $self->{x} = $Games::PangZero::ScreenWidth - $self->{w} + 16; $self->{speedX} = 0;
156 }
157 }
158
159 sub Draw {
160 my ($self) = @_;
161 my ($surface, $srcrect, $srcx, $srcy, $srcw, $srch);
162
163 return if ($self->{killed});
164 $surface = $self->{surface};
165 $surface = $self->{whiteSurface} if $self->{invincible} > 0 and (int($self->{invincible} / 2) % 3 == 0);
166
167 $srcw = $srch = 64;
168 if ($self->{state} eq 'idle') {
169 $srcx = $self->{dir} * 128;
170 $srcy = 64;
171 } elsif ($self->{state} eq 'walk') {
172 $srcx = $self->{dir} * $self->{animPhases} * 64 + (int($self->{x} / 50) % $self->{animPhases}) * 64;
173 $srcy = 0;
174 } elsif ($self->{state} eq 'demo') {
175 $srcx = $self->{dir} * $self->{animPhases} * 64 + (int($Games::PangZero::Game->{anim} / 16) % $self->{animPhases}) * 64;
176 $srcy = 0;
177 } elsif ($self->{state} eq 'shoot') {
178 $srcx = $self->{dir} * 128 + 64;
179 $srcx -= 64 if ($self->{delay} <= 1);
180 $srcy = 64;
181 } elsif ($self->{state} eq 'fly') {
182 $srcx = ($self->{dir} > 0 ? 0 : 64);
183 $srcy = 128;
184 }
185 $srcrect = SDL::Rect->new($srcx, $srcy, $srcw, $srch );
186 $self->TransferRect();
187 SDL::Video::blit_surface($surface, $srcrect, $Games::PangZero::App, $self->{rect});
188 }
189
190 sub Kill {
191 my ($self) = @_;
192
193 return if $Games::PangZero::Cheat;
194 return if $self->{invincible} > 0;
195 $self->{justkilled} = 1;
196 $Games::PangZero::GameEvents{'kill'} = 1;
197 print "player killed\n" if $ENV{PANGZERO_TEST};
198 }
199
200 sub Earthquake {
201 my ($self, $amplitude) = @_;
202
203 return if $self->{state} eq 'fly';
204 $self->{speedY} = -($amplitude->[0]);
205 $self->{dir} = $amplitude->[1] > $self->{x} ? 0 : 1;
206 $self->{state} = 'fly';
207 $self->{y} -= 3;
208 }
209
210 sub DeleteHarpoons {
211 my ($self) = @_;
212 my (@gameObjects, $harpoon);
213
214 @gameObjects = @Games::PangZero::GameObjects;
215 foreach $harpoon (@gameObjects) {
216 $harpoon->Delete if ($harpoon->{guy} and $harpoon->{guy} eq $self);
217 }
218 }
219
220 sub GiveScore {
221 my ($self, $score) = @_;
222
223 my $player = $self->{player};
224 $player->{score} += $score;
225 if ($player->{score} >= $player->{scoreforbonuslife}) {
226 ++$player->{lives};
227 $player->{scoreforbonuslife} += 200000;
228 Games::PangZero::Music::PlaySound('bonuslife');
229 }
230 }
231
232 1;
0 ##########################################################################
1 package Games::PangZero::HalfCutter;
2 ##########################################################################
3
4 @ISA = qw(Games::PangZero::Harpoon);
5 use strict;
6 use warnings;
7
8 sub Create {
9 return Games::PangZero::HalfCutter->new(@_);
10 }
11
12 sub new {
13 my $class = shift;
14 my $self = Games::PangZero::Harpoon->new(@_);
15 $self->{popEffect} = 'HalfCutter';
16 $self->{originalSurface} = $self->{surface};
17 bless $self, $class;
18 }
19
20 sub Advance {
21 my $self = shift;
22
23 $self->{surface} = (($Games::PangZero::Game->{anim} % 15) < 3) ? $Games::PangZero::WhiteHarpoonSurface : $self->{originalSurface};
24 $self->SUPER::Advance();
25 }
26
27 1;
0 ##########################################################################
1 package Games::PangZero::Harpoon;
2 ##########################################################################
3
4 @ISA = qw(Games::PangZero::GameObject);
5 use vars qw(%Harpoons $HarpoonId);
6
7 sub Create {
8 return Games::PangZero::Harpoon->new(@_);
9 }
10
11 sub new {
12 my ($class, $guy) = @_;
13 my ($self);
14
15 $self = Games::PangZero::GameObject->new();
16 %{$self} = ( %{$self},
17 'x' => $guy->{x} + 22,
18 'y' => $Games::PangZero::ScreenHeight - 32,
19 'w' => 18,
20 'h' => 32,
21 'speedY' => -3,
22 'speedX' => 0,
23 'guy' => $guy,
24 'surface' => $guy->{player}->{harpoonSurface},
25 'popEffect' => '',
26 'id' => ++$HarpoonId,
27 );
28 $Harpoons{$self->{id}} = $self;
29 bless $self, $class;
30 }
31
32 sub Delete {
33 my $self = shift;
34
35 delete $Harpoons{$self->{id}};
36 --$self->{guy}->{harpoons};
37 $self->SUPER::Delete();
38 }
39
40 sub Advance {
41 my $self = shift;
42
43 if ($self->{y} < 0) {
44 $self->Delete();
45 return;
46 }
47 $self->{y} += $self->{speedY};
48 $self->{h} = $Games::PangZero::ScreenHeight - $self->{y};
49 }
50
51 sub GetAnimPhase {
52 my $self = shift;
53
54 return (int($Games::PangZero::Game->{anim} / 4) % 3) + 1;
55 }
56
57 sub Draw {
58 my $self = shift;
59 my ($x, $y, $h, $maxh, $dstrect, $srcrect);
60
61 $self->TransferRect();
62 $y = $self->{y};
63 $dstrect = SDL::Rect->new( $self->{x} + $Games::PangZero::ScreenMargin, 0, $self->{w}, 0 );
64 $srcrect = SDL::Rect->new( (0, 64, 32, 96)[ $self->GetAnimPhase() ], 0, $self->{w}, 0 );
65 $maxh = 160;
66
67 # The harpoon needs to be drawn from tile pieces.
68 # $y iterates from $self->{y} to $Games::PangZero::ScreenHeight
69 # We draw at most $maxh height tiles at a time.
70
71 while ($y < $Games::PangZero::ScreenHeight) {
72 $h = $Games::PangZero::ScreenHeight - $y;
73 $h = $maxh if $h > $maxh;
74 $dstrect->y( $y + $Games::PangZero::ScreenMargin );
75 $dstrect->h( $h );
76 $srcrect->h( $h );
77 SDL::Video::blit_surface($self->{surface}, $srcrect, $Games::PangZero::App, $dstrect );
78
79 # Prepare for next piece
80 $y += $h;
81 $srcrect->y( 32 ); # First piece starts at 0, rest start at 32
82 $maxh = 128;
83 }
84 }
85
86 1;
0 ##########################################################################
1 package Games::PangZero::Hexa;
2 ##########################################################################
3
4 @ISA = qw(Games::PangZero::Ball);
5
6 sub new {
7 my $class = shift;
8 my $self = Games::PangZero::Ball->new(@_);
9 $self->{speedX} = ($Games::PangZero::Game->Rand(1.25) + 1.25) * ($self->{speedX} > 0 ? 1 : -1);
10 $self->{speedY} = -4 + abs($self->{speedX});
11
12 bless $self, $class;
13 }
14
15 sub Draw {
16 my $self = shift;
17 my ($rect, $srcx, $phase);
18
19 return if $Games::PangZero::GamePause > 0 and $Games::PangZero::GamePause < 100 and (int($Games::PangZero::GamePause / 3) % 4) < 2;
20
21 $self->TransferRect();
22 if ($self->{ismagic} and int($Games::PangZero::Game->{anim} / 3) % 3 == 0) {
23 SDL::Video::blit_surface($self->{surface}, $self->{desc}->{magicrect}, $Games::PangZero::App, $self->{rect});
24 } else {
25 $rect = $self->{desc}->{rect};
26 $phase = int($Games::PangZero::Game->{anim} / 5) % 3;
27 $phase = 2 - $phase if $self->{speedX} < 0;
28 $srcx = $phase * $self->{w};
29 $rect->x( $rect->x + $srcx );
30 SDL::Video::blit_surface($self->{surface}, $rect, $Games::PangZero::App, $self->{rect} );
31 $rect->x( $rect->x - $srcx );
32 }
33 }
34
35 sub AdjustChildren {
36 my ($self, $child1, $child2) = @_;
37 if ($self->{hasmagic}) {
38 $child2->GiveMagic();
39 }
40 }
41
42 1;
0 ##########################################################################
1 # HIGH SCORE TABLE
2 ##########################################################################
3 package Games::PangZero::Highscore;
4
5 use vars qw( @Games::PangZero::UnsavedHighScores );
6
7 foreach (@Games::PangZero::DifficultyLevels) {
8 $_->{highScoreTablePan} = [ ['UPI', 250000], ['UPI', 200000], ['UPI', 150000], ['UPI', 100000], ['UPI', 50000] ];
9 $_->{highScoreTablePan} = [ ['UPI', 2500], ['UPI', 2000], ['UPI', 1500], ['UPI', 1000], ['UPI', 500] ] if $_->{name} eq 'Miki';
10 $_->{highLevelTablePan} = [ ['UPI', 50], ['UPI', 40], ['UPI', 30], ['UPI', 20], ['UPI', 10] ];
11 $_->{highLevelTablePan} = [ ['UPI', 20], ['UPI', 16], ['UPI', 12], ['UPI', 8], ['UPI', 4] ] if $_->{name} eq 'Miki';
12 $_->{highScoreTableCha} = [ ['UPI', 250000], ['UPI', 200000], ['UPI', 150000], ['UPI', 100000], ['UPI', 50000] ];
13 $_->{highLevelTableCha} = [ ['UPI', 30], ['UPI', 25], ['UPI', 20], ['UPI', 15], ['UPI', 10] ];
14 }
15
16 sub AddHighScore {
17 my ($player, $score, $level) = @_;
18
19 unshift @Games::PangZero::UnsavedHighScores, [$player, $score, $level];
20 }
21
22 sub MergeUnsavedHighScores {
23 my ($table) = @_;
24 my ($unsavedHighScore, $player, $score, $level);
25
26 die unless ($table =~ /^(Cha|Pan)$/);
27 foreach $unsavedHighScore (@Games::PangZero::UnsavedHighScores) {
28 ($player, $score, $level) = @{$unsavedHighScore};
29 &MergeUnsavedHighScore( $Games::PangZero::DifficultyLevel->{"highScoreTable$table"}, $player, $score );
30 &MergeUnsavedHighScore( $Games::PangZero::DifficultyLevel->{"highLevelTable$table"}, $player, $level );
31 }
32
33 splice @{$Games::PangZero::DifficultyLevel->{"highScoreTable$table"}}, 5;
34 splice @{$Games::PangZero::DifficultyLevel->{"highLevelTable$table"}}, 5;
35 @Games::PangZero::UnsavedHighScores = ();
36 my $newHighScore = &InputPlayerNames($table);
37 if ($newHighScore) {
38 $Games::PangZero::Game->RunHighScore( $Games::PangZero::DifficultyLevelIndex, $table, 0 );
39 }
40 }
41
42 sub MergeUnsavedHighScore {
43 my ($highScoreList, $player, $score) = @_;
44 my ($i);
45
46 for ($i = 0; $i < scalar @{$highScoreList}; ++$i) {
47 if ($highScoreList->[$i]->[1] < $score) {
48 splice @{$highScoreList}, $i, 0, [$player, $score];
49 return;
50 }
51 }
52 }
53
54 sub InputPlayerNames {
55 my ($table) = @_;
56 my ($highScoreEntry, $player, $score, $message, $retval);
57
58 die unless ($table =~ /^(Cha|Pan)$/);
59 $retval = 0;
60 foreach $highScoreEntry (@{$Games::PangZero::DifficultyLevel->{"highScoreTable$table"}}, @{$Games::PangZero::DifficultyLevel->{"highLevelTable$table"}}) {
61 $player = $highScoreEntry->[0];
62 next unless ref $player;
63 unless ($player->{highScoreName}) {
64 $score = $highScoreEntry->[1];
65 $message = $score < 1000 ? "Level $score" : "Score $score";
66 $player->{highScoreName} = &InputPlayerName($player, $message);
67 }
68 $highScoreEntry->[0] = $player->{highScoreName};
69 $retval = 1;
70 }
71 foreach $player (@Games::PangZero::Players) {
72 delete $player->{highScoreName};
73 }
74 return $retval;
75 }
76
77 sub InputPlayerName {
78 my ($player, $message) = @_;
79 my ($nameMenuItem, @menuItems, $x, $y, $yInc);
80
81 SDL::Events::enable_unicode(1);
82 $Games::PangZero::UnicodeMode = 1;
83 my $name = ($player->{name} or '') . '|';
84 my $guy = Games::PangZero::Guy->new($player);
85 ($guy->{x}, $guy->{y}) = (150, 150);
86 $guy->DemoMode();
87
88 ($x, $y, $yInc) = (230, 80, 45);
89 push @menuItems, (
90 Games::PangZero::MenuItem->new( $x, $y += $yInc, "HIGH SCORE!!!"),
91 Games::PangZero::MenuItem->new( $x, $y += $yInc, $message),
92 Games::PangZero::MenuItem->new( $x, $y += $yInc, "Please enter your name:"),
93 $nameMenuItem = Games::PangZero::MenuItem->new( $x, $y += $yInc, $name ),
94 );
95 push @Games::PangZero::GameObjects, ($guy, @menuItems);
96
97 while (1) {
98 $Games::PangZero::LastUnicodeKey = 0;
99 $Games::PangZero::Game->MenuAdvance();
100 last if $Games::PangZero::Game->{abortgame};
101 if (%Games::PangZero::Events) {
102 if ($Games::PangZero::MenuEvents{BACKSP}) {
103 substr($name, -2, 1, ''); # Remove next to last char
104 $nameMenuItem->SetText($name);
105 } elsif ($Games::PangZero::MenuEvents{BUTTON}) {
106 last;
107 } elsif ($Games::PangZero::LastUnicodeKey < 127 and $Games::PangZero::LastUnicodeKey >= 32 and length($name) < 9) {
108 substr($name, -1, 0, chr($Games::PangZero::LastUnicodeKey)); # Insert before last char
109 $nameMenuItem->SetText($name);
110 }
111 }
112 }
113 $name =~ s/\|$//;
114 $player->{name} = $name;
115 $name = "Anonymous" if $name =~ /^\s*$/;
116 $guy->Delete();
117 foreach (@menuItems) {
118 $_->Delete();
119 }
120 SDL::Events::enable_unicode(0); $Games::PangZero::UnicodeMode = 0;
121 return $name;
122 }
123
124 1;
0 ##########################################################################
1 package Games::PangZero::Joystick;
2 ##########################################################################
3
4 use vars qw(@Games::PangZero::Joysticks @Games::PangZero::JoystickButtons);
5
6 sub InitJoystick {
7 my ($numJoysticks, $joystick, $numButtons, $i);
8
9 $numJoysticks = SDL::Joystick::num_joysticks();
10 for ($i = 0; $i < $numJoysticks; $i++) {
11 print STDERR "Found joystick " , $i+1 , ": " , SDL::Joystick::name($i), "\n";
12 $joystick = SDL::Joystick->new($i);
13 next unless $joystick;
14 $numButtons = SDL::Joystick::num_buttons($joystick);
15 next unless $numButtons;
16 push @Games::PangZero::Joysticks, $joystick;
17 push @Games::PangZero::JoystickButtons, $numButtons;
18 print STDERR "Joystick opened, $numButtons buttons.\n";
19 }
20 }
21
22 sub ReadJoystick {
23 my ($readBothAxes) = @_;
24 my ($i, $button, $buttonPressed);
25
26 $i = 0;
27 foreach my $joystick (@Games::PangZero::Joysticks) {
28 my $axis = SDL::Joystick::get_axis($joystick, 0);
29 if ($axis <= -10000) {
30 $Games::PangZero::Events{"L$i"} = $Games::PangZero::MenuEvents{LEFT} = 1 unless $Games::PangZero::Keys{"L$i"};
31 $Games::PangZero::Keys{"L$i"} = 1;
32 $Games::PangZero::Keys{"R$i"} = 0;
33 } elsif ($axis >= 10000) {
34 $Games::PangZero::Events{"R$i"} = $Games::PangZero::MenuEvents{RIGHT} = 1 unless $Games::PangZero::Keys{"R$i"};
35 $Games::PangZero::Keys{"R$i"} = 1;
36 $Games::PangZero::Keys{"L$i"} = 0;
37 } else {
38 $Games::PangZero::Keys{"L$i"} = 0;
39 $Games::PangZero::Keys{"R$i"} = 0;
40 }
41
42 if ($readBothAxes) {
43 $axis = SDL::Joystick::get_axis($joystick, 1);
44 if ($axis <= -10000) {
45 $Games::PangZero::Events{"U$i"} = $Games::PangZero::MenuEvents{UP} = 1 unless $Games::PangZero::Keys{"U$i"};
46 $Games::PangZero::Keys{"U$i"} = 1;
47 $Games::PangZero::Keys{"D$i"} = 0;
48 } elsif ($axis >= 10000) {
49 $Games::PangZero::Events{"D$i"} = $Games::PangZero::MenuEvents{DOWN} = 1 unless $Games::PangZero::Keys{"D$i"};
50 $Games::PangZero::Keys{"D$i"} = 1;
51 $Games::PangZero::Keys{"U$i"} = 0;
52 } else {
53 $Games::PangZero::Keys{"D$i"} = 0;
54 $Games::PangZero::Keys{"U$i"} = 0;
55 }
56 }
57
58 $buttonPressed = 0;
59 for ($button = 0; $button < $Games::PangZero::JoystickButtons[$i]; $button++) {
60 if (SDL::Joystick::get_button($joystick, $button)) {
61 $buttonPressed = 1;
62 last;
63 }
64 }
65 if ($buttonPressed and not $Games::PangZero::Keys{"B$i"}) {
66 $Games::PangZero::Events{"B$i"} = $Games::PangZero::MenuEvents{BUTTON} = 1;
67 }
68 $Games::PangZero::Keys{"B$i"} = $buttonPressed;
69 $i++;
70 }
71 }
72
73 1;
0 ##########################################################################
1 package Games::PangZero::MachineGun;
2 ##########################################################################
3
4 @ISA = qw(Games::PangZero::Harpoon);
5 use strict;
6 use warnings;
7 use vars qw(@SrcRects);
8
9 @SrcRects = (
10 SDL::Rect->new( 0, 160, 32, 32 ),
11 SDL::Rect->new( 32, 160, 32, 32 ),
12 SDL::Rect->new( 64, 160, 32, 32 ),
13 );
14
15 sub Create {
16 return ( Games::PangZero::MachineGun->new(@_, 0), Games::PangZero::MachineGun->new(@_, 1), Games::PangZero::MachineGun->new(@_, 2) );
17 }
18
19 sub new {
20 my ($class, $guy, $index) = @_;
21 my ($self);
22
23 $self = Games::PangZero::Harpoon->new($guy);
24 %{$self} = ( %{$self},
25 'x' => $guy->{x} + 16,
26 'y' => $guy->{y} - 16,
27 'w' => 32,
28 'h' => 32,
29 'index' => $index,
30 'speedY' => -9,
31 'speedX' => (-2, 0, 2)[$index],
32 );
33 bless $self, $class;
34 }
35
36 sub Delete {
37 my $self = shift;
38
39 --$self->{guy}->{harpoons} if $self->{index} == 1;
40 delete $Harpoon::Harpoons{$self->{id}};
41 $self->Games::PangZero::GameObject::Delete();
42 }
43
44 sub Advance {
45 my $self = shift;
46
47 if ($self->{y} < 0
48 or $self->{x} < 0
49 or $self->{x} > $Games::PangZero::ScreenWidth - $self->{w}) {
50 $self->Delete();
51 return;
52 }
53 $self->{y} += $self->{speedY};
54 $self->{x} += $self->{speedX};
55 }
56
57 sub Draw {
58 my $self = shift;
59
60 $self->TransferRect();
61 SDL::Video::blit_surface($self->{surface}, $SrcRects[$self->{index}], $Games::PangZero::App, $self->{rect});
62 }
63
64 1;
0 ##########################################################################
1 package Games::PangZero::Meltdown;
2 ##########################################################################
3
4 @ISA = qw(Games::PangZero::GameObject);
5 use strict;
6 use warnings;
7
8 sub new {
9 my ($class) = @_;
10 my ($self, $surface);
11
12 $self = Games::PangZero::GameObject->new();
13 $surface = SDL::Image::load( "$Games::PangZero::DataDir/meltdown.png" );
14 %{$self} = ( %{$self},
15 'x' => ($Games::PangZero::ScreenWidth - $surface->w) / 2,
16 'y' => -$surface->h,
17 'w' => $surface->w,
18 'h' => $surface->h,
19 'speedY' => 0,
20 'surface' => $surface,
21 'bounce' => 0,
22 );
23 bless $self, $class;
24 }
25
26 sub Advance {
27 my $self = shift;
28 $self->{speedY} += 0.1;
29 $self->{y} += $self->{speedY};
30
31 if ($self->{bounce} == 0 and $self->{y} > $Games::PangZero::ScreenHeight - $self->{h}) {
32 $self->{bounce} = 1;
33 $self->{speedY} = -5;
34 $self->{y} = $Games::PangZero::ScreenHeight - $self->{h};
35 }
36
37 if ($self->{bounce} and $self->{y} > $Games::PangZero::PhysicalScreenHeight) {
38 $self->Delete;
39 }
40 }
41
42 sub Draw {
43 my $self = shift;
44
45 $self->TransferRect();
46 SDL::Video::blit_surface($self->{surface}, SDL::Rect->new(0, 0, $self->{surface}->w, $self->{surface}->h), $Games::PangZero::App, $self->{rect} );
47 }
48
49 1;
0 ##########################################################################
1 package Games::PangZero::Menu;
2 ##########################################################################
3
4 use SDL::Events;
5 use Games::PangZero::FpsIndicator;
6 use Games::PangZero::MenuItem;
7
8 @ISA = qw(Games::PangZero::GameBase);
9 use vars qw(@syms);
10 @syms = qw(UNKNOWN FIRST BACKSPACE TAB CLEAR RETURN PAUSE ESCAPE SPACE EXCLAIM QUOTEDBL HASH DOLLAR AMPERSAND QUOTE LEFTPAREN RIGHTPAREN ASTERISK PLUS COMMA MINUS PERIOD SLASH 0 1 2 3 4 5 6 7 8 9 COLON SEMICOLON LESS EQUALS GREATER QUESTION AT LEFTBRACKET BACKSLASH RIGHTBRACKET CARET UNDERSCORE BACKQUOTE a b c d e f g h i j k l m n o p q r s t u v w x y z DELETE WORLD_0 WORLD_1 WORLD_2 WORLD_3 WORLD_4 WORLD_5 WORLD_6 WORLD_7 WORLD_8 WORLD_9 WORLD_10 WORLD_11 WORLD_12 WORLD_13 WORLD_14 WORLD_15 WORLD_16 WORLD_17 WORLD_18 WORLD_19 WORLD_20 WORLD_21 WORLD_22 WORLD_23 WORLD_24 WORLD_25 WORLD_26 WORLD_27 WORLD_28 WORLD_29 WORLD_30 WORLD_31 WORLD_32 WORLD_33 WORLD_34 WORLD_35 WORLD_36 WORLD_37 WORLD_38 WORLD_39 WORLD_40 WORLD_41 WORLD_42 WORLD_43 WORLD_44 WORLD_45 WORLD_46 WORLD_47 WORLD_48 WORLD_49 WORLD_50 WORLD_51 WORLD_52 WORLD_53 WORLD_54 WORLD_55 WORLD_56 WORLD_57 WORLD_58 WORLD_59 WORLD_60 WORLD_61 WORLD_62 WORLD_63 WORLD_64 WORLD_65 WORLD_66 WORLD_67 WORLD_68 WORLD_69 WORLD_70 WORLD_71 WORLD_72 WORLD_73 WORLD_74 WORLD_75 WORLD_76 WORLD_77 WORLD_78 WORLD_79 WORLD_80 WORLD_81 WORLD_82 WORLD_83 WORLD_84 WORLD_85 WORLD_86 WORLD_87 WORLD_88 WORLD_89 WORLD_90 WORLD_91 WORLD_92 WORLD_93 WORLD_94 WORLD_95 KP0 KP1 KP2 KP3 KP4 KP5 KP6 KP7 KP8 KP9 KP_PERIOD KP_DIVIDE KP_MULTIPLY KP_MINUS KP_PLUS KP_ENTER KP_EQUALS UP DOWN RIGHT LEFT INSERT HOME END PAGEUP PAGEDOWN F1 F2 F3 F4 F5 F6 F7 F8 F9 F10 F11 F12 F13 F14 F15 NUMLOCK CAPSLOCK SCROLLOCK RSHIFT LSHIFT RCTRL LCTRL RALT LALT RMETA LMETA LSUPER RSUPER MODE COMPOSE HELP PRINT SYSREQ BREAK MENU POWER EURO UNDO LAST );
11
12 sub Exit {
13 my $self = shift;
14
15 Games::PangZero::Config::SaveConfig();
16 $self->SUPER::Exit();
17 }
18
19 sub SetGameSpeed {
20 $Games::PangZero::GameSpeed = 1.0;
21 }
22
23 sub ShowTooltip {
24 my $self = shift;
25 my (@lines, $y, $yinc, $rect);
26
27 @lines = @_;
28 @lines = ("Pang Zero $Games::PangZero::VERSION (C) 2006 by UPi (upi\@sourceforge.net)",
29 "Use cursor keys to navigate menu, Enter to select",
30 "P pauses the game, Esc quits") unless scalar @lines;
31
32 $Games::PangZero::ScoreFont->use();
33 ($y, $yinc) = ($Games::PangZero::ScreenHeight + 35, 20);
34 $rect = SDL::Rect->new(0, $y, $Games::PangZero::PhysicalScreenWidth, $Games::PangZero::PhysicalScreenHeight - $y );
35 SDL::Video::fill_rect($Games::PangZero::Background, $rect, SDL::Video::map_RGB($Games::PangZero::Background->format(), 0, 0, 0));
36 foreach (@lines) {
37 SDLx::SFont::print_text( $Games::PangZero::Background, 10, $y, $_ ) if $y + $yinc < $Games::PangZero::PhysicalScreenHeight;
38
39 $y += $yinc;
40 }
41 $rect = SDL::Rect->new( 0, $Games::PangZero::ScreenHeight + 35, $Games::PangZero::PhysicalScreenWidth, $Games::PangZero::PhysicalScreenWidth - $y );
42 SDL::Video::blit_surface($Games::PangZero::Background, $rect, $Games::PangZero::App, $rect);
43 $Games::PangZero::MenuFont->use();
44 }
45
46 sub MenuAdvance {
47 my $self = shift;
48
49 my $advance = $self->CalculateAdvances();
50 %Games::PangZero::Events = %Games::PangZero::MenuEvents = ();
51 %Games::PangZero::GameEvents = ();
52 Games::PangZero::HandleEvents('readbothaxes');
53 while ($advance--) {
54 $self->AdvanceGameObjects();
55 }
56 while (ref($Games::PangZero::GameObjects[$#Games::PangZero::GameObjects]) ne 'Games::PangZero::MenuItem') {
57 unshift @Games::PangZero::GameObjects, (pop @Games::PangZero::GameObjects);
58 }
59 $self->DrawGame();
60 }
61
62 sub SetCurrentItemIndex {
63 my ($self, $index) = @_;
64
65 return if ($index < 0 or $index >= scalar @{$self->{menuItems}} or not $self->{menuItems}->[$index]->CanSelect());
66 $self->{currentItemIndex} = $index;
67 $self->{currentItem} = $self->{menuItems}->[$index];
68 $self->{currentItem}->Select();
69 }
70
71 sub EnterSubMenu {
72 my $self = shift;
73 my ($recall, $menuItem);
74
75 $recall->{oldItems} = $self->{menuItems};
76 $recall->{oldCurrentItemIndex} = $self->{currentItemIndex};
77 foreach $menuItem (@{$self->{menuItems}}) { $menuItem->Hide(); }
78 $self->{menuItems} = [];
79
80 return $recall;
81 }
82
83 sub LeaveSubMenu {
84 my ($self, $recall) = @_;
85 my ($menuItem);
86
87 foreach $menuItem (@{$self->{menuItems}}) { $menuItem->HideAndDelete(); }
88 $self->{menuItems} = $recall->{oldItems};
89 foreach $menuItem (@{$self->{menuItems}}) { $menuItem->Show(); }
90 $self->SetCurrentItemIndex($recall->{oldCurrentItemIndex});
91 $self->{abortgame} = 0;
92 }
93
94 sub HandleUpDownKeys {
95 my $self = shift;
96
97 if ($Games::PangZero::MenuEvents{DOWN}) {
98 $self->SetCurrentItemIndex( $self->{currentItemIndex} + 1 );
99 }
100 if ($Games::PangZero::MenuEvents{UP}) {
101 $self->SetCurrentItemIndex( $self->{currentItemIndex} - 1 );
102 }
103 }
104
105 sub KeyToText {
106 my ($key) = @_;
107 eval("SDLK_$_ eq $key") and return ucfirst(lc($_)) foreach @syms;
108 print "No match for $key\n";
109 return "???";
110 }
111
112 sub KeysToText {
113 my $keys = shift;
114 my ($retval);
115 if ( $keys->[0] =~ /^[LRB](\d)+$/ ) {
116 return "Joystick $1";
117 }
118 return join(' / ', KeyToText($keys->[0]), KeyToText($keys->[1]), KeyToText($keys->[2]) );
119 }
120
121 sub RunTutorial {
122 my ($self, $ball) = @_;
123 my $recall = $self->EnterSubMenu();
124 my @oldGameObjects = @Games::PangZero::GameObjects;
125 my %oldGuys = %Guy::Guys;
126 my %oldHarpoons = %Harpoon::Harpoons;
127 my $oldGame = $Games::PangZero::Game;
128
129 $Games::PangZero::ScoreFont->use();
130 $Games::PangZero::Game = Games::PangZero::TutorialGame->new;
131 $Games::PangZero::Game->SetChallenge($ball);
132 $Games::PangZero::Game->Run();
133 $Games::PangZero::MenuFont->use();
134 $self->SetGameSpeed();
135
136 @Games::PangZero::GameObjects = @oldGameObjects;
137 %Guy::Guys = %oldGuys;
138 %Harpoon::Harpoons = %oldHarpoons;
139 $Games::PangZero::Game = $oldGame;
140 $self->LeaveSubMenu($recall);
141 }
142
143 sub RunTutorialMenu {
144 my $self = shift;
145 my ($baseX, $menuItem);
146
147 my $recall = $self->EnterSubMenu();
148 $self->{title}->Hide();
149 my $baseY = 50;
150
151 my @tutorials = (
152 ['n2', 'Normal Ball', 'There is nothing special about this ball. Just keep shooting it.'],
153 ['b0', 'Bouncy Ball', 'This ball bounces higher than the normal ball.', 'Otherwise it behaves the same.'],
154 ['h0', 'Hexa', 'The Hexa is weightless and travels in a straight line.', 'With practice you can shoot it just as easily as the normal ball.'],
155 ['w1', 'Water Ball', 'The water ball pops each time it bounces.', 'This can create a tide of small balls fast.', 'Mop it up quickly.'],
156 ['f1', 'Fragile Ball', 'The fragile ball shatters into little bits the moment it is hit.', 'Prepare for a shower of small balls.'],
157 ['death', 'Death Ball', 'This ball cannot be killed with your harpoon.', 'Shooting will make it multiply. Too many death balls cause meltdown.', 'Evade it for 20 seconds to get rid of it.'],
158 ['seeker', 'Seeker Ball', 'The seeker ball will chase you forever.', 'You have to keep moving and shooting to evade it.'],
159 ['quake', 'Earthquake Ball', 'This ball is super heavy.', 'In fact the earth will quake each time it bounces.', 'Shoot it quickly, or it will send you flying.'],
160 ['u0', 'Upside Down Ball', 'This crazy ball bounces on the top of the screen.', 'Maybe it came from an alternate universe,', 'where gravity is negative?'],
161 ['super0, n1', 'Super Ball', 'The Super Ball is your friend. It will still kill you on touch.', 'The green super ball will pause the game for 8 seconds.', 'The gold super ball will kill every ball.'],
162 );
163
164 push @{$self->{menuItems}},
165 Games::PangZero::MenuItem->new( 50, $baseY, "Back to main menu");
166 # Games::PangZero::MenuItem->new( 50, $baseY += 40, "Run Demo" );
167
168 $baseY = 110;
169 $baseX = 50;
170 foreach (@tutorials) {
171 my @tutItem = @{$_};
172 my $challenge = shift @tutItem;
173 my $menuItem = Games::PangZero::MenuItem->new( $baseX, $baseY += 40, @tutItem );
174 $menuItem->{challenge} = $challenge;
175 push @{$self->{menuItems}}, $menuItem;
176 if ($baseY + 140 >= $Games::PangZero::ScreenHeight) {
177 $baseY = 110;
178 $baseX = 450;
179 }
180 }
181 push @Games::PangZero::GameObjects, (@{$self->{menuItems}});
182 $self->SetCurrentItemIndex(1);
183
184 while (1) {
185 $self->MenuAdvance();
186 last if $self->{abortgame};
187 $self->HandleUpDownKeys();
188
189 if ($Games::PangZero::MenuEvents{LEFT} and $self->{currentItemIndex} >= 6 and $self->{currentItemIndex} <= 10) {
190 $self->SetCurrentItemIndex($self->{currentItemIndex} - 5);
191 }
192 if ($Games::PangZero::MenuEvents{RIGHT} and $self->{currentItemIndex} >= 1 and $self->{currentItemIndex} <= 5) {
193 $self->SetCurrentItemIndex($self->{currentItemIndex} + 5);
194 }
195 if ($Games::PangZero::MenuEvents{BUTTON}) {
196 if (0 == $self->{currentItemIndex}) {
197 last;
198 # } elsif (1 == $self->{currentItemIndex}) {
199 # $self->{result} = 'demo';
200 # last;
201 } else {
202 $self->RunTutorial($self->{currentItem}->{challenge});
203 }
204 }
205 }
206
207 $self->LeaveSubMenu($recall);
208 $self->{title}->Show();
209 }
210
211 sub RunCredits {
212 my ($self, $demo) = @_;
213 my ($i, $ball, @balls);
214
215 my $time = $self->{anim};
216 my $recall = $self->EnterSubMenu();
217 my @oldGameObjects = @Games::PangZero::GameObjects;
218 foreach my $gameObject (@Games::PangZero::GameObjects) {
219 $gameObject->Clear();
220 }
221 @Games::PangZero::GameObjects = ($self->{title});
222 push @Games::PangZero::GameObjects, Games::PangZero::FpsIndicator->new();
223 my ($y, $yinc) = (110, 36);
224 push @{$self->{menuItems}}, (
225 Games::PangZero::MenuItem->new( 100, $y += $yinc, "Written by: UPi <upi\@sourceforge.net>"),
226 Games::PangZero::MenuItem->new( 100, $y += $yinc, "Music by: SAdam" ),
227 Games::PangZero::MenuItem->new( 100, $y += $yinc, "Graphics by: UPi, DaniGM, EBlanca" ),
228 Games::PangZero::MenuItem->new( 100, $y += $yinc * 1.5, "TESTERS" ),
229 Games::PangZero::MenuItem->new( 100, $y += $yinc, "Ulmar, Surba, Miki, Aisha, Descant" ),
230 Games::PangZero::MenuItem->new( 100, $y += $yinc * 1.5, "http://apocalypse.rulez.org/pangzero" ),
231 );
232 foreach $i (@{$self->{menuItems}}) { $i->Center(); }
233
234 for ($i = 0; $i < 20; ++$i) {
235 $ball = Games::PangZero::Ball::Spawn( $Games::PangZero::BallDesc{'credits1'}, 100, 1, 0 );
236 $ball->{y} = $i * -5;
237 push @balls, ($ball);
238 $ball = Games::PangZero::Ball::Spawn( $Games::PangZero::BallDesc{'credits2'}, $Games::PangZero::ScreenWidth - 132, -1, 0 );
239 $ball->{y} = $i * -5;
240 push @balls, ($ball);
241 }
242 push @Games::PangZero::GameObjects, @balls;
243 push @Games::PangZero::GameObjects, (@{$self->{menuItems}});
244
245 while (1) {
246 $self->MenuAdvance();
247 last if $self->{abortgame};
248 if ($demo) {
249 last if %Games::PangZero::Events;
250 last if $self->{anim} - $time > 20 * 100; # 30s
251 }
252 }
253
254 @Games::PangZero::GameObjects = @oldGameObjects;
255 foreach (@balls) { $_->Delete(); }
256 $self->LeaveSubMenu($recall);
257 }
258
259 sub RunHighScore {
260 my ($self, $difficultyLevel, $table, $auto) = @_;
261 my ($time, $recall, $y, $yinc, $retval);
262
263 die unless $table =~ /^(Cha|Pan)$/;
264 $time = 0;
265 $recall = $self->EnterSubMenu();
266 ($y, $yinc) = (110, 40);
267 $difficultyLevel = $Games::PangZero::DifficultyLevels[$difficultyLevel];
268 push @{$self->{menuItems}}, (
269 Games::PangZero::MenuItem->new( 320, 50, ($table eq 'Cha' ? 'Challenge Game - ' : 'Panic Game - ') . $difficultyLevel->{name} ), #. " difficulty" ),
270 Games::PangZero::MenuItem->new( 50, $y, "Highest Score" ),
271 Games::PangZero::MenuItem->new( 480, $y, "Highest Level" ),
272 );
273 $self->{menuItems}->[0]->Center();
274 $y += $yinc;
275 foreach (@{$difficultyLevel->{"highScoreTable$table"}}) {
276 push @{$self->{menuItems}}, ( Games::PangZero::MenuItem->new( 10, $y += $yinc, $_->[0] ) );
277 push @{$self->{menuItems}}, ( Games::PangZero::MenuItem->new( 250, $y, $_->[1] ) );
278 }
279 $y = 110 + $yinc;
280 foreach (@{$difficultyLevel->{"highLevelTable$table"}}) {
281 push @{$self->{menuItems}}, ( Games::PangZero::MenuItem->new( 460, $y += $yinc, $_->[0] ) );
282 push @{$self->{menuItems}}, ( Games::PangZero::MenuItem->new( 700, $y, $_->[1] ) );
283 }
284 push @Games::PangZero::GameObjects, (@{$self->{menuItems}});
285
286 while (not $retval) {
287 $self->MenuAdvance();
288 if ($self->{abortgame}) {
289 $retval = 'abortgame'; last;
290 }
291 if ($auto) {
292 $retval = 'next' if ++$time > 100 * 6;
293 $retval = 'abortgame' if %Games::PangZero::Events;
294 } else {
295 if ($Games::PangZero::MenuEvents{LEFT} or $Games::PangZero::MenuEvents{UP}) {
296 $retval = 'prev'; last;
297 } elsif ($Games::PangZero::MenuEvents{RIGHT} or $Games::PangZero::MenuEvents{DOWN}) {
298 $retval = 'next'; last;
299 } elsif ($Games::PangZero::MenuEvents{BUTTON}) {
300 $retval = 'abortgame';
301 }
302 }
303 }
304 $self->LeaveSubMenu($recall);
305 return $retval;
306 }
307
308 sub RunHighScores {
309 my ($self, $auto) = @_;
310 my ($recall, $retval, $i, $table, @tables);
311
312 if ($auto) {
313 $self->ShowTooltip();
314 } else {
315 $self->ShowTooltip("Use arrow keys to navigate, Esc to go back");
316 }
317 $recall = $self->EnterSubMenu();
318 $self->{title}->Hide();
319 $table = 0;
320 @tables = ( [0, 'Pan'], [0, 'Cha'], [1, 'Pan'], [1, 'Cha'], [2, 'Pan'], [2, 'Cha'], [3, 'Pan'], [3, 'Cha'], [4, 'Pan'] );
321
322 while (1) {
323 $retval = $self->RunHighScore( @{$tables[$table]}, $auto );
324 if ($retval eq 'next') {
325 ++$table;
326 $table = 0 if $table == scalar @tables;
327 last if $table == 0 and $auto;
328 } elsif ($retval eq 'prev') {
329 --$table;
330 $table = $#tables if $table < 0;
331 } else {
332 last;
333 }
334 }
335
336 $self->ShowTooltip();
337 $self->{title}->Show();
338 $self->LeaveSubMenu($recall);
339 }
340
341 sub UpdateBallMixerMenu {
342 my $self = shift;
343
344 $self->{menuItems}->[1]->SetParameter( $Games::PangZero::DeathBallsEnabled ? 'on' : 'off' );
345 $self->{menuItems}->[2]->SetParameter( $Games::PangZero::EarthquakeBallsEnabled ? 'on' : 'off' );
346 $self->{menuItems}->[3]->SetParameter( $Games::PangZero::WaterBallsEnabled ? 'on' : 'off' );
347 $self->{menuItems}->[4]->SetParameter( $Games::PangZero::SeekerBallsEnabled ? 'on' : 'off' );
348 }
349
350 sub RunBallMixerMenu {
351 my $self = shift;
352 my ($recall);
353
354 $recall = $self->EnterSubMenu();
355 my ($y, $yinc) = (110, 40);
356 push @{$self->{menuItems}}, (
357 Games::PangZero::MenuItem->new( 100, $y += $yinc, "Back to options menu"),
358 Games::PangZero::MenuItem->new( 100, $y += $yinc + 20, "Death Balls: ", "Death balls multiply every time you shoot them.", "You can get rid of them by NOT shooting them for 20 seconds." ),
359 Games::PangZero::MenuItem->new( 100, $y += $yinc, "Earthquake Balls: ", "Earthquake balls shake the ground when they bounce.", "This sends you flying. Very dangerous." ),
360 Games::PangZero::MenuItem->new( 100, $y += $yinc, "Water Balls: ", "Water balls quickly dissolve, creating a flood of small balls." ),
361 Games::PangZero::MenuItem->new( 100, $y += $yinc, "Seeker Balls: ", "This ball picks a target, and chases him." ),
362 );
363 $self->UpdateBallMixerMenu();
364 push @Games::PangZero::GameObjects, (@{$self->{menuItems}});
365 $self->SetCurrentItemIndex(0);
366
367 while (1) {
368 $self->MenuAdvance();
369 last if $self->{abortgame};
370 $self->HandleUpDownKeys();
371
372 if ($Games::PangZero::MenuEvents{BUTTON}) {
373 last if $self->{currentItemIndex} == 0; # Back to main
374 if ($self->{currentItemIndex} == 1) {
375 $Games::PangZero::DeathBallsEnabled = 1 - $Games::PangZero::DeathBallsEnabled; $self->UpdateBallMixerMenu();
376 } elsif ($self->{currentItemIndex} == 2) {
377 $Games::PangZero::EarthquakeBallsEnabled = 1 - $Games::PangZero::EarthquakeBallsEnabled; $self->UpdateBallMixerMenu();
378 } elsif ($self->{currentItemIndex} == 3) {
379 $Games::PangZero::WaterBallsEnabled = 1 - $Games::PangZero::WaterBallsEnabled; $self->UpdateBallMixerMenu();
380 } elsif ($self->{currentItemIndex} == 4) {
381 $Games::PangZero::SeekerBallsEnabled = 1 - $Games::PangZero::SeekerBallsEnabled; $self->UpdateBallMixerMenu();
382 }
383 }
384 }
385
386 $self->LeaveSubMenu($recall);
387 }
388
389 sub UpdateOptionsMenu {
390 my $self = shift;
391
392 $self->{menuItems}->[1]->SetParameter( $Games::PangZero::Slippery ? 'on' : 'off' );
393 $self->{menuItems}->[3]->SetParameter( $Games::PangZero::SoundEnabled ? 'on' : 'off');
394 $self->{menuItems}->[4]->SetParameter( $Games::PangZero::MusicEnabled ? 'on' : 'off');
395 $self->{menuItems}->[5]->SetText('< ' . ('Windowed', 'Fullscreen', 'Widescreen')[$Games::PangZero::FullScreen]
396 . ($self->{restart} ? ' (requires restart)' : '') . ' >');
397 $self->{menuItems}->[6]->SetParameter( $Games::PangZero::ShowWebsite eq $Games::PangZero::VERSION ? 'no' : 'yes' );
398 }
399
400 sub RunOptionsMenu {
401 my $self = shift;
402 my $recall = $self->EnterSubMenu();
403 my ($y, $yinc) = (80, 38);
404 push @{$self->{menuItems}}, (
405 Games::PangZero::MenuItem->new( 100, $y += $yinc, "Back to main menu"),
406 Games::PangZero::MenuItem->new( 100, $y += $yinc + 20, "Slippery floor: ", "Turning this on creates and icy floor that you slide on", "This makes the game a lot harder!" ),
407 Games::PangZero::MenuItem->new( 100, $y += $yinc, "Ball Mixer...", "Turn the special balls on and off.", "This can make the game easier." ),
408 Games::PangZero::MenuItem->new( 100, $y += $yinc, "Sound: ", "Press Enter to turn sound effects on/off." ),
409 Games::PangZero::MenuItem->new( 100, $y += $yinc, "Music: ", "Press Enter to turn the background music on/off." ),
410 Games::PangZero::MenuItem->new( 68, $y += $yinc, "Fullscreen", "Press Left/Right to set the screen mode.", "If you have a wide screen (e.g. 16:9), use the Widescreen option.", "This doesn't take effect until you quit and restart the game." ),
411 Games::PangZero::MenuItem->new( 100, $y += $yinc, "Show website at exit: ", "Should Pang Zero take you to our web site at exit?", "True enlightenment awaits you online!" ),
412 );
413 $self->UpdateOptionsMenu();
414 push @Games::PangZero::GameObjects, (@{$self->{menuItems}});
415 $self->SetCurrentItemIndex(0);
416
417 while (1) {
418 $self->MenuAdvance();
419 last if $self->{abortgame};
420 $self->HandleUpDownKeys();
421
422 if ($Games::PangZero::MenuEvents{LEFT} and $self->{currentItemIndex} == 5) {
423 if ($Games::PangZero::FullScreen > 0) { --$Games::PangZero::FullScreen; $self->{restart} = 1; }
424 $self->UpdateOptionsMenu();
425 }
426 if ($Games::PangZero::MenuEvents{RIGHT} and $self->{currentItemIndex} == 5) {
427 if ($Games::PangZero::FullScreen < 2) { ++$Games::PangZero::FullScreen; $self->{restart} = 1; }
428 $self->UpdateOptionsMenu();
429 }
430 if ($Games::PangZero::MenuEvents{BUTTON}) {
431 last if $self->{currentItemIndex} == 0; # Back to main
432 if ($self->{currentItemIndex} == 2) {
433 $self->RunBallMixerMenu();
434 } elsif ($self->{currentItemIndex} == 1) {
435 $Games::PangZero::Slippery = $Games::PangZero::Slippery ? 0 : 1; $self->UpdateOptionsMenu();
436 } elsif ($self->{currentItemIndex} == 3) {
437 $Games::PangZero::SoundEnabled = 1 - $Games::PangZero::SoundEnabled; $self->UpdateOptionsMenu();
438 } elsif ($self->{currentItemIndex} == 4) {
439 Games::PangZero::Music::SetMusicEnabled(1 - $Games::PangZero::MusicEnabled); $self->UpdateOptionsMenu();
440 } elsif ($self->{currentItemIndex} == 6) {
441 $Games::PangZero::ShowWebsite = $Games::PangZero::ShowWebsite eq $Games::PangZero::VERSION ? 0 : $Games::PangZero::VERSION; $self->UpdateOptionsMenu();
442 }
443 }
444 }
445
446 $self->LeaveSubMenu($recall);
447 }
448
449 sub UpdateControlsMenu {
450 my $self = shift;
451
452 $self->{menuItems}->[1]->SetText("< Number of Players: $Games::PangZero::NumGuys >");
453 for (my $i = 1 ; $i <= 6; ++$i) {
454 if ($i > $Games::PangZero::NumGuys) {
455 $self->{menuItems}->[$i+1]->Hide();
456 $self->{keysAsText}->[$i-1]->Hide();
457 } else {
458 $self->{menuItems}->[$i+1]->Show();
459 $self->{keysAsText}->[$i-1]->Show();
460 }
461 }
462 }
463
464 sub RunControlsMenu {
465 my $self = shift;
466 my ($menuItem, @keysAsText, @yPositions);
467
468 my $recall = $self->EnterSubMenu();
469 $self->{title}->Hide();
470 my $baseY = 50;
471
472 push @{$self->{menuItems}},
473 Games::PangZero::MenuItem->new( 50, $baseY, "Back to main menu"),
474 Games::PangZero::MenuItem->new( 18, $baseY += 40, "<>", "Use left and right key to set the number of players here.", "The more the merrier!", "Don't forget to set their keys below." );
475 for ( my $i = 1; $i <= 6; ++$i ) {
476 $yPositions[$i] = $baseY + 20 + $i * 40;
477 push @{$self->{menuItems}}, (Games::PangZero::MenuItem->new( 50, $yPositions[$i], "Player $i"));
478 push @keysAsText, (Games::PangZero::MenuItem->new( 220, $yPositions[$i], KeysToText($Games::PangZero::Players[$i-1]->{keys})) );
479 }
480 push @Games::PangZero::GameObjects, (@keysAsText, @{$self->{menuItems}});
481 $self->{keysAsText} = \@keysAsText;
482 $self->UpdateControlsMenu();
483 $self->SetCurrentItemIndex(1);
484
485 while (1) {
486 $self->MenuAdvance();
487 last if $self->{abortgame};
488 $self->HandleUpDownKeys();
489 if ($Games::PangZero::MenuEvents{LEFT} and $self->{currentItemIndex} == 1) {
490 --$Games::PangZero::NumGuys if $Games::PangZero::NumGuys > 1;
491 $self->UpdateControlsMenu();
492 }
493 if ($Games::PangZero::MenuEvents{RIGHT} and $self->{currentItemIndex} == 1) {
494 ++$Games::PangZero::NumGuys if $Games::PangZero::NumGuys < 6;
495 $self->UpdateControlsMenu();
496 }
497 if ($Games::PangZero::MenuEvents{BUTTON}) {
498 last if $self->{currentItemIndex} == 0; # Back to main
499 next if $self->{currentItemIndex} == 1;
500 my $player = $Games::PangZero::Players[$self->{currentItemIndex} - 2];
501 my $key = 0;
502 my $keysAsText = $keysAsText[$self->{currentItemIndex} - 2];
503 $self->{currentItem}->Hide();
504 $keysAsText->Hide();
505 my @prompts = ("Press 'LEFT' key or joystick button", "Press 'RIGHT' key", "Press 'FIRE' key");
506 my $keyMenuItem = Games::PangZero::MenuItem->new( 100, $yPositions[$self->{currentItemIndex} - 1], $prompts[0] );
507 push @Games::PangZero::GameObjects, ($keyMenuItem);
508 $keyMenuItem->Select;
509 while (1) {
510 $self->MenuAdvance();
511 if ($self->{abortgame}) {
512 $self->{abortgame} = 0;
513 goto endOfKeyEntry;
514 }
515 if (%Games::PangZero::Events) {
516 my ($event) = %Games::PangZero::Events;
517 if ($event =~ /^B(\d+)$/) {
518 $player->{keys} = ["L$1", "R$1", "B$1"];
519 last;
520 }
521 $player->{keys}->[$key] = $event;
522 ++$key;
523 last if $key >= 3;
524 $keyMenuItem->SetText($prompts[$key]);
525 }
526 }
527
528 $keyMenuItem->SetText('Select character');
529 my $guy = Games::PangZero::Guy->new($player);
530 $guy->{x} = $keyMenuItem->{targetX} + $keyMenuItem->{w} + 10;
531 $guy->{y} = $keyMenuItem->{targetY} - 10;
532 $guy->DemoMode();
533 splice @Games::PangZero::GameObjects, -2, 0, $guy;
534 while (1) {
535 $self->MenuAdvance();
536 if ($self->{abortgame}) {
537 $self->{abortgame} = 0;
538 goto endOfKeyEntry;
539 }
540 if ($Games::PangZero::Events{$player->{keys}->[0]}) {
541 --$player->{imagefileindex};
542 $player->{imagefileindex} = $#Games::PangZero::GuyImageFiles if $player->{imagefileindex} < 0;
543 Games::PangZero::Graphics::MakeGuySurface($player);
544 $guy->{surface} = $player->{guySurface};
545 $guy->CalculateAnimPhases();
546 } elsif ($Games::PangZero::Events{$player->{keys}->[1]}) {
547 ++$player->{imagefileindex};
548 $player->{imagefileindex} = 0 if $player->{imagefileindex} > $#Games::PangZero::GuyImageFiles;
549 Games::PangZero::Graphics::MakeGuySurface($player);
550 $guy->{surface} = $player->{guySurface};
551 $guy->CalculateAnimPhases();
552 } elsif ($Games::PangZero::Events{$player->{keys}->[2]}) {
553 last;
554 }
555 }
556
557 $keyMenuItem->SetText('Select color');
558 while (1) {
559 $self->MenuAdvance();
560 if ($self->{abortgame}) {
561 $self->{abortgame} = 0;
562 goto endOfKeyEntry;
563 }
564 if ($Games::PangZero::Events{$player->{keys}->[0]}) {
565 --$player->{colorindex};
566 $player->{colorindex} = $#Games::PangZero::GuyColors if $player->{colorindex} < 0;
567 Games::PangZero::Graphics::MakeGuySurface($player);
568 $guy->{surface} = $player->{guySurface};
569 } elsif ($Games::PangZero::Events{$player->{keys}->[1]}) {
570 ++$player->{colorindex};
571 $player->{colorindex} = 0 if $player->{colorindex} > $#Games::PangZero::GuyColors;
572 Games::PangZero::Graphics::MakeGuySurface($player);
573 $guy->{surface} = $player->{guySurface};
574 } elsif ($Games::PangZero::Events{$player->{keys}->[2]}) {
575 last;
576 }
577 }
578
579 endOfKeyEntry:
580 $guy->Delete() if $guy;
581 $self->{currentItem}->Show();
582 $self->{currentItem}->Select;
583 $keysAsText->SetText(KeysToText($player->{keys}));
584 $keysAsText->Show;
585 $keyMenuItem->HideAndDelete;
586 }
587 }
588
589 foreach my $menuItem (@keysAsText) { $menuItem->HideAndDelete(); }
590 $self->LeaveSubMenu($recall);
591 $self->{title}->Show();
592 delete $self->{keysAsText};
593 }
594
595 sub UpdateGameMenu {
596 my $self = shift;
597
598 $self->{menuItems}->[3]->SetText("< Difficulty: $Games::PangZero::DifficultyLevel->{name} >");
599 $self->{menuItems}->[4]->SetText("< Weapon Duration: $Games::PangZero::WeaponDuration->{name} >");
600 }
601
602 sub RunGameMenu {
603 my $self = shift;
604 my $recall = $self->EnterSubMenu();
605 my ($y, $yinc) = (110, 40);
606 push @{$self->{menuItems}}, (
607 Games::PangZero::MenuItem->new( 100, $y += $yinc, "Back to main menu", "Press Enter to return to the main menu"),
608 Games::PangZero::MenuItem->new( 100, $y += $yinc + 20, "Start Panic Game", "In Panic Mode, the balls continuously fall from the sky.", "Can you keep up the pace?", "This game is for advanced players." ),
609 Games::PangZero::MenuItem->new( 100, $y += $yinc, "Start Challenge Game", "More and more difficult levels challenge your skill.", "This game is best for beginners." ),
610 Games::PangZero::MenuItem->new( 68, $y += $yinc, "<>", "Press the Left and Right keys to set the game difficulty.", "The game speed and number of harpoons depend on this setting.", "The `Miki' level is for Deathball Specialists (Panic mode only)." ),
611 Games::PangZero::MenuItem->new( 68, $y += $yinc, "<>", "Press the Left and Right keys to set the bonus weapon duration.", "This will determine how long you can use bonus weapons." ),
612 );
613 $self->UpdateGameMenu();
614 push @Games::PangZero::GameObjects, (@{$self->{menuItems}});
615 $self->SetCurrentItemIndex($Games::PangZero::LastGameMenuResult ? $Games::PangZero::LastGameMenuResult : 1);
616
617 while (1) {
618 $self->MenuAdvance();
619 last if $self->{abortgame};
620 $self->HandleUpDownKeys();
621
622 if ($Games::PangZero::MenuEvents{LEFT} and $self->{currentItemIndex} == 3) {
623 Games::PangZero::Config::SetDifficultyLevel($Games::PangZero::DifficultyLevelIndex - 1);
624 $self->UpdateGameMenu();
625 }
626 if ($Games::PangZero::MenuEvents{RIGHT} and $self->{currentItemIndex} == 3) {
627 Games::PangZero::Config::SetDifficultyLevel($Games::PangZero::DifficultyLevelIndex + 1);
628 $self->UpdateGameMenu();
629 }
630 if ($Games::PangZero::MenuEvents{LEFT} and $self->{currentItemIndex} == 4) {
631 Games::PangZero::Config::SetWeaponDuration($Games::PangZero::WeaponDurationIndex - 1);
632 $self->UpdateGameMenu();
633 }
634 if ($Games::PangZero::MenuEvents{RIGHT} and $self->{currentItemIndex} == 4) {
635 Games::PangZero::Config::SetWeaponDuration($Games::PangZero::WeaponDurationIndex + 1);
636 $self->UpdateGameMenu();
637 }
638 if ($Games::PangZero::MenuEvents{BUTTON}) {
639 last if $self->{currentItemIndex} == 0; # Back to main
640 if ($self->{currentItemIndex} == 1) {
641 $self->{result} = 'panic';
642 } elsif ($self->{currentItemIndex} == 2) {
643 if ($Games::PangZero::DifficultyLevel->{name} ne 'Miki') {
644 $self->{result} = 'challenge';
645 } else {
646 $self->ShowTooltip("Miki difficulty level is for panic mode only.");
647 }
648 }
649 }
650 last if $self->{result};
651 }
652
653 $Games::PangZero::LastGameMenuResult = $self->{currentItemIndex};
654 $self->LeaveSubMenu($recall);
655 }
656
657 sub OnMenuIdle {
658 my $self = shift;
659
660 ++$self->{idle};
661 if ($self->{idle} == 1) { $self->RunHighScores('auto'); }
662 elsif ($self->{idle} == 2) { $self->RunCredits('demo'); }
663 elsif ($self->{idle} == 3) { $self->{idle} = 0; return 'demo'; }
664 return '';
665 }
666
667 sub Run {
668 my $self = shift;
669 my ($y, $yinc, $idle);
670
671 $self->ResetGame();
672 $Games::PangZero::ScoreFont->use();
673 ($y, $yinc) = ($Games::PangZero::ScreenHeight + 15, 20);
674 SDLx::SFont::print_text( $Games::PangZero::Background, 10, $y += $yinc, "Pang Zero $Games::PangZero::VERSION (C) 2006 by UPi (upi\@sourceforge.net)" ) if $y + $yinc * 2 < $Games::PangZero::PhysicalScreenHeight;
675
676 SDLx::SFont::print_text( $Games::PangZero::Background, 10, $y += $yinc, "Use cursor keys to navigate menu, Enter to select" ) if $y + $yinc * 2 < $Games::PangZero::PhysicalScreenHeight;
677
678 SDLx::SFont::print_text( $Games::PangZero::Background, 10, $y += $yinc, "P pauses the game, Esc quits" ) if $y + $yinc * 2 < $Games::PangZero::PhysicalScreenHeight;
679
680 SDL::Video::blit_surface($Games::PangZero::Background, SDL::Rect->new(0, 0, $Games::PangZero::App->w, $Games::PangZero::App->h), $Games::PangZero::App, SDL::Rect->new(0, 0, $Games::PangZero::App->w, $Games::PangZero::App->h));
681
682 $Games::PangZero::MenuFont->use();
683 push @Games::PangZero::GameObjects, (Games::PangZero::FpsIndicator->new());
684 $self->SetGameSpeed();
685 $Games::PangZero::GamePause = 0;
686
687 ($y, $yinc) = (90, 40);
688
689 $self->{menuItems} = [
690 Games::PangZero::MenuItem->new( 100, $y += $yinc, "Start Game" ),
691 Games::PangZero::MenuItem->new( 100, $y += $yinc, "Options", "Various game settings" ),
692 Games::PangZero::MenuItem->new( 100, $y += $yinc, "Setup players", "Set the number of players, setup keys and joysticks" ),
693 Games::PangZero::MenuItem->new( 100, $y += $yinc, "Help", "How to play the game, demo of special balls" ),
694 Games::PangZero::MenuItem->new( 100, $y += $yinc, "Credits", "You might be wondering: Who has created Pang Zero?", "Wonder no more." ),
695 Games::PangZero::MenuItem->new( 100, $y += $yinc, "High Scores", "Hall of Fame." ),
696 Games::PangZero::MenuItem->new( 100, $y += $yinc, "Exit Game", "Press Enter to exit the game" ),
697 ];
698
699 $self->{title} = Games::PangZero::MenuItem->new( 300, 60, "PANG ZERO" );
700 $self->{title}->{filled} = 1;
701 $self->{title}->{fillcolor} = SDL::Video::map_RGB($Games::PangZero::Background->format(), 0, 128, 255);
702 $self->{title}->Center();
703
704 push @Games::PangZero::GameObjects, (
705 Games::PangZero::Ball::Spawn($Games::PangZero::BallDesc[8], -1, 1),
706 Games::PangZero::Ball::Spawn($Games::PangZero::BallDesc[0], -1, 0),
707 Games::PangZero::Ball::Spawn($Games::PangZero::BallDesc{super0}, -1, 1),
708 Games::PangZero::Ball::Spawn($Games::PangZero::BallDesc[2], -1, 0),
709 Games::PangZero::Ball::Spawn($Games::PangZero::BallDesc[5], -1, 1),
710 $self->{title},
711 @{$self->{menuItems}},
712 );
713
714 $self->SetCurrentItemIndex( 0 );
715 Games::PangZero::GameTimer::ResetTimer();
716
717 while (1) {
718 $self->MenuAdvance();
719 $self->Exit() if $self->{abortgame};
720 $self->HandleUpDownKeys();
721 last if $self->{result};
722 if ($Games::PangZero::MenuEvents{BUTTON}) {
723 if ($self->{currentItemIndex} == 0) {
724 $self->RunGameMenu();
725 } elsif ($self->{currentItemIndex} == 1) {
726 $self->RunOptionsMenu();
727 } elsif ($self->{currentItemIndex} == 2) {
728 $self->RunControlsMenu();
729 } elsif ($self->{currentItemIndex} == 3) {
730 $self->RunTutorialMenu;
731 } elsif ($self->{currentItemIndex} == 4) {
732 $self->RunCredits();
733 } elsif ($self->{currentItemIndex} == 5) {
734 $self->RunHighScores();
735 }
736 $self->Exit() if $self->{currentItemIndex} == 6;
737 }
738 if (%Games::PangZero::Events) {
739 $idle = 0;
740 } else {
741 if (++$idle > 1000) {
742 $self->{result} = $self->OnMenuIdle();
743 $idle = 0;
744 }
745 }
746 }
747
748 $Games::PangZero::ScoreFont->use();
749 return $self->{result};
750 }
751
752 1;
0 ##########################################################################
1 package Games::PangZero::MenuItem;
2 ##########################################################################
3
4 @ISA = qw(Games::PangZero::GameObject);
5 use strict;
6 use warnings;
7 use vars qw($Gravity);
8 $Gravity = 0.2;
9
10 sub new {
11 my ($class, $x, $y, $text) = @_;
12 my $self = Games::PangZero::GameObject->new();
13 %{$self} = ( %{$self},
14 'targetX' => $x,
15 'targetY' => $y,
16 'h' => 42,
17 'selected' => 0,
18 'filled' => 0,
19 'fillcolor' => SDL::Video::map_RGB($Games::PangZero::Background->format(), 0, 0, 128),
20 'parameter' => 0,
21 'tooltip' => [ @_[4 .. $#_] ],
22 );
23 bless $self, $class;
24 $self->SetText($text);
25 $self->SetInitialSpeed();
26 return $self;
27 }
28
29 sub Center {
30 my $self = shift;
31 $self->{targetX} = ( $Games::PangZero::ScreenWidth - $self->{w} ) / 2;
32 }
33
34 sub Show {
35 my $self = shift;
36 return if $self->CanSelect();
37 $self->SetInitialSpeed();
38 }
39
40 sub Hide {
41 my $self = shift;
42 $self->SUPER::Clear();
43 $self->{state} = 'leaving';
44 $self->{speedX} = rand(10) - 5;
45 }
46
47 sub HideAndDelete {
48 my $self = shift;
49 $self->Hide();
50 $self->{deleteAfterHiding} = 1;
51 }
52
53 sub Delete {
54 my $self = shift;
55 $self->{selected} = $self->{filled} = 0;
56 $self->SUPER::Delete();
57 }
58
59 sub ApproachingSpeed {
60 my ($position, $speed, $target) = @_;
61
62 if ($position + $speed * abs($speed / $Gravity) / 2 > $target) {
63 return $speed - $Gravity;
64 } else {
65 return $speed + $Gravity;
66 }
67 }
68
69 sub Advance {
70 my $self = shift;
71
72 if ('entering' eq $self->{state}) {
73 $self->{x} += $self->{speedX};
74 $self->{y} += $self->{speedY};
75 $self->{speedX} = ApproachingSpeed($self->{x}, $self->{speedX}, $self->{targetX});
76 $self->{speedY} = ApproachingSpeed($self->{y}, $self->{speedY}, $self->{targetY});
77 if ( abs($self->{x} - $self->{targetX}) + abs($self->{y} - $self->{targetY}) < 2 ) {
78 $self->{x} = $self->{targetX};
79 $self->{y} = $self->{targetY};
80 $self->{state} = 'shown';
81 }
82 } elsif ('leaving' eq $self->{state}) {
83 $self->{x} += $self->{speedX};
84 $self->{y} += $self->{speedY};
85 $self->{speedY} += $Gravity;
86 if ($self->{y} > $Games::PangZero::PhysicalScreenWidth) {
87 $self->{state} = 'hidden';
88 $self->Delete() if $self->{deleteAfterHiding}
89 }
90 }
91 }
92
93 sub Draw {
94 my $self = shift;
95
96 return if $self->{state} eq 'hidden';
97 $self->TransferRect();
98 if ($self->{selected} or $self->{filled}) {
99 SDL::Video::fill_rect($Games::PangZero::App, $self->{rect}, $self->{fillcolor});
100 }
101 SDLx::SFont::print_text( $Games::PangZero::App,$self->{x} + 5 +$Games::PangZero::ScreenMargin, $self->{y} + $Games::PangZero::ScreenMargin, $self->{text});
102 }
103
104 sub SetInitialSpeed {
105 my $self = shift;
106 $self->{x} = $self->{targetX} + rand(500) - 250;
107 $self->{y} = $Games::PangZero::PhysicalScreenHeight;
108 $self->{speedY} = -sqrt( 2 * $Gravity * ($self->{y} - $self->{targetY}) );
109 $self->{speedX} = 0;
110 $self->{state} = 'entering';
111 }
112
113 sub InternalSetText {
114 my ($self, $text) = @_;
115 $self->SUPER::Clear();
116 $self->{text} = $text;
117 $self->{w} = Games::PangZero::Graphics::TextWidth($text) + 10;
118 }
119
120 sub SetText {
121 my ($self, $text) = @_;
122 $self->{parameter} = '';
123 $self->{basetext} = $text;
124 $self->InternalSetText($text);
125 }
126
127 sub SetParameter {
128 my ($self, $parameter) = @_;
129 $self->{parameter} = $parameter;
130 $self->InternalSetText($self->{basetext} . ' ' . $parameter);
131 }
132
133 sub Select {
134 my ($self) = @_;
135
136 foreach my $item (@Games::PangZero::GameObjects) {
137 $item->{selected} = 0 if ref $item eq 'Games::PangZero::MenuItem';
138 }
139 $self->{selected} = 1;
140 $Games::PangZero::Game->ShowTooltip( @{$self->{tooltip}} );
141 }
142
143 sub CanSelect {
144 my ($self) = @_;
145
146 return $self->{state} =~ /(?:entering|shown)/;
147 }
148
149 1;
0 package Games::PangZero::Music;
1
2 use SDL::Mixer ':init';
3 use SDL::Mixer::Samples;
4 use SDL::Mixer::Channels;
5 use SDL::Mixer::Music;
6 use SDL::Mixer::MixChunk;
7 use SDL::Mixer::MixMusic;
8
9 sub LoadMusic {
10 my ($filename) = @_;
11
12 return undef unless -f $filename;
13 return SDL::Mixer::Music::load_MUS($filename);
14 }
15
16 sub LoadSounds {
17 my $init_flags = SDL::Mixer::init( MIX_INIT_MP3 | MIX_INIT_OGG);
18
19 $Mixer = SDL::Mixer::open_audio( 22050, AUDIO_S16, 2, 1024 ) + 1;
20 unless($Mixer) {
21 warn SDL::get_error();
22 return 0;
23 }
24
25 my ($soundName, $fileName);
26 while (($soundName, $fileName) = each %Games::PangZero::Sounds) {
27 $Sounds{$soundName} = SDL::Mixer::Samples::load_WAV("$Games::PangZero::DataDir/$fileName");
28 }
29
30 if (-f "$Games::PangZero::DataDir/UPiPang.mp3" && ($init_flags & MIX_INIT_MP3)) {
31 $Games::PangZero::music = LoadMusic("$Games::PangZero::DataDir/UPiPang.mp3");
32 } elsif (-f "$Games::PangZero::DataDir/UPiPang.ogg" && ($init_flags & MIX_INIT_OGG)) {
33 $Games::PangZero::music = LoadMusic("$Games::PangZero::DataDir/UPiPang.ogg");
34 } else {
35 $Games::PangZero::music = LoadMusic("$Games::PangZero::DataDir/UPiPang.mid");
36 }
37 SetMusicEnabled($Games::PangZero::MusicEnabled);
38 }
39
40 sub PlaySound {
41 return unless $Games::PangZero::SoundEnabled;
42 my $sound = shift;
43 $Mixer and $Sounds{$sound} and SDL::Mixer::Channels::play_channel( -1, $Sounds{$sound}, 0 );
44 }
45
46 sub SetMusicEnabled {
47 return $Games::PangZero::MusicEnabled = 0 unless $Games::PangZero::music;
48 my $musicEnabled = shift;
49
50 $Games::PangZero::MusicEnabled = $musicEnabled ? 1 : 0;
51 if ( (not $musicEnabled) and SDL::Mixer::Music::playing_music() ) {
52 SDL::Mixer::Music::halt_music();
53 }
54 if ($musicEnabled and not SDL::Mixer::Music::playing_music()) {
55 SDL::Mixer::Music::play_music($Games::PangZero::music, -1);
56 }
57 }
58
59 1;
0 ##########################################################################
1 # PALETTE MANIPULATION
2 ##########################################################################
3
4 package Games::PangZero::Palette;
5
6 sub RgbToHsi {
7 my ($r, $g, $b) = @_;
8 my ($min, $max, $delta, $h, $s, $i);
9
10 if ($r > $g) {
11 $max = $r > $b ? $r : $b;
12 $min = $g < $b ? $g : $b;
13 } else {
14 $max = $g > $b ? $g : $b;
15 $min = $r < $b ? $r : $b;
16 }
17 $i = ($min + $max) / 2;
18 if ($min == $max) {
19 return (0, 0, $i);
20 }
21
22 $delta = ($max - $min);
23 if ($i < 128) {
24 $s = 255 * $delta / ($min + $max);
25 } else {
26 $s = 255 * $delta / (511 - $min - $max);
27 }
28
29 if ($r == $max) {
30 $h = ($g - $b) / $delta;
31 } elsif ($g == $max) {
32 $h = 2 + ($b - $r) / $delta;
33 } else {
34 $h = 4 + ($r - $g) / $delta;
35 }
36 $h = $h * 42.5;
37 $h += 255 if $h < 0;
38 $h -= 255 if $h > 255;
39
40 return ($h, $s, $i);
41 }
42
43 sub HsiToRgb {
44 my ($h, $s, $i) = @_;
45 my ($m1, $m2);
46
47 if ($s < 1) {
48 $i = int($i + 0.5);
49 return ($i, $i, $i);
50 }
51
52 if ($i < 128) {
53 $m2 = ($i * (255 + $s)) / 65025.0;
54 } else {
55 $m2 = ($i + $s - ($i * $s) / 255.0) / 255.0;
56 }
57 $m1 = ($i / 127.5) - $m2;
58
59 return (
60 &GetHsiValue( $m1, $m2, $h + 85),
61 &GetHsiValue( $m1, $m2, $h),
62 &GetHsiValue( $m1, $m2, $h - 85)
63 );
64 }
65
66 sub GetHsiValue {
67 my ($n1, $n2, $hue) = @_;
68 my ($value);
69
70 $hue -= 255 if ($hue > 255);
71 $hue += 255 if ($hue < 0);
72 if ($hue < 42.5) {
73 $value = $n1 + ($n2 - $n1) * ($hue / 42.5);
74 } elsif ($hue < 127.5) {
75 $value = $n2;
76 } elsif ($hue < 170) {
77 $value = $n1 + ($n2 - $n1) * ((170 - $hue) / 42.5);
78 } else {
79 $value = $n1;
80 }
81 return int($value * 255 + 0.5);
82 }
83
84 1;
0 ##########################################################################
1 package Games::PangZero::PanicGame;
2 ##########################################################################
3
4 @ISA = qw(Games::PangZero::PlayableGameBase);
5
6 sub new {
7 my ($class) = @_;
8 my $self = Games::PangZero::PlayableGameBase->new();
9 %{$self} = (%{$self},
10 'spawndelay' => 0,
11 'superballdelay' => 0,
12 'leveladvance' => 0,
13 'panicleveldesc' => undef,
14 );
15 bless $self, $class;
16 }
17
18 sub ResetGame {
19 my $self = shift;
20 $self->SUPER::ResetGame();
21 $self->{spawndelay} = 0;
22 $self->{superballdelay} = 2500 + $self->Rand(2500); # 25sec - 50sec
23 $self->{superballdelay} *= $Games::PangZero::DifficultyLevel->{superball};
24 }
25
26 sub SetGameSpeed {
27 my ($self) = @_;
28
29 $Games::PangZero::GameSpeed = $self->{leveldesc}->{gamespeed} * 0.8 * $Games::PangZero::DifficultyLevel->{speed};
30 }
31
32 sub SetGameLevel {
33 my ($self, $level) = @_;
34 my ($levelIndex);
35
36 $levelIndex = ($level > $#Games::PangZero::PanicLevels) ? $#Games::PangZero::PanicLevels : $level;
37 $self->{leveldesc} = $Games::PangZero::PanicLevels[$levelIndex];
38 die unless $self->{leveldesc};
39 $self->{leveladvance} = 0;
40 $self->SUPER::SetGameLevel($level);
41 }
42
43 sub AdvanceGame {
44 my ($self) = @_;
45
46 $self->SpawnBalls() if $Games::PangZero::GamePause <= 0;
47 $self->SUPER::AdvanceGame();
48 }
49
50 sub SpawnBalls {
51 my $self = shift;
52 my ($randmax, $rnd, $ballName, $balldesc, $deathBallCount, $earthquakeBallCount, $hasBonus);
53
54 --$self->{superballdelay};
55 if ($self->{superballdelay} <= 0) {
56 push @Games::PangZero::GameObjects, (
57 Games::PangZero::Ball::Spawn($Games::PangZero::BallDesc{sprintf('super%d', $self->Rand(2))}, -1, $self->Rand(40) < 20 ? 0 : 1) );
58 $self->{superballdelay} = (2500 + $self->Rand(2000)) * $Games::PangZero::DifficultyLevel->{superball}; # 25sec - 45sec
59 }
60
61 --$self->{spawndelay};
62 return if $self->{spawndelay} > 0;
63 $deathBallCount = $earthquakeBallCount = -1;
64 $randmax = 10000;
65 while ($self->{spawndelay} <= 0) {
66 if ($Games::PangZero::DifficultyLevel->{name} eq 'Miki') {
67 $balldesc = $Games::PangZero::BallDesc{'death'};
68 last;
69 }
70 $rnd = int($self->Rand($randmax));
71 $randmax = 0;
72
73 # We try to find the balldesc that falls at $rnd
74 my $ballRoulette = $self->{leveldesc}->{balls};
75 for (my $i = 0; $i < scalar @{$ballRoulette}; $i+=2) {
76 my $rouletteWeight = $ballRoulette->[$i+1];
77 $randmax += $rouletteWeight;
78 $rnd -= $rouletteWeight;
79 if ($rnd < 0) {
80 $ballName = $ballRoulette->[$i];
81 last;
82 }
83 }
84 next unless ($ballName); # $rnd too large.. We'll have a better $randmax this time!
85
86 ($balldesc) = $Games::PangZero::BallDesc{$ballName};
87 if ($balldesc->{class} eq 'DeathBall') {
88 next unless $Games::PangZero::DeathBallsEnabled;
89 $deathBallCount = Games::PangZero::DeathBall::CountDeathBalls() if $deathBallCount < 0; # Lazy counting
90 next if $deathBallCount >= 2;
91 }
92 if ($balldesc->{class} eq 'EarthquakeBall') {
93 next unless $Games::PangZero::EarthquakeBallsEnabled;
94 $earthquakeBallCount = Games::PangZero::EarthquakeBall::CountEarthquakeBalls if $earthquakeBallCount < 0;
95 next if $earthquakeBallCount >= 1;
96 }
97 if ($balldesc->{class} eq 'WaterBall') {
98 next unless $Games::PangZero::WaterBallsEnabled;
99 }
100 if ($balldesc->{class} eq 'SeekerBall') {
101 next unless $Games::PangZero::SeekerBallsEnabled;
102 }
103 last if $balldesc;
104 }
105
106 $hasBonus = 1 if ($balldesc->{width} >= 32) and ($self->Rand(1) < $Games::PangZero::DifficultyLevel->{bonusprobability});
107
108 push @Games::PangZero::GameObjects, ( Games::PangZero::Ball::Spawn($balldesc, -1, $self->Rand(40) < 20 ? 0 : 1, $hasBonus) );
109 $self->{spawndelay} = $self->{leveldesc}->{spawndelay} * $balldesc->{spawndelay} * 50;
110 $self->{spawndelay} /= ($Games::PangZero::NumGuys + 1) / 2;
111 $self->{spawndelay} *= $Games::PangZero::DifficultyLevel->{spawnmultiplier};
112 }
113
114 sub OnBallPopped {
115 my $self = shift;
116
117 ++$self->{leveladvance};
118 if ($self->{leveladvance} >= 18) {
119 Games::PangZero::Music::PlaySound('level');
120 $self->SetGameLevel($self->{level}+1);
121 }
122 }
123
124 sub DrawLevelIndicator {
125 my ($self, $x, $y) = @_;
126
127 $self->{levelIndicatorRect} = SDL::Rect->new($x, $y, 140, $self->{scoreBoardHeight}) unless $self->{levelIndicatorRect};
128 SDL::Video::fill_rect($Games::PangZero::App, $self->{levelIndicatorRect}, SDL::Video::map_RGB($Games::PangZero::App->format(), 0, 0, 0));
129 SDL::Video::blit_surface($Games::PangZero::LevelIndicatorSurface2, SDL::Rect->new($x, $y, $Games::PangZero::LevelIndicatorSurface2->w, $Games::PangZero::LevelIndicatorSurface2->h),
130 $Games::PangZero::App, SDL::Rect->new($x, $y, 0, 0));
131 SDL::Video::blit_surface($Games::PangZero::LevelIndicatorSurface, SDL::Rect->new(0, 0, 130 * $self->{leveladvance} / 17, 30), $Games::PangZero::App, SDL::Rect->new($x, $y, 0, 0));
132 SDLx::SFont::print_text( $Games::PangZero::App, $x + 25, $y + 3, 'Level ' . ($self->{level}+1) );
133
134 SDLx::SFont::print_text( $Games::PangZero::App, $x, $y + 40, sprintf('spd: %d/%d', $Games::PangZero::GameSpeed * 100, $self->{leveldesc}->{spawndelay}) ) if $self->{scoreBoardHeight} >= 64;
135
136 }
137
138 1;
0 ##########################################################################
1 package Games::PangZero::PlayableGameBase;
2 ##########################################################################
3
4 @ISA = qw( Games::PangZero::GameBase );
5
6 use strict;
7 use warnings;
8 use SDL::Video;
9
10 sub new {
11 my ($class) = @_;
12 my $self = Games::PangZero::GameBase->new();
13 %{$self} = (%{$self},
14 'playersalive' => 0,
15 'level' => 0,
16 'backgrounds' => [ qw( desert2.png l1.jpg l2.jpg l3.jpg l4.jpg l5.jpg l6.jpg l7.jpg l8.jpg l9.jpg )],
17 );
18 bless $self, $class;
19 }
20
21 sub ResetGame {
22 my $self = shift;
23 $self->SUPER::ResetGame();
24 $self->{playersalive} = 0;
25 $Games::PangZero::GamePause = 0;
26
27 foreach my $player (@Games::PangZero::Players) {
28 last if $player->{number} >= $Games::PangZero::NumGuys;
29 $self->SpawnPlayer($player);
30 }
31 $self->SetGameLevel(0);
32 $self->LayoutScoreBoard();
33 push @Games::PangZero::GameObjects, (Games::PangZero::FpsIndicator->new());
34 }
35
36 sub SetGameSpeed {
37 my $self = shift;
38
39 $Games::PangZero::GameSpeed = 0.8 * $Games::PangZero::DifficultyLevel->{speed};
40 }
41
42 sub SetGameLevel {
43 my ($self, $level) = @_;
44
45 $self->{level} = $level;
46 if (($level % 10) == 9) {
47 $self->SetBackground( int($level / 10) + 1 );
48 }
49 $self->SetGameSpeed();
50 }
51
52 sub SpawnPlayer {
53 my ($self, $player) = @_;
54 $player->{score} = 0;
55 $player->{scoreforbonuslife} = 200000;
56 $player->{lives} = 2;
57 $player->{startX} = ($Games::PangZero::ScreenWidth - $Games::PangZero::NumGuys * 60) / 2 + 60 * ($player->{number}+0.5) - 32;
58 $player->{respawn} = -1;
59 my $guy = Games::PangZero::Guy->new($player);
60 push @Games::PangZero::GameObjects, ($guy);
61 ++$self->{playersalive};
62 return $guy;
63 }
64
65 sub AdvanceGameObjects {
66 my ($self) = @_;
67
68 $self->SUPER::AdvanceGameObjects();
69 $self->RespawnPlayers();
70 --$Games::PangZero::GamePause if $Games::PangZero::GamePause > 0;
71 }
72
73 sub RespawnPlayers {
74 my $self = shift;
75
76 foreach my $player (@Games::PangZero::Players) {
77 last if $player->{number} >= $Games::PangZero::NumGuys;
78 if ($player->{respawn} > 0) {
79 --$player->{respawn};
80 $player->{score} = int($player->{respawn} / 100) if $self->{playersalive};
81 if ($player->{respawn} <= 0) {
82 my $guy = $self->SpawnPlayer($player);
83 $guy->{invincible} = 500;
84 }
85 }
86 }
87 }
88
89 sub PlayerNextLife {
90 my ($self, $guy) = @_;
91
92 $guy->DeleteHarpoons;
93 if ($guy->{player}->{lives}--) {
94 $guy->{x} = $guy->{player}->{startX};
95 $guy->{y} = $Games::PangZero::ScreenHeight - $guy->{h};
96 $guy->{state} = 'idle';
97 $guy->{speedY} = $guy->{speedX} = 0;
98 $guy->{invincible} = 500; # 0.5s
99 $guy->{killed} = 0;
100 $guy->{justkilled} = 0;
101 $self->{playerspawned} = 1;
102 } else {
103 # One player less
104 Games::PangZero::Highscore::AddHighScore($guy->{player}, $guy->{player}->{score}, $self->{level} + 1);
105 $guy->Delete();
106 --$self->{playersalive};
107 $guy->{player}->{respawn} = 6000; # 60s
108 }
109 }
110
111 sub PlayerDeathSequence {
112 my $self = shift;
113 my (@killedGuys, @deadGuys, $guy, $i);
114
115 $self->DrawGame();
116 Games::PangZero::Music::PlaySound('death');
117 Games::PangZero::Graphics::RenderBorder($Games::PangZero::WhiteBorderSurface, $Games::PangZero::App);
118 SDL::Video::flip($Games::PangZero::App);
119 $self->Delay(10);
120 Games::PangZero::Graphics::RenderBorder($Games::PangZero::RedBorderSurface, $Games::PangZero::App);
121 Games::PangZero::Graphics::RenderBorder($Games::PangZero::RedBorderSurface, $Games::PangZero::Background);
122 SDL::Video::flip($Games::PangZero::App);
123 $self->Delay(90);
124
125 @killedGuys = grep { $_->{justkilled}; } @Games::PangZero::GameObjects;
126 foreach $guy (@killedGuys) {
127 $guy->Clear();
128 $guy->{killed} = 1;
129 push @deadGuys, (Games::PangZero::DeadGuy->new($guy));
130 }
131 push @Games::PangZero::GameObjects, (@deadGuys);
132
133 for ($i = 0; $i < 300; ++$i) {
134 Games::PangZero::HandleEvents();
135 return if $self->{abortgame};
136 my $advance = $self->CalculateAdvances();
137 while ($advance--) {
138 foreach my $gameObject (@deadGuys) {
139 $gameObject->Advance();
140 }
141 }
142 $self->DrawGame();
143 last if $deadGuys[0]->{deleted};
144 }
145
146 foreach $guy (@killedGuys) {
147 $self->PlayerNextLife($guy);
148 }
149
150 Games::PangZero::Graphics::RenderBorder($Games::PangZero::BorderSurface, $Games::PangZero::App);
151 Games::PangZero::Graphics::RenderBorder($Games::PangZero::BorderSurface, $Games::PangZero::Background);
152 }
153
154 sub SuperKill {
155 my ($self, $guy) = @_;
156 my @gameObjects = @Games::PangZero::GameObjects;
157 my $sound = 0;
158 foreach my $ball (@gameObjects) {
159 next unless $ball->isa("Games::PangZero::Ball");
160 $ball->Pop($guy, 'superkill');
161 $sound = 1;
162 }
163 Games::PangZero::Music::PlaySound('pop') if $sound;
164 }
165
166 sub PopEveryBall {
167 my $self = shift;
168 my @guys = ();
169 my @gameObjects = @Games::PangZero::GameObjects;
170 foreach (@gameObjects) {
171 if ($_->isa('Games::PangZero::Ball')) {
172 $_->Pop(undef, 'meltdown');
173 } elsif ('Games::PangZero::Guy' eq ref $_) {
174 push @guys, $_;
175 }
176 }
177 return @guys;
178 }
179
180 sub DeathballMeltdown {
181 my ($self) = @_;
182 my ($i, $allKilled, @guys, @killedGuys, @deadGuys);
183 $self->{nocollision} = 1;
184 my $meltdown = Games::PangZero::Meltdown->new();
185 push @Games::PangZero::GameObjects, $meltdown;
186
187 for ($i = 0; $i < 300; ++$i) {
188 %Games::PangZero::Events = ();
189 Games::PangZero::HandleEvents();
190 return if $self->{abortgame};
191 my $advance = $self->CalculateAdvances();
192 while ($advance--) {
193 # TODO REINSTATE THIS IN 1.1!!! $self->PreAdvanceAction(); # Hook for something special
194 $self->SUPER::AdvanceGameObjects();
195 $Games::PangZero::GamePause = 0;
196 if ($meltdown->{bounce} and not $allKilled) {
197 $allKilled = 1;
198 @guys = $self->PopEveryBall();
199 foreach (@guys) {
200 $_->{killed} = 1;
201 push @deadGuys, Games::PangZero::DeadGuy->new($_);
202 push @killedGuys, $_;
203 }
204 push @Games::PangZero::GameObjects, (@deadGuys);
205 }
206 }
207 $self->DrawGame();
208 }
209
210 foreach (@killedGuys) {
211 $self->PlayerNextLife($_);
212 }
213
214 $self->{nocollision} = 0;
215 }
216
217
218 ##########################################################################
219 # GAME DRAWING
220 ##########################################################################
221
222 sub DrawScoreBoard {
223 my ($self) = @_;
224
225 $self->DrawLevelIndicator( 10, $self->{scoreBoardTop} );
226 for (my $i = 0; $i < $Games::PangZero::NumGuys; ++$i) {
227 $self->DrawScore( $Games::PangZero::Players[$i], $Games::PangZero::Players[$i]->{scoreX}, $Games::PangZero::Players[$i]->{scoreY} );
228 }
229 }
230
231 sub LayoutScoreBoard {
232 my ($self) = @_;
233 my $scoreBoardTop = $Games::PangZero::ScreenHeight + $Games::PangZero::ScreenMargin * 2 + 5;
234 my $scoreBoardHeight = $Games::PangZero::PhysicalScreenHeight - $scoreBoardTop;
235 my $rowHeight = 64;
236 my $leftMargin = 150;
237 my $rows = $Games::PangZero::NumGuys > 4 ? 2 : 1;
238 $rows = 1 if ($scoreBoardTop + $rows * $rowHeight > $Games::PangZero::PhysicalScreenHeight);
239
240 if ($scoreBoardTop + $rows * $rowHeight > $Games::PangZero::PhysicalScreenHeight) {
241 $rowHeight = 32;
242 $scoreBoardTop = $Games::PangZero::PhysicalScreenHeight - 32;
243 }
244
245 my $guysPerRow = int ($Games::PangZero::NumGuys / $rows + 0.5);
246 my $widthPerGuy = ($Games::PangZero::PhysicalScreenWidth - $leftMargin) / $guysPerRow;
247
248 for (my $i = 0; $i < $Games::PangZero::NumGuys; ++$i) {
249 $Games::PangZero::Players[$i]->{scoreX} = $leftMargin + ($i % $guysPerRow) * $widthPerGuy;
250 $Games::PangZero::Players[$i]->{scoreY} = $scoreBoardTop + int ($i / $guysPerRow) * $rowHeight;
251 $Games::PangZero::Players[$i]->{scoreRect} =
252 SDL::Rect->new($Games::PangZero::Players[$i]->{scoreX}, $Games::PangZero::Players[$i]->{scoreY}, 130, $rowHeight);
253 }
254
255 $self->{scoreBoardTop} = $scoreBoardTop;
256 $self->{scoreBoardHeight} = $scoreBoardHeight;
257 $self->{rowHeight} = $rowHeight;
258
259 }
260
261 sub DrawLevelIndicator {
262 my ($self, $x, $y) = @_;
263 $self->{levelIndicatorRect} = SDL::Rect->new($x, $y, 100, 32) unless $self->{levelIndicatorRect};
264 SDL::Video::fill_rect($Games::PangZero::App, $self->{levelIndicatorRect}, SDL::Video::map_RGB($Games::PangZero::App->format(), 0, 0, 0));
265 SDLx::SFont::print_text( $Games::PangZero::App, $x, $y + 3, 'Level ' . ($self->{level}+1) );
266
267 }
268
269 sub PrintNumber {
270 my ($self, $player, $x, $y, $number) = @_;
271 my $numberText = sprintf("%d", $number);
272 my $srcrect = SDL::Rect->new(0, 160, 16, 16);
273 my $dstrect = SDL::Rect->new($x, $y, 16, 16);
274
275 for (my $i = 0; $i < length($numberText); ++$i) {
276 $srcrect->x(320 + (ord(substr($numberText, $i)) - ord('0')) * 16);
277 $dstrect->x($x + $i * 16);
278 SDL::Video::blit_surface($player->{guySurface}, $srcrect, $Games::PangZero::App, $dstrect );
279 }
280 }
281
282 sub DrawScore {
283 my ($self, $player, $x, $y, $livesY) = @_;
284
285 SDL::Video::fill_rect($Games::PangZero::App, $player->{scoreRect}, SDL::Video::map_RGB($Games::PangZero::App->format(), 0, 0, 0));
286 $self->PrintNumber( $player, $x, $y, $player->{score});
287
288 $livesY = $self->{rowHeight} > 32 ? $y + 24 : $y + 16;
289 my $dstrect = SDL::Rect->new($x, $livesY, 32, 32);
290 my $srcrect = ($self->{rowHeight} <= 32)
291 ? SDL::Rect->new(320, 176, 16, 16)
292 : SDL::Rect->new(320, 128, 32, 32);
293
294 if ($player->{lives} > 3) {
295 SDL::Video::blit_surface($player->{guySurface}, $srcrect, $Games::PangZero::App, $dstrect );
296 $self->PrintNumber( $player, $x + $srcrect->w() + 8, $livesY + ($srcrect->h() - 16 ) / 2, $player->{lives} );
297 } else {
298 foreach my $i ( 0 .. $player->{lives}-1 ) {
299 $dstrect->x( $x + $i * ($srcrect->w() + 4) );
300 SDL::Video::blit_surface($player->{guySurface}, $srcrect, $Games::PangZero::App, $dstrect );
301 }
302 }
303 }
304
305 sub PreAdvanceAction {}
306
307 sub AdvanceGame {
308 my $self = shift;
309 %Games::PangZero::GameEvents = ();
310 $self->PreAdvanceAction(); # Hook for something special
311
312 if ($self->{superKillCount} > 0) {
313 if (--$self->{superKillDelay} <= 0) {
314 --$self->{superKillCount};
315 $self->{superKillDelay} = 50;
316 $self->SuperKill($self->{superKillGuy});
317 }
318 $Games::PangZero::GamePause = 0;
319 }
320
321 $self->AdvanceGameObjects();
322 if ($Games::PangZero::GameEvents{earthquake}) {
323 Games::PangZero::Music::PlaySound('quake');
324 foreach my $guy (@Games::PangZero::GameObjects) {
325 $guy->Earthquake($Games::PangZero::GameEvents{earthquake}) if ref $guy eq 'Games::PangZero::Guy';
326 }
327 }
328
329 if ($Games::PangZero::GameEvents{'pop'}) {
330 Games::PangZero::Music::PlaySound('pop');
331 }
332
333 if ($Games::PangZero::GameEvents{meltdown} and $Games::PangZero::DifficultyLevel->{name} ne 'Miki') {
334 $self->DeathballMeltdown();
335 } elsif ($Games::PangZero::GameEvents{kill} ) {
336 $self->PlayerDeathSequence();
337 return if $self->{playersalive} <= 0;
338 $Games::PangZero::GamePause = 200 if $Games::PangZero::GamePause < 200;
339 Games::PangZero::GamePause::Show();
340 } elsif ($Games::PangZero::GameEvents{magic}) {
341 if ($Games::PangZero::GamePause < 200) {
342 $Games::PangZero::GamePause = 200;
343 Games::PangZero::Music::PlaySound('pause');
344 Games::PangZero::GamePause::Show();
345 }
346 } elsif ($Games::PangZero::GameEvents{superpause}) {
347 if ($Games::PangZero::GamePause < 800) {
348 $Games::PangZero::GamePause = 800;
349 Games::PangZero::Music::PlaySound('pause');
350 Games::PangZero::GamePause::Show();
351 }
352 } elsif ($Games::PangZero::GameEvents{superkill}) {
353 $self->{superKillCount} = 5;
354 $self->{superKillDelay} = 0;
355 $self->{superKillGuy} = $Games::PangZero::GameEvents{superkillguy};
356 $self->{spawndelay} = 250;
357 $self->{superballdelay} += 1000; # 10 second penalty
358 my @gameObjects = @Games::PangZero::GameObjects;
359 foreach my $spawningBall (@gameObjects) {
360 $spawningBall->Delete if $spawningBall->{spawning};
361 }
362 }
363 }
364
365 sub Run {
366 my ($self) = shift;
367
368 $self->ResetGame();
369 Games::PangZero::GameTimer::ResetTimer();
370
371 $self->{superKillCount} = 0;
372 $self->{superKillDelay} = 0;
373 $self->{superKillGuy} = undef;
374
375 while (! $self->{abortgame}) {
376
377 # Calculate advance (how many game updates to perform)
378 my $advance = $self->CalculateAdvances();
379
380 # Advance the game
381
382 %Games::PangZero::Events = ();
383 Games::PangZero::HandleEvents();
384 while ($advance-- && ! $self->{abortgame}) {
385 $self->AdvanceGame();
386 }
387
388 if ($self->{playersalive} <= 0) {
389 my $gameoverSurface = SDL::Image::load("$Games::PangZero::DataDir/gameover.png");
390 my @gameObjects = @Games::PangZero::GameObjects;
391 foreach (@gameObjects) { $_->Delete() if ('Games::PangZero::DeadGuy' eq ref $_); }
392 $self->DrawGame();
393 SDL::Video::blit_surface($gameoverSurface, SDL::Rect->new(0, 0, $gameoverSurface->w, $gameoverSurface->h),
394 $Games::PangZero::App, SDL::Rect->new(
395 ($Games::PangZero::PhysicalScreenWidth - $gameoverSurface->w) / 2, $Games::PangZero::PhysicalScreenHeight / 2 - 100,
396 $gameoverSurface->w, $gameoverSurface->h));
397 SDL::Video::flip($Games::PangZero::App);
398 SDL::delay(1000);
399 for (my $i=0; $i < 20; ++$i) {
400 SDL::delay(100);
401 %Games::PangZero::Events = ();
402 Games::PangZero::HandleEvents();
403 last if $self->{abortgame};
404 last if %Games::PangZero::Events;
405 }
406 last;
407 }
408 $self->DrawGame();
409 }
410 $self->SUPER::ResetGame();
411 }
412
413 1;
0 ##########################################################################
1 package Games::PangZero::PlaybackGame;
2 ##########################################################################
3
4 @ISA = qw(Games::PangZero::PanicGame);
5 use strict;
6 use warnings;
7 use SDL::Video;
8
9 sub new {
10 my ($class, $numGuys, $difficultyLevel, $record, $rand, $messages) = @_;
11 my $self = Games::PangZero::PanicGame->new();
12 %{$self} = (%{$self},
13 'record' => $record,
14 'rand' => $rand,
15 'messages' => $messages,
16 );
17 bless $self, $class;
18 $self->InitPlayback($numGuys);
19 Games::PangZero::Config::SetDifficultyLevel($difficultyLevel);
20 return $self;
21 }
22
23 sub InitPlayback {
24 my ($self, $numGuys) = @_;
25 $self->{recordpointer} = 0;
26 $self->{randpointer} = 0;
27 $self->{oldnumguys} = $Games::PangZero::NumGuys;
28 $self->{olddifficultylevel} = $Games::PangZero::DifficultyLevelIndex;
29 $Games::PangZero::NumGuys = $numGuys;
30
31 for (my $i=0; $i < $numGuys; ++$i) {
32 $Games::PangZero::Players[$i]->{oldkeys} = $Games::PangZero::Players[$i]->{keys};
33 $Games::PangZero::Players[$i]->{keys} = [ "DLEFT$i", "DRIGHT$i", "DFIRE$i" ];
34 }
35 }
36
37 sub RestoreGameSettings {
38 my $self = shift;
39
40 for (my $i=0; $i < $Games::PangZero::NumGuys; ++$i) {
41 $Games::PangZero::Players[$i]->{keys} = $Games::PangZero::Players[$i]->{oldkeys};
42 delete $Games::PangZero::Players[$i]->{oldkeys};
43 }
44 $Games::PangZero::NumGuys = $self->{oldnumguys};
45 Games::PangZero::Config::SetDifficultyLevel($self->{olddifficultylevel});
46 }
47
48 sub CalculateAdvances {
49 my $self = shift;
50
51 return length($self->{record}) if $self->{skip};
52 return $self->SUPER::CalculateAdvances() * ($Games::PangZero::Keys{::SDLK_f()} ? 15 : 1);
53 }
54
55 sub Rand {
56 my $self = shift;
57
58 my $result = $self->{rand}->[$self->{randpointer}];
59 ++$self->{randpointer};
60 return $result;
61 }
62
63 sub PreAdvanceAction {
64 my $self = shift;
65 my ($record, $keys);
66
67 for (my $i=0; $i < $Games::PangZero::NumGuys; ++$i) {
68
69 $record = substr($self->{record}, $self->{recordpointer}++, 1);
70 $keys = $Games::PangZero::Players[$i]->{keys};
71 $Games::PangZero::Keys{$keys->[0]} = $record & 1;
72 $Games::PangZero::Keys{$keys->[1]} = $record & 2;
73 $Games::PangZero::Events{$keys->[2]} = $record & 4;
74 $Games::PangZero::GameEvents{superkill} = 1 if $Games::PangZero::NumGuys == 1 and $record & 8;
75 }
76
77 $self->{abortgame} = 1 if $self->{recordpointer} >= length $self->{record};
78
79 if ($self->{messages}) {
80 my $message = $self->{messages}->{$self->{recordpointer}};
81 $self->DisplayMessage($message) if $message;
82 }
83 }
84
85 sub DisplayMessage {
86 my ($self, $message) = @_;
87 my ($len, $adv) = (0, 0);
88 my $x = ( $Games::PangZero::PhysicalScreenWidth - &Games::PangZero::Graphics::TextWidth($message) ) / 2;
89 my $y = $Games::PangZero::PhysicalScreenHeight / 2;
90 $self->DrawGame();
91
92 while (1) {
93 Games::PangZero::HandleEvents();
94 return if $self->{abortgame};
95 my $advance = $self->CalculateAdvances();
96 $adv += $advance;
97 $len = int($adv / 5);
98
99 SDLx::SFont::print_text( $Games::PangZero::App,$x, $y, substr($message, 0, $len) );
100
101 SDL::Video::flip($Games::PangZero::App);
102 last if $len > length($message) + 15;
103 }
104 SDL::Video::blit_surface($Games::PangZero::Background, SDL::Rect->new(0, $y, $Games::PangZero::PhysicalScreenWidth, $y, 40), $Games::PangZero::App, SDL::Rect->new(0, $y, $Games::PangZero::App->w, $Games::PangZero::App->h));
105 }
106
107 1;
0 ##########################################################################
1 package Games::PangZero::Pop;
2 ##########################################################################
3
4 @ISA = qw(Games::PangZero::GameObject);
5
6 @Description = (
7 { 'xoffset' => 0, 'yoffset' => 0, 'srcx' => 128, 'srcy' => 0, 'sizex' => 128, 'sizey' => 106, },
8 { 'xoffset' => 0, 'yoffset' => 0, 'srcx' => 96, 'srcy' => 0, 'sizex' => 96, 'sizey' => 80, },
9 { 'xoffset' => 0, 'yoffset' => 0, 'srcx' => 64, 'srcy' => 0, 'sizex' => 64, 'sizey' => 53, },
10 { 'xoffset' => 0, 'yoffset' => 0, 'srcx' => 32, 'srcy' => 0, 'sizex' => 32, 'sizey' => 28, },
11 { 'xoffset' => 0, 'yoffset' => 0, 'srcx' => 16, 'srcy' => 0, 'sizex' => 16, 'sizey' => 15, },
12
13 { 'xoffset' => 0, 'yoffset' => 0, 'srcx' => 192, 'srcy' => 0, 'sizex' => 64, 'sizey' => 52, },
14 { 'xoffset' => 0, 'yoffset' => 0, 'srcx' => 96, 'srcy' => 0, 'sizex' => 32, 'sizey' => 28, },
15 { 'xoffset' => 0, 'yoffset' => 0, 'srcx' => 48, 'srcy' => 0, 'sizex' => 16, 'sizey' => 14, },
16 );
17
18 sub new {
19 my ($class, $x, $y, $index, $surface) = @_;
20 my $desc = $Games::PangZero::Pop::Description[$index],
21 my $self = Games::PangZero::GameObject->new();
22 %{$self} = ( %{$self},
23 'x' => $x + $desc->{xoffset},
24 'y' => $y + $desc->{yoffset},
25 'w' => $desc->{sizex},
26 'h' => $desc->{sizey},
27 'desc' => $desc,
28 'anim' => 0,
29 'surface' => $surface,
30 );
31 bless $self, $class;
32 }
33
34 sub Advance {
35 my $self = shift;
36
37 if (++$self->{anim} >= 20) {
38 $self->Delete();
39 }
40 }
41
42 sub Draw {
43 my $self = shift;
44 $self->TransferRect();
45 my $phase = int($self->{anim} / 5);
46 $phase = 3 if $phase > 3;
47 my $srcrect = SDL::Rect->new(
48 $self->{desc}->{srcx} + $phase * $self->{w},
49 $self->{desc}->{srcy},
50 $self->{w},
51 $self->{h} );
52 SDL::Video::blit_surface($self->{surface}, $srcrect, $Games::PangZero::App, $self->{rect} );
53 }
54
55 1;
0 ##########################################################################
1 package Games::PangZero::PowerWire;
2 ##########################################################################
3
4 @ISA = qw(Games::PangZero::Harpoon);
5 use strict;
6 use warnings;
7
8 sub Create {
9 return Games::PangZero::PowerWire->new(@_);
10 }
11
12 sub new {
13 my $class = shift;
14 my $self = Games::PangZero::Harpoon->new(@_);
15 %{$self} = ( %{$self},
16 'topdelay' => 200,
17 );
18 bless $self, $class;
19 }
20
21 sub Advance {
22 my $self = shift;
23
24 if ($self->{y} > 0) {
25 return $self->SUPER::Advance();
26 }
27 $self->{y} = 0;
28 --$self->{topdelay};
29 if ($self->{topdelay} <= 0) {
30 $self->Delete();
31 }
32 }
33
34 sub GetAnimPhase {
35 my $self = shift;
36
37 if ($self->{y} <= 0) {
38 return 0;
39 }
40 return $self->SUPER::GetAnimPhase();
41 }
42
43 1;
0 ##########################################################################
1 package Games::PangZero::RecordGame;
2 ##########################################################################
3
4 @ISA = qw(Games::PangZero::PanicGame);
5 use strict;
6 use warnings;
7 use SDL;
8
9 sub Rand {
10 my $self = shift;
11 my $result = int(rand($_[0]) * 100) / 100;
12 push @{$self->{rand}}, ($result);
13 return $result;
14 }
15
16 sub Rewind {
17 my $self = shift;
18 my ($recordEnd, $playback);
19
20 $recordEnd = length($self->{record}) - $Games::PangZero::NumGuys * 1000;
21 return if $recordEnd <= 0;
22 $self->{record} = substr($self->{record}, 0, $recordEnd);
23 $Games::PangZero::Game = $playback
24 = DemoPlaybackGame->new($Games::PangZero::NumGuys, $Games::PangZero::DifficultyLevel, $self->{record}, $self->{rand}, {});
25 $playback->{skip} = 1;
26 SDL::Video::blit_surface($Games::PangZero::Background, SDL::Rect->new(0, 0, $Games::PangZero::App->w, $Games::PangZero::App->h), $Games::PangZero::App, SDL::Rect->new(0, 0, $Games::PangZero::App->w, $Games::PangZero::App->h));
27 $playback->Run();
28
29 $playback->RestoreGameSettings();
30 %{$self} = %{$playback};
31 $Games::PangZero::Game = $self;
32 $self->{abortgame} = 0;
33 print "Splicing {rand}: original length is ", scalar(@{$self->{rand}}), "; playback randpointer is $playback->{randpointer}.\n";
34 splice @{$self->{rand}}, $playback->{randpointer};
35 SDL::Video::blit_surface($Games::PangZero::Background, SDL::Rect->new(0, 0, $Games::PangZero::App->w, $Games::PangZero::App->h), $Games::PangZero::App, SDL::Rect->new(0, 0, $Games::PangZero::App->w, $Games::PangZero::App->h));
36 $self->DrawGame();
37 %Games::PangZero::Events = %Games::PangZero::Keys = ();
38
39 while( not %Games::PangZero::Events ) {
40 Games::PangZero::HandleEvents();
41 SDL::delay(100);
42 }
43 GameTimer::ResetTimer();
44 }
45
46 sub PreAdvanceAction {
47 my $self = shift;
48 $self->Rewind() if $Games::PangZero::Events{::SDLK_F3()};
49
50 for (my $i = 0; $i < $Games::PangZero::NumGuys; ++$i) {
51 my $keys = $Games::PangZero::Players[$i]->{keys};
52 my $record = 0;
53 $record += 1 if $Games::PangZero::Keys{$keys->[0]};
54 $record += 2 if $Games::PangZero::Keys{$keys->[1]};
55 $record += 4 if $Games::PangZero::Events{$keys->[2]};
56 if ($Games::PangZero::Events{SDLK_F2()} and $Games::PangZero::NumGuys == 1) {
57 $record += 8;
58 $Games::PangZero::GameEvents{superkill} = 1;
59 }
60 $self->{record} .= $record;
61 }
62 }
63
64 1;
0 ##########################################################################
1 package Games::PangZero::SeekerBall;
2 ##########################################################################
3
4 @ISA = qw( Games::PangZero::Ball );
5 use strict;
6 use warnings;
7
8 sub new {
9 my $class = shift;
10 my $self = Games::PangZero::Ball->new(@_);
11 my @guys = grep {ref $_ eq 'Games::PangZero::Guy'} @Games::PangZero::GameObjects;
12 $self->{target} = $guys[$Games::PangZero::Game->Rand(scalar @guys)];
13 $self->{deltaX} = (-$self->{w} + $self->{target}->{w}) / 2;
14 die unless $self->{target};
15
16 bless $self, $class;
17 }
18
19 sub NormalAdvance {
20 my $self = shift;
21
22 my $multiplier = ($self->{y} > $Games::PangZero::ScreenHeight - 120) ? 0 : 25;
23 unless( $Games::PangZero::GamePause > 0 ) {
24 if ($self->{x} + $self->{speedX} * $multiplier > $self->{target}->{x} + $self->{deltaX}) {
25 $self->{speedX} -= 0.08;
26 } else {
27 $self->{speedX} += 0.08;
28 }
29 }
30 $self->SUPER::NormalAdvance();
31 }
32
33 sub AdjustChildren {
34 my ($self, $child1, $child2) = @_;
35
36 $self->SUPER::AdjustChildren($child1, $child2);
37 $child1->{speedX} *= 2;
38 $child1->{deltaX} -= 30;
39 $child1->{target} = $self->{target};
40 $child2->{speedX} *= 2;
41 $child2->{deltaX} += 30;
42 $child2->{target} = $self->{target};
43 }
44
45 sub GiveMagic {
46 }
47
48 sub Draw {
49 my $self = shift;
50
51 $self->SUPER::Draw();
52 my $guySurface = $self->{target}->{player}->{guySurface};
53 my $srcrect = ($self->{w} <= 32)
54 ? SDL::Rect->new(320, 176, 16, 16)
55 : SDL::Rect->new(320, 128, 32, 32);
56 my $dstrect = SDL::Rect->new(
57 $self->{x} + $Games::PangZero::ScreenMargin + ($self->{w} - $srcrect->w()) / 2,
58 $self->{y} + $Games::PangZero::ScreenMargin + ($self->{h} - $srcrect->h()) / 2 + 2, $srcrect->w, $srcrect->h);
59 SDL::Video::blit_surface($guySurface, $srcrect, $Games::PangZero::App, $dstrect);
60 }
61
62 1;
0 ##########################################################################
1 package Games::PangZero::SlowEffect;
2 ##########################################################################
3
4 @ISA = qw(Games::PangZero::GameObject);
5 use strict;
6 use warnings;
7
8 sub new {
9 my ($class) = @_;
10 my $self = Games::PangZero::GameObject->new();
11 %{$self} = ( %{$self},
12 'timeout' => 1500, # Lasts for 15s
13 );
14 # TODO Play a sound here
15 bless $self, $class;
16 return $self;
17 }
18
19 sub RemoveSlowEffects {
20 @Games::PangZero::GameObjects = grep { ref $_ ne 'Games::PangZero::SlowEffect' } @Games::PangZero::GameObjects;
21 }
22
23 sub Advance {
24 my ($self) = @_;
25 my ($timeout, $slowratio);
26
27 $timeout = --$self->{timeout};
28 if ( $timeout == 256 ) {
29 # TODO Play a sound here
30 }
31 if ( $timeout > 256 ) {
32 $Games::PangZero::GameSpeed = 0.2;
33 } elsif ( $timeout > 0 ) {
34 $Games::PangZero::Game->SetGameSpeed();
35 $slowratio = int(256 - $timeout) / 256;
36 $Games::PangZero::GameSpeed = $Games::PangZero::GameSpeed * $slowratio + 0.2 * (1.0 - $slowratio);
37 } else {
38 $Games::PangZero::Game->SetGameSpeed();
39 $self->Delete();
40 return;
41 }
42 }
43
44 sub Draw {
45 }
46
47 sub Clear {
48 }
49
50 1;
0 ##########################################################################
1 package Games::PangZero::SuperBall;
2 ##########################################################################
3
4 @ISA = qw(Games::PangZero::Ball);
5 use strict;
6 use warnings;
7
8 sub new {
9 my $class = shift;
10 my $self = Games::PangZero::Ball->new(@_);
11 $self->{effect} = 1; # 0 : superpause; 1 : superkill
12 bless $self, $class;
13 $self->SwitchEffect();
14 return $self;
15 }
16
17 sub SwitchEffect {
18 my $self = shift;
19 $self->{effect} = 1 - $self->{effect};
20 $self->{surface} = $Games::PangZero::BallSurfaces{($self->{effect} ? 'gold' : 'green') . ($self->{w} > 64 ? 1 : 2)};
21 }
22
23 sub Bounce {
24 my $self = shift;
25
26 $self->SwitchEffect();
27 }
28
29 sub SpawnChildren {
30 return ();
31 }
32
33 sub Pop {
34 my $self = shift;
35 my ($poppedBy) = @_;
36
37 $self->SUPER::Pop(@_);
38 if ($self->{effect} == 0) {
39 $Games::PangZero::GameEvents{superpause} = 1;
40 } else {
41 $Games::PangZero::GameEvents{superkill} = 1;
42 $Games::PangZero::GameEvents{superkillguy} = $poppedBy;
43 }
44 }
45
46 sub GiveMagic {
47 }
48
49 1;
0 ##########################################################################
1 package Games::PangZero::TutorialGame;
2 ##########################################################################
3
4 @ISA = qw(Games::PangZero::ChallengeGame);
5 use strict;
6 use warnings;
7
8 sub SetChallenge {
9 my ($self, $challenge) = @_;
10
11 $self->{challenge} = $challenge;
12 }
13
14 sub SetGameLevel {
15 my ($self, $level) = @_;
16
17 $self->Games::PangZero::PlayableGameBase::SetGameLevel($level);
18 $self->SpawnChallenge();
19 }
20
21 sub AdvanceGame {
22 my ($self) = @_;
23
24 if ($self->{nextlevel}) {
25 $self->{countDown} = 200;
26 delete $self->{nextlevel};
27 }
28 if ($self->{playerspawned}) {
29 $self->SpawnChallenge();
30 $self->{playerspawned} = 0;
31 }
32 if ($self->{countDown}) {
33 if (--$self->{countDown} < 1) {
34 $self->{abortgame} = 1;
35 }
36 }
37 $self->Games::PangZero::PlayableGameBase::AdvanceGame();
38 }
39
40 1;
0 ##########################################################################
1 package Games::PangZero::UpsideDownBall;
2 ##########################################################################
3
4 @ISA = qw( Games::PangZero::Ball );
5 use strict;
6 use warnings;
7
8 sub NormalAdvance {
9 my ($self) = @_;
10
11 $self->{speedY} = -$self->{speedY};
12 $self->{y} = $Games::PangZero::ScreenHeight - $self->{h} - $self->{y};
13 $self->SUPER::NormalAdvance();
14 $self->{speedY} = -$self->{speedY};
15 $self->{y} = $Games::PangZero::ScreenHeight - $self->{h} - $self->{y};
16 }
17
18 1;
0 ##########################################################################
1 package Games::PangZero::WaterBall;
2 ##########################################################################
3
4 @ISA = qw( Games::PangZero::Ball );
5
6 sub Bounce {
7 my $self = shift;
8 if ($self->{desc}->{nextgen}) {
9 $self->{bonus} = 0;
10 $self->Pop(undef, '');
11 }
12 }
13
14 1;
0 ##########################################################################
1 package Games::PangZero::XmasBall;
2 ##########################################################################
3
4 @ISA = qw(Games::PangZero::Ball);
5 use strict;
6 use warnings;
7
8 sub SpawnChildren {
9 return ();
10 }
11
12 sub Pop {
13 my $self = shift;
14 $self->SUPER::Pop(@_);
15 my $bonusdrop = Games::PangZero::BonusDrop->new($self);
16 my @collectedSubs = ( \&OnCollectedLife, \&OnCollectedScore, \&OnCollectedScore, \&OnCollectedInvulnerability, \&OnCollectedInvulnerability );
17 if ($Games::PangZero::Game->Rand(2 * scalar @collectedSubs) < scalar @collectedSubs) {
18 $bonusdrop->{desc} = { 'srcRect' => SDL::Rect->new(0, 0, 32, 32), };
19 $bonusdrop->SetOnCollectedSub( $collectedSubs[int $Games::PangZero::Game->Rand(scalar @collectedSubs)] );
20 }
21 push @Games::PangZero::GameObjects, $bonusdrop;
22 }
23
24 sub GiveMagic {
25 }
26 sub GiveBonus {
27 }
28
29 sub OnCollectedLife {
30 my ($bonus, $guy) = @_;
31 $guy->{player}->{lives}++;
32 Games::PangZero::Music::PlaySound('bonuslife');
33 }
34
35 sub OnCollectedScore {
36 my ($bonus, $guy) = @_;
37 $guy->GiveScore(50000);
38 Games::PangZero::Music::PlaySound('score');
39 }
40
41 sub OnCollectedInvulnerability {
42 my ($bonus, $guy) = @_;
43 $guy->{invincible} = 500;
44 }
45
46 1;
0 package Games::PangZero;
1
2 $Cheat = 0;
3 $VERSION = '1.4.1';
4 $DataDir = ''; # Set it to a path to avoid autodetection (e.g. /opt/pangzero/data)
5
6 =comment
7
8 ##########################################################################
9 #
10 # PANG ZERO
11 # Copyright (C) 2006 by UPi <upi at sourceforge.net>
12 #
13 ##########################################################################
14
15 This program is free software; you can redistribute it and//or modify
16 it under the terms of the GNU General Public License version 2, as
17 published by the Free Software Foundation.
18
19 This program is distributed in the hope that it will be useful,
20 but WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 GNU General Public License for more details.
23
24 You should have received a copy of the GNU General Public License
25 along with this program; if not, write to the Free Software
26 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27
28
29 ##########################################################################
30 # TODO:
31 ##########################################################################
32 * P4 Bonus probability is balldesc based.
33 * P3 Tour mode..?
34 * P5 Graphics and help for machine gun and power wire
35 * P5 Demo of beating the game at 'normal' difficulty
36 * P4 Even more forgiving collision detection (?)
37 * P4 Smooth numbers in the scoreboard
38 * P3 RotoZoomer smooth parameter to eliminate warning..
39 * P3 Set DataDir with command line parameter.
40 * P2 Roll your own game
41 * P4 Reorg menu: MenuItem->Update(), MenuItem->Left(), MenuItem->Right(), ...
42
43 Next release:
44 * Sound effect for matrix effect...
45 * Handle possible HST corruption if game exits while merging scores
46
47
48 ##########################################################################
49 # QUICK GUIDE FOR WOULD-BE DEVELOPERS
50 ##########################################################################
51
52 '
53 This file contains the entire source code of Pang Zero. I know that this is
54 an odd design, but it works for me. You can split the file easily if you
55 want to.
56
57 The parts of the file are organized like this:
58
59 1. INITIALIZATION OF GLOBAL OBJECTS (configuration, balls, levels, etc)
60 2. HIGH SCORE TABLE
61 3. GAME OBJECT PACKAGES
62 4. UTILITY PACKAGES AND METHODS
63 5. GAMEBASE AND DESCENDENT PACKAGES (includes the menu)
64 6. "MAIN" PROGRAM LOOP
65 '
66 =cut
67
68 use SDL ':init';
69 use SDL::Surface;
70 use SDL::Palette;
71 use SDL::PixelFormat;
72 use SDL::Video;
73 use SDL::Event;
74 use SDL::Events;
75 use SDL::Color;
76 use SDL::Config;
77 use SDL::Cursor;
78 use SDL::GFX::Rotozoom;
79 use SDL::Joystick;
80 use SDL::Mouse;
81 use SDL::Image;
82 use SDLx::SFont;
83
84 use Carp;
85
86 # SDL objects
87
88 use vars qw (
89 $App $VERSION $RotoZoomer $Background $ScoreFont $MenuFont $GlossyFont
90 %BallSurfaces
91 $BorderSurface $WhiteBorderSurface $RedBorderSurface $BonusSurface $LevelIndicatorSurface $LevelIndicatorSurface2
92 $WhiteHarpoonSurface
93 %Sounds $Mixer
94 );
95
96 # Pang Zero variables and objects
97
98 use vars qw (
99 $DataDir $ScreenHeight $ScreenWidth $PhysicalScreenWidth $PhysicalScreenHeight $ScreenMargin
100 $SoundEnabled $MusicEnabled $FullScreen $ShowWebsite
101 $DeathBallsEnabled $EarthquakeBallsEnabled $WaterBallsEnabled $SeekerBallsEnabled $Slippery
102 @DifficultyLevels $DifficultyLevelIndex $DifficultyLevel
103 @WeaponDurations $WeaponDuration $WeaponDurationIndex
104 @GameObjects %GameEvents $GameSpeed $GamePause $Game
105 @Players @GuyImageFiles @GuyColors $NumGuys
106 @BallDesc %BallDesc @ChallengeLevels @PanicLevels
107 $UnicodeMode $LastUnicodeKey %Keys %Events %MenuEvents );
108
109
110 use Games::PangZero::Globals;
111 use Games::PangZero::GameObject;
112 use Games::PangZero::Ball;
113 use Games::PangZero::BonusDrop;
114 use Games::PangZero::GameBase;
115 use Games::PangZero::PlayableGameBase;
116 use Games::PangZero::ChallengeGame;
117 use Games::PangZero::Config;
118 use Games::PangZero::DeadGuy;
119 use Games::PangZero::DeathBall;
120 use Games::PangZero::PanicGame;
121 use Games::PangZero::DemoGame;
122 use Games::PangZero::PlaybackGame;
123 use Games::PangZero::DemoPlaybackGame;
124 use Games::PangZero::RecordGame;
125 use Games::PangZero::DemoRecordGame;
126 use Games::PangZero::EarthquakeBall;
127 use Games::PangZero::FpsIndicator;
128 use Games::PangZero::FragileBall;
129 use Games::PangZero::GamePause;
130 use Games::PangZero::GameTimer;
131 use Games::PangZero::Graphics;
132 use Games::PangZero::Guy;
133 use Games::PangZero::Harpoon;
134 use Games::PangZero::HalfCutter;
135 use Games::PangZero::Hexa;
136 use Games::PangZero::Highscore;
137 use Games::PangZero::Joystick;
138 use Games::PangZero::MachineGun;
139 use Games::PangZero::Meltdown;
140 use Games::PangZero::Menu;
141 use Games::PangZero::MenuItem;
142 use Games::PangZero::Music;
143 use Games::PangZero::Palette;
144 use Games::PangZero::Pop;
145 use Games::PangZero::PowerWire;
146 use Games::PangZero::SeekerBall;
147 use Games::PangZero::SlowEffect;
148 use Games::PangZero::SuperBall;
149 use Games::PangZero::TutorialGame;
150 use Games::PangZero::UpsideDownBall;
151 use Games::PangZero::WaterBall;
152 use Games::PangZero::XmasBall;
153
154 sub SaveScreenshot {
155 my $i = 0;
156 my $filename;
157 do { $filename = sprintf("screenshot%03d.bmp", $i); ++$i } while (-f $filename);
158 SDL::Video::save_BMP($App, $filename);
159 }
160
161 sub Pause {
162 my $pausedSurface = SDL::Image::load("$DataDir/paused.png");
163 my $event = SDL::Event->new();
164
165 SDL::Video::blit_surface($pausedSurface, SDL::Rect->new(0, 0, $pausedSurface->w, $pausedSurface->h),
166 $App, SDL::Rect->new(($PhysicalScreenWidth - $pausedSurface->w) / 2, $PhysicalScreenHeight / 2 - 100, 0, 0));
167 $App->sync();
168 $Keys = ();
169 $Events = ();
170 while (1) { # Paused, wait for keypress
171 SDL::Events::pump_events();
172 SDL::Events::wait_event($event);
173 last if $event->type() == SDL_KEYDOWN and $event->key_sym == SDLK_p;
174 if ($event->type() == SDL_KEYDOWN and $event->key_sym == SDLK_ESCAPE) {
175 $Game->{abortgame} = 1;
176 last;
177 }
178 $Game->Exit() if $event->type() == SDL_QUIT;
179 }
180 SDL::Video::blit_surface($Background, SDL::Rect->new(0, 0, $App->w, $App->h), $App, SDL::Rect->new(0, 0, $App->w, $App->h));
181 Games::PangZero::GameTimer::ResetTimer();
182 }
183
184 sub HandleEvents {
185 my ($readBothJoystickAxes) = @_;
186 my ($event, $type);
187
188 $event = SDL::Event->new;
189 while (1) {
190 SDL::Events::pump_events();
191 last unless SDL::Events::poll_event($event);
192 $type = $event->type();
193
194 if ($type == SDL_QUIT) {
195 $Game->Exit();
196 }
197 elsif ($type == SDL_KEYDOWN) {
198 my $keypressed = $event->key_sym;
199 if ($keypressed == SDLK_ESCAPE) {
200 $Game->{abortgame} = 1;
201 } elsif ($keypressed == SDLK_F1) {
202 SaveScreenshot();
203 } elsif ($keypressed == SDLK_p and not $UnicodeMode) {
204 Pause();
205 } else {
206 $Keys{$keypressed} = 1;
207 $Events{$keypressed} = 1;
208 $MenuEvents{UP} = 1 if $keypressed == SDLK_UP();
209 $MenuEvents{DOWN} = 1 if $keypressed == SDLK_DOWN();
210 $MenuEvents{LEFT} = 1 if $keypressed == SDLK_LEFT();
211 $MenuEvents{RIGHT} = 1 if $keypressed == SDLK_RIGHT();
212 $MenuEvents{BUTTON} = 1 if $keypressed == SDLK_RETURN();
213 $MenuEvents{BACKSP} = 1 if $keypressed == SDLK_BACKSPACE;
214 $LastUnicodeKey = $event->key_unicode() if $UnicodeMode;
215 }
216 }
217 elsif ($type == SDL_KEYUP) {
218 my $keypressed = $event->key_sym;
219 $Keys{$keypressed} = 0;
220 }
221 }
222
223 Games::PangZero::Joystick::ReadJoystick($readBothJoystickAxes);
224 }
225
226 sub DoMenu {
227 my $oldScreenHeight = $ScreenHeight;
228 my $oldScreenWidth = $ScreenWidth;
229 $ScreenWidth = $PhysicalScreenWidth - $ScreenMargin * 2;
230 $ScreenWidth = int($ScreenWidth / 32) * 32;
231 $Game = Games::PangZero::Menu->new();
232 my $retval = $Game->Run();
233 Games::PangZero::Config::SaveConfig();
234
235 $ScreenWidth = $oldScreenWidth;
236 $ScreenHeight = $oldScreenHeight;
237
238 return $retval;
239 }
240
241 sub DoDemo {
242 my $messages = $Game->{messages} = {
243 1 => "Use harpoons to pop the balloons",
244 160 => "Pop them, and they split in two",
245 300 => "Pop them again and again",
246 530 => "Popping the smallest ballons makes them disappear",
247 630 => "The green Super Ball gives you a lot of free time",
248 720 => "Use this time wisely!",
249 1150 => "Making a lot of small balls is dangerous! Observe...",
250 1600 => "Don't let the balloons touch you!",
251 1708 => "Dying gives you some free time.",
252 1900 => "So does shooting the flashing balloons.",
253 2370 => "The yellow Super Ball destroys every balloon",
254 2650 => "And now... THE SPECIAL BALL DEMO!",
255 2950 => "The Bouncy Ball bounces twice as high as normal balls.",
256 3620 => "See?",
257 4222 => "The Hexa Ball is weightless and travels in a straight line.",
258 4500 => "So does its offspring.",
259 5210 => "The blue Water Ball splits every time it bounces.",
260 5900 => "This can cause a tide of small balls!",
261 6630 => "The Earthquake Ball will really shake you up.",
262 7100 => "Its offspring is not as dangerous, but still annoying.",
263 7800 => "Behold, the Death Ball. It cannot be killed!!!",
264 8120 => "No, really, it can't! In fact, shooting it makes it multiply.",
265 8220 => "If you avoid it for 20 secs, Deathballs will get bored and go away.",
266 8320 => "Also, the yellow Super Ball will destroy the Deathballs for you.",
267 8800 => "Shooting it too much will lead to the Deathball Meltdown.",
268 9550 => "Last but not least: here's the Seeker Ball!",
269 9900 => "This ball will stalk you forever.",
270 10100 => "Whew! This concludes the Special Ball Demo. Have fun playing!",
271 };
272 my $record = 0 x 23 . 1 x 18 . 0 x 19 . 2 x 7 . 0 x 31 . 4 x 1 . 0 x 44 . 2 x 43 . 0 x 7 . 4 x 1 . 0 x 22 . 1 x 10 . 0 x 17 . 2 x 38 . 0 x 16 . 2 x 22 . 0 x 42 . 4 x 1 . 0 x 54 . 1 x 43 . 0 x 2 . 4 x 1 . 0 x 28 . 1 x 27 . 0 x 8 . 4 x 1 . 0 x 98 . 2 x 19 . 0 x 11 . 4 x 1 . 0 x 27 . 1 x 24 . 5 x 1 . 1 x 1 . 0 x 17 . 1 x 9 . 0 x 2 . 4 x 1 . 0 x 51 . 2 x 19 . 0 x 14 . 4 x 1 . 0 x 48 . 1 x 14 . 0 x 2 . 4 x 1 . 0 x 51 . 1 x 8 . 0 x 25 . 4 x 1 . 0 x 49 . 2 x 25 . 0 x 3 . 4 x 1 . 0 x 53 . 1 x 12 . 0 x 9 . 4 x 1 . 0 x 101 . 1 x 9 . 0 x 4 . 4 x 1 . 0 x 68 . 1 x 7 . 5 x 1 . 0 x 75 . 2 x 14 . 0 x 2 . 4 x 1 . 0 x 64 . 2 x 38 . 0 x 3 . 4 x 1 . 0 x 13 . 2 x 13 . 0 x 25 . 2 x 25 . 0 x 5 . 4 x 1 . 0 x 54 . 4 x 1 . 0 x 69 . 1 x 3 . 0 x 15 . 4 x 1 . 0 x 19 . 2 x 17 . 0 x 94 . 2 x 28 . 0 x 27 . 2 x 52 . 0 x 22 . 4 x 1 . 0 x 34 . 1 x 28 . 0 x 34 . 1 x 29 . 0 x 24 . 4 x 1 . 0 x 80 . 1 x 15 . 0 x 116 . 1 x 10 . 5 x 1 . 1 x 1 . 0 x 808 . 2 x 35 . 0 x 16 . 4 x 1 . 0 x 55 . 1 x 46 . 5 x 1 . 1 x 2 . 0 x 368 . 8 x 1 . 0 x 487 . 1 x 27 . 0 x 48 . 2 x 8 . 6 x 1 . 2 x 7 . 0 x 7 . 2 x 18 . 6 x 1 . 2 x 11 . 0 x 119 . 1 x 1 . 0 x 167 . 8 x 1 . 0 x 1177 . 2 x 24 . 0 x 121 . 2 x 22 . 0 x 2 . 4 x 1 . 0 x 31 . 2 x 15 . 0 x 9 . 2 x 4 . 6 x 1 . 2 x 5 . 0 x 8 . 2 x 10 . 0 x 69 . 8 x 1 . 0 x 338 . 1 x 87 . 0 x 152 . 2 x 52 . 0 x 112 . 1 x 27 . 0 x 2 . 4 x 1 . 0 x 71 . 1 x 41 . 0 x 4 . 4 x 1 . 0 x 65 . 2 x 24 . 0 x 209 . 8 x 1 . 0 x 579 . 1 x 3 . 0 x 13 . 2 x 3 . 0 x 14 . 4 x 1 . 0 x 58 . 2 x 28 . 0 x 9 . 4 x 1 . 0 x 93 . 2 x 37 . 0 x 26 . 2 x 11 . 0 x 22 . 2 x 9 . 6 x 1 . 2 x 6 . 6 x 1 . 2 x 7 . 6 x 1 . 2 x 5 . 6 x 1 . 2 x 7 . 6 x 1 . 2 x 16 . 6 x 1 . 2 x 9 . 6 x 1 . 2 x 20 . 1 x 7 . 0 x 21 . 2 x 13 . 1 x 3 . 5 x 1 . 1 x 8 . 5 x 1 . 1 x 6 . 5 x 1 . 1 x 6 . 5 x 1 . 1 x 35 . 0 x 6 . 5 x 1 . 1 x 6 . 0 x 11 . 2 x 12 . 6 x 1 . 2 x 8 . 6 x 1 . 1 x 6 . 5 x 1 . 1 x 3 . 0 x 3 . 4 x 1 . 1 x 3 . 0 x 3 . 5 x 1 . 1 x 4 . 0 x 15 . 1 x 2 . 5 x 1 . 1 x 4 . 0 x 4 . 5 x 1 . 1 x 7 . 5 x 1 . 1 x 4 . 0 x 5 . 1 x 6 . 0 x 2 . 4 x 1 . 1 x 4 . 0 x 4 . 4 x 1 . 0 x 3 . 1 x 4 . 0 x 3 . 4 x 1 . 0 x 10 . 2 x 14 . 6 x 1 . 2 x 2 . 1 x 5 . 5 x 1 . 1 x 6 . 5 x 1 . 1 x 5 . 5 x 1 . 1 x 2 . 0 x 3 . 4 x 1 . 1 x 3 . 0 x 3 . 4 x 1 . 1 x 3 . 0 x 2 . 1 x 2 . 5 x 1 . 1 x 4 . 5 x 1 . 1 x 6 . 5 x 1 . 1 x 7 . 5 x 1 . 1 x 7 . 0 x 2 . 2 x 4 . 6 x 1 . 2 x 4 . 0 x 2 . 2 x 2 . 6 x 1 . 2 x 6 . 6 x 1 . 2 x 7 . 1 x 5 . 5 x 1 . 1 x 1 . 0 x 5 . 2 x 6 . 6 x 1 . 2 x 2 . 0 x 4 . 1 x 3 . 5 x 1 . 1 x 1 . 0 x 8 . 2 x 4 . 6 x 1 . 2 x 1 . 0 x 3 . 1 x 4 . 0 x 7 . 2 x 6 . 6 x 1 . 2 x 8 . 6 x 1 . 2 x 6 . 6 x 1 . 2 x 3 . 0 x 3 . 1 x 3 . 0 x 10 . 2 x 7 . 0 x 2 . 1 x 1 . 5 x 1 . 1 x 5 . 0 x 2 . 4 x 1 . 1 x 2 . 0 x 4 . 4 x 1 . 0 x 2 . 1 x 2 . 0 x 3 . 1 x 1 . 5 x 1 . 1 x 5 . 5 x 1 . 1 x 3 . 0 x 4 . 5 x 1 . 1 x 1 . 0 x 4 . 4 x 1 . 1 x 2 . 0 x 4 . 4 x 1 . 1 x 1 . 0 x 6 . 4 x 1 . 1 x 1 . 0 x 5 . 1 x 1 . 5 x 1 . 1 x 2 . 0 x 3 . 1 x 1 . 5 x 1 . 1 x 6 . 5 x 1 . 1 x 7 . 5 x 1 . 1 x 6 . 5 x 1 . 1 x 6 . 5 x 1 . 1 x 7 . 0 x 12 . 2 x 7 . 0 x 2 . 4 x 1 . 2 x 2 . 0 x 4 . 4 x 1 . 0 x 135 . 8 x 1 . 0 x 252 . 1 x 57 . 0 x 199 . 2 x 37 . 0 x 3 . 1 x 1 . 5 x 1 . 1 x 29 . 0 x 21 . 1 x 30 . 0 x 37 . 4 x 1 . 0 x 77 . 1 x 17 . 0 x 4 . 2 x 126 . 3 x 1 . 1 x 52 . 5 x 1 . 1 x 64 . 0 x 39 . 8 x 1 . 0 x 140;
273 my $rand = [2199.02,1.12,0.11,1.24,0.11,1.21,0.33,0.19,0.16,0.12,0.07,0.28,0.68];
274
275 Games::PangZero::Config::SaveConfig();
276 $Game = Games::PangZero::DemoPlaybackGame->new( 1, 3, $record, $rand, $messages );
277 $Game->Run();
278 Games::PangZero::Config::LoadConfig();
279 $Game->RestoreGameSettings();
280 }
281
282 sub DoRecordDemo {
283 my ($numguys, $difficulty) = ($NumGuys, $DifficultyLevelIndex);
284
285 $NumGuys = 1;
286 Games::PangZero::Config::SetDifficultyLevel(3);
287 $Game = Games::PangZero::DemoRecordGame->new();
288 $Game->Run();
289 print "\n\$record = '", $Game->{record}, "';\n";
290 print "\$rand = [", join( ', ', @{$Game->{rand}} ), "];\n\n";
291 $NumGuys = $numguys;
292 Games::PangZero::Config::SetDifficultyLevel($difficulty);
293 }
294
295
296 ##########################################################################
297 # MAIN PROGRAM STARTS HERE
298 ##########################################################################
299
300 sub Initialize {
301
302 eval { SDL::init(SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) };
303 die "Unable to initialize SDL: $@" if $@;
304
305 Games::PangZero::Config::FindDataDir();
306 Games::PangZero::Config::LoadConfig();
307 print "Data directory is at '$DataDir'\n";
308 my $sdlFlags;
309 if (Games::PangZero::Config::IsMicrosoftWindows()) {
310 $sdlFlags = SDL_ANYFORMAT;
311 } else {
312 if ($Games::PangZero::FullScreen) {
313 $sdlFlags = SDL_HWSURFACE | SDL_HWACCEL | SDL_DOUBLEBUF | SDL_ANYFORMAT | SDL_FULLSCREEN;
314 } else {
315 $sdlFlags = SDL_HWSURFACE | SDL_HWACCEL | SDL_DOUBLEBUF | SDL_ANYFORMAT;
316 }
317 }
318
319 ($PhysicalScreenWidth, $PhysicalScreenHeight) = Games::PangZero::Graphics::FindVideoMode();
320
321 my $icon = SDL::Video::load_BMP("$DataDir/icon.bmp");
322 SDL::Video::set_color_key($icon, SDL_SRCCOLORKEY, SDL::Color->new(0, 255, 0));
323 SDL::Video::wm_set_icon($icon);
324 $App = SDL::Video::set_video_mode($PhysicalScreenWidth,
325 $PhysicalScreenHeight,
326 32, $sdlFlags);
327 SDL::Video::wm_set_caption("Pang Zero $VERSION", "Pang Zero $VERSION");
328 SDL::Mouse::show_cursor(0);
329
330 $Background = SDL::Surface->new( Games::PangZero::Config::IsMicrosoftWindows() ? SDL_SWSURFACE() : SDL_HWSURFACE(), $App->w, $App->h, 16);
331 $Background = SDL::Video::display_format($Background);
332 $ScoreFont = SDLx::SFont->new("$DataDir/brandybun3.png");
333 $MenuFont = SDLx::SFont->new("$DataDir/font2.png");
334 $GlossyFont = SDLx::SFont->new("$DataDir/glossyfont.png");
335
336 Games::PangZero::Graphics::LoadSurfaces();
337 Games::PangZero::Music::LoadSounds();
338 Games::PangZero::Joystick::InitJoystick();
339 }
340
341 sub MainLoop {
342 my $menuResult = DoMenu();
343 if ($menuResult eq 'demo') {
344 DoDemo();
345 }
346
347 #$Game = Games::PangZero::DemoRecordGame->new();
348 $Game = ($menuResult eq 'challenge')
349 ? Games::PangZero::ChallengeGame->new()
350 : Games::PangZero::PanicGame->new();
351 @Games::PangZero::Highscore::UnsavedHighScores = ();
352 $Game->Run();
353
354 bless $Game, 'Games::PangZero::Menu';
355 $Games::PangZero::MenuFont->use();
356 Games::PangZero::Highscore::MergeUnsavedHighScores($menuResult eq 'challenge' ? 'Cha' : 'Pan');
357
358 return;
359
360 my ($filename, $i) = ('', 1);
361 do { $filename = sprintf("record%03d.txt", $i); ++$i } while (-f $filename);
362 open RECORD, ">$filename";
363 print RECORD "NumGuys = $NumGuys;\nDifficultyLevelIndex = $DifficultyLevelIndex;\nrecord = '$Game->{record}';\n",
364 "DeathBallsEnabled = $DeathBallsEnabled;\nEarthquakeBallsEnabled = $EarthquakeBallsEnabled;\n",
365 "WaterBallsEnabled = $WaterBallsEnabled;\nSeekerBallsEnabled = $SeekerBallsEnabled;\n",
366 'rand = [', join(',', @{$Game->{rand}}), "];\n\n";
367 close RECORD;
368
369 $Game = Games::PangZero::DemoPlaybackGame->new($NumGuys, $DifficultyLevelIndex, $Game->{record}, $Game->{rand}, {});
370 $Game->Run();
371 $Game->RestoreGameSettings();
372 }
373
374 sub ShowErrorMessage {
375 my ($message) = @_;
376 print "Pang Zero $VERSION died:\n$message\n";
377 }
378
379 sub ShowWebPage {
380 my ($url) = @_;
381
382 return if $ENV{PANGZERO_TEST};
383
384 if (Games::PangZero::Config::IsMicrosoftWindows()) {
385 my $ws = "$DataDir/website.html";
386 $ws =~ s/\//\\\\/g;
387 exec 'cmd', '/c', $ws;
388 exit;
389 } elsif ($ENV{'DISPLAY'}) {
390 my @tryCommands = (
391 "which gnome-open > /dev/null 2>&1 && (gnome-open $url&)",
392 "which mozilla-firefox > /dev/null 2>&1 && (mozilla-firefox $url&)",
393 "which firefox > /dev/null 2>&1 && (firefox $url&)",
394 "which mozilla > /dev/null 2>&1 && (mozilla $url&)",
395 "which konqueror > /dev/null 2>&1 && (konqueror $url&)",
396 );
397 foreach (@tryCommands) {
398 return if system($_) == 0;
399 }
400 } else {
401 print "Visit $url for more info about Pang Zero $Games::PangZero::VERSION\n";
402 }
403 }
404
405 1;
0 use strict;
1 use warnings;
2 use Test::More;
3 use Games::PangZero;
4
5 $ENV{PANGZERO_TEST} = 1;
6
7 $ENV{SDL_VIDEODRIVER} = 'dummy';
8 Games::PangZero::Initialize();
9 $Games::PangZero::Game = Games::PangZero::PanicGame->new();
10 @Games::PangZero::Highscore::UnsavedHighScores = ();
11 $Games::PangZero::Game->Run();
12
13 delete $ENV{SDL_VIDEODRIVER};
14 pass('Game Ran!!');
15 done_testing();