Subversion Repositories Kolibri OS

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

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