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