1 /*
2 * OpenTyrian: A modern cross-platform port of Tyrian
3 * Copyright (C) 2007-2009 The OpenTyrian Development Team
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20 /* File notes:
21 * Two players duke it out in a Scorched Earth style game.
22 * Most of the variables referring to the players are global as
23 * they are often edited and that's how the original was written.
24 *
25 * Currently this file is at its final stage for vanilla destruct.
26 * Almost all of the left/right code duplications is gone. Most of the
27 * functions have been examined and tightened up, none of the enums
28 * start with '1', and the various large functions have been divided into
29 * smaller chunks.
30 *
31 * Destruct also supports some 'hidden' configuration that's just too awesome
32 * to not have available. Destruct has no configuration options in game, but
33 * that doesn't stop us from changing various limiting vars and letting
34 * people remap the keyboard. AIs may also be introduced here; fighting a
35 * stateless AI isn't really challenging afterall.
36 *
37 * This hidden config also allows for a hidden game mode! Though as a custom
38 * game mode wouldn't show up in the data files it forces us to distinguish
39 * between the constant DESTRUCT_MODES (5) and MAX_MODES (6). DESTRUCT_MODES
40 * is only used with loaded data.
41 *
42 * Things I wanted to do but can't: Remove references to VGAScreen. For
43 * a multitude of reasons this just isn't feasable. It would have been nice
44 * to increase the playing field though...
45 */
46
47 /*** Headers ***/
48 #include "opentyr.h"
49 #include "destruct.h"
50
51 #include "config.h"
52 #include "fonthand.h"
53 #include "helptext.h"
54 #include "keyboard.h"
55 #include "loudness.h"
56 #include "mtrand.h"
57 #include "nortsong.h"
58 #include "palette.h"
59 #include "picload.h"
60 #include "sprite.h"
61 #include "varz.h"
62 #include "vga256d.h"
63 #include "video.h"
64
65 #include <assert.h>
66
67 /*** Defines ***/
68 #define UNIT_HEIGHT 12
69 #define MAX_KEY_OPTIONS 4
70
71 /*** Enums ***/
72 enum de_state_t { STATE_INIT, STATE_RELOAD, STATE_CONTINUE };
73 enum de_player_t { PLAYER_LEFT = 0, PLAYER_RIGHT = 1, MAX_PLAYERS = 2 };
74 enum de_team_t { TEAM_LEFT = 0, TEAM_RIGHT = 1, MAX_TEAMS = 2 };
75 enum de_mode_t { MODE_5CARDWAR = 0, MODE_TRADITIONAL, MODE_HELIASSAULT,
76 MODE_HELIDEFENSE, MODE_OUTGUNNED, MODE_CUSTOM,
77 MODE_FIRST = MODE_5CARDWAR, MODE_LAST = MODE_CUSTOM,
78 MAX_MODES = 6, MODE_NONE = -1 };
79 enum de_unit_t { UNIT_TANK = 0, UNIT_NUKE, UNIT_DIRT, UNIT_SATELLITE,
80 UNIT_MAGNET, UNIT_LASER, UNIT_JUMPER, UNIT_HELI,
81 UNIT_FIRST = UNIT_TANK, UNIT_LAST = UNIT_HELI,
82 MAX_UNITS = 8, UNIT_NONE = -1 };
83 enum de_shot_t { SHOT_TRACER = 0, SHOT_SMALL, SHOT_LARGE, SHOT_MICRO,
84 SHOT_SUPER, SHOT_DEMO, SHOT_SMALLNUKE, SHOT_LARGENUKE,
85 SHOT_SMALLDIRT, SHOT_LARGEDIRT, SHOT_MAGNET, SHOT_MINILASER,
86 SHOT_MEGALASER, SHOT_LASERTRACER, SHOT_MEGABLAST, SHOT_MINI,
87 SHOT_BOMB,
88 SHOT_FIRST = SHOT_TRACER, SHOT_LAST = SHOT_BOMB,
89 MAX_SHOT_TYPES = 17, SHOT_INVALID = -1 };
90 enum de_expl_t { EXPL_NONE, EXPL_MAGNET, EXPL_DIRT, EXPL_NORMAL }; /* this needs a better name */
91 enum de_trails_t { TRAILS_NONE, TRAILS_NORMAL, TRAILS_FULL };
92 enum de_pixel_t { PIXEL_BLACK = 0, PIXEL_DIRT = 25 };
93 enum de_mapflags_t { MAP_NORMAL = 0x00, MAP_WALLS = 0x01, MAP_RINGS = 0x02,
94 MAP_HOLES = 0x04, MAP_FUZZY = 0x08, MAP_TALL = 0x10 };
95
96 /* keys and moves should line up. */
97 enum de_keys_t { KEY_LEFT = 0, KEY_RIGHT, KEY_UP, KEY_DOWN, KEY_CHANGE, KEY_FIRE, KEY_CYUP, KEY_CYDN, MAX_KEY = 8};
98 enum de_move_t { MOVE_LEFT = 0, MOVE_RIGHT, MOVE_UP, MOVE_DOWN, MOVE_CHANGE, MOVE_FIRE, MOVE_CYUP, MOVE_CYDN, MAX_MOVE = 8};
99
100 /* The tracerlaser is dummied out. It works but (probably due to the low
101 * MAX_SHOTS) is not assigned to anything. The bomb does not work.
102 */
103
104
105 /*** Structs ***/
106 struct destruct_config_s {
107
108 unsigned int max_shots;
109 unsigned int min_walls;
110 unsigned int max_walls;
111 unsigned int max_explosions;
112 unsigned int max_installations;
113 bool allow_custom;
114 bool alwaysalias;
115 bool jumper_straight[2];
116 bool ai[2];
117 };
118 struct destruct_unit_s {
119
120 /* Positioning/movement */
121 unsigned int unitX; /* yep, one's an int and the other is a real */
122 float unitY;
123 float unitYMov;
124 bool isYInAir;
125
126 /* What it is and what it fires */
127 enum de_unit_t unitType;
128 enum de_shot_t shotType;
129
130 /* What it's pointed */
131 float angle;
132 float power;
133
134 /* Misc */
135 int lastMove;
136 unsigned int ani_frame;
137 int health;
138 };
139 struct destruct_shot_s {
140
141 bool isAvailable;
142
143 float x;
144 float y;
145 float xmov;
146 float ymov;
147 bool gravity;
148 unsigned int shottype;
149 //int shotdur; /* This looks to be unused */
150 unsigned int trailx[4], traily[4], trailc[4];
151 };
152 struct destruct_explo_s {
153
154 bool isAvailable;
155
156 unsigned int x, y;
157 unsigned int explowidth;
158 unsigned int explomax;
159 unsigned int explofill;
160 enum de_expl_t exploType;
161 };
162 struct destruct_moves_s {
163 bool actions[MAX_MOVE];
164 };
165 struct destruct_keys_s {
166 SDLKey Config[MAX_KEY][MAX_KEY_OPTIONS];
167 };
168 struct destruct_ai_s {
169
170 int c_Angle, c_Power, c_Fire;
171 unsigned int c_noDown;
172 };
173 struct destruct_player_s {
174
175 bool is_cpu;
176 struct destruct_ai_s aiMemory;
177
178 struct destruct_unit_s * unit;
179 struct destruct_moves_s moves;
180 struct destruct_keys_s keys;
181
182 enum de_team_t team;
183 unsigned int unitsRemaining;
184 unsigned int unitSelected;
185 unsigned int shotDelay;
186 unsigned int score;
187 };
188 struct destruct_wall_s {
189
190 bool wallExist;
191 unsigned int wallX, wallY;
192 };
193 struct destruct_world_s {
194
195 /* Map data & screen pointer */
196 unsigned int baseMap[320];
197 SDL_Surface * VGAScreen;
198 struct destruct_wall_s * mapWalls;
199
200 /* Map configuration */
201 enum de_mode_t destructMode;
202 unsigned int mapFlags;
203 };
204
205 /*** Function decs ***/
206 //Prep functions
207 static void JE_destructMain( void );
208 static void JE_introScreen( void );
209 static enum de_mode_t JE_modeSelect( void );
210 static void JE_helpScreen( void );
211 static void JE_pauseScreen( void );
212
213 //level generating functions
214 static void JE_generateTerrain( void );
215 static void DE_generateBaseTerrain( unsigned int, unsigned int *);
216 static void DE_drawBaseTerrain( unsigned int * );
217 static void DE_generateUnits( unsigned int * );
218 static void DE_generateWalls( struct destruct_world_s * );
219 static void DE_generateRings(SDL_Surface *, Uint8 );
220 static void DE_ResetLevel( void );
221 static unsigned int JE_placementPosition( unsigned int, unsigned int, unsigned int * );
222
223 //drawing functions
224 static void JE_aliasDirt( SDL_Surface * );
225 static void DE_RunTickDrawCrosshairs( void );
226 static void DE_RunTickDrawHUD( void );
227 static void DE_GravityDrawUnit( enum de_player_t, struct destruct_unit_s * );
228 static void DE_RunTickAnimate( void );
229 static void DE_RunTickDrawWalls( void );
230 static void DE_DrawTrails( struct destruct_shot_s *, unsigned int, unsigned int, unsigned int );
231 static void JE_tempScreenChecking( void );
232 static void JE_superPixel( unsigned int, unsigned int );
233 static void JE_pixCool( unsigned int, unsigned int, Uint8 );
234
235 //player functions
236 static void DE_RunTickGetInput( void );
237 static void DE_ProcessInput( void );
238 static void DE_ResetPlayers( void );
239 static void DE_ResetAI( void );
240 static void DE_ResetActions( void );
241 static void DE_RunTickAI( void );
242
243 //unit functions
244 static void DE_RaiseAngle( struct destruct_unit_s * );
245 static void DE_LowerAngle( struct destruct_unit_s * );
246 static void DE_RaisePower( struct destruct_unit_s * );
247 static void DE_LowerPower( struct destruct_unit_s * );
248 static void DE_CycleWeaponUp( struct destruct_unit_s * );
249 static void DE_CycleWeaponDown( struct destruct_unit_s * );
250 static void DE_RunMagnet( enum de_player_t, struct destruct_unit_s * );
251 static void DE_GravityFlyUnit( struct destruct_unit_s * );
252 static void DE_GravityLowerUnit( struct destruct_unit_s * );
253 static void DE_DestroyUnit( enum de_player_t, struct destruct_unit_s * );
254 static void DE_ResetUnits( void );
255 static inline bool DE_isValidUnit( struct destruct_unit_s *);
256
257 //weapon functions
258 static void DE_ResetWeapons( void );
259 static void DE_RunTickShots( void );
260 static void DE_RunTickExplosions( void );
261 static void DE_TestExplosionCollision( unsigned int, unsigned int);
262 static void JE_makeExplosion( unsigned int, unsigned int, enum de_shot_t );
263 static void DE_MakeShot( enum de_player_t, const struct destruct_unit_s *, int );
264
265 //gameplay functions
266 static enum de_state_t DE_RunTick( void );
267 static void DE_RunTickCycleDeadUnits( void );
268 static void DE_RunTickGravity( void );
269 static bool DE_RunTickCheckEndgame( void );
270 static bool JE_stabilityCheck( unsigned int, unsigned int );
271
272 //sound
273 static void DE_RunTickPlaySounds( void );
274 static void JE_eSound( unsigned int );
275
276
277
278 /*** Weapon configurations ***/
279
280 /* Part of me wants to leave these as bytes to save space. */
281 static const bool demolish[MAX_SHOT_TYPES] = {false, false, false, false, false, true, true, true, false, false, false, false, true, false, true, false, true};
282 //static const int shotGr[MAX_SHOT_TYPES] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 101};
283 static const int shotTrail[MAX_SHOT_TYPES] = {TRAILS_NONE, TRAILS_NONE, TRAILS_NONE, TRAILS_NORMAL, TRAILS_NORMAL, TRAILS_NORMAL, TRAILS_FULL, TRAILS_FULL, TRAILS_NONE, TRAILS_NONE, TRAILS_NONE, TRAILS_NORMAL, TRAILS_FULL, TRAILS_NORMAL, TRAILS_FULL, TRAILS_NORMAL, TRAILS_NONE};
284 //static const int shotFuse[MAX_SHOT_TYPES] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0};
285 static const int shotDelay[MAX_SHOT_TYPES] = {10, 30, 80, 20, 60, 100, 140, 200, 20, 60, 5, 15, 50, 5, 80, 16, 0};
286 static const int shotSound[MAX_SHOT_TYPES] = {S_SELECT, S_WEAPON_2, S_WEAPON_1, S_WEAPON_7, S_WEAPON_7, S_EXPLOSION_9, S_EXPLOSION_22, S_EXPLOSION_22, S_WEAPON_5, S_WEAPON_13, S_WEAPON_10, S_WEAPON_15, S_WEAPON_15, S_WEAPON_26, S_WEAPON_14, S_WEAPON_7, S_WEAPON_7};
287 static const int exploSize[MAX_SHOT_TYPES] = {4, 20, 30, 14, 22, 16, 40, 60, 10, 30, 0, 5, 10, 3, 15, 7, 0};
288 static const bool shotBounce[MAX_SHOT_TYPES] = {false, false, false, false, false, false, false, false, false, false, false, true, true, true, true, false, true};
289 static const int exploDensity[MAX_SHOT_TYPES] = { 2, 5, 10, 15, 20, 15, 25, 30, 40, 80, 0, 30, 30, 4, 30, 5, 0};
290 static const int shotDirt[MAX_SHOT_TYPES] = {EXPL_NORMAL, EXPL_NORMAL, EXPL_NORMAL, EXPL_NORMAL, EXPL_NORMAL, EXPL_NORMAL, EXPL_NORMAL, EXPL_NORMAL, EXPL_DIRT, EXPL_DIRT, EXPL_MAGNET, EXPL_NORMAL, EXPL_NORMAL, EXPL_NORMAL, EXPL_NORMAL, EXPL_NORMAL, EXPL_NONE};
291 static const int shotColor[MAX_SHOT_TYPES] = {16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 10, 10, 10, 10, 16, 0};
292
293 static const int defaultWeapon[MAX_UNITS] = {SHOT_SMALL, SHOT_MICRO, SHOT_SMALLDIRT, SHOT_INVALID, SHOT_MAGNET, SHOT_MINILASER, SHOT_MICRO, SHOT_MINI};
294 static const int defaultCpuWeapon[MAX_UNITS] = {SHOT_SMALL, SHOT_MICRO, SHOT_DEMO, SHOT_INVALID, SHOT_MAGNET, SHOT_MINILASER, SHOT_MICRO, SHOT_MINI};
295 static const int defaultCpuWeaponB[MAX_UNITS] = {SHOT_DEMO, SHOT_SMALLNUKE, SHOT_DEMO, SHOT_INVALID, SHOT_MAGNET, SHOT_MEGALASER, SHOT_MICRO, SHOT_MINI};
296 static const int systemAngle[MAX_UNITS] = {true, true, true, false, false, true, false, false};
297 static const int baseDamage[MAX_UNITS] = {200, 120, 400, 300, 80, 150, 600, 40};
298 static const int systemAni[MAX_UNITS] = {false, false, false, true, false, false, false, true};
299
300 static bool weaponSystems[MAX_UNITS][MAX_SHOT_TYPES] =
301 {
302 {1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // normal
303 {0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // nuke
304 {0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0}, // dirt
305 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // worthless
306 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}, // magnet
307 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0}, // laser
308 {1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}, // jumper
309 {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0} // helicopter
310 };
311
312 /* More constant configuration settings. */
313 /* Music that destruct will play. You can check out musmast.c to see what is what. */
314 static const JE_byte goodsel[14] /*[1..14]*/ = {1, 2, 6, 12, 13, 14, 17, 23, 24, 26, 28, 29, 32, 33};
315
316 /* Unit creation. Need to move this later: Doesn't belong here */
317 static JE_byte basetypes[10][11] /*[1..8, 1..11]*/ = /* [0] is amount of units*/
318 {
319 {5, UNIT_TANK, UNIT_TANK, UNIT_NUKE, UNIT_DIRT, UNIT_DIRT, UNIT_SATELLITE, UNIT_MAGNET, UNIT_LASER, UNIT_JUMPER, UNIT_HELI}, /*Normal*/
320 {1, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_TANK}, /*Traditional*/
321 {4, UNIT_HELI, UNIT_HELI, UNIT_HELI, UNIT_HELI, UNIT_HELI, UNIT_HELI, UNIT_HELI, UNIT_HELI, UNIT_HELI, UNIT_HELI}, /*Weak Heli attack fleet*/
322 {8, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_NUKE, UNIT_NUKE, UNIT_NUKE, UNIT_DIRT, UNIT_MAGNET, UNIT_LASER, UNIT_JUMPER}, /*Strong Heli defense fleet*/
323 {8, UNIT_HELI, UNIT_HELI, UNIT_HELI, UNIT_HELI, UNIT_HELI, UNIT_HELI, UNIT_HELI, UNIT_HELI, UNIT_HELI, UNIT_HELI}, /*Strong Heli attack fleet*/
324 {4, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_NUKE, UNIT_NUKE, UNIT_DIRT, UNIT_MAGNET, UNIT_JUMPER, UNIT_JUMPER}, /*Weak Heli defense fleet*/
325 {8, UNIT_TANK, UNIT_NUKE, UNIT_DIRT, UNIT_SATELLITE, UNIT_MAGNET, UNIT_LASER, UNIT_JUMPER, UNIT_HELI, UNIT_TANK, UNIT_NUKE}, /*Overpowering fleet*/
326 {4, UNIT_TANK, UNIT_TANK, UNIT_NUKE, UNIT_DIRT, UNIT_TANK, UNIT_LASER, UNIT_JUMPER, UNIT_HELI, UNIT_NUKE, UNIT_JUMPER}, /*Weak fleet*/
327 {1, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_TANK}, /*Custom1, to be edited*/
328 {1, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_TANK} /*Custom2, to be edited*/
329 };
330 static const unsigned int baseLookup[MAX_PLAYERS][MAX_MODES] =
331 {
332 {0, 1, 3, 4, 6, 8},
333 {0, 1, 2, 5, 7, 9}
334 };
335
336
337 static const JE_byte GraphicBase[MAX_PLAYERS][MAX_UNITS] =
338 {
339 { 1, 6, 11, 58, 63, 68, 96, 153},
340 { 20, 25, 30, 77, 82, 87, 115, 172}
341 };
342
343 static const JE_byte ModeScore[MAX_PLAYERS][MAX_MODES] =
344 {
345 {1, 0, 0, 5, 0, 1},
346 {1, 0, 5, 0, 1, 1}
347 };
348
349 static SDLKey defaultKeyConfig[MAX_PLAYERS][MAX_KEY][MAX_KEY_OPTIONS] =
350 {
351 { {SDLK_c},
352 {SDLK_v},
353 {SDLK_a},
354 {SDLK_z},
355 {SDLK_LALT},
356 {SDLK_x, SDLK_LSHIFT},
357 {SDLK_LCTRL},
358 {SDLK_SPACE}
359 },
360 { {SDLK_LEFT, SDLK_KP4},
361 {SDLK_RIGHT, SDLK_KP6},
362 {SDLK_UP, SDLK_KP8},
363 {SDLK_DOWN, SDLK_KP2},
364 {SDLK_BACKSLASH, SDLK_KP5},
365 {SDLK_INSERT, SDLK_RETURN, SDLK_KP0, SDLK_KP_ENTER},
366 {SDLK_PAGEUP, SDLK_KP9},
367 {SDLK_PAGEDOWN, SDLK_KP3}
368 }
369 };
370
371
372 /*** Globals ***/
373 static SDL_Surface *destructTempScreen;
374 static JE_boolean destructFirstTime;
375
376 static struct destruct_config_s config = { 40, 20, 20, 40, 10, false, false, {true, false}, {true, false} };
377 static struct destruct_player_s destruct_player[MAX_PLAYERS];
378 static struct destruct_world_s world;
379 static struct destruct_shot_s * shotRec;
380 static struct destruct_explo_s * exploRec;
381
382
383 /*** Startup ***/
string_to_unit_enum(const char * str)384 static enum de_unit_t string_to_unit_enum(const char * str) {
385
386 // A config helper function. Probably not useful anywhere else.
387 enum de_unit_t i;
388 static const char * unit_names[] =
389 { "UNIT_TANK", "UNIT_NUKE", "UNIT_DIRT", "UNIT_SATELLITE",
390 "UNIT_MAGNET", "UNIT_LASER", "UNIT_JUMPER", "UNIT_HELI" };
391
392 for (i = UNIT_FIRST; i < MAX_UNITS; i++) {
393 if(strcmp(unit_names[i], str) == 0) { return(i); }
394 }
395
396 return(UNIT_NONE);
397 }
write_default_destruct_config(void)398 static bool write_default_destruct_config( void ) {
399
400 cJSON * root;
401 cJSON * level1, * level2, * level3, * setting;
402
403
404 //If I read the file right, all of these will return NULL on failure.
405 //Well that'll be a little bit tedious to check for each time, but using
406 //gotos can help clear everything up since only one thing is freed.
407 if((root = cJSON_CreateObject()) == NULL) { goto label_failure; }
408
409
410 if((level1 = cJSON_CreateOrGetObjectItem(root, "general")) == NULL) { goto label_failure; }
411 cJSON_ForceType(level1, cJSON_Object);
412
413 //general
414 if((setting = cJSON_CreateOrGetObjectItem(level1, "alwaysalias")) == NULL) { goto label_failure; }
415 cJSON_SetBoolean(setting, false);
416 if((setting = cJSON_CreateOrGetObjectItem(level1, "tracerlaser")) == NULL) { goto label_failure; }
417 cJSON_SetBoolean(setting, false);
418 if((setting = cJSON_CreateOrGetObjectItem(level1, "max_shots")) == NULL) { goto label_failure; }
419 cJSON_SetNumber(setting, 40);
420 if((setting = cJSON_CreateOrGetObjectItem(level1, "min_walls")) == NULL) { goto label_failure; }
421 cJSON_SetNumber(setting, 20);
422 if((setting = cJSON_CreateOrGetObjectItem(level1, "max_walls")) == NULL) { goto label_failure; }
423 cJSON_SetNumber(setting, 20);
424 if((setting = cJSON_CreateOrGetObjectItem(level1, "max_explosions")) == NULL) { goto label_failure; }
425 cJSON_SetNumber(setting, 40);
426
427 //players general
428 if((level2 = cJSON_CreateOrGetObjectItem(level1, "player1")) == NULL) { goto label_failure; }
429 cJSON_ForceType(level2, cJSON_Object);
430 if((setting = cJSON_CreateOrGetObjectItem(level2, "ai")) == NULL) { goto label_failure; }
431 cJSON_SetBoolean(setting, true);
432 if((setting = cJSON_CreateOrGetObjectItem(level2, "jumper_fires_straight")) == NULL) { goto label_failure; }
433 cJSON_SetBoolean(setting, true);
434
435 if((level3 = cJSON_CreateOrGetObjectItem(level2, "keys")) == NULL) { goto label_failure; }
436 cJSON_ForceType(level3, cJSON_Object);
437 if((setting = cJSON_CreateOrGetObjectItem(level3, "__comment")) == NULL) { goto label_failure; }
438 cJSON_SetString(setting, "You may configure the keys here. Nums correspond to SDL defines. It's better than nothing.");
439 if((setting = cJSON_CreateOrGetObjectItem(level3, "left1")) == NULL) { goto label_failure; }
440 cJSON_SetNumber(setting, SDLK_c);
441 if((setting = cJSON_CreateOrGetObjectItem(level3, "right1")) == NULL) { goto label_failure; }
442 cJSON_SetNumber(setting, SDLK_v);
443 if((setting = cJSON_CreateOrGetObjectItem(level3, "up1")) == NULL) { goto label_failure; }
444 cJSON_SetNumber(setting, SDLK_a);
445 if((setting = cJSON_CreateOrGetObjectItem(level3, "down1")) == NULL) { goto label_failure; }
446 cJSON_SetNumber(setting, SDLK_z);
447 if((setting = cJSON_CreateOrGetObjectItem(level3, "change1")) == NULL) { goto label_failure; }
448 cJSON_SetNumber(setting, SDLK_LALT);
449 if((setting = cJSON_CreateOrGetObjectItem(level3, "fire1")) == NULL) { goto label_failure; }
450 cJSON_SetNumber(setting, SDLK_x);
451 if((setting = cJSON_CreateOrGetObjectItem(level3, "fire2")) == NULL) { goto label_failure; }
452 cJSON_SetNumber(setting, SDLK_LSHIFT);
453 if((setting = cJSON_CreateOrGetObjectItem(level3, "cyup1")) == NULL) { goto label_failure; }
454 cJSON_SetNumber(setting, SDLK_LCTRL);
455 if((setting = cJSON_CreateOrGetObjectItem(level3, "cydn1")) == NULL) { goto label_failure; }
456 cJSON_SetNumber(setting, SDLK_SPACE);
457
458 if((level2 = cJSON_CreateOrGetObjectItem(level1, "player2")) == NULL) { goto label_failure; }
459 cJSON_ForceType(level2, cJSON_Object);
460 if((setting = cJSON_CreateOrGetObjectItem(level2, "ai")) == NULL) { goto label_failure; }
461 cJSON_SetBoolean(setting, false);
462 if((setting = cJSON_CreateOrGetObjectItem(level2, "jumper_fires_straight")) == NULL) { goto label_failure; }
463 cJSON_SetBoolean(setting, false);
464
465 if((level3 = cJSON_CreateOrGetObjectItem(level2, "keys")) == NULL) { goto label_failure; }
466 cJSON_ForceType(level3, cJSON_Object);
467 if((setting = cJSON_CreateOrGetObjectItem(level3, "left1")) == NULL) { goto label_failure; }
468 cJSON_SetNumber(setting, SDLK_LEFT);
469 if((setting = cJSON_CreateOrGetObjectItem(level3, "left2")) == NULL) { goto label_failure; }
470 cJSON_SetNumber(setting, SDLK_KP4);
471 if((setting = cJSON_CreateOrGetObjectItem(level3, "right1")) == NULL) { goto label_failure; }
472 cJSON_SetNumber(setting, SDLK_RIGHT);
473 if((setting = cJSON_CreateOrGetObjectItem(level3, "right2")) == NULL) { goto label_failure; }
474 cJSON_SetNumber(setting, SDLK_KP6);
475 if((setting = cJSON_CreateOrGetObjectItem(level3, "up1")) == NULL) { goto label_failure; }
476 cJSON_SetNumber(setting, SDLK_UP);
477 if((setting = cJSON_CreateOrGetObjectItem(level3, "up2")) == NULL) { goto label_failure; }
478 cJSON_SetNumber(setting, SDLK_KP8);
479 if((setting = cJSON_CreateOrGetObjectItem(level3, "down1")) == NULL) { goto label_failure; }
480 cJSON_SetNumber(setting, SDLK_DOWN);
481 if((setting = cJSON_CreateOrGetObjectItem(level3, "down2")) == NULL) { goto label_failure; }
482 cJSON_SetNumber(setting, SDLK_KP2);
483 if((setting = cJSON_CreateOrGetObjectItem(level3, "change1")) == NULL) { goto label_failure; }
484 cJSON_SetNumber(setting, SDLK_BACKSLASH);
485 if((setting = cJSON_CreateOrGetObjectItem(level3, "change2")) == NULL) { goto label_failure; }
486 cJSON_SetNumber(setting, SDLK_KP5);
487 if((setting = cJSON_CreateOrGetObjectItem(level3, "fire1")) == NULL) { goto label_failure; }
488 cJSON_SetNumber(setting, SDLK_INSERT);
489 if((setting = cJSON_CreateOrGetObjectItem(level3, "fire2")) == NULL) { goto label_failure; }
490 cJSON_SetNumber(setting, SDLK_RETURN);
491 if((setting = cJSON_CreateOrGetObjectItem(level3, "fire3")) == NULL) { goto label_failure; }
492 cJSON_SetNumber(setting, SDLK_KP0);
493 if((setting = cJSON_CreateOrGetObjectItem(level3, "fire4")) == NULL) { goto label_failure; }
494 cJSON_SetNumber(setting, SDLK_KP_ENTER);
495 if((setting = cJSON_CreateOrGetObjectItem(level3, "cyup1")) == NULL) { goto label_failure; }
496 cJSON_SetNumber(setting, SDLK_PAGEUP);
497 if((setting = cJSON_CreateOrGetObjectItem(level3, "cyup2")) == NULL) { goto label_failure; }
498 cJSON_SetNumber(setting, SDLK_KP9);
499 if((setting = cJSON_CreateOrGetObjectItem(level3, "cydn1")) == NULL) { goto label_failure; }
500 cJSON_SetNumber(setting, SDLK_PAGEDOWN);
501 if((setting = cJSON_CreateOrGetObjectItem(level3, "cydn2")) == NULL) { goto label_failure; }
502 cJSON_SetNumber(setting, SDLK_KP3);
503
504 //custom mode
505 if((level1 = cJSON_CreateOrGetObjectItem(root, "custom")) == NULL) { goto label_failure; }
506 cJSON_ForceType(level1, cJSON_Object);
507
508 if((setting = cJSON_CreateOrGetObjectItem(level1, "enable")) == NULL) { goto label_failure; }
509 cJSON_SetBoolean(setting, false);
510
511 //player 1 (I could but won't bother looping this)
512 if((level2 = cJSON_CreateOrGetObjectItem(level1, "player1")) == NULL) { goto label_failure; }
513 cJSON_ForceType(level2, cJSON_Object);
514 if((setting = cJSON_CreateOrGetObjectItem(level2, "num_units")) == NULL) { goto label_failure; }
515 cJSON_SetNumber(setting, 10);
516 if((setting = cJSON_CreateOrGetObjectItem(level2, "__comment")) == NULL) { goto label_failure; }
517 cJSON_SetString(setting, "This handles probability. Always have 10 entries.");
518
519 if((setting = cJSON_CreateOrGetObjectItem(level2, "unit1")) == NULL) { goto label_failure; }
520 cJSON_SetString(setting, "UNIT_TANK");
521 if((setting = cJSON_CreateOrGetObjectItem(level2, "unit2")) == NULL) { goto label_failure; }
522 cJSON_SetString(setting, "UNIT_TANK");
523 if((setting = cJSON_CreateOrGetObjectItem(level2, "unit3")) == NULL) { goto label_failure; }
524 cJSON_SetString(setting, "UNIT_NUKE");
525 if((setting = cJSON_CreateOrGetObjectItem(level2, "unit4")) == NULL) { goto label_failure; }
526 cJSON_SetString(setting, "UNIT_DIRT");
527 if((setting = cJSON_CreateOrGetObjectItem(level2, "unit5")) == NULL) { goto label_failure; }
528 cJSON_SetString(setting, "UNIT_DIRT");
529 if((setting = cJSON_CreateOrGetObjectItem(level2, "unit6")) == NULL) { goto label_failure; }
530 cJSON_SetString(setting, "UNIT_SATELLITE");
531 if((setting = cJSON_CreateOrGetObjectItem(level2, "unit7")) == NULL) { goto label_failure; }
532 cJSON_SetString(setting, "UNIT_MAGNET");
533 if((setting = cJSON_CreateOrGetObjectItem(level2, "unit8")) == NULL) { goto label_failure; }
534 cJSON_SetString(setting, "UNIT_LASER");
535 if((setting = cJSON_CreateOrGetObjectItem(level2, "unit9")) == NULL) { goto label_failure; }
536 cJSON_SetString(setting, "UNIT_JUMPER");
537 if((setting = cJSON_CreateOrGetObjectItem(level2, "unit10")) == NULL) { goto label_failure; }
538 cJSON_SetString(setting, "UNIT_HELI");
539
540 if((level2 = cJSON_CreateOrGetObjectItem(level1, "player2")) == NULL) { goto label_failure; }
541 cJSON_ForceType(level2, cJSON_Object);
542 if((setting = cJSON_CreateOrGetObjectItem(level2, "num_units")) == NULL) { goto label_failure; }
543 cJSON_SetNumber(setting, 10);
544
545 if((setting = cJSON_CreateOrGetObjectItem(level2, "unit1")) == NULL) { goto label_failure; }
546 cJSON_SetString(setting, "UNIT_TANK");
547 if((setting = cJSON_CreateOrGetObjectItem(level2, "unit2")) == NULL) { goto label_failure; }
548 cJSON_SetString(setting, "UNIT_TANK");
549 if((setting = cJSON_CreateOrGetObjectItem(level2, "unit3")) == NULL) { goto label_failure; }
550 cJSON_SetString(setting, "UNIT_NUKE");
551 if((setting = cJSON_CreateOrGetObjectItem(level2, "unit4")) == NULL) { goto label_failure; }
552 cJSON_SetString(setting, "UNIT_DIRT");
553 if((setting = cJSON_CreateOrGetObjectItem(level2, "unit5")) == NULL) { goto label_failure; }
554 cJSON_SetString(setting, "UNIT_DIRT");
555 if((setting = cJSON_CreateOrGetObjectItem(level2, "unit6")) == NULL) { goto label_failure; }
556 cJSON_SetString(setting, "UNIT_SATELLITE");
557 if((setting = cJSON_CreateOrGetObjectItem(level2, "unit7")) == NULL) { goto label_failure; }
558 cJSON_SetString(setting, "UNIT_MAGNET");
559 if((setting = cJSON_CreateOrGetObjectItem(level2, "unit8")) == NULL) { goto label_failure; }
560 cJSON_SetString(setting, "UNIT_LASER");
561 if((setting = cJSON_CreateOrGetObjectItem(level2, "unit9")) == NULL) { goto label_failure; }
562 cJSON_SetString(setting, "UNIT_JUMPER");
563 if((setting = cJSON_CreateOrGetObjectItem(level2, "unit10")) == NULL) { goto label_failure; }
564 cJSON_SetString(setting, "UNIT_HELI");
565
566 save_json(root, "destruct.conf");
567 return(true);
568
569 label_failure:
570 cJSON_Delete(root);
571 return(false);
572 }
load_destruct_config(void)573 static void load_destruct_config( void ) {
574
575 unsigned int j, k;
576 enum de_player_t i;
577 enum de_unit_t temp;
578 char buffer[40];
579 const char * key_names[] = { "left", "right", "up", "down", "change", "fire", "cyup", "cydn" };
580 cJSON * root;
581 cJSON * level1, * level2, * level3, * setting;
582
583 // The config file is not modified in game in order to 'keep' with the
584 // original (unconfigurable) feel. This code was copied from elsewhere.
585 root = load_json("destruct.conf");
586 if (root == NULL) {
587 write_default_destruct_config();
588 return;
589 }
590
591 //load these general config items. I don't consider sanity checks
592 //necessary; either the game isn't playable or you eat up all your memory
593 //when using unreasonable values. Either way, no exploit here.
594 level1 = cJSON_GetObjectItem(root, "general");
595 if (level1 != NULL)
596 {
597 if ((setting = cJSON_GetObjectItem(level1, "alwaysalias"))) {
598 config.alwaysalias = (setting->type == cJSON_True);
599 }
600 if ((setting = cJSON_GetObjectItem(level1, "tracerlaser"))) {
601 weaponSystems[UNIT_LASER][SHOT_LASERTRACER] = (setting->type == cJSON_True);
602 }
603 if ((setting = cJSON_GetObjectItem(level1, "max_shots")) && setting->type == cJSON_Number) {
604 config.max_shots = setting->valueint;
605 }
606 if ((setting = cJSON_GetObjectItem(level1, "min_walls")) && setting->type == cJSON_Number) {
607 config.min_walls = setting->valueint;
608 }
609 if ((setting = cJSON_GetObjectItem(level1, "max_walls")) && setting->type == cJSON_Number) {
610 config.max_walls = setting->valueint;
611 if(config.min_walls > config.max_walls) { config.min_walls = config.max_walls; }
612 }
613 if ((setting = cJSON_GetObjectItem(level1, "max_explosions")) && setting->type == cJSON_Number) {
614 config.max_explosions = setting->valueint;
615 }
616
617 //player configuration
618 for(i = PLAYER_LEFT; i < MAX_PLAYERS; i++) {
619 sprintf(buffer, "player%i", i+1);
620 level2 = cJSON_GetObjectItem(level1, buffer);
621 if (level2 != NULL)
622 {
623 if ((setting = cJSON_GetObjectItem(level2, "jumper_fires_straight"))) {
624 config.jumper_straight[i] = (setting->type == cJSON_True);
625 }
626 if ((setting = cJSON_GetObjectItem(level2, "ai"))) {
627 config.ai[i] = (setting->type == cJSON_True);
628 }
629 //key configuration
630 level3 = cJSON_GetObjectItem(level2, "keys");
631 if (level3 != NULL)
632 {
633 for (j = 0; j < COUNTOF(key_names); j++) {
634 for (k = 0; k < MAX_KEY_OPTIONS; k++) {
635 sprintf(buffer, "%s%i", key_names[j], k+1);
636 if ((setting = cJSON_GetObjectItem(level3, buffer)) && setting->type == cJSON_Number) {
637 defaultKeyConfig[i][j][k] = setting->valueint;
638 }
639 else { //assume that if we are reading keys the defaults are null and void
640 defaultKeyConfig[i][j][k] = SDLK_UNKNOWN;
641 }
642 }
643 }
644 }
645 }
646 }
647 }
648
649 //Now let's hit the custom mode...
650 level1 = cJSON_GetObjectItem(root, "custom");
651
652 if (level1 != NULL)
653 {
654 //general custom
655 if ((setting = cJSON_GetObjectItem(level1, "enable"))) {
656 config.allow_custom = (setting->type == cJSON_True);
657 }
658
659 //player configuration
660 for(i = PLAYER_LEFT; i < MAX_PLAYERS; i++) {
661 sprintf(buffer, "player%i", i+1);
662 level2 = cJSON_GetObjectItem(level1, buffer);
663 if (level2 != NULL)
664 {
665 if ((setting = cJSON_GetObjectItem(level2, "num_units"))) {
666 basetypes[8 + i][0] = setting->valueint;
667 }
668 for(j = 1; j < 11; j++) {
669 sprintf(buffer, "unit%i", j);
670 if ((setting = cJSON_GetObjectItem(level2, buffer)) && setting->type == cJSON_String) {
671 temp = string_to_unit_enum(setting->valuestring);
672 if(temp != UNIT_NONE) {
673 basetypes[8 + i][j] = temp;
674 }
675 }
676 }
677 }
678 }
679 }
680
681 //wrap up
682 cJSON_Delete(root);
683 }
JE_destructGame(void)684 void JE_destructGame( void )
685 {
686 unsigned int i;
687
688 /* This is the entry function. Any one-time actions we need to
689 * perform can go in here. */
690 JE_clr256(VGAScreen);
691 JE_showVGA();
692
693 load_destruct_config();
694
695 //malloc things that have customizable sizes
696 shotRec = malloc(sizeof(struct destruct_shot_s) * config.max_shots);
697 exploRec = malloc(sizeof(struct destruct_explo_s) * config.max_explosions);
698 world.mapWalls = malloc(sizeof(struct destruct_wall_s) * config.max_walls);
699
700 //Malloc enough structures to cover all of this session's possible needs.
701 for(i = 0; i < 10; i++) {
702 config.max_installations = MAX(config.max_installations, basetypes[i][0]);
703 }
704 destruct_player[PLAYER_LEFT ].unit = malloc(sizeof(struct destruct_unit_s) * config.max_installations);
705 destruct_player[PLAYER_RIGHT].unit = malloc(sizeof(struct destruct_unit_s) * config.max_installations);
706
707 destructTempScreen = game_screen;
708 world.VGAScreen = VGAScreen;
709
710 JE_loadCompShapes(&eShapes[0], '~');
711 fade_black(1);
712
713 JE_destructMain();
714
715 //and of course exit actions go here.
716 free(shotRec);
717 free(exploRec);
718 free(world.mapWalls);
719 free(destruct_player[PLAYER_LEFT ].unit);
720 free(destruct_player[PLAYER_RIGHT].unit);
721 }
722
JE_destructMain(void)723 static void JE_destructMain( void )
724 {
725 enum de_state_t curState;
726
727
728 JE_loadPic(VGAScreen, 11, false);
729 JE_introScreen();
730
731 DE_ResetPlayers();
732
733 destruct_player[PLAYER_LEFT ].is_cpu = config.ai[PLAYER_LEFT];
734 destruct_player[PLAYER_RIGHT].is_cpu = config.ai[PLAYER_RIGHT];
735
736 while(1)
737 {
738 world.destructMode = JE_modeSelect();
739
740 if(world.destructMode == MODE_NONE) {
741 break; /* User is quitting */
742 }
743
744 do
745 {
746
747 destructFirstTime = true;
748 JE_loadPic(VGAScreen, 11, false);
749
750 DE_ResetUnits();
751 DE_ResetLevel();
752 do {
753 curState = DE_RunTick();
754 } while(curState == STATE_CONTINUE);
755
756 fade_black(25);
757 }
758 while (curState == STATE_RELOAD);
759 }
760 }
761
JE_introScreen(void)762 static void JE_introScreen( void )
763 {
764 memcpy(VGAScreen2->pixels, VGAScreen->pixels, VGAScreen2->h * VGAScreen2->pitch);
765 JE_outText(VGAScreen, JE_fontCenter(specialName[7], TINY_FONT), 90, specialName[7], 12, 5);
766 JE_outText(VGAScreen, JE_fontCenter(miscText[64], TINY_FONT), 180, miscText[64], 15, 2);
767 JE_outText(VGAScreen, JE_fontCenter(miscText[65], TINY_FONT), 190, miscText[65], 15, 2);
768 JE_showVGA();
769 fade_palette(colors, 15, 0, 255);
770
771 newkey = false;
772 while (!newkey)
773 {
774 service_SDL_events(false);
775 SDL_Delay(16);
776 }
777
778 fade_black(15);
779 memcpy(VGAScreen->pixels, VGAScreen2->pixels, VGAScreen->h * VGAScreen->pitch);
780 JE_showVGA();
781 }
782
783 /* JE_modeSelect
784 *
785 * This function prints the DESTRUCT mode selection menu.
786 * The return value is the selected mode, or -1 (MODE_NONE)
787 * if the user quits.
788 */
DrawModeSelectMenu(enum de_mode_t mode)789 static void DrawModeSelectMenu( enum de_mode_t mode ) {
790
791 int i;
792
793 /* Helper function of JE_modeSelect. Do not use elsewhere. */
794 for (i = 0; i < DESTRUCT_MODES; i++)
795 { /* What a large function call. */
796 JE_textShade(VGAScreen, JE_fontCenter(destructModeName[i], TINY_FONT), 82 + i * 12, destructModeName[i], 12, (i == mode) * 4, FULL_SHADE);
797 }
798 if (config.allow_custom == true)
799 {
800 JE_textShade(VGAScreen, JE_fontCenter("Custom", TINY_FONT), 82 + i * 12, "Custom", 12, (i == mode) * 4, FULL_SHADE);
801 }
802 }
JE_modeSelect(void)803 static enum de_mode_t JE_modeSelect( void )
804 {
805 enum de_mode_t mode;
806
807
808 memcpy(VGAScreen2->pixels, VGAScreen->pixels, VGAScreen2->h * VGAScreen2->pitch);
809 mode = MODE_5CARDWAR;
810
811 // Draw the menu and fade us in
812 DrawModeSelectMenu(mode);
813
814 JE_showVGA();
815 fade_palette(colors, 15, 0, 255);
816
817 /* Get input in a loop. */
818 while(1)
819 {
820 /* Re-draw the menu every iteration */
821 DrawModeSelectMenu(mode);
822 JE_showVGA();
823
824 /* Grab keys */
825 newkey = false;
826 do {
827 service_SDL_events(false);
828 SDL_Delay(16);
829 } while(!newkey);
830
831 /* See what was pressed */
832 if (keysactive[SDLK_ESCAPE])
833 {
834 mode = MODE_NONE; /* User is quitting, return failure */
835 break;
836 }
837 if (keysactive[SDLK_RETURN])
838 {
839 break; /* User has selected, return choice */
840 }
841 if (keysactive[SDLK_UP])
842 {
843 if(mode == MODE_FIRST)
844 {
845 if (config.allow_custom == true)
846 {
847 mode = MODE_LAST;
848 } else {
849 mode = MODE_LAST-1;
850 }
851 } else {
852 mode--;
853 }
854 }
855 if (keysactive[SDLK_DOWN])
856 {
857 if(mode >= MODE_LAST-1)
858 {
859 if (config.allow_custom == true && mode == MODE_LAST-1)
860 {
861 mode++;
862 } else {
863 mode = MODE_FIRST;
864 }
865 } else {
866 mode++;
867 }
868 }
869 }
870
871 fade_black(15);
872 memcpy(VGAScreen->pixels, VGAScreen2->pixels, VGAScreen->h * VGAScreen->pitch);
873 JE_showVGA();
874 return(mode);
875 }
876
JE_generateTerrain(void)877 static void JE_generateTerrain( void )
878 {
879 /* The unique modifiers:
880 Altered generation (really tall)
881 Fuzzy hills
882 Rings of dirt
883
884 The non-unique ones;:
885 Rings of not dirt (holes)
886 Walls
887 */
888
889 world.mapFlags = MAP_NORMAL;
890
891 if(mt_rand() % 2 == 0)
892 {
893 world.mapFlags |= MAP_WALLS;
894 }
895 if(mt_rand() % 4 == 0)
896 {
897 world.mapFlags |= MAP_HOLES;
898 }
899 switch(mt_rand() % 4)
900 {
901 case 0:
902 world.mapFlags |= MAP_FUZZY;
903 break;
904
905 case 1:
906 world.mapFlags |= MAP_TALL;
907 break;
908
909 case 2:
910 world.mapFlags |= MAP_RINGS;
911 break;
912 }
913
914 play_song(goodsel[mt_rand() % 14] - 1);
915
916 DE_generateBaseTerrain(world.mapFlags, world.baseMap);
917 DE_generateUnits(world.baseMap);
918 DE_generateWalls(&world);
919 DE_drawBaseTerrain(world.baseMap);
920
921 if (world.mapFlags & MAP_RINGS)
922 {
923 DE_generateRings(world.VGAScreen, PIXEL_DIRT);
924 }
925 if (world.mapFlags & MAP_HOLES)
926 {
927 DE_generateRings(world.VGAScreen, PIXEL_BLACK);
928 }
929
930 JE_aliasDirt(world.VGAScreen);
931 JE_showVGA();
932
933 memcpy(destructTempScreen->pixels, VGAScreen->pixels, destructTempScreen->pitch * destructTempScreen->h);
934 }
DE_generateBaseTerrain(unsigned int mapFlags,unsigned int * baseWorld)935 static void DE_generateBaseTerrain( unsigned int mapFlags, unsigned int * baseWorld)
936 {
937 unsigned int i;
938 unsigned int newheight, HeightMul;
939 float sinewave, sinewave2, cosinewave, cosinewave2;
940
941
942 /* The 'terrain' is actually the video buffer :). If it's brown, flu... er,
943 * brown pixels are what we check for collisions with. */
944
945 /* The ranges here are between .01 and roughly 0.07283...*/
946 sinewave = mt_rand_lt1() * M_PI / 50 + 0.01f;
947 sinewave2 = mt_rand_lt1() * M_PI / 50 + 0.01f;
948 cosinewave = mt_rand_lt1() * M_PI / 50 + 0.01f;
949 cosinewave2 = mt_rand_lt1() * M_PI / 50 + 0.01f;
950 HeightMul = 20;
951
952 /* This block just exists to mix things up. */
953 if(mapFlags & MAP_FUZZY)
954 {
955 sinewave = M_PI - mt_rand_lt1() * 0.3f;
956 sinewave2 = M_PI - mt_rand_lt1() * 0.3f;
957 }
958 if(mapFlags & MAP_TALL)
959 {
960 HeightMul = 100;
961 }
962
963 /* Now compute a height for each of our lines. */
964 for (i = 1; i <= 318; i++)
965 {
966 newheight = roundf(sinf(sinewave * i) * HeightMul + sinf(sinewave2 * i) * 15 +
967 cosf(cosinewave * i) * 10 + sinf(cosinewave2 * i) * 15) + 130;
968
969 /* Bind it; we have mins and maxs */
970 if (newheight < 40)
971 {
972 newheight = 40;
973 }
974 else if (newheight > 195) {
975 newheight = 195;
976 }
977 baseWorld[i] = newheight;
978 }
979 /* The base world has been created. */
980 }
DE_drawBaseTerrain(unsigned int * baseWorld)981 static void DE_drawBaseTerrain( unsigned int * baseWorld)
982 {
983 unsigned int i;
984
985
986 for (i = 1; i <= 318; i++)
987 {
988 JE_rectangle(VGAScreen, i, baseWorld[i], i, 199, PIXEL_DIRT);
989 }
990 }
991
DE_generateUnits(unsigned int * baseWorld)992 static void DE_generateUnits( unsigned int * baseWorld )
993 {
994 unsigned int i, j, numSatellites;
995
996
997 for (i = 0; i < MAX_PLAYERS; i++)
998 {
999 numSatellites = 0;
1000 destruct_player[i].unitsRemaining = 0;
1001
1002 for (j = 0; j < basetypes[baseLookup[i][world.destructMode]][0]; j++)
1003 {
1004 /* Not everything is the same between players */
1005 if(i == PLAYER_LEFT)
1006 {
1007 destruct_player[i].unit[j].unitX = (mt_rand() % 120) + 10;
1008 }
1009 else
1010 {
1011 destruct_player[i].unit[j].unitX = 320 - ((mt_rand() % 120) + 22);
1012 }
1013
1014 destruct_player[i].unit[j].unitY = JE_placementPosition(destruct_player[i].unit[j].unitX - 1, 14, baseWorld);
1015 destruct_player[i].unit[j].unitType = basetypes[baseLookup[i][world.destructMode]][(mt_rand() % 10) + 1];
1016
1017 /* Sats are special cases since they are useless. They don't count
1018 * as active units and we can't have a team of all sats */
1019 if (destruct_player[i].unit[j].unitType == UNIT_SATELLITE)
1020 {
1021 if (numSatellites == basetypes[baseLookup[i][world.destructMode]][0])
1022 {
1023 destruct_player[i].unit[j].unitType = UNIT_TANK;
1024 destruct_player[i].unitsRemaining++;
1025 } else {
1026 /* Place the satellite. Note: Earlier we cleared
1027 * space with JE_placementPosition. Now we are randomly
1028 * placing the sat's Y. It can be generated in hills
1029 * and there is a clearing underneath it. This CAN
1030 * be fixed but won't be for classic.
1031 */
1032 destruct_player[i].unit[j].unitY = 30 + (mt_rand() % 40);
1033 numSatellites++;
1034 }
1035 }
1036 else
1037 {
1038 destruct_player[i].unitsRemaining++;
1039 }
1040
1041 /* Now just fill in the rest of the unit's values. */
1042 destruct_player[i].unit[j].lastMove = 0;
1043 destruct_player[i].unit[j].unitYMov = 0;
1044 destruct_player[i].unit[j].isYInAir = false;
1045 destruct_player[i].unit[j].angle = 0;
1046 destruct_player[i].unit[j].power = (destruct_player[i].unit[j].unitType == UNIT_LASER) ? 6 : 3;
1047 destruct_player[i].unit[j].shotType = defaultWeapon[destruct_player[i].unit[j].unitType];
1048 destruct_player[i].unit[j].health = baseDamage[destruct_player[i].unit[j].unitType];
1049 destruct_player[i].unit[j].ani_frame = 0;
1050 }
1051 }
1052 }
DE_generateWalls(struct destruct_world_s * gameWorld)1053 static void DE_generateWalls( struct destruct_world_s * gameWorld )
1054 {
1055 unsigned int i, j, wallX;
1056 unsigned int wallHeight, remainWalls;
1057 unsigned int tries;
1058 bool isGood;
1059
1060
1061 if ((world.mapFlags & MAP_WALLS) == false)
1062 {
1063 /* Just clear them out */
1064 for (i = 0; i < config.max_walls; i++)
1065 {
1066 gameWorld->mapWalls[i].wallExist = false;
1067 }
1068 return;
1069 }
1070
1071 remainWalls = (rand() % (config.max_walls - config.min_walls + 1)) + config.min_walls;
1072
1073 do {
1074
1075 /* Create a wall. Decide how tall the wall will be */
1076 wallHeight = (mt_rand() % 5) + 1;
1077 if(wallHeight > remainWalls)
1078 {
1079 wallHeight = remainWalls;
1080 }
1081
1082 /* Now find a good place to put the wall. */
1083 tries = 0;
1084 do {
1085
1086 isGood = true;
1087 wallX = (mt_rand() % 300) + 10;
1088
1089 /* Is this X already occupied? In the original Tyrian we only
1090 * checked to make sure four units on each side were unobscured.
1091 * That's not very scalable; instead I will check every unit,
1092 * but I'll only try plotting an unobstructed X four times.
1093 * After that we'll cover up what may; having a few units
1094 * stuck behind walls makes things mildly interesting.
1095 */
1096 for (i = 0; i < MAX_PLAYERS; i++)
1097 {
1098 for (j = 0; j < config.max_installations; j++)
1099 {
1100 if ((wallX > destruct_player[i].unit[j].unitX - 12)
1101 && (wallX < destruct_player[i].unit[j].unitX + 13))
1102 {
1103 isGood = false;
1104 goto label_outer_break; /* I do feel that outer breaking is a legitimate goto use. */
1105 }
1106 }
1107 }
1108
1109 label_outer_break:
1110 tries++;
1111
1112 } while(isGood == false && tries < 5);
1113
1114
1115 /* We now have a valid X. Create the wall. */
1116 for (i = 1; i <= wallHeight; i++)
1117 {
1118 gameWorld->mapWalls[remainWalls - i].wallExist = true;
1119 gameWorld->mapWalls[remainWalls - i].wallX = wallX;
1120 gameWorld->mapWalls[remainWalls - i].wallY = JE_placementPosition(wallX, 12, gameWorld->baseMap) - 14 * i;
1121 }
1122
1123 remainWalls -= wallHeight;
1124
1125 } while (remainWalls != 0);
1126 }
1127
DE_generateRings(SDL_Surface * screen,Uint8 pixel)1128 static void DE_generateRings( SDL_Surface * screen, Uint8 pixel )
1129 {
1130 unsigned int i, j, tempSize, rings;
1131 int tempPosX1, tempPosY1, tempPosX2, tempPosY2;
1132 float tempRadian;
1133
1134
1135 rings = mt_rand() % 6 + 1;
1136 for (i = 1; i <= rings; i++)
1137 {
1138 tempPosX1 = (mt_rand() % 320);
1139 tempPosY1 = (mt_rand() % 160) + 20;
1140 tempSize = (mt_rand() % 40) + 10; /*Size*/
1141
1142 for (j = 1; j <= tempSize * tempSize * 2; j++)
1143 {
1144 tempRadian = mt_rand_lt1() * (2 * M_PI);
1145 tempPosY2 = tempPosY1 + roundf(cosf(tempRadian) * (mt_rand_lt1() * 0.1f + 0.9f) * tempSize);
1146 tempPosX2 = tempPosX1 + roundf(sinf(tempRadian) * (mt_rand_lt1() * 0.1f + 0.9f) * tempSize);
1147 if ((tempPosY2 > 12) && (tempPosY2 < 200)
1148 && (tempPosX2 > 0) && (tempPosX2 < 319))
1149 {
1150 ((Uint8 *)screen->pixels)[tempPosX2 + tempPosY2 * screen->pitch] = pixel;
1151 }
1152 }
1153 }
1154 }
1155
aliasDirtPixel(const SDL_Surface * screen,unsigned int x,unsigned int y,const Uint8 * s)1156 static unsigned int aliasDirtPixel(const SDL_Surface * screen, unsigned int x, unsigned int y, const Uint8 * s) {
1157
1158 //A helper function used when aliasing dirt. That's a messy process;
1159 //let's contain the mess here.
1160 unsigned int newColor = PIXEL_BLACK;
1161
1162
1163 if ((y > 0) && (*(s - screen->pitch) == PIXEL_DIRT)) { // look up
1164 newColor += 1;
1165 }
1166 if ((y < screen->h - 1u) && (*(s + screen->pitch) == PIXEL_DIRT)) { // look down
1167 newColor += 3;
1168 }
1169 if ((x > 0) && (*(s - 1) == PIXEL_DIRT)) { // look left
1170 newColor += 2;
1171 }
1172 if ((x < screen->pitch - 1u) && (*(s + 1) == PIXEL_DIRT)) { // look right
1173 newColor += 2;
1174 }
1175 if (newColor != PIXEL_BLACK) {
1176 return(newColor + 16); // 16 must be the start of the brown pixels.
1177 }
1178
1179 return(PIXEL_BLACK);
1180 }
JE_aliasDirt(SDL_Surface * screen)1181 static void JE_aliasDirt( SDL_Surface * screen )
1182 {
1183 /* This complicated looking function goes through the whole screen
1184 * looking for brown pixels which just happen to be next to non-brown
1185 * pixels. It's an aliaser, just like it says. */
1186 unsigned int x, y;
1187
1188
1189 /* This is a pointer to a screen. If you don't like pointer arithmetic,
1190 * you won't like this function. */
1191 Uint8 *s = screen->pixels;
1192 s += 12 * screen->pitch;
1193
1194 for (y = 12; y < (unsigned)screen->h; y++) {
1195 for (x = 0; x < screen->pitch; x++) {
1196 if (*s == PIXEL_BLACK) {
1197 *s = aliasDirtPixel(screen, x, y, s);
1198 }
1199
1200 s++;
1201 }
1202 }
1203 }
1204
JE_placementPosition(unsigned int passed_x,unsigned int width,unsigned int * world)1205 static unsigned int JE_placementPosition( unsigned int passed_x, unsigned int width, unsigned int * world )
1206 {
1207 unsigned int i, new_y;
1208
1209
1210 /* This is the function responsible for carving out chunks of land.
1211 * There's a bug here, but it's a pretty major gameplay altering one:
1212 * areas can be carved out for units that are aerial or in mountains.
1213 * This can result in huge caverns. Ergo, it's a feature :)
1214 *
1215 * I wondered if it might be better to not carve out land at all.
1216 * On testing I determined that was distracting and added nothing. */
1217 new_y = 0;
1218 for (i = passed_x; i <= passed_x + width - 1; i++)
1219 {
1220 if (new_y < world[i])
1221 new_y = world[i];
1222 }
1223
1224 for (i = passed_x; i <= passed_x + width - 1; i++)
1225 {
1226 world[i] = new_y;
1227 }
1228
1229 return new_y;
1230 }
1231
JE_stabilityCheck(unsigned int x,unsigned int y)1232 static bool JE_stabilityCheck( unsigned int x, unsigned int y )
1233 {
1234 unsigned int i, numDirtPixels;
1235 Uint8 * s;
1236
1237
1238 numDirtPixels = 0;
1239 s = destructTempScreen->pixels;
1240 s += x + (y * destructTempScreen->pitch) - 1;
1241
1242 /* Check the 12 pixels on the bottom border of our object */
1243 for (i = 0; i < 12; i++)
1244 {
1245 if (*s == PIXEL_DIRT)
1246 numDirtPixels++;
1247
1248 s++;
1249 }
1250
1251 /* If there are fewer than 10 brown pixels we don't consider it a solid base */
1252 return (numDirtPixels < 10);
1253 }
1254
JE_tempScreenChecking(void)1255 static void JE_tempScreenChecking( void ) /*and copy to vgascreen*/
1256 {
1257 Uint8 *s = VGAScreen->pixels;
1258 s += 12 * VGAScreen->pitch;
1259
1260 Uint8 *temps = destructTempScreen->pixels;
1261 temps += 12 * destructTempScreen->pitch;
1262
1263 for (int y = 12; y < VGAScreen->h; y++)
1264 {
1265 for (int x = 0; x < VGAScreen->pitch; x++)
1266 {
1267 // This block is what fades out explosions. The palette from 241
1268 // to 255 fades from a very dark red to a very bright yellow.
1269 if (*temps >= 241)
1270 {
1271 if (*temps == 241)
1272 *temps = PIXEL_BLACK;
1273 else
1274 (*temps)--;
1275 }
1276
1277 // This block is for aliasing dirt. Computers are fast these days,
1278 // and it's fun.
1279 if (config.alwaysalias == true && *temps == PIXEL_BLACK) {
1280 *temps = aliasDirtPixel(VGAScreen, x, y, temps);
1281 }
1282
1283 /* This is copying from our temp screen to VGAScreen */
1284 *s = *temps;
1285
1286 s++;
1287 temps++;
1288 }
1289 }
1290 }
1291
JE_makeExplosion(unsigned int tempPosX,unsigned int tempPosY,enum de_shot_t shottype)1292 static void JE_makeExplosion( unsigned int tempPosX, unsigned int tempPosY, enum de_shot_t shottype )
1293 {
1294 unsigned int i, tempExploSize;
1295
1296
1297 /* First find an open explosion. If we can't find one, return.*/
1298 for (i = 0; i < config.max_explosions; i++)
1299 {
1300 if (exploRec[i].isAvailable == true)
1301 break;
1302 }
1303 if (i == config.max_explosions) /* No empty slots */
1304 {
1305 return;
1306 }
1307
1308
1309 exploRec[i].isAvailable = false;
1310 exploRec[i].x = tempPosX;
1311 exploRec[i].y = tempPosY;
1312 exploRec[i].explowidth = 2;
1313
1314 if(shottype != SHOT_INVALID)
1315 {
1316 tempExploSize = exploSize[shottype];
1317 if (tempExploSize < 5)
1318 JE_eSound(3);
1319 else if (tempExploSize < 15)
1320 JE_eSound(4);
1321 else if (tempExploSize < 20)
1322 JE_eSound(12);
1323 else if (tempExploSize < 40)
1324 JE_eSound(11);
1325 else
1326 {
1327 JE_eSound(12);
1328 JE_eSound(11);
1329 }
1330
1331 exploRec[i].explomax = tempExploSize;
1332 exploRec[i].explofill = exploDensity[shottype];
1333 exploRec[i].exploType = shotDirt[shottype];
1334 }
1335 else
1336 {
1337 JE_eSound(4);
1338 exploRec[i].explomax = (mt_rand() % 40) + 10;
1339 exploRec[i].explofill = (mt_rand() % 60) + 20;
1340 exploRec[i].exploType = EXPL_NORMAL;
1341 }
1342 }
1343
JE_eSound(unsigned int sound)1344 static void JE_eSound( unsigned int sound )
1345 {
1346 static int exploSoundChannel = 0;
1347
1348 if (++exploSoundChannel > 5)
1349 {
1350 exploSoundChannel = 1;
1351 }
1352
1353 soundQueue[exploSoundChannel] = sound;
1354 }
1355
JE_superPixel(unsigned int tempPosX,unsigned int tempPosY)1356 static void JE_superPixel( unsigned int tempPosX, unsigned int tempPosY )
1357 {
1358 const unsigned int starPattern[5][5] = {
1359 { 0, 0, 246, 0, 0 },
1360 { 0, 247, 249, 247, 0 },
1361 { 246, 249, 252, 249, 246 },
1362 { 0, 247, 249, 247, 0 },
1363 { 0, 0, 246, 0, 0 }
1364 };
1365 const unsigned int starIntensity[5][5] = {
1366 { 0, 0, 1, 0, 0 },
1367 { 0, 1, 2, 1, 0 },
1368 { 1, 2, 4, 2, 1 },
1369 { 0, 1, 2, 1, 0 },
1370 { 0, 0, 1, 0, 0 }
1371 };
1372
1373 int x, y, maxX, maxY;
1374 unsigned int rowLen;
1375 Uint8 *s;
1376
1377
1378 maxX = destructTempScreen->pitch;
1379 maxY = destructTempScreen->h;
1380
1381 rowLen = destructTempScreen->pitch;
1382 s = destructTempScreen->pixels;
1383 s += (rowLen * (tempPosY - 2)) + (tempPosX - 2);
1384
1385 for (y = 0; y < 5; y++, s += rowLen - 5)
1386 {
1387 if ((signed)tempPosY + y - 2 < 0 /* would be out of bounds */
1388 || (signed)tempPosY + y - 2 >= maxY) { continue; }
1389
1390 for (x = 0; x < 5; x++, s++)
1391 {
1392 if ((signed)tempPosX + x - 2 < 0
1393 || (signed)tempPosX + x - 2 >= maxX) { continue; }
1394
1395 if (starPattern[y][x] == 0) { continue; } /* this is just to speed it up */
1396
1397 /* at this point *s is our pixel. Our constant arrays tell us what
1398 * to do with it. */
1399 if (*s < starPattern[y][x])
1400 {
1401 *s = starPattern[y][x];
1402 }
1403 else if (*s + starIntensity[y][x] > 255)
1404 {
1405 *s = 255;
1406 }
1407 else
1408 {
1409 *s += starIntensity[y][x];
1410 }
1411 }
1412 }
1413 }
1414
JE_helpScreen(void)1415 static void JE_helpScreen( void )
1416 {
1417 unsigned int i, j;
1418
1419
1420 //JE_getVGA(); didn't do anything anyway?
1421 fade_black(15);
1422 memcpy(VGAScreen2->pixels, VGAScreen->pixels, VGAScreen2->h * VGAScreen2->pitch);
1423 JE_clr256(VGAScreen);
1424
1425 for(i = 0; i < 2; i++)
1426 {
1427 JE_outText(VGAScreen, 100, 5 + i * 90, destructHelp[i * 12 + 0], 2, 4);
1428 JE_outText(VGAScreen, 100, 15 + i * 90, destructHelp[i * 12 + 1], 2, 1);
1429 for (j = 3; j <= 12; j++)
1430 {
1431 JE_outText(VGAScreen, ((j - 1) % 2) * 160 + 10, 15 + ((j - 1) / 2) * 12 + i * 90, destructHelp[i * 12 + j-1], 1, 3);
1432 }
1433 }
1434 JE_outText(VGAScreen, 30, 190, destructHelp[24], 3, 4);
1435 JE_showVGA();
1436 fade_palette(colors, 15, 0, 255);
1437
1438 do /* wait until user hits a key */
1439 {
1440 service_SDL_events(true);
1441 SDL_Delay(16);
1442 }
1443 while (!newkey);
1444
1445 fade_black(15);
1446 memcpy(VGAScreen->pixels, VGAScreen2->pixels, VGAScreen->h * VGAScreen->pitch);
1447 JE_showVGA();
1448 fade_palette(colors, 15, 0, 255);
1449 }
1450
1451
JE_pauseScreen(void)1452 static void JE_pauseScreen( void )
1453 {
1454 set_volume(tyrMusicVolume / 2, fxVolume);
1455
1456 /* Save our current screen/game world. We don't want to screw it up while paused. */
1457 memcpy(VGAScreen2->pixels, VGAScreen->pixels, VGAScreen2->h * VGAScreen2->pitch);
1458 JE_outText(VGAScreen, JE_fontCenter(miscText[22], TINY_FONT), 90, miscText[22], 12, 5);
1459 JE_showVGA();
1460
1461 do /* wait until user hits a key */
1462 {
1463 service_SDL_events(true);
1464 SDL_Delay(16);
1465 }
1466 while (!newkey);
1467
1468 /* Restore current screen & volume*/
1469 memcpy(VGAScreen->pixels, VGAScreen2->pixels, VGAScreen->h * VGAScreen->pitch);
1470 JE_showVGA();
1471
1472 set_volume(tyrMusicVolume, fxVolume);
1473 }
1474
1475 /* DE_ResetX
1476 *
1477 * The reset functions clear the state of whatefer they are assigned to.
1478 */
DE_ResetUnits(void)1479 static void DE_ResetUnits( void )
1480 {
1481 unsigned int p, u;
1482
1483
1484 for (p = 0; p < MAX_PLAYERS; ++p)
1485 for (u = 0; u < config.max_installations; ++u)
1486 destruct_player[p].unit[u].health = 0;
1487 }
DE_ResetPlayers(void)1488 static void DE_ResetPlayers( void )
1489 {
1490 unsigned int i;
1491
1492
1493 for (i = 0; i < MAX_PLAYERS; ++i)
1494 {
1495 destruct_player[i].is_cpu = false;
1496 destruct_player[i].unitSelected = 0;
1497 destruct_player[i].shotDelay = 0;
1498 destruct_player[i].score = 0;
1499 destruct_player[i].aiMemory.c_Angle = 0;
1500 destruct_player[i].aiMemory.c_Power = 0;
1501 destruct_player[i].aiMemory.c_Fire = 0;
1502 destruct_player[i].aiMemory.c_noDown = 0;
1503 memcpy(destruct_player[i].keys.Config, defaultKeyConfig[i], sizeof(destruct_player[i].keys.Config));
1504 }
1505 }
DE_ResetWeapons(void)1506 static void DE_ResetWeapons( void )
1507 {
1508 unsigned int i;
1509
1510
1511 for (i = 0; i < config.max_shots; i++)
1512 shotRec[i].isAvailable = true;
1513
1514 for (i = 0; i < config.max_explosions; i++)
1515 exploRec[i].isAvailable = true;
1516 }
DE_ResetLevel(void)1517 static void DE_ResetLevel( void )
1518 {
1519 /* Okay, let's prep the arena */
1520
1521 DE_ResetWeapons();
1522
1523 JE_generateTerrain();
1524 DE_ResetAI();
1525 }
DE_ResetAI(void)1526 static void DE_ResetAI( void )
1527 {
1528 unsigned int i, j;
1529 struct destruct_unit_s * ptr;
1530
1531
1532 for (i = PLAYER_LEFT; i < MAX_PLAYERS; i++)
1533 {
1534 if (destruct_player[i].is_cpu == false) { continue; }
1535 ptr = destruct_player[i].unit;
1536
1537 for( j = 0; j < config.max_installations; j++, ptr++)
1538 {
1539 if(DE_isValidUnit(ptr) == false)
1540 continue;
1541
1542 if (systemAngle[ptr->unitType] || ptr->unitType == UNIT_HELI)
1543 ptr->angle = M_PI_4;
1544 else
1545 ptr->angle = 0;
1546
1547 ptr->power = (ptr->unitType == UNIT_LASER) ? 6 : 4;
1548
1549 if (world.mapFlags & MAP_WALLS)
1550 ptr->shotType = defaultCpuWeaponB[ptr->unitType];
1551 else
1552 ptr->shotType = defaultCpuWeapon[ptr->unitType];
1553 }
1554 }
1555 }
DE_ResetActions(void)1556 static void DE_ResetActions( void )
1557 {
1558 unsigned int i;
1559
1560
1561 for(i = 0; i < MAX_PLAYERS; i++)
1562 { /* Zero it all. A memset would do the trick */
1563 memset(&(destruct_player[i].moves), 0, sizeof(destruct_player[i].moves));
1564 }
1565 }
1566 /* DE_RunTick
1567 *
1568 * Runs one tick. One tick involves handling physics, drawing crap,
1569 * moving projectiles and explosions, and getting input.
1570 * Returns true while the game is running or false if the game is
1571 * to be terminated.
1572 */
DE_RunTick(void)1573 static enum de_state_t DE_RunTick( void )
1574 {
1575 static unsigned int endDelay;
1576
1577
1578 setjasondelay(1);
1579
1580 memset(soundQueue, 0, sizeof(soundQueue));
1581 JE_tempScreenChecking();
1582
1583 DE_ResetActions();
1584 DE_RunTickCycleDeadUnits();
1585
1586
1587 DE_RunTickGravity();
1588 DE_RunTickAnimate();
1589 DE_RunTickDrawWalls();
1590 DE_RunTickExplosions();
1591 DE_RunTickShots();
1592 DE_RunTickAI();
1593 DE_RunTickDrawCrosshairs();
1594 DE_RunTickDrawHUD();
1595 JE_showVGA();
1596
1597 if (destructFirstTime)
1598 {
1599 fade_palette(colors, 25, 0, 255);
1600 destructFirstTime = false;
1601 endDelay = 0;
1602 }
1603
1604 DE_RunTickGetInput();
1605 DE_ProcessInput();
1606
1607 if (endDelay > 0)
1608 {
1609 if(--endDelay == 0)
1610 {
1611 return(STATE_RELOAD);
1612 }
1613 }
1614 else if ( DE_RunTickCheckEndgame() == true)
1615 {
1616 endDelay = 80;
1617 }
1618
1619 DE_RunTickPlaySounds();
1620
1621 /* The rest of this cruft needs to be put in appropriate sections */
1622 if (keysactive[SDLK_F10])
1623 {
1624 destruct_player[PLAYER_LEFT].is_cpu = !destruct_player[PLAYER_LEFT].is_cpu;
1625 keysactive[SDLK_F10] = false;
1626 }
1627 if (keysactive[SDLK_F11])
1628 {
1629 destruct_player[PLAYER_RIGHT].is_cpu = !destruct_player[PLAYER_RIGHT].is_cpu;
1630 keysactive[SDLK_F11] = false;
1631 }
1632 if (keysactive[SDLK_p])
1633 {
1634 JE_pauseScreen();
1635 keysactive[lastkey_sym] = false;
1636 }
1637
1638 if (keysactive[SDLK_F1])
1639 {
1640 JE_helpScreen();
1641 keysactive[lastkey_sym] = false;
1642 }
1643
1644 wait_delay();
1645
1646 if (keysactive[SDLK_ESCAPE])
1647 {
1648 keysactive[SDLK_ESCAPE] = false;
1649 return(STATE_INIT); /* STATE_INIT drops us to the mode select */
1650 }
1651
1652 if (keysactive[SDLK_BACKSPACE])
1653 {
1654 keysactive[SDLK_BACKSPACE] = false;
1655 return(STATE_RELOAD); /* STATE_RELOAD creates a new map */
1656 }
1657
1658 return(STATE_CONTINUE);
1659 }
1660
1661 /* DE_RunTickX
1662 *
1663 * Handles something that we do once per tick, such as
1664 * track ammo and move asplosions.
1665 */
DE_RunTickCycleDeadUnits(void)1666 static void DE_RunTickCycleDeadUnits( void )
1667 {
1668 unsigned int i;
1669 struct destruct_unit_s * unit;
1670
1671
1672 /* This code automatically switches the active unit if it is destroyed
1673 * and skips over the useless satellite */
1674 for (i = 0; i < MAX_PLAYERS; i++)
1675 {
1676 if (destruct_player[i].unitsRemaining == 0) { continue; }
1677
1678 unit = &(destruct_player[i].unit[destruct_player[i].unitSelected]);
1679 while(DE_isValidUnit(unit) == false
1680 || unit->shotType == SHOT_INVALID)
1681 {
1682 destruct_player[i].unitSelected++;
1683 unit++;
1684 if (destruct_player[i].unitSelected >= config.max_installations)
1685 {
1686 destruct_player[i].unitSelected = 0;
1687 unit = destruct_player[i].unit;
1688 }
1689 }
1690 }
1691 }
DE_RunTickGravity(void)1692 static void DE_RunTickGravity( void )
1693 {
1694 unsigned int i, j;
1695 struct destruct_unit_s * unit;
1696
1697
1698 for (i = 0; i < MAX_PLAYERS; i++)
1699 {
1700
1701 unit = destruct_player[i].unit;
1702 for (j = 0; j < config.max_installations; j++, unit++)
1703 {
1704 if (DE_isValidUnit(unit) == false) /* invalid unit */
1705 continue;
1706
1707 switch(unit->unitType)
1708 {
1709 case UNIT_SATELLITE: /* satellites don't fall down */
1710 break;
1711
1712 case UNIT_HELI:
1713 case UNIT_JUMPER:
1714 if (unit->isYInAir == true) /* unit is falling down, at least in theory */
1715 {
1716 DE_GravityFlyUnit(unit);
1717 break;
1718 }
1719 /* else fall through and treat as a normal unit */
1720
1721 default:
1722 DE_GravityLowerUnit(unit);
1723 }
1724
1725 /* Draw the unit. */
1726 DE_GravityDrawUnit(i, unit);
1727 }
1728 }
1729 }
DE_GravityDrawUnit(enum de_player_t team,struct destruct_unit_s * unit)1730 static void DE_GravityDrawUnit( enum de_player_t team, struct destruct_unit_s * unit )
1731 {
1732 unsigned int anim_index;
1733
1734
1735 anim_index = GraphicBase[team][unit->unitType] + unit->ani_frame;
1736 if (unit->unitType == UNIT_HELI)
1737 {
1738 /* Adjust animation index if we are travelling right or left. */
1739 if (unit->lastMove < -2)
1740 anim_index += 5;
1741 else if (unit->lastMove > 2)
1742 anim_index += 10;
1743 }
1744 else /* This handles our cannons and the like */
1745 {
1746 anim_index += floorf(unit->angle * 9.99f / M_PI);
1747 }
1748
1749 blit_sprite2(VGAScreen, unit->unitX, roundf(unit->unitY) - 13, eShapes[0], anim_index);
1750 }
DE_GravityLowerUnit(struct destruct_unit_s * unit)1751 static void DE_GravityLowerUnit( struct destruct_unit_s * unit )
1752 {
1753 /* units fall at a constant speed. The heli is an odd case though;
1754 * we simply give it a downward velocity, but due to a buggy implementation
1755 * the chopper didn't lower until you tried to fly it up. Tyrian 2000 fixes
1756 * this by not making the chopper a special case. I've decided to actually
1757 * mix both; the chopper is given a slight downward acceleration (simulating
1758 * a 'rocky' takeoff), and it is lowered like a regular unit, but not as
1759 * quickly.
1760 */
1761 if(unit->unitY < 199) { /* checking takes time, don't check if it's at the bottom */
1762 if (JE_stabilityCheck(unit->unitX, roundf(unit->unitY)))
1763 {
1764 switch(unit->unitType)
1765 {
1766 case UNIT_HELI:
1767 unit->unitYMov = 1.5f;
1768 unit->unitY += 0.2f;
1769 break;
1770
1771 default:
1772 unit->unitY += 1;
1773 }
1774
1775 if (unit->unitY > 199) /* could be possible */
1776 unit->unitY = 199;
1777 }
1778 }
1779 }
DE_GravityFlyUnit(struct destruct_unit_s * unit)1780 static void DE_GravityFlyUnit( struct destruct_unit_s * unit )
1781 {
1782 if (unit->unitY + unit->unitYMov > 199) /* would hit bottom of screen */
1783 {
1784 unit->unitY = 199;
1785 unit->unitYMov = 0;
1786 unit->isYInAir = false;
1787 return;
1788 }
1789
1790 /* move the unit and alter acceleration */
1791 unit->unitY += unit->unitYMov;
1792 if (unit->unitY < 24) /* This stops units from going above the screen */
1793 {
1794 unit->unitYMov = 0;
1795 unit->unitY = 24;
1796 }
1797
1798 if (unit->unitType == UNIT_HELI) /* helicopters fall more slowly */
1799 unit->unitYMov += 0.0001f;
1800 else
1801 unit->unitYMov += 0.03f;
1802
1803 if (!JE_stabilityCheck(unit->unitX, roundf(unit->unitY)))
1804 {
1805 unit->unitYMov = 0;
1806 unit->isYInAir = false;
1807 }
1808 }
DE_RunTickAnimate(void)1809 static void DE_RunTickAnimate( void )
1810 {
1811 unsigned int p, u;
1812 struct destruct_unit_s * ptr;
1813
1814
1815 for (p = 0; p < MAX_PLAYERS; ++p)
1816 {
1817 ptr = destruct_player[p].unit;
1818 for (u = 0; u < config.max_installations; ++u, ++ptr)
1819 {
1820 /* Don't mess with any unit that is unallocated
1821 * or doesn't animate and is set to frame 0 */
1822 if(DE_isValidUnit(ptr) == false) { continue; }
1823 if(systemAni[ptr->unitType] == false && ptr->ani_frame == 0) { continue; }
1824
1825 if (++(ptr->ani_frame) > 3)
1826 {
1827 ptr->ani_frame = 0;
1828 }
1829 }
1830 }
1831 }
DE_RunTickDrawWalls(void)1832 static void DE_RunTickDrawWalls( void )
1833 {
1834 unsigned int i;
1835
1836
1837 for (i = 0; i < config.max_walls; i++)
1838 {
1839 if (world.mapWalls[i].wallExist)
1840 {
1841 blit_sprite2(VGAScreen, world.mapWalls[i].wallX, world.mapWalls[i].wallY, eShapes[0], 42);
1842 }
1843 }
1844 }
DE_RunTickExplosions(void)1845 static void DE_RunTickExplosions( void )
1846 {
1847 unsigned int i, j;
1848 int tempPosX, tempPosY;
1849 float tempRadian;
1850
1851
1852 /* Run through all open explosions. They are not sorted in any way */
1853 for (i = 0; i < config.max_explosions; i++)
1854 {
1855 if (exploRec[i].isAvailable == true) { continue; } /* Nothing to do */
1856
1857 for (j = 0; j < exploRec[i].explofill; j++)
1858 {
1859 /* An explosion is comprised of multiple 'flares' that fan out.
1860 Calculate where this 'flare' will end up */
1861 tempRadian = mt_rand_lt1() * (2 * M_PI);
1862 tempPosY = exploRec[i].y + roundf(cosf(tempRadian) * mt_rand_lt1() * exploRec[i].explowidth);
1863 tempPosX = exploRec[i].x + roundf(sinf(tempRadian) * mt_rand_lt1() * exploRec[i].explowidth);
1864
1865 /* Our game allows explosions to wrap around. This looks to have
1866 * originally been a bug that was left in as being fun, but we are
1867 * going to replicate it w/o risking out of bound arrays. */
1868
1869 while(tempPosX < 0) { tempPosX += 320; }
1870 while(tempPosX > 320) { tempPosX -= 320; }
1871
1872 /* We don't draw our explosion if it's out of bounds vertically */
1873 if (tempPosY >= 200 || tempPosY <= 15) { continue; }
1874
1875 /* And now the drawing. There are only two types of explosions
1876 * right now; dirt and flares. Dirt simply draws a brown pixel;
1877 * flares explode and have a star formation. */
1878 switch(exploRec[i].exploType)
1879 {
1880 case EXPL_DIRT:
1881 ((Uint8 *)destructTempScreen->pixels)[tempPosX + tempPosY * destructTempScreen->pitch] = PIXEL_DIRT;
1882 break;
1883
1884 case EXPL_NORMAL:
1885 JE_superPixel(tempPosX, tempPosY);
1886 DE_TestExplosionCollision(tempPosX, tempPosY);
1887 break;
1888
1889 default:
1890 assert(false);
1891 break;
1892 }
1893 }
1894
1895 /* Widen the explosion and delete it if necessary. */
1896 exploRec[i].explowidth++;
1897 if (exploRec[i].explowidth == exploRec[i].explomax)
1898 {
1899 exploRec[i].isAvailable = true;
1900 }
1901 }
1902 }
DE_TestExplosionCollision(unsigned int PosX,unsigned int PosY)1903 static void DE_TestExplosionCollision( unsigned int PosX, unsigned int PosY)
1904 {
1905 unsigned int i, j;
1906 struct destruct_unit_s * unit;
1907
1908
1909 for (i = PLAYER_LEFT; i < MAX_PLAYERS; i++)
1910 {
1911 unit = destruct_player[i].unit;
1912 for (j = 0; j < config.max_installations; j++, unit++)
1913 {
1914 if (DE_isValidUnit(unit) == true
1915 && PosX > unit->unitX && PosX < unit->unitX + 11
1916 && PosY < unit->unitY && PosY > unit->unitY - 11)
1917 {
1918 unit->health--;
1919 if (unit->health <= 0)
1920 {
1921 DE_DestroyUnit(i, unit);
1922 }
1923 }
1924 }
1925 }
1926 }
DE_DestroyUnit(enum de_player_t playerID,struct destruct_unit_s * unit)1927 static void DE_DestroyUnit( enum de_player_t playerID, struct destruct_unit_s * unit )
1928 {
1929 /* This function call was an evil evil piece of brilliance before. Go on.
1930 * Look at the older revisions. It passed the result of a comparison.
1931 * MULTIPLIED. This is at least a little clearer... */
1932 JE_makeExplosion(unit->unitX + 5, roundf(unit->unitY) - 5, (unit->unitType == UNIT_HELI) ? SHOT_SMALL : SHOT_INVALID); /* Helicopters explode like small shots do. Invalids are their own special case. */
1933
1934 if (unit->unitType != UNIT_SATELLITE) /* increment score */
1935 { /* todo: change when teams are created. Hacky kludge for now.*/
1936 destruct_player[playerID].unitsRemaining--;
1937 destruct_player[((playerID == PLAYER_LEFT) ? PLAYER_RIGHT : PLAYER_LEFT)].score++;
1938 }
1939 }
1940
DE_RunTickShots(void)1941 static void DE_RunTickShots( void )
1942 {
1943 unsigned int i, j, k;
1944 unsigned int tempTrails;
1945 unsigned int tempPosX, tempPosY;
1946 struct destruct_unit_s * unit;
1947
1948
1949 for (i = 0; i < config.max_shots; i++)
1950 {
1951 if (shotRec[i].isAvailable == true) { continue; } /* Nothing to do */
1952
1953 /* Move the shot. Simple displacement */
1954 shotRec[i].x += shotRec[i].xmov;
1955 shotRec[i].y += shotRec[i].ymov;
1956
1957 /* If the shot can bounce off the map, bounce it */
1958 if (shotBounce[shotRec[i].shottype])
1959 {
1960 if (shotRec[i].y > 199 || shotRec[i].y < 14)
1961 {
1962 shotRec[i].y -= shotRec[i].ymov;
1963 shotRec[i].ymov = -shotRec[i].ymov;
1964 }
1965 if (shotRec[i].x < 1 || shotRec[i].x > 318)
1966 {
1967 shotRec[i].x -= shotRec[i].xmov;
1968 shotRec[i].xmov = -shotRec[i].xmov;
1969 }
1970 }
1971 else /* If it cannot, apply normal physics */
1972 {
1973 shotRec[i].ymov += 0.05f; /* add gravity */
1974
1975 if (shotRec[i].y > 199) /* We hit the floor */
1976 {
1977 shotRec[i].y -= shotRec[i].ymov;
1978 shotRec[i].ymov = -shotRec[i].ymov * 0.8f; /* bounce at reduced velocity */
1979
1980 /* Don't allow a bouncing shot to bounce straight up and down */
1981 if (shotRec[i].xmov == 0)
1982 {
1983 shotRec[i].xmov += mt_rand_lt1() - 0.5f;
1984 }
1985 }
1986 }
1987
1988 /* Shot has gone out of bounds. Eliminate it. */
1989 if (shotRec[i].x > 318 || shotRec[i].x < 1)
1990 {
1991 shotRec[i].isAvailable = true;
1992 continue;
1993 }
1994
1995 /* Now check for collisions. */
1996
1997 /* Don't bother checking for collisions above the map :) */
1998 if (shotRec[i].y <= 14)
1999 continue;
2000
2001 tempPosX = roundf(shotRec[i].x);
2002 tempPosY = roundf(shotRec[i].y);
2003
2004 /*Check building hits*/
2005 for(j = 0; j < MAX_PLAYERS; j++)
2006 {
2007 unit = destruct_player[j].unit;
2008 for(k = 0; k < config.max_installations; k++, unit++)
2009 {
2010 if (DE_isValidUnit(unit) == false)
2011 continue;
2012
2013 if (tempPosX > unit->unitX && tempPosX < unit->unitX + 11
2014 && tempPosY < unit->unitY && tempPosY > unit->unitY - 13)
2015 {
2016 shotRec[i].isAvailable = true;
2017 JE_makeExplosion(tempPosX, tempPosY, shotRec[i].shottype);
2018 }
2019 }
2020 }
2021
2022 tempTrails = (shotColor[shotRec[i].shottype] << 4) - 3;
2023 JE_pixCool(tempPosX, tempPosY, tempTrails);
2024
2025 /*Draw the shot trail (if applicable) */
2026 switch (shotTrail[shotRec[i].shottype])
2027 {
2028 case TRAILS_NONE:
2029 break;
2030 case TRAILS_NORMAL:
2031 DE_DrawTrails( &(shotRec[i]), 2, 4, tempTrails - 3 );
2032 break;
2033 case TRAILS_FULL:
2034 DE_DrawTrails( &(shotRec[i]), 4, 3, tempTrails - 1 );
2035 break;
2036 }
2037
2038 /* Bounce off of or destroy walls */
2039 for (j = 0; j < config.max_walls; j++)
2040 {
2041 if (world.mapWalls[j].wallExist == true
2042 && tempPosX >= world.mapWalls[j].wallX && tempPosX <= world.mapWalls[j].wallX + 11
2043 && tempPosY >= world.mapWalls[j].wallY && tempPosY <= world.mapWalls[j].wallY + 14)
2044 {
2045 if (demolish[shotRec[i].shottype])
2046 {
2047 /* Blow up the wall and remove the shot. */
2048 world.mapWalls[j].wallExist = false;
2049 shotRec[i].isAvailable = true;
2050 JE_makeExplosion(tempPosX, tempPosY, shotRec[i].shottype);
2051 continue;
2052 }
2053 else
2054 {
2055 /* Otherwise, bounce. */
2056 if (shotRec[i].x - shotRec[i].xmov < world.mapWalls[j].wallX
2057 || shotRec[i].x - shotRec[i].xmov > world.mapWalls[j].wallX + 11)
2058 {
2059 shotRec[i].xmov = -shotRec[i].xmov;
2060 }
2061 if (shotRec[i].y - shotRec[i].ymov < world.mapWalls[j].wallY
2062 || shotRec[i].y - shotRec[i].ymov > world.mapWalls[j].wallY + 14)
2063 {
2064 if (shotRec[i].ymov < 0)
2065 shotRec[i].ymov = -shotRec[i].ymov;
2066 else
2067 shotRec[i].ymov = -shotRec[i].ymov * 0.8f;
2068 }
2069
2070 tempPosX = roundf(shotRec[i].x);
2071 tempPosY = roundf(shotRec[i].y);
2072 }
2073 }
2074 }
2075
2076 /* Our last collision check, at least for now. We hit dirt. */
2077 if((((Uint8 *)destructTempScreen->pixels)[tempPosX + tempPosY * destructTempScreen->pitch]) == PIXEL_DIRT)
2078 {
2079 shotRec[i].isAvailable = true;
2080 JE_makeExplosion(tempPosX, tempPosY, shotRec[i].shottype);
2081 continue;
2082 }
2083 }
2084 }
DE_DrawTrails(struct destruct_shot_s * shot,unsigned int count,unsigned int decay,unsigned int startColor)2085 static void DE_DrawTrails( struct destruct_shot_s * shot, unsigned int count, unsigned int decay, unsigned int startColor )
2086 {
2087 int i;
2088
2089
2090 for (i = count-1; i >= 0; i--) /* going in reverse is important as it affects how we draw */
2091 {
2092 if (shot->trailc[i] > 0 && shot->traily[i] > 12) /* If it exists and if it's not out of bounds, draw it. */
2093 {
2094 JE_pixCool(shot->trailx[i], shot->traily[i], shot->trailc[i]);
2095 }
2096
2097 if (i == 0) /* The first trail we create. */
2098 {
2099 shot->trailx[i] = roundf(shot->x);
2100 shot->traily[i] = roundf(shot->y);
2101 shot->trailc[i] = startColor;
2102 }
2103 else /* The newer trails decay into the older trails.*/
2104 {
2105 shot->trailx[i] = shot->trailx[i-1];
2106 shot->traily[i] = shot->traily[i-1];
2107 if (shot->trailc[i-1] > 0)
2108 {
2109 shot->trailc[i] = shot->trailc[i-1] - decay;
2110 }
2111 }
2112 }
2113 }
DE_RunTickAI(void)2114 static void DE_RunTickAI( void )
2115 {
2116 unsigned int i, j;
2117 struct destruct_player_s * ptrPlayer, * ptrTarget;
2118 struct destruct_unit_s * ptrUnit, * ptrCurUnit;
2119
2120
2121 for (i = 0; i < MAX_PLAYERS; i++)
2122 {
2123 ptrPlayer = &(destruct_player[i]);
2124 if (ptrPlayer->is_cpu == false)
2125 {
2126 continue;
2127 }
2128
2129
2130 /* I've been thinking, purely hypothetically, about what it would take
2131 * to have multiple computer opponents. The answer? A lot of crap
2132 * and a 'target' variable in the destruct_player struct. */
2133 j = i + 1;
2134 if (j >= MAX_PLAYERS)
2135 {
2136 j = 0;
2137 }
2138
2139 ptrTarget = &(destruct_player[j]);
2140 ptrCurUnit = &(ptrPlayer->unit[ptrPlayer->unitSelected]);
2141
2142
2143 /* This is the start of the original AI. Heh. AI. */
2144
2145 if (ptrPlayer->aiMemory.c_noDown > 0)
2146 ptrPlayer->aiMemory.c_noDown--;
2147
2148 /* Until all structs are properly divvied up this must only apply to player1 */
2149 if (mt_rand() % 100 > 80)
2150 {
2151 ptrPlayer->aiMemory.c_Angle += (mt_rand() % 3) - 1;
2152
2153 if (ptrPlayer->aiMemory.c_Angle > 1)
2154 ptrPlayer->aiMemory.c_Angle = 1;
2155 else
2156 if (ptrPlayer->aiMemory.c_Angle < -1)
2157 ptrPlayer->aiMemory.c_Angle = -1;
2158 }
2159 if (mt_rand() % 100 > 90)
2160 {
2161 if (ptrPlayer->aiMemory.c_Angle > 0 && ptrCurUnit->angle > (M_PI_2) - (M_PI / 9))
2162 ptrPlayer->aiMemory.c_Angle = 0;
2163 else
2164 if (ptrPlayer->aiMemory.c_Angle < 0 && ptrCurUnit->angle < M_PI / 8)
2165 ptrPlayer->aiMemory.c_Angle = 0;
2166 }
2167
2168 if (mt_rand() % 100 > 93)
2169 {
2170 ptrPlayer->aiMemory.c_Power += (mt_rand() % 3) - 1;
2171
2172 if (ptrPlayer->aiMemory.c_Power > 1)
2173 ptrPlayer->aiMemory.c_Power = 1;
2174 else
2175 if (ptrPlayer->aiMemory.c_Power < -1)
2176 ptrPlayer->aiMemory.c_Power = -1;
2177 }
2178 if (mt_rand() % 100 > 90)
2179 {
2180 if (ptrPlayer->aiMemory.c_Power > 0 && ptrCurUnit->power > 4)
2181 ptrPlayer->aiMemory.c_Power = 0;
2182 else
2183 if (ptrPlayer->aiMemory.c_Power < 0 && ptrCurUnit->power < 3)
2184 ptrPlayer->aiMemory.c_Power = 0;
2185 else
2186 if (ptrCurUnit->power < 2)
2187 ptrPlayer->aiMemory.c_Power = 1;
2188 }
2189
2190 // prefer helicopter
2191 ptrUnit = ptrPlayer->unit;
2192 for (j = 0; j < config.max_installations; j++, ptrUnit++)
2193 {
2194 if (DE_isValidUnit(ptrUnit) && ptrUnit->unitType == UNIT_HELI)
2195 {
2196 ptrPlayer->unitSelected = j;
2197 break;
2198 }
2199 }
2200
2201 if (ptrCurUnit->unitType == UNIT_HELI)
2202 {
2203 if (ptrCurUnit->isYInAir == false)
2204 {
2205 ptrPlayer->aiMemory.c_Power = 1;
2206 }
2207 if (mt_rand() % ptrCurUnit->unitX > 100)
2208 {
2209 ptrPlayer->aiMemory.c_Power = 1;
2210 }
2211 if (mt_rand() % 240 > ptrCurUnit->unitX)
2212 {
2213 ptrPlayer->moves.actions[MOVE_RIGHT] = true;
2214 }
2215 else if ((mt_rand() % 20) + 300 < ptrCurUnit->unitX)
2216 {
2217 ptrPlayer->moves.actions[MOVE_LEFT] = true;
2218 }
2219 else if (mt_rand() % 30 == 1)
2220 {
2221 ptrPlayer->aiMemory.c_Angle = (mt_rand() % 3) - 1;
2222 }
2223 if (ptrCurUnit->unitX > 295 && ptrCurUnit->lastMove > 1)
2224 {
2225 ptrPlayer->moves.actions[MOVE_LEFT] = true;
2226 ptrPlayer->moves.actions[MOVE_RIGHT] = false;
2227 }
2228 if (ptrCurUnit->unitType != UNIT_HELI || ptrCurUnit->lastMove > 3 || (ptrCurUnit->unitX > 160 && ptrCurUnit->lastMove > -3))
2229 {
2230 if (mt_rand() % (int)roundf(ptrCurUnit->unitY) < 150 && ptrCurUnit->unitYMov < 0.01f && (ptrCurUnit->unitX < 160 || ptrCurUnit->lastMove < 2))
2231 {
2232 ptrPlayer->moves.actions[MOVE_FIRE] = true;
2233 }
2234 ptrPlayer->aiMemory.c_noDown = (5 - abs(ptrCurUnit->lastMove)) * (5 - abs(ptrCurUnit->lastMove)) + 3;
2235 ptrPlayer->aiMemory.c_Power = 1;
2236 } else {
2237 ptrPlayer->moves.actions[MOVE_FIRE] = false;
2238 }
2239
2240 ptrUnit = ptrTarget->unit;
2241 for (j = 0; j < config.max_installations; j++, ptrUnit++)
2242 {
2243 if (abs(ptrUnit->unitX - ptrCurUnit->unitX) < 8)
2244 {
2245 /* I get it. This makes helicoptors hover over
2246 * their enemies. */
2247 if (ptrUnit->unitType == UNIT_SATELLITE)
2248 {
2249 ptrPlayer->moves.actions[MOVE_FIRE] = false;
2250 }
2251 else
2252 {
2253 ptrPlayer->moves.actions[MOVE_LEFT] = false;
2254 ptrPlayer->moves.actions[MOVE_RIGHT] = false;
2255 if (ptrCurUnit->lastMove < -1)
2256 {
2257 ptrCurUnit->lastMove++;
2258 }
2259 else if (ptrCurUnit->lastMove > 1)
2260 {
2261 ptrCurUnit->lastMove--;
2262 }
2263 }
2264 }
2265 }
2266 } else {
2267 ptrPlayer->moves.actions[MOVE_FIRE] = 1;
2268 }
2269
2270 if (mt_rand() % 200 > 198)
2271 {
2272 ptrPlayer->moves.actions[MOVE_CHANGE] = true;
2273 ptrPlayer->aiMemory.c_Angle = 0;
2274 ptrPlayer->aiMemory.c_Power = 0;
2275 ptrPlayer->aiMemory.c_Fire = 0;
2276 }
2277
2278 if (mt_rand() % 100 > 98 || ptrCurUnit->shotType == SHOT_TRACER)
2279 { /* Clearly the CPU doesn't like the tracer :) */
2280 ptrPlayer->moves.actions[MOVE_CYDN] = true;
2281 }
2282 if (ptrPlayer->aiMemory.c_Angle > 0)
2283 {
2284 ptrPlayer->moves.actions[MOVE_LEFT] = true;
2285 }
2286 if (ptrPlayer->aiMemory.c_Angle < 0)
2287 {
2288 ptrPlayer->moves.actions[MOVE_RIGHT] = true;
2289 }
2290 if (ptrPlayer->aiMemory.c_Power > 0)
2291 {
2292 ptrPlayer->moves.actions[MOVE_UP] = true;
2293 }
2294 if (ptrPlayer->aiMemory.c_Power < 0 && ptrPlayer->aiMemory.c_noDown == 0)
2295 {
2296 ptrPlayer->moves.actions[MOVE_DOWN] = true;
2297 }
2298 if (ptrPlayer->aiMemory.c_Fire > 0)
2299 {
2300 ptrPlayer->moves.actions[MOVE_FIRE] = true;
2301 }
2302
2303 if (ptrCurUnit->unitYMov < -0.1f && ptrCurUnit->unitType == UNIT_HELI)
2304 {
2305 ptrPlayer->moves.actions[MOVE_FIRE] = false;
2306 }
2307
2308 /* This last hack was down in the processing section.
2309 * What exactly it was doing there I do not know */
2310 if(ptrCurUnit->unitType == UNIT_LASER || ptrCurUnit->isYInAir == true) {
2311 ptrPlayer->aiMemory.c_Power = 0;
2312 }
2313 }
2314 }
DE_RunTickDrawCrosshairs(void)2315 static void DE_RunTickDrawCrosshairs( void )
2316 {
2317 unsigned int i;
2318 int tempPosX, tempPosY;
2319 int direction;
2320 struct destruct_unit_s * curUnit;
2321
2322
2323 /* Draw the crosshairs. Most vehicles aim left or right. Helis can aim
2324 * either way and this must be accounted for.
2325 */
2326 for (i = 0; i < MAX_PLAYERS; i++)
2327 {
2328 direction = (i == PLAYER_LEFT) ? -1 : 1;
2329 curUnit = &(destruct_player[i].unit[destruct_player[i].unitSelected]);
2330
2331 if (curUnit->unitType == UNIT_HELI)
2332 {
2333 tempPosX = curUnit->unitX + roundf(0.1f * curUnit->lastMove * curUnit->lastMove * curUnit->lastMove) + 5;
2334 tempPosY = roundf(curUnit->unitY) + 1;
2335 } else {
2336 tempPosX = roundf(curUnit->unitX + 6 - cosf(curUnit->angle) * (curUnit->power * 8 + 7) * direction);
2337 tempPosY = roundf(curUnit->unitY - 7 - sinf(curUnit->angle) * (curUnit->power * 8 + 7));
2338 }
2339
2340 /* Draw it. Clip away from the HUD though. */
2341 if(tempPosY > 9)
2342 {
2343 if(tempPosY > 11)
2344 {
2345 if(tempPosY > 13)
2346 {
2347 /* Top pixel */
2348 JE_pix(VGAScreen, tempPosX, tempPosY - 2, 3);
2349 }
2350 /* Middle three pixels */
2351 JE_pix(VGAScreen, tempPosX + 3, tempPosY, 3);
2352 JE_pix(VGAScreen, tempPosX, tempPosY, 14);
2353 JE_pix(VGAScreen, tempPosX - 3, tempPosY, 3);
2354 }
2355 /* Bottom pixel */
2356 JE_pix(VGAScreen, tempPosX, tempPosY + 2, 3);
2357 }
2358 }
2359 }
DE_RunTickDrawHUD(void)2360 static void DE_RunTickDrawHUD( void )
2361 {
2362 unsigned int i;
2363 unsigned int startX;
2364 char tempstr[16]; /* Max size needed: 16 assuming 10 digit int max. */
2365 struct destruct_unit_s * curUnit;
2366
2367
2368 for (i = 0; i < MAX_PLAYERS; i++)
2369 {
2370 curUnit = &(destruct_player[i].unit[destruct_player[i].unitSelected]);
2371 startX = ((i == PLAYER_LEFT) ? 0 : 320 - 150);
2372
2373 fill_rectangle_xy(VGAScreen, startX + 5, 3, startX + 14, 8, 241);
2374 JE_rectangle(VGAScreen, startX + 4, 2, startX + 15, 9, 242);
2375 JE_rectangle(VGAScreen, startX + 3, 1, startX + 16, 10, 240);
2376 fill_rectangle_xy(VGAScreen, startX + 18, 3, startX + 140, 8, 241);
2377 JE_rectangle(VGAScreen, startX + 17, 2, startX + 143, 9, 242);
2378 JE_rectangle(VGAScreen, startX + 16, 1, startX + 144, 10, 240);
2379
2380 blit_sprite2(VGAScreen, startX + 4, 0, eShapes[0], 191 + curUnit->shotType);
2381
2382 JE_outText (VGAScreen, startX + 20, 3, weaponNames[curUnit->shotType], 15, 2);
2383 sprintf (tempstr, "dmg~%d~", curUnit->health);
2384 JE_outText (VGAScreen, startX + 75, 3, tempstr, 15, 0);
2385 sprintf (tempstr, "pts~%d~", destruct_player[i].score);
2386 JE_outText (VGAScreen, startX + 110, 3, tempstr, 15, 0);
2387 }
2388 }
DE_RunTickGetInput(void)2389 static void DE_RunTickGetInput( void )
2390 {
2391 unsigned int player_index, key_index, slot_index;
2392 SDLKey key;
2393
2394 /* destruct_player.keys holds our key config. Players will eventually be
2395 * allowed to can change their key mappings. destruct_player.moves and
2396 * destruct_player.keys line up; rather than manually checking left and
2397 * right we can just loop through the indexes and set the actions as
2398 * needed. */
2399 service_SDL_events(true);
2400
2401 for(player_index = 0; player_index < MAX_PLAYERS; player_index++)
2402 {
2403 for(key_index = 0; key_index < MAX_KEY; key_index++)
2404 {
2405 for(slot_index = 0; slot_index < MAX_KEY_OPTIONS; slot_index++)
2406 {
2407 key = destruct_player[player_index].keys.Config[key_index][slot_index];
2408 if(key == SDLK_UNKNOWN) { break; }
2409 if(keysactive[key] == true)
2410 {
2411 /* The right key was clearly pressed */
2412 destruct_player[player_index].moves.actions[key_index] = true;
2413
2414 /* Some keys we want to toggle afterwards */
2415 if(key_index == KEY_CHANGE ||
2416 key_index == KEY_CYUP ||
2417 key_index == KEY_CYDN)
2418 {
2419 keysactive[key] = false;
2420 }
2421 break;
2422 }
2423 }
2424 }
2425 }
2426 }
DE_ProcessInput(void)2427 static void DE_ProcessInput( void )
2428 {
2429 int direction;
2430
2431 unsigned int player_index;
2432 struct destruct_unit_s * curUnit;
2433
2434
2435 for (player_index = 0; player_index < MAX_PLAYERS; player_index++)
2436 {
2437 if (destruct_player[player_index].unitsRemaining <= 0) { continue; }
2438
2439 direction = (player_index == PLAYER_LEFT) ? -1 : 1;
2440 curUnit = &(destruct_player[player_index].unit[destruct_player[player_index].unitSelected]);
2441
2442 if (systemAngle[curUnit->unitType] == true) /* selected unit may change shot angle */
2443 {
2444 if (destruct_player[player_index].moves.actions[MOVE_LEFT] == true)
2445 {
2446 (player_index == PLAYER_LEFT) ? DE_RaiseAngle(curUnit) : DE_LowerAngle(curUnit);
2447 }
2448 if (destruct_player[player_index].moves.actions[MOVE_RIGHT] == true)
2449 {
2450 (player_index == PLAYER_LEFT) ? DE_LowerAngle(curUnit) : DE_RaiseAngle(curUnit);
2451
2452 }
2453 } else if (curUnit->unitType == UNIT_HELI) {
2454 if (destruct_player[player_index].moves.actions[MOVE_LEFT] == true && curUnit->unitX > 5)
2455 if (JE_stabilityCheck(curUnit->unitX - 5, roundf(curUnit->unitY)))
2456 {
2457 if (curUnit->lastMove > -5)
2458 {
2459 curUnit->lastMove--;
2460 }
2461 curUnit->unitX--;
2462 if (JE_stabilityCheck(curUnit->unitX, roundf(curUnit->unitY)))
2463 {
2464 curUnit->isYInAir = true;
2465 }
2466 }
2467 if (destruct_player[player_index].moves.actions[MOVE_RIGHT] == true && curUnit->unitX < 305)
2468 {
2469 if (JE_stabilityCheck(curUnit->unitX + 5, roundf(curUnit->unitY)))
2470 {
2471 if (curUnit->lastMove < 5)
2472 {
2473 curUnit->lastMove++;
2474 }
2475 curUnit->unitX++;
2476 if (JE_stabilityCheck(curUnit->unitX, roundf(curUnit->unitY)))
2477 {
2478 curUnit->isYInAir = true;
2479 }
2480 }
2481 }
2482 }
2483
2484 if (curUnit->unitType != UNIT_LASER)
2485
2486 { /*increasepower*/
2487 if (destruct_player[player_index].moves.actions[MOVE_UP] == true)
2488 {
2489 if (curUnit->unitType == UNIT_HELI)
2490 {
2491 curUnit->isYInAir = true;
2492 curUnit->unitYMov -= 0.1f;
2493 }
2494 else if (curUnit->unitType == UNIT_JUMPER
2495 && curUnit->isYInAir == false) {
2496 curUnit->unitYMov = -3;
2497 curUnit->isYInAir = true;
2498 }
2499 else {
2500 DE_RaisePower(curUnit);
2501 }
2502 }
2503 /*decreasepower*/
2504 if (destruct_player[player_index].moves.actions[MOVE_DOWN] == true)
2505 {
2506 if (curUnit->unitType == UNIT_HELI && curUnit->isYInAir == true)
2507 {
2508 curUnit->unitYMov += 0.1f;
2509 } else {
2510 DE_LowerPower(curUnit);
2511 }
2512 }
2513 }
2514
2515 /*up/down weapon. These just cycle until a valid weapon is found */
2516 if (destruct_player[player_index].moves.actions[MOVE_CYUP] == true)
2517 {
2518 DE_CycleWeaponUp(curUnit);
2519 }
2520 if (destruct_player[player_index].moves.actions[MOVE_CYDN] == true)
2521 {
2522 DE_CycleWeaponDown(curUnit);
2523 }
2524
2525 /* Change. Since change would change out curUnit pointer, let's just do it last.
2526 * Validity checking is performed at the beginning of the tick. */
2527 if (destruct_player[player_index].moves.actions[MOVE_CHANGE] == true)
2528 {
2529 destruct_player[player_index].unitSelected++;
2530 if (destruct_player[player_index].unitSelected >= config.max_installations)
2531 {
2532 destruct_player[player_index].unitSelected = 0;
2533 }
2534 }
2535
2536 /*Newshot*/
2537 if (destruct_player[player_index].shotDelay > 0)
2538 {
2539 destruct_player[player_index].shotDelay--;
2540 }
2541 if (destruct_player[player_index].moves.actions[MOVE_FIRE] == true
2542 && (destruct_player[player_index].shotDelay == 0))
2543 {
2544 destruct_player[player_index].shotDelay = shotDelay[curUnit->shotType];
2545
2546 switch(shotDirt[curUnit->shotType])
2547 {
2548 case EXPL_NONE:
2549 break;
2550
2551 case EXPL_MAGNET:
2552 DE_RunMagnet(player_index, curUnit);
2553 break;
2554
2555 case EXPL_DIRT:
2556 case EXPL_NORMAL:
2557 DE_MakeShot(player_index, curUnit, direction);
2558 break;
2559
2560 default:
2561 assert(false);
2562 }
2563 }
2564 }
2565 }
2566
DE_CycleWeaponUp(struct destruct_unit_s * unit)2567 static void DE_CycleWeaponUp( struct destruct_unit_s * unit )
2568 {
2569 do
2570 {
2571 unit->shotType++;
2572 if (unit->shotType > SHOT_LAST)
2573 {
2574 unit->shotType = SHOT_FIRST;
2575 }
2576 } while (weaponSystems[unit->unitType][unit->shotType] == 0);
2577 }
DE_CycleWeaponDown(struct destruct_unit_s * unit)2578 static void DE_CycleWeaponDown( struct destruct_unit_s * unit )
2579 {
2580 do
2581 {
2582 unit->shotType--;
2583 if (unit->shotType < SHOT_FIRST)
2584 {
2585 unit->shotType = SHOT_LAST;
2586 }
2587 } while (weaponSystems[unit->unitType][unit->shotType] == 0);
2588 }
2589
2590
DE_MakeShot(enum de_player_t curPlayer,const struct destruct_unit_s * curUnit,int direction)2591 static void DE_MakeShot( enum de_player_t curPlayer, const struct destruct_unit_s * curUnit, int direction )
2592 {
2593 unsigned int i;
2594 unsigned int shotIndex;
2595
2596
2597 /* First, find an empty shot struct we can use */
2598 for (i = 0; ; i++)
2599 {
2600 if (i >= config.max_shots) { return; } /* no empty slots. Do nothing. */
2601
2602 if (shotRec[i].isAvailable)
2603 {
2604 shotIndex = i;
2605 break;
2606 }
2607 }
2608 if (curUnit->unitType == UNIT_HELI && curUnit->isYInAir == false)
2609 { /* Helis can't fire when they are on the ground. */
2610 return;
2611 }
2612
2613 /* Play the firing sound */
2614 soundQueue[curPlayer] = shotSound[curUnit->shotType];
2615
2616 /* Create our shot. Some units have differing logic here */
2617 switch (curUnit->unitType)
2618 {
2619 case UNIT_HELI:
2620
2621 shotRec[shotIndex].x = curUnit->unitX + curUnit->lastMove * 2 + 5;
2622 shotRec[shotIndex].xmov = 0.02f * curUnit->lastMove * curUnit->lastMove * curUnit->lastMove;
2623
2624 /* If we are trying in vain to move up off the screen, act differently.*/
2625 if (destruct_player[curPlayer].moves.actions[MOVE_UP] && curUnit->unitY < 30)
2626 {
2627 shotRec[shotIndex].y = curUnit->unitY;
2628 shotRec[shotIndex].ymov = 0.1f;
2629
2630 if (shotRec[shotIndex].xmov < 0)
2631 {
2632 shotRec[shotIndex].xmov += 0.1f;
2633 }
2634 else if (shotRec[shotIndex].xmov > 0)
2635 {
2636 shotRec[shotIndex].xmov -= 0.1f;
2637 }
2638 }
2639 else
2640 {
2641 shotRec[shotIndex].y = curUnit->unitY + 1;
2642 shotRec[shotIndex].ymov = 0.5f + curUnit->unitYMov * 0.1f;
2643 }
2644 break;
2645
2646 case UNIT_JUMPER: /* Jumpers are normally only special for the left hand player. Bug? Or feature? */
2647
2648 if(config.jumper_straight[curPlayer])
2649 {
2650 /* This is identical to the default case.
2651 * I considered letting the switch fall through
2652 * but that's more confusing to people who aren't used
2653 * to that quirk of switch. */
2654
2655 shotRec[shotIndex].x = curUnit->unitX + 6 - cosf(curUnit->angle) * 10 * direction;
2656 shotRec[shotIndex].y = curUnit->unitY - 7 - sinf(curUnit->angle) * 10;
2657 shotRec[shotIndex].xmov = -cosf(curUnit->angle) * curUnit->power * direction;
2658 shotRec[shotIndex].ymov = -sinf(curUnit->angle) * curUnit->power;
2659 }
2660 else
2661 {
2662 /* This is not identical to the default case. */
2663
2664 shotRec[shotIndex].x = curUnit->unitX + 2;
2665 shotRec[shotIndex].xmov = -cosf(curUnit->angle) * curUnit->power * direction;
2666
2667 if (curUnit->isYInAir == true)
2668 {
2669 shotRec[shotIndex].ymov = 1;
2670 shotRec[shotIndex].y = curUnit->unitY + 2;
2671 } else {
2672 shotRec[shotIndex].ymov = -2;
2673 shotRec[shotIndex].y = curUnit->unitY - 12;
2674 }
2675 }
2676 break;
2677
2678 default:
2679
2680 shotRec[shotIndex].x = curUnit->unitX + 6 - cosf(curUnit->angle) * 10 * direction;
2681 shotRec[shotIndex].y = curUnit->unitY - 7 - sinf(curUnit->angle) * 10;
2682 shotRec[shotIndex].xmov = -cosf(curUnit->angle) * curUnit->power * direction;
2683 shotRec[shotIndex].ymov = -sinf(curUnit->angle) * curUnit->power;
2684 break;
2685 }
2686
2687 /* Now set/clear out a few last details. */
2688 shotRec[shotIndex].isAvailable = false;
2689
2690 shotRec[shotIndex].shottype = curUnit->shotType;
2691 //shotRec[shotIndex].shotdur = shotFuse[shotRec[shotIndex].shottype];
2692
2693 shotRec[shotIndex].trailc[0] = 0;
2694 shotRec[shotIndex].trailc[1] = 0;
2695 shotRec[shotIndex].trailc[2] = 0;
2696 shotRec[shotIndex].trailc[3] = 0;
2697 }
DE_RunMagnet(enum de_player_t curPlayer,struct destruct_unit_s * magnet)2698 static void DE_RunMagnet( enum de_player_t curPlayer, struct destruct_unit_s * magnet )
2699 {
2700 unsigned int i;
2701 enum de_player_t curEnemy;
2702 int direction;
2703 struct destruct_unit_s * enemyUnit;
2704
2705
2706 curEnemy = (curPlayer == PLAYER_LEFT) ? PLAYER_RIGHT : PLAYER_LEFT;
2707 direction = (curPlayer == PLAYER_LEFT) ? -1 : 1;
2708
2709 /* Push all shots that are in front of the magnet */
2710 for (i = 0; i < config.max_shots; i++)
2711 {
2712 if (shotRec[i].isAvailable == false)
2713 {
2714 if ((curPlayer == PLAYER_LEFT && shotRec[i].x > magnet->unitX)
2715 || (curPlayer == PLAYER_RIGHT && shotRec[i].x < magnet->unitX))
2716 {
2717 shotRec[i].xmov += magnet->power * 0.1f * -direction;
2718 }
2719 }
2720 }
2721
2722 enemyUnit = destruct_player[curEnemy].unit;
2723 for (i = 0; i < config.max_installations; i++, enemyUnit++) /* magnets push coptors */
2724 {
2725 if (DE_isValidUnit(enemyUnit)
2726 && enemyUnit->unitType == UNIT_HELI
2727 && enemyUnit->isYInAir == true)
2728 {
2729 if ((curEnemy == PLAYER_RIGHT && destruct_player[curEnemy].unit[i].unitX + 11 < 318)
2730 || (curEnemy == PLAYER_LEFT && destruct_player[curEnemy].unit[i].unitX > 1))
2731 {
2732 enemyUnit->unitX -= 2 * direction;
2733 }
2734 }
2735 }
2736 magnet->ani_frame = 1;
2737 }
DE_RaiseAngle(struct destruct_unit_s * unit)2738 static void DE_RaiseAngle( struct destruct_unit_s * unit )
2739 {
2740 unit->angle += 0.01f;
2741 if (unit->angle > M_PI_2 - 0.01f)
2742 {
2743 unit->angle = M_PI_2 - 0.01f;
2744 }
2745 }
DE_LowerAngle(struct destruct_unit_s * unit)2746 static void DE_LowerAngle( struct destruct_unit_s * unit )
2747 {
2748 unit->angle -= 0.01f;
2749 if (unit->angle < 0)
2750 {
2751 unit->angle = 0;
2752 }
2753 }
DE_RaisePower(struct destruct_unit_s * unit)2754 static void DE_RaisePower( struct destruct_unit_s * unit )
2755 {
2756 unit->power += 0.05f;
2757 if (unit->power > 5)
2758 {
2759 unit->power = 5;
2760 }
2761 }
DE_LowerPower(struct destruct_unit_s * unit)2762 static void DE_LowerPower( struct destruct_unit_s * unit )
2763 {
2764 unit->power -= 0.05f;
2765 if (unit->power < 1)
2766 {
2767 unit->power = 1;
2768 }
2769 }
2770
2771 /* DE_isValidUnit
2772 *
2773 * Returns true if the unit's health is above 0 and false
2774 * otherwise. This mainly exists because the 'health' var
2775 * serves two roles and that can get confusing.
2776 */
DE_isValidUnit(struct destruct_unit_s * unit)2777 static inline bool DE_isValidUnit( struct destruct_unit_s * unit )
2778 {
2779 return(unit->health > 0);
2780 }
2781
2782
DE_RunTickCheckEndgame(void)2783 static bool DE_RunTickCheckEndgame( void )
2784 {
2785 if (destruct_player[PLAYER_LEFT].unitsRemaining == 0)
2786 {
2787 destruct_player[PLAYER_RIGHT].score += ModeScore[PLAYER_LEFT][world.destructMode];
2788 soundQueue[7] = V_CLEARED_PLATFORM;
2789 return(true);
2790 }
2791 if (destruct_player[PLAYER_RIGHT].unitsRemaining == 0)
2792 {
2793 destruct_player[PLAYER_LEFT].score += ModeScore[PLAYER_RIGHT][world.destructMode];
2794 soundQueue[7] = V_CLEARED_PLATFORM;
2795 return(true);
2796 }
2797 return(false);
2798 }
DE_RunTickPlaySounds(void)2799 static void DE_RunTickPlaySounds( void )
2800 {
2801 unsigned int i, tempSampleIndex, tempVolume;
2802
2803
2804 for (i = 0; i < COUNTOF(soundQueue); i++)
2805 {
2806 if (soundQueue[i] != S_NONE)
2807 {
2808 tempSampleIndex = soundQueue[i];
2809 if (i == 7)
2810 {
2811 tempVolume = fxPlayVol;
2812 }
2813 else
2814 {
2815 tempVolume = fxPlayVol / 2;
2816 }
2817
2818 JE_multiSamplePlay(digiFx[tempSampleIndex-1], fxSize[tempSampleIndex-1], i, tempVolume);
2819 soundQueue[i] = S_NONE;
2820 }
2821 }
2822 }
2823
JE_pixCool(unsigned int x,unsigned int y,Uint8 c)2824 static void JE_pixCool( unsigned int x, unsigned int y, Uint8 c )
2825 {
2826 JE_pix(VGAScreen, x, y, c);
2827 JE_pix(VGAScreen, x - 1, y, c - 2);
2828 JE_pix(VGAScreen, x + 1, y, c - 2);
2829 JE_pix(VGAScreen, x, y - 1, c - 2);
2830 JE_pix(VGAScreen, x, y + 1, c - 2);
2831 }
2832