~ubuntu-branches/debian/stretch/opentyrian/stretch

« back to all changes in this revision

Viewing changes to src/destruct.c

  • Committer: Package Import Robot
  • Author(s): Etienne Millon
  • Date: 2015-03-31 08:48:54 UTC
  • Revision ID: package-import@ubuntu.com-20150331084854-f5a4uoz7uv3vopk6
Tags: upstream-2.1.20130907+dfsg
ImportĀ upstreamĀ versionĀ 2.1.20130907+dfsg

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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 ***/
 
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
}
 
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
}
 
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
}
 
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
 
 
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
 
 
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
 */
 
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
}
 
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
 
 
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
}
 
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
}
 
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
 
 
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
}
 
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
 
 
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
 
 
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
}
 
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
 
 
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
 
 
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
 
 
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
 
 
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
 
 
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
 
 
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
 
 
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
 
 
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
 */
 
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
}
 
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
}
 
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
}
 
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
}
 
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
}
 
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
 */
 
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
 */
 
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
}
 
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
}
 
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
}
 
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
}
 
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
}
 
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
}
 
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
}
 
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
}
 
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
}
 
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
 
 
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
}
 
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
}
 
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
}
 
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
}
 
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
}
 
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
}
 
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
 
 
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
}
 
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
 
 
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
}
 
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
}
 
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
}
 
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
}
 
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
}
 
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
 */
 
2777
static inline bool DE_isValidUnit( struct destruct_unit_s * unit )
 
2778
{
 
2779
        return(unit->health > 0);
 
2780
}
 
2781
 
 
2782
 
 
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
}
 
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
 
 
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
}