Files
commandergenius/project/jni/application/opentyrian/src/destruct.cpp
2010-09-23 13:22:15 +03:00

2864 lines
83 KiB
C++

/*
* OpenTyrian Classic: A modern cross-platform port of Tyrian
* Copyright (C) 2007-2009 The OpenTyrian Development Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/* File notes:
* Two players duke it out in a Scorched Earth style game.
* Most of the variables referring to the players are global as
* they are often edited and that's how the original was written.
*
* Currently this file is at its final stage for vanilla destruct.
* Almost all of the left/right code duplications is gone. Most of the
* functions have been examined and tightened up, none of the enums
* start with '1', and the various large functions have been divided into
* smaller chunks.
*
* Destruct also supports some 'hidden' configuration that's just too awesome
* to not have available. Destruct has no configuration options in game, but
* that doesn't stop us from changing various limiting vars and letting
* people remap the keyboard. AIs may also be introduced here; fighting a
* stateless AI isn't really challenging afterall.
*
* This hidden config also allows for a hidden game mode! Though as a custom
* game mode wouldn't show up in the data files it forces us to distinguish
* between the constant DESTRUCT_MODES (5) and MAX_MODES (6). DESTRUCT_MODES
* is only used with loaded data.
*
* Things I wanted to do but can't: Remove references to VGAScreen. For
* a multitude of reasons this just isn't feasable. It would have been nice
* to increase the playing field though...
*/
/*** Headers ***/
#include "opentyr.h"
#include "destruct.h"
#include "config.h"
#include "fonthand.h"
#include "helptext.h"
#include "keyboard.h"
#include "loudness.h"
#include "mtrand.h"
#include "nortsong.h"
#include "palette.h"
#include "picload.h"
#include "sprite.h"
#include "vga256d.h"
#include "video.h"
#include <assert.h>
extern JE_byte soundQueue[8];
/*** Defines ***/
#define UNIT_HEIGHT 12
#define MAX_KEY_OPTIONS 4
/*** Enums ***/
enum de_state_t { STATE_INIT, STATE_RELOAD, STATE_CONTINUE };
enum de_player_t { PLAYER_LEFT = 0, PLAYER_RIGHT = 1, MAX_PLAYERS = 2 };
enum de_team_t { TEAM_LEFT = 0, TEAM_RIGHT = 1, MAX_TEAMS = 2 };
enum de_mode_t { MODE_5CARDWAR = 0, MODE_TRADITIONAL, MODE_HELIASSAULT,
MODE_HELIDEFENSE, MODE_OUTGUNNED, MODE_CUSTOM,
MODE_FIRST = MODE_5CARDWAR, MODE_LAST = MODE_CUSTOM,
MAX_MODES = 6, MODE_NONE = -1 };
enum de_unit_t { UNIT_TANK = 0, UNIT_NUKE, UNIT_DIRT, UNIT_SATELLITE,
UNIT_MAGNET, UNIT_LASER, UNIT_JUMPER, UNIT_HELI,
UNIT_FIRST = UNIT_TANK, UNIT_LAST = UNIT_HELI,
MAX_UNITS = 8, UNIT_NONE = -1 };
enum de_shot_t { SHOT_TRACER = 0, SHOT_SMALL, SHOT_LARGE, SHOT_MICRO,
SHOT_SUPER, SHOT_DEMO, SHOT_SMALLNUKE, SHOT_LARGENUKE,
SHOT_SMALLDIRT, SHOT_LARGEDIRT, SHOT_MAGNET, SHOT_MINILASER,
SHOT_MEGALASER, SHOT_LASERTRACER, SHOT_MEGABLAST, SHOT_MINI,
SHOT_BOMB,
SHOT_FIRST = SHOT_TRACER, SHOT_LAST = SHOT_BOMB,
MAX_SHOT_TYPES = 17, SHOT_INVALID = -1 };
enum de_expl_t { EXPL_NONE, EXPL_MAGNET, EXPL_DIRT, EXPL_NORMAL }; /* this needs a better name */
enum de_trails_t { TRAILS_NONE, TRAILS_NORMAL, TRAILS_FULL };
enum de_pixel_t { PIXEL_BLACK = 0, PIXEL_DIRT = 25 };
enum de_mapflags_t { MAP_NORMAL = 0x00, MAP_WALLS = 0x01, MAP_RINGS = 0x02,
MAP_HOLES = 0x04, MAP_FUZZY = 0x08, MAP_TALL = 0x10 };
/* keys and moves should line up. */
enum de_keys_t { KEY_LEFT = 0, KEY_RIGHT, KEY_UP, KEY_DOWN, KEY_CHANGE, KEY_FIRE, KEY_CYUP, KEY_CYDN, MAX_KEY = 8};
enum de_move_t { MOVE_LEFT = 0, MOVE_RIGHT, MOVE_UP, MOVE_DOWN, MOVE_CHANGE, MOVE_FIRE, MOVE_CYUP, MOVE_CYDN, MAX_MOVE = 8};
/* The tracerlaser is dummied out. It works but (probably due to the low
* MAX_SHOTS) is not assigned to anything. The bomb does not work.
*/
/*** Structs ***/
struct destruct_config_s {
unsigned int max_shots;
unsigned int min_walls;
unsigned int max_walls;
unsigned int max_explosions;
unsigned int max_installations;
bool allow_custom;
bool alwaysalias;
bool jumper_straight[2];
bool ai[2];
};
struct destruct_unit_s {
/* Positioning/movement */
unsigned int unitX; /* yep, one's an int and the other is a real */
float unitY;
float unitYMov;
bool isYInAir;
/* What it is and what it fires */
enum de_unit_t unitType;
enum de_shot_t shotType;
/* What it's pointed */
float angle;
float power;
/* Misc */
int lastMove;
unsigned int ani_frame;
int health;
};
struct destruct_shot_s {
bool isAvailable;
float x;
float y;
float xmov;
float ymov;
bool gravity;
unsigned int shottype;
//int shotdur; /* This looks to be unused */
unsigned int trailx[4], traily[4], trailc[4];
};
struct destruct_explo_s {
bool isAvailable;
unsigned int x, y;
unsigned int explowidth;
unsigned int explomax;
unsigned int explofill;
enum de_expl_t exploType;
};
struct destruct_moves_s {
bool actions[MAX_MOVE];
};
struct destruct_keys_s {
SDLKey Config[MAX_KEY][MAX_KEY_OPTIONS];
};
struct destruct_ai_s {
int c_Angle, c_Power, c_Fire;
unsigned int c_noDown;
};
struct destruct_player_s {
bool is_cpu;
struct destruct_ai_s aiMemory;
struct destruct_unit_s * unit;
struct destruct_moves_s moves;
struct destruct_keys_s keys;
enum de_team_t team;
unsigned int unitsRemaining;
unsigned int unitSelected;
unsigned int shotDelay;
unsigned int score;
};
struct destruct_wall_s {
bool wallExist;
unsigned int wallX, wallY;
};
struct destruct_world_s {
/* Map data & screen pointer */
unsigned int baseMap[320];
SDL_Surface * VGAScreen;
struct destruct_wall_s * mapWalls;
/* Map configuration */
enum de_mode_t destructMode;
unsigned int mapFlags;
};
/*** Function decs ***/
//Prep functions
void JE_destructMain( void );
void JE_introScreen( void );
enum de_mode_t JE_modeSelect( void );
void JE_helpScreen( void );
void JE_pauseScreen( void );
//level generating functions
void JE_generateTerrain( void );
void DE_generateBaseTerrain( unsigned int, unsigned int *);
void DE_drawBaseTerrain( unsigned int * );
void DE_generateUnits( unsigned int * );
void DE_generateWalls( struct destruct_world_s * );
void DE_generateRings(SDL_Surface *, Uint8 );
void DE_ResetLevel( void );
unsigned int JE_placementPosition( unsigned int, unsigned int, unsigned int * );
//drawing functions
void JE_aliasDirt( SDL_Surface * );
void DE_RunTickDrawCrosshairs( void );
void DE_RunTickDrawHUD( void );
void DE_GravityDrawUnit( enum de_player_t, struct destruct_unit_s * );
void DE_RunTickAnimate( void );
void DE_RunTickDrawWalls( void );
void DE_DrawTrails( struct destruct_shot_s *, unsigned int, unsigned int, unsigned int );
void JE_tempScreenChecking( void );
void JE_superPixel( unsigned int, unsigned int );
void JE_pixCool( unsigned int, unsigned int, Uint8 );
//player functions
void DE_RunTickGetInput( void );
void DE_ProcessInput( void );
void DE_ResetPlayers( void );
void DE_ResetAI( void );
void DE_ResetActions( void );
void DE_RunTickAI( void );
//unit functions
void DE_RaiseAngle( struct destruct_unit_s * );
void DE_LowerAngle( struct destruct_unit_s * );
void DE_RaisePower( struct destruct_unit_s * );
void DE_LowerPower( struct destruct_unit_s * );
void DE_CycleWeaponUp( struct destruct_unit_s * );
void DE_CycleWeaponDown( struct destruct_unit_s * );
void DE_RunMagnet( enum de_player_t, struct destruct_unit_s * );
void DE_GravityFlyUnit( struct destruct_unit_s * );
void DE_GravityLowerUnit( struct destruct_unit_s * );
void DE_DestroyUnit( enum de_player_t, struct destruct_unit_s * );
void DE_ResetUnits( void );
static inline bool DE_isValidUnit( struct destruct_unit_s *);
//weapon functions
void DE_ResetWeapons( void );
void DE_RunTickShots( void );
void DE_RunTickExplosions( void );
void DE_TestExplosionCollision( unsigned int, unsigned int);
void JE_makeExplosion( unsigned int, unsigned int, enum de_shot_t );
void DE_MakeShot( enum de_player_t, const struct destruct_unit_s *, int );
//gameplay functions
enum de_state_t DE_RunTick( void );
void DE_RunTickCycleDeadUnits( void );
void DE_RunTickGravity( void );
bool DE_RunTickCheckEndgame( void );
bool JE_stabilityCheck( unsigned int, unsigned int );
//sound
void DE_RunTickPlaySounds( void );
void JE_eSound( unsigned int );
/*** Weapon configurations ***/
/* Part of me wants to leave these as bytes to save space. */
const bool demolish[MAX_SHOT_TYPES] = {false, false, false, false, false, true, true, true, false, false, false, false, true, false, true, false, true};
//const int shotGr[MAX_SHOT_TYPES] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 101};
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};
//const int shotFuse[MAX_SHOT_TYPES] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0};
const int shotDelay[MAX_SHOT_TYPES] = {10, 30, 80, 20, 60, 100, 140, 200, 20, 60, 5, 15, 50, 5, 80, 16, 0};
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};
const int exploSize[MAX_SHOT_TYPES] = {4, 20, 30, 14, 22, 16, 40, 60, 10, 30, 0, 5, 10, 3, 15, 7, 0};
const bool shotBounce[MAX_SHOT_TYPES] = {false, false, false, false, false, false, false, false, false, false, false, true, true, true, true, false, true};
const int exploDensity[MAX_SHOT_TYPES] = { 2, 5, 10, 15, 20, 15, 25, 30, 40, 80, 0, 30, 30, 4, 30, 5, 0};
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};
const int shotColor[MAX_SHOT_TYPES] = {16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 10, 10, 10, 10, 16, 0};
const int defaultWeapon[MAX_UNITS] = {SHOT_SMALL, SHOT_MICRO, SHOT_SMALLDIRT, SHOT_INVALID, SHOT_MAGNET, SHOT_MINILASER, SHOT_MICRO, SHOT_MINI};
const int defaultCpuWeapon[MAX_UNITS] = {SHOT_SMALL, SHOT_MICRO, SHOT_DEMO, SHOT_INVALID, SHOT_MAGNET, SHOT_MINILASER, SHOT_MICRO, SHOT_MINI};
const int defaultCpuWeaponB[MAX_UNITS] = {SHOT_DEMO, SHOT_SMALLNUKE, SHOT_DEMO, SHOT_INVALID, SHOT_MAGNET, SHOT_MEGALASER, SHOT_MICRO, SHOT_MINI};
const int systemAngle[MAX_UNITS] = {true, true, true, false, false, true, false, false};
const int baseDamage[MAX_UNITS] = {200, 120, 400, 300, 80, 150, 600, 40};
const int systemAni[MAX_UNITS] = {false, false, false, true, false, false, false, true};
bool weaponSystems[MAX_UNITS][MAX_SHOT_TYPES] =
{
{1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // normal
{0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // nuke
{0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0}, // dirt
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // worthless
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}, // magnet
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0}, // laser
{1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0}, // jumper
{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0} // helicopter
};
/* More constant configuration settings. */
/* Music that destruct will play. You can check out musmast.c to see what is what. */
const JE_byte goodsel[14] /*[1..14]*/ = {1, 2, 6, 12, 13, 14, 17, 23, 24, 26, 28, 29, 32, 33};
/* Unit creation. Need to move this later: Doesn't belong here */
JE_byte basetypes[10][11] /*[1..8, 1..11]*/ = /* [0] is amount of units*/
{
{5, UNIT_TANK, UNIT_TANK, UNIT_NUKE, UNIT_DIRT, UNIT_DIRT, UNIT_SATELLITE, UNIT_MAGNET, UNIT_LASER, UNIT_JUMPER, UNIT_HELI}, /*Normal*/
{1, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_TANK, UNIT_TANK}, /*Traditional*/
{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*/
{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*/
{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*/
{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*/
{8, UNIT_TANK, UNIT_NUKE, UNIT_DIRT, UNIT_SATELLITE, UNIT_MAGNET, UNIT_LASER, UNIT_JUMPER, UNIT_HELI, UNIT_TANK, UNIT_NUKE}, /*Overpowering fleet*/
{4, UNIT_TANK, UNIT_TANK, UNIT_NUKE, UNIT_DIRT, UNIT_TANK, UNIT_LASER, UNIT_JUMPER, UNIT_HELI, UNIT_NUKE, UNIT_JUMPER}, /*Weak fleet*/
{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*/
{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*/
};
const unsigned int baseLookup[MAX_PLAYERS][MAX_MODES] =
{
{0, 1, 3, 4, 6, 8},
{0, 1, 2, 5, 7, 9}
};
const JE_byte GraphicBase[MAX_PLAYERS][MAX_UNITS] =
{
{ 1, 6, 11, 58, 63, 68, 96, 153},
{ 20, 25, 30, 77, 82, 87, 115, 172}
};
const JE_byte ModeScore[MAX_PLAYERS][MAX_MODES] =
{
{1, 0, 0, 5, 0, 1},
{1, 0, 5, 0, 1, 1}
};
SDLKey defaultKeyConfig[MAX_PLAYERS][MAX_KEY][MAX_KEY_OPTIONS] =
{
#ifdef ANDROID
{ {SDLK_a},
{SDLK_d},
{SDLK_w},
{SDLK_s},
{SDLK_q},
{SDLK_e},
{SDLK_z},
{SDLK_x}
},
{ {SDLK_LEFT},
{SDLK_RIGHT},
{SDLK_UP},
{SDLK_DOWN},
{SDLK_RETURN},
{SDLK_SPACE},
{SDLK_LCTRL},
{SDLK_LALT}
}
#else
{ {SDLK_c},
{SDLK_v},
{SDLK_a},
{SDLK_z},
{SDLK_LALT},
{SDLK_x, SDLK_LSHIFT},
{SDLK_LCTRL},
{SDLK_SPACE}
},
{ {SDLK_LEFT, SDLK_KP4},
{SDLK_RIGHT, SDLK_KP6},
{SDLK_UP, SDLK_KP8},
{SDLK_DOWN, SDLK_KP2},
{SDLK_BACKSLASH, SDLK_KP5},
{SDLK_INSERT, SDLK_RETURN, SDLK_KP0, SDLK_KP_ENTER},
{SDLK_PAGEUP, SDLK_KP9},
{SDLK_PAGEDOWN, SDLK_KP3}
}
#endif
};
/*** Globals ***/
SDL_Surface *destructTempScreen;
JE_boolean destructFirstTime;
static struct destruct_config_s config = { 40, 20, 20, 40, 10, false, false, {true, false}, {true, false} };
static struct destruct_player_s player[MAX_PLAYERS];
static struct destruct_world_s world;
static struct destruct_shot_s * shotRec;
static struct destruct_explo_s * exploRec;
/*** Startup ***/
enum de_unit_t string_to_unit_enum(const char * str) {
// A config helper function. Probably not useful anywhere else.
//enum de_unit_t i;
int i;
static const char * unit_names[] =
{ "UNIT_TANK", "UNIT_NUKE", "UNIT_DIRT", "UNIT_SATELLITE",
"UNIT_MAGNET", "UNIT_LASER", "UNIT_JUMPER", "UNIT_HELI" };
for (i = UNIT_FIRST; i < MAX_UNITS; i++) {
if(strcmp(unit_names[i], str) == 0) { return((enum de_unit_t)i); }
}
return(UNIT_NONE);
}
bool write_default_destruct_config( void ) {
#ifndef ANDROID
cJSON * root;
cJSON * level1, * level2, * level3, * setting;
//If I read the file right, all of these will return NULL on failure.
//Well that'll be a little bit tedious to check for each time, but using
//gotos can help clear everything up since only one thing is freed.
if((root = cJSON_CreateObject()) == NULL) { goto label_failure; }
if((level1 = cJSON_CreateOrGetObjectItem(root, "general")) == NULL) { goto label_failure; }
cJSON_ForceType(level1, cJSON_Object);
//general
if((setting = cJSON_CreateOrGetObjectItem(level1, "alwaysalias")) == NULL) { goto label_failure; }
cJSON_SetBoolean(setting, false);
if((setting = cJSON_CreateOrGetObjectItem(level1, "tracerlaser")) == NULL) { goto label_failure; }
cJSON_SetBoolean(setting, false);
if((setting = cJSON_CreateOrGetObjectItem(level1, "max_shots")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, 40);
if((setting = cJSON_CreateOrGetObjectItem(level1, "min_walls")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, 20);
if((setting = cJSON_CreateOrGetObjectItem(level1, "max_walls")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, 20);
if((setting = cJSON_CreateOrGetObjectItem(level1, "max_explosions")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, 40);
//players general
if((level2 = cJSON_CreateOrGetObjectItem(level1, "player1")) == NULL) { goto label_failure; }
cJSON_ForceType(level2, cJSON_Object);
if((setting = cJSON_CreateOrGetObjectItem(level2, "ai")) == NULL) { goto label_failure; }
cJSON_SetBoolean(setting, true);
if((setting = cJSON_CreateOrGetObjectItem(level2, "jumper_fires_straight")) == NULL) { goto label_failure; }
cJSON_SetBoolean(setting, true);
if((level3 = cJSON_CreateOrGetObjectItem(level2, "keys")) == NULL) { goto label_failure; }
cJSON_ForceType(level3, cJSON_Object);
if((setting = cJSON_CreateOrGetObjectItem(level3, "__comment")) == NULL) { goto label_failure; }
cJSON_SetString(setting, "You may configure the keys here. Nums correspond to SDL defines. It's better than nothing.");
if((setting = cJSON_CreateOrGetObjectItem(level3, "left1")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, SDLK_c);
if((setting = cJSON_CreateOrGetObjectItem(level3, "right1")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, SDLK_v);
if((setting = cJSON_CreateOrGetObjectItem(level3, "up1")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, SDLK_a);
if((setting = cJSON_CreateOrGetObjectItem(level3, "down1")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, SDLK_z);
if((setting = cJSON_CreateOrGetObjectItem(level3, "change1")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, SDLK_LALT);
if((setting = cJSON_CreateOrGetObjectItem(level3, "fire1")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, SDLK_x);
if((setting = cJSON_CreateOrGetObjectItem(level3, "fire2")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, SDLK_LSHIFT);
if((setting = cJSON_CreateOrGetObjectItem(level3, "cyup1")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, SDLK_LCTRL);
if((setting = cJSON_CreateOrGetObjectItem(level3, "cydn1")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, SDLK_SPACE);
if((level2 = cJSON_CreateOrGetObjectItem(level1, "player2")) == NULL) { goto label_failure; }
cJSON_ForceType(level2, cJSON_Object);
if((setting = cJSON_CreateOrGetObjectItem(level2, "ai")) == NULL) { goto label_failure; }
cJSON_SetBoolean(setting, false);
if((setting = cJSON_CreateOrGetObjectItem(level2, "jumper_fires_straight")) == NULL) { goto label_failure; }
cJSON_SetBoolean(setting, false);
if((level3 = cJSON_CreateOrGetObjectItem(level2, "keys")) == NULL) { goto label_failure; }
cJSON_ForceType(level3, cJSON_Object);
if((setting = cJSON_CreateOrGetObjectItem(level3, "left1")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, SDLK_LEFT);
if((setting = cJSON_CreateOrGetObjectItem(level3, "left2")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, SDLK_KP4);
if((setting = cJSON_CreateOrGetObjectItem(level3, "right1")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, SDLK_RIGHT);
if((setting = cJSON_CreateOrGetObjectItem(level3, "right2")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, SDLK_KP6);
if((setting = cJSON_CreateOrGetObjectItem(level3, "up1")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, SDLK_UP);
if((setting = cJSON_CreateOrGetObjectItem(level3, "up2")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, SDLK_KP8);
if((setting = cJSON_CreateOrGetObjectItem(level3, "down1")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, SDLK_DOWN);
if((setting = cJSON_CreateOrGetObjectItem(level3, "down2")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, SDLK_KP2);
if((setting = cJSON_CreateOrGetObjectItem(level3, "change1")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, SDLK_BACKSLASH);
if((setting = cJSON_CreateOrGetObjectItem(level3, "change2")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, SDLK_KP5);
if((setting = cJSON_CreateOrGetObjectItem(level3, "fire1")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, SDLK_INSERT);
if((setting = cJSON_CreateOrGetObjectItem(level3, "fire2")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, SDLK_RETURN);
if((setting = cJSON_CreateOrGetObjectItem(level3, "fire3")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, SDLK_KP0);
if((setting = cJSON_CreateOrGetObjectItem(level3, "fire4")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, SDLK_KP_ENTER);
if((setting = cJSON_CreateOrGetObjectItem(level3, "cyup1")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, SDLK_PAGEUP);
if((setting = cJSON_CreateOrGetObjectItem(level3, "cyup2")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, SDLK_KP9);
if((setting = cJSON_CreateOrGetObjectItem(level3, "cydn1")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, SDLK_PAGEDOWN);
if((setting = cJSON_CreateOrGetObjectItem(level3, "cydn2")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, SDLK_KP3);
//custom mode
if((level1 = cJSON_CreateOrGetObjectItem(root, "custom")) == NULL) { goto label_failure; }
cJSON_ForceType(level1, cJSON_Object);
if((setting = cJSON_CreateOrGetObjectItem(level1, "enable")) == NULL) { goto label_failure; }
cJSON_SetBoolean(setting, false);
//player 1 (I could but won't bother looping this)
if((level2 = cJSON_CreateOrGetObjectItem(level1, "player1")) == NULL) { goto label_failure; }
cJSON_ForceType(level2, cJSON_Object);
if((setting = cJSON_CreateOrGetObjectItem(level2, "num_units")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, 10);
if((setting = cJSON_CreateOrGetObjectItem(level2, "__comment")) == NULL) { goto label_failure; }
cJSON_SetString(setting, "This handles probability. Always have 10 entries.");
if((setting = cJSON_CreateOrGetObjectItem(level2, "unit1")) == NULL) { goto label_failure; }
cJSON_SetString(setting, "UNIT_TANK");
if((setting = cJSON_CreateOrGetObjectItem(level2, "unit2")) == NULL) { goto label_failure; }
cJSON_SetString(setting, "UNIT_TANK");
if((setting = cJSON_CreateOrGetObjectItem(level2, "unit3")) == NULL) { goto label_failure; }
cJSON_SetString(setting, "UNIT_NUKE");
if((setting = cJSON_CreateOrGetObjectItem(level2, "unit4")) == NULL) { goto label_failure; }
cJSON_SetString(setting, "UNIT_DIRT");
if((setting = cJSON_CreateOrGetObjectItem(level2, "unit5")) == NULL) { goto label_failure; }
cJSON_SetString(setting, "UNIT_DIRT");
if((setting = cJSON_CreateOrGetObjectItem(level2, "unit6")) == NULL) { goto label_failure; }
cJSON_SetString(setting, "UNIT_SATELLITE");
if((setting = cJSON_CreateOrGetObjectItem(level2, "unit7")) == NULL) { goto label_failure; }
cJSON_SetString(setting, "UNIT_MAGNET");
if((setting = cJSON_CreateOrGetObjectItem(level2, "unit8")) == NULL) { goto label_failure; }
cJSON_SetString(setting, "UNIT_LASER");
if((setting = cJSON_CreateOrGetObjectItem(level2, "unit9")) == NULL) { goto label_failure; }
cJSON_SetString(setting, "UNIT_JUMPER");
if((setting = cJSON_CreateOrGetObjectItem(level2, "unit10")) == NULL) { goto label_failure; }
cJSON_SetString(setting, "UNIT_HELI");
if((level2 = cJSON_CreateOrGetObjectItem(level1, "player2")) == NULL) { goto label_failure; }
cJSON_ForceType(level2, cJSON_Object);
if((setting = cJSON_CreateOrGetObjectItem(level2, "num_units")) == NULL) { goto label_failure; }
cJSON_SetNumber(setting, 10);
if((setting = cJSON_CreateOrGetObjectItem(level2, "unit1")) == NULL) { goto label_failure; }
cJSON_SetString(setting, "UNIT_TANK");
if((setting = cJSON_CreateOrGetObjectItem(level2, "unit2")) == NULL) { goto label_failure; }
cJSON_SetString(setting, "UNIT_TANK");
if((setting = cJSON_CreateOrGetObjectItem(level2, "unit3")) == NULL) { goto label_failure; }
cJSON_SetString(setting, "UNIT_NUKE");
if((setting = cJSON_CreateOrGetObjectItem(level2, "unit4")) == NULL) { goto label_failure; }
cJSON_SetString(setting, "UNIT_DIRT");
if((setting = cJSON_CreateOrGetObjectItem(level2, "unit5")) == NULL) { goto label_failure; }
cJSON_SetString(setting, "UNIT_DIRT");
if((setting = cJSON_CreateOrGetObjectItem(level2, "unit6")) == NULL) { goto label_failure; }
cJSON_SetString(setting, "UNIT_SATELLITE");
if((setting = cJSON_CreateOrGetObjectItem(level2, "unit7")) == NULL) { goto label_failure; }
cJSON_SetString(setting, "UNIT_MAGNET");
if((setting = cJSON_CreateOrGetObjectItem(level2, "unit8")) == NULL) { goto label_failure; }
cJSON_SetString(setting, "UNIT_LASER");
if((setting = cJSON_CreateOrGetObjectItem(level2, "unit9")) == NULL) { goto label_failure; }
cJSON_SetString(setting, "UNIT_JUMPER");
if((setting = cJSON_CreateOrGetObjectItem(level2, "unit10")) == NULL) { goto label_failure; }
cJSON_SetString(setting, "UNIT_HELI");
save_json(root, "destruct.conf");
return(true);
label_failure:
cJSON_Delete(root);
#endif
return(false);
}
void load_destruct_config( void ) {
#ifndef ANDROID
unsigned int j, k;
int i;//enum de_player_t i;
enum de_unit_t temp;
char buffer[40];
const char * key_names[] = { "left", "right", "up", "down", "change", "fire", "cyup", "cydn" };
cJSON * root;
cJSON * level1, * level2, * level3, * setting;
// The config file is not modified in game in order to 'keep' with the
// original (unconfigurable) feel. This code was copied from elsewhere.
root = load_json("destruct.conf");
if (root == NULL) {
write_default_destruct_config();
return;
}
//load these general config items. I don't consider sanity checks
//necessary; either the game isn't playable or you eat up all your memory
//when using unreasonable values. Either way, no exploit here.
level1 = cJSON_GetObjectItem(root, "general");
if (level1 != NULL)
{
if ((setting = cJSON_GetObjectItem(level1, "alwaysalias"))) {
config.alwaysalias = (setting->type == cJSON_True);
}
if ((setting = cJSON_GetObjectItem(level1, "tracerlaser"))) {
weaponSystems[UNIT_LASER][SHOT_LASERTRACER] = (setting->type == cJSON_True);
}
if ((setting = cJSON_GetObjectItem(level1, "max_shots")) && setting->type == cJSON_Number) {
config.max_shots = setting->valueint;
}
if ((setting = cJSON_GetObjectItem(level1, "min_walls")) && setting->type == cJSON_Number) {
config.min_walls = setting->valueint;
}
if ((setting = cJSON_GetObjectItem(level1, "max_walls")) && setting->type == cJSON_Number) {
config.max_walls = setting->valueint;
if(config.min_walls > config.max_walls) { config.min_walls = config.max_walls; }
}
if ((setting = cJSON_GetObjectItem(level1, "max_explosions")) && setting->type == cJSON_Number) {
config.max_explosions = setting->valueint;
}
//player configuration
for(i = PLAYER_LEFT; i < MAX_PLAYERS; i++) {
sprintf(buffer, "player%i", i+1);
level2 = cJSON_GetObjectItem(level1, buffer);
if (level2 != NULL)
{
if ((setting = cJSON_GetObjectItem(level2, "jumper_fires_straight"))) {
config.jumper_straight[i] = (setting->type == cJSON_True);
}
if ((setting = cJSON_GetObjectItem(level2, "ai"))) {
config.ai[i] = (setting->type == cJSON_True);
}
//key configuration
level3 = cJSON_GetObjectItem(level2, "keys");
if (level3 != NULL)
{
for (j = 0; j < COUNTOF(key_names); j++) {
for (k = 0; k < MAX_KEY_OPTIONS; k++) {
sprintf(buffer, "%s%i", key_names[j], k+1);
if ((setting = cJSON_GetObjectItem(level3, buffer)) && setting->type == cJSON_Number) {
defaultKeyConfig[i][j][k] = (SDLKey)setting->valueint;
}
else { //assume that if we are reading keys the defaults are null and void
defaultKeyConfig[i][j][k] = SDLK_UNKNOWN;
}
}
}
}
}
}
}
//Now let's hit the custom mode...
level1 = cJSON_GetObjectItem(root, "custom");
if (level1 != NULL)
{
//general custom
if ((setting = cJSON_GetObjectItem(level1, "enable"))) {
config.allow_custom = (setting->type == cJSON_True);
}
//player configuration
for(i = PLAYER_LEFT; i < MAX_PLAYERS; i++) {
sprintf(buffer, "player%i", i+1);
level2 = cJSON_GetObjectItem(level1, buffer);
if (level2 != NULL)
{
if ((setting = cJSON_GetObjectItem(level2, "num_units"))) {
basetypes[8 + i][0] = setting->valueint;
}
for(j = 1; j < 11; j++) {
sprintf(buffer, "unit%i", j);
if ((setting = cJSON_GetObjectItem(level2, buffer)) && setting->type == cJSON_String) {
temp = string_to_unit_enum(setting->valuestring);
if(temp != UNIT_NONE) {
basetypes[8 + i][j] = temp;
}
}
}
}
}
}
//wrap up
cJSON_Delete(root);
#endif
}
void JE_destructGame( void )
{
unsigned int i;
/* This is the entry function. Any one-time actions we need to
* perform can go in here. */
JE_clr256(VGAScreen);
JE_showVGA();
load_destruct_config();
//malloc things that have customizable sizes
shotRec = (destruct_shot_s *)malloc(sizeof(struct destruct_shot_s) * config.max_shots);
exploRec = (destruct_explo_s *)malloc(sizeof(struct destruct_explo_s) * config.max_explosions);
world.mapWalls = (destruct_wall_s *)malloc(sizeof(struct destruct_wall_s) * config.max_walls);
//Malloc enough structures to cover all of this session's possible needs.
for(i = 0; i < 10; i++) {
config.max_installations = MAX(config.max_installations, basetypes[i][0]);
}
player[PLAYER_LEFT ].unit = (destruct_unit_s *)malloc(sizeof(struct destruct_unit_s) * config.max_installations);
player[PLAYER_RIGHT].unit = (destruct_unit_s *)malloc(sizeof(struct destruct_unit_s) * config.max_installations);
destructTempScreen = game_screen;
world.VGAScreen = VGAScreen;
JE_loadCompShapes(&eShapes1, '~');
fade_black(1);
JE_destructMain();
//and of course exit actions go here.
free(shotRec);
free(exploRec);
free(world.mapWalls);
free(player[PLAYER_LEFT ].unit);
free(player[PLAYER_RIGHT].unit);
}
void JE_destructMain( void )
{
enum de_state_t curState;
JE_loadPic(VGAScreen, 11, false);
JE_introScreen();
DE_ResetPlayers();
player[PLAYER_LEFT ].is_cpu = config.ai[PLAYER_LEFT];
player[PLAYER_RIGHT].is_cpu = config.ai[PLAYER_RIGHT];
while(1)
{
world.destructMode = JE_modeSelect();
if(world.destructMode == MODE_NONE) {
break; /* User is quitting */
}
do
{
destructFirstTime = true;
JE_loadPic(VGAScreen, 11, false);
DE_ResetUnits();
DE_ResetLevel();
do {
curState = DE_RunTick();
} while(curState == STATE_CONTINUE);
fade_black(25);
}
while (curState == STATE_RELOAD);
}
}
void JE_introScreen( void )
{
memcpy(VGAScreen2->pixels, VGAScreen->pixels, VGAScreen2->h * VGAScreen2->pitch);
JE_outText(VGAScreen, JE_fontCenter(specialName[7], TINY_FONT), 90, specialName[7], 12, 5);
JE_outText(VGAScreen, JE_fontCenter(miscText[64], TINY_FONT), 180, miscText[64], 15, 2);
JE_outText(VGAScreen, JE_fontCenter(miscText[65], TINY_FONT), 190, miscText[65], 15, 2);
JE_showVGA();
fade_palette(colors, 15, 0, 255);
newkey = false;
while (!newkey)
{
service_SDL_events(false);
SDL_Delay(16);
}
fade_black(15);
memcpy(VGAScreen->pixels, VGAScreen2->pixels, VGAScreen->h * VGAScreen->pitch);
JE_showVGA();
}
/* JE_modeSelect
*
* This function prints the DESTRUCT mode selection menu.
* The return value is the selected mode, or -1 (MODE_NONE)
* if the user quits.
*/
void DrawModeSelectMenu( enum de_mode_t mode ) {
int i;
/* Helper function of JE_modeSelect. Do not use elsewhere. */
for (i = 0; i < DESTRUCT_MODES; i++)
{ /* What a large function call. */
JE_textShade(VGAScreen, JE_fontCenter(destructModeName[i], TINY_FONT), 82 + i * 12, destructModeName[i], 12, (i == mode) * 4, FULL_SHADE);
}
if (config.allow_custom == true)
{
JE_textShade(VGAScreen, JE_fontCenter("Custom", TINY_FONT), 82 + i * 12, "Custom", 12, (i == mode) * 4, FULL_SHADE);
}
}
enum de_mode_t JE_modeSelect( void )
{
int mode; //enum de_mode_t mode;
memcpy(VGAScreen2->pixels, VGAScreen->pixels, VGAScreen2->h * VGAScreen2->pitch);
mode = MODE_5CARDWAR;
// Draw the menu and fade us in
DrawModeSelectMenu((de_mode_t)mode);
JE_showVGA();
fade_palette(colors, 15, 0, 255);
/* Get input in a loop. */
while(1)
{
/* Re-draw the menu every iteration */
DrawModeSelectMenu((de_mode_t)mode);
JE_showVGA();
/* Grab keys */
newkey = false;
do {
service_SDL_events(false);
SDL_Delay(16);
} while(!newkey);
/* See what was pressed */
if (keysactive[SDLK_ESCAPE])
{
mode = MODE_NONE; /* User is quitting, return failure */
break;
}
if (keysactive[SDLK_RETURN] || keysactive[SDLK_SPACE])
{
break; /* User has selected, return choice */
}
if (keysactive[SDLK_UP] || keysactive[SDLK_LCTRL])
{
if(mode == MODE_FIRST)
{
if (config.allow_custom == true)
{
mode = MODE_LAST;
} else {
mode = MODE_LAST-1;
}
} else {
mode--;
}
}
if (keysactive[SDLK_DOWN] || keysactive[SDLK_LALT])
{
if(mode >= MODE_LAST-1)
{
if (config.allow_custom == true && mode == MODE_LAST-1)
{
mode++;
} else {
mode = MODE_FIRST;
}
} else {
mode++;
}
}
}
fade_black(15);
memcpy(VGAScreen->pixels, VGAScreen2->pixels, VGAScreen->h * VGAScreen->pitch);
JE_showVGA();
return((de_mode_t)mode);
}
void JE_generateTerrain( void )
{
/* The unique modifiers:
Altered generation (really tall)
Fuzzy hills
Rings of dirt
The non-unique ones;:
Rings of not dirt (holes)
Walls
*/
world.mapFlags = MAP_NORMAL;
if(mt_rand() % 2 == 0)
{
world.mapFlags |= MAP_WALLS;
}
if(mt_rand() % 4 == 0)
{
world.mapFlags |= MAP_HOLES;
}
switch(mt_rand() % 4)
{
case 0:
world.mapFlags |= MAP_FUZZY;
break;
case 1:
world.mapFlags |= MAP_TALL;
break;
case 2:
world.mapFlags |= MAP_RINGS;
break;
}
play_song(goodsel[mt_rand() % 14] - 1);
DE_generateBaseTerrain(world.mapFlags, world.baseMap);
DE_generateUnits(world.baseMap);
DE_generateWalls(&world);
DE_drawBaseTerrain(world.baseMap);
if (world.mapFlags & MAP_RINGS)
{
DE_generateRings(world.VGAScreen, PIXEL_DIRT);
}
if (world.mapFlags & MAP_HOLES)
{
DE_generateRings(world.VGAScreen, PIXEL_BLACK);
}
JE_aliasDirt(world.VGAScreen);
JE_showVGA();
memcpy(destructTempScreen->pixels, VGAScreen->pixels, destructTempScreen->pitch * destructTempScreen->h);
}
void DE_generateBaseTerrain( unsigned int mapFlags, unsigned int * baseWorld)
{
unsigned int i;
unsigned int newheight, HeightMul;
float sinewave, sinewave2, cosinewave, cosinewave2;
/* The 'terrain' is actually the video buffer :). If it's brown, flu... er,
* brown pixels are what we check for collisions with. */
/* The ranges here are between .01 and roughly 0.07283...*/
sinewave = mt_rand_lt1() * M_PI / 50 + 0.01f;
sinewave2 = mt_rand_lt1() * M_PI / 50 + 0.01f;
cosinewave = mt_rand_lt1() * M_PI / 50 + 0.01f;
cosinewave2 = mt_rand_lt1() * M_PI / 50 + 0.01f;
HeightMul = 20;
/* This block just exists to mix things up. */
if(mapFlags & MAP_FUZZY)
{
sinewave = M_PI - mt_rand_lt1() * 0.3f;
sinewave2 = M_PI - mt_rand_lt1() * 0.3f;
}
if(mapFlags & MAP_TALL)
{
HeightMul = 100;
}
/* Now compute a height for each of our lines. */
for (i = 1; i <= 318; i++)
{
newheight = roundf(sinf(sinewave * i) * HeightMul + sinf(sinewave2 * i) * 15 +
cosf(cosinewave * i) * 10 + sinf(cosinewave2 * i) * 15) + 130;
/* Bind it; we have mins and maxs */
if (newheight < 40)
{
newheight = 40;
}
else if (newheight > 195) {
newheight = 195;
}
baseWorld[i] = newheight;
}
/* The base world has been created. */
}
void DE_drawBaseTerrain( unsigned int * baseWorld)
{
unsigned int i;
for (i = 1; i <= 318; i++)
{
JE_rectangle(VGAScreen, i, baseWorld[i], i, 199, PIXEL_DIRT);
}
}
void DE_generateUnits( unsigned int * baseWorld )
{
unsigned int i, j, numSatellites;
for (i = 0; i < MAX_PLAYERS; i++)
{
numSatellites = 0;
player[i].unitsRemaining = 0;
for (j = 0; j < basetypes[baseLookup[i][world.destructMode]][0]; j++)
{
/* Not everything is the same between players */
if(i == PLAYER_LEFT)
{
player[i].unit[j].unitX = (mt_rand() % 120) + 10;
}
else
{
player[i].unit[j].unitX = 320 - ((mt_rand() % 120) + 22);
}
player[i].unit[j].unitY = JE_placementPosition(player[i].unit[j].unitX - 1, 14, baseWorld);
player[i].unit[j].unitType = (de_unit_t)basetypes[baseLookup[i][world.destructMode]][(mt_rand() % 10) + 1];
/* Sats are special cases since they are useless. They don't count
* as active units and we can't have a team of all sats */
if (player[i].unit[j].unitType == UNIT_SATELLITE)
{
if (numSatellites == basetypes[baseLookup[i][world.destructMode]][0])
{
player[i].unit[j].unitType = UNIT_TANK;
player[i].unitsRemaining++;
} else {
/* Place the satellite. Note: Earlier we cleared
* space with JE_placementPosition. Now we are randomly
* placing the sat's Y. It can be generated in hills
* and there is a clearing underneath it. This CAN
* be fixed but won't be for classic.
*/
player[i].unit[j].unitY = 30 + (mt_rand() % 40);
numSatellites++;
}
}
else
{
player[i].unitsRemaining++;
}
/* Now just fill in the rest of the unit's values. */
player[i].unit[j].lastMove = 0;
player[i].unit[j].unitYMov = 0;
player[i].unit[j].isYInAir = false;
player[i].unit[j].angle = 0;
player[i].unit[j].power = (player[i].unit[j].unitType == UNIT_LASER) ? 6 : 3;
player[i].unit[j].shotType = (de_shot_t)defaultWeapon[player[i].unit[j].unitType];
player[i].unit[j].health = baseDamage[player[i].unit[j].unitType];
player[i].unit[j].ani_frame = 0;
}
}
}
void DE_generateWalls( struct destruct_world_s * gameWorld )
{
unsigned int i, j, wallX;
unsigned int wallHeight, remainWalls;
unsigned int tries;
bool isGood;
if ((world.mapFlags & MAP_WALLS) == false)
{
/* Just clear them out */
for (i = 0; i < config.max_walls; i++)
{
gameWorld->mapWalls[i].wallExist = false;
}
return;
}
remainWalls = (rand() % (config.max_walls - config.min_walls + 1)) + config.min_walls;
do {
/* Create a wall. Decide how tall the wall will be */
wallHeight = (mt_rand() % 5) + 1;
if(wallHeight > remainWalls)
{
wallHeight = remainWalls;
}
/* Now find a good place to put the wall. */
tries = 0;
do {
isGood = true;
wallX = (mt_rand() % 300) + 10;
/* Is this X already occupied? In the original Tyrian we only
* checked to make sure four units on each side were unobscured.
* That's not very scalable; instead I will check every unit,
* but I'll only try plotting an unobstructed X four times.
* After that we'll cover up what may; having a few units
* stuck behind walls makes things mildly interesting.
*/
for (i = 0; i < MAX_PLAYERS; i++)
{
for (j = 0; j < config.max_installations; j++)
{
if ((wallX > player[i].unit[j].unitX - 12)
&& (wallX < player[i].unit[j].unitX + 13))
{
isGood = false;
goto label_outer_break; /* I do feel that outer breaking is a legitimate goto use. */
}
}
}
label_outer_break:
tries++;
} while(isGood == false && tries < 5);
/* We now have a valid X. Create the wall. */
for (i = 1; i <= wallHeight; i++)
{
gameWorld->mapWalls[remainWalls - i].wallExist = true;
gameWorld->mapWalls[remainWalls - i].wallX = wallX;
gameWorld->mapWalls[remainWalls - i].wallY = JE_placementPosition(wallX, 12, gameWorld->baseMap) - 14 * i;
}
remainWalls -= wallHeight;
} while (remainWalls != 0);
}
void DE_generateRings( SDL_Surface * screen, Uint8 pixel )
{
unsigned int i, j, tempSize, rings;
int tempPosX1, tempPosY1, tempPosX2, tempPosY2;
float tempRadian;
rings = mt_rand() % 6 + 1;
for (i = 1; i <= rings; i++)
{
tempPosX1 = (mt_rand() % 320);
tempPosY1 = (mt_rand() % 160) + 20;
tempSize = (mt_rand() % 40) + 10; /*Size*/
for (j = 1; j <= tempSize * tempSize * 2; j++)
{
tempRadian = mt_rand_lt1() * (2 * M_PI);
tempPosY2 = tempPosY1 + roundf(cosf(tempRadian) * (mt_rand_lt1() * 0.1f + 0.9f) * tempSize);
tempPosX2 = tempPosX1 + roundf(sinf(tempRadian) * (mt_rand_lt1() * 0.1f + 0.9f) * tempSize);
if ((tempPosY2 > 12) && (tempPosY2 < 200)
&& (tempPosX2 > 0) && (tempPosX2 < 319))
{
((Uint8 *)screen->pixels)[tempPosX2 + tempPosY2 * screen->pitch] = pixel;
}
}
}
}
unsigned int __aliasDirtPixel(const SDL_Surface * screen, unsigned int x, unsigned int y, const Uint8 * s) {
//A helper function used when aliasing dirt. That's a messy process;
//let's contain the mess here.
unsigned int newColor = PIXEL_BLACK;
if ((y > 0) && (*(s - screen->pitch) == PIXEL_DIRT)) { // look up
newColor += 1;
}
if ((y < screen->h - 1u) && (*(s + screen->pitch) == PIXEL_DIRT)) { // look down
newColor += 3;
}
if ((x > 0) && (*(s - 1) == PIXEL_DIRT)) { // look left
newColor += 2;
}
if ((x < screen->pitch - 1u) && (*(s + 1) == PIXEL_DIRT)) { // look right
newColor += 2;
}
if (newColor != PIXEL_BLACK) {
return(newColor + 16); // 16 must be the start of the brown pixels.
}
return(PIXEL_BLACK);
}
void JE_aliasDirt( SDL_Surface * screen )
{
/* This complicated looking function goes through the whole screen
* looking for brown pixels which just happen to be next to non-brown
* pixels. It's an aliaser, just like it says. */
unsigned int x, y;
/* This is a pointer to a screen. If you don't like pointer arithmetic,
* you won't like this function. */
Uint8 *s = (Uint8 *)screen->pixels;
s += 12 * screen->pitch;
for (y = 12; y < (unsigned)screen->h; y++) {
for (x = 0; x < screen->pitch; x++) {
if (*s == PIXEL_BLACK) {
*s = __aliasDirtPixel(screen, x, y, s);
}
s++;
}
}
}
unsigned int JE_placementPosition( unsigned int passed_x, unsigned int width, unsigned int * world )
{
unsigned int i, new_y;
/* This is the function responsible for carving out chunks of land.
* There's a bug here, but it's a pretty major gameplay altering one:
* areas can be carved out for units that are aerial or in mountains.
* This can result in huge caverns. Ergo, it's a feature :)
*
* I wondered if it might be better to not carve out land at all.
* On testing I determined that was distracting and added nothing. */
new_y = 0;
for (i = passed_x; i <= passed_x + width - 1; i++)
{
if (new_y < world[i])
new_y = world[i];
}
for (i = passed_x; i <= passed_x + width - 1; i++)
{
world[i] = new_y;
}
return new_y;
}
bool JE_stabilityCheck( unsigned int x, unsigned int y )
{
unsigned int i, numDirtPixels;
Uint8 * s;
numDirtPixels = 0;
s = (Uint8 *)destructTempScreen->pixels;
s += x + (y * destructTempScreen->pitch) - 1;
/* Check the 12 pixels on the bottom border of our object */
for (i = 0; i < 12; i++)
{
if (*s == PIXEL_DIRT)
numDirtPixels++;
s++;
}
/* If there are fewer than 10 brown pixels we don't consider it a solid base */
return (numDirtPixels < 10);
}
void JE_tempScreenChecking( void ) /*and copy to vgascreen*/
{
Uint8 *s = (Uint8 *)VGAScreen->pixels;
s += 12 * VGAScreen->pitch;
Uint8 *temps = (Uint8 *)destructTempScreen->pixels;
temps += 12 * destructTempScreen->pitch;
for (int y = 12; y < VGAScreen->h; y++)
{
for (int x = 0; x < VGAScreen->pitch; x++)
{
// This block is what fades out explosions. The palette from 241
// to 255 fades from a very dark red to a very bright yellow.
if (*temps >= 241)
{
if (*temps == 241)
*temps = PIXEL_BLACK;
else
(*temps)--;
}
// This block is for aliasing dirt. Computers are fast these days,
// and it's fun.
if (config.alwaysalias == true && *temps == PIXEL_BLACK) {
*temps = __aliasDirtPixel(VGAScreen, x, y, temps);
}
/* This is copying from our temp screen to VGAScreen */
*s = *temps;
s++;
temps++;
}
}
}
void JE_makeExplosion( unsigned int tempPosX, unsigned int tempPosY, enum de_shot_t shottype )
{
unsigned int i, tempExploSize;
/* First find an open explosion. If we can't find one, return.*/
for (i = 0; i < config.max_explosions; i++)
{
if (exploRec[i].isAvailable == true)
break;
}
if (i == config.max_explosions) /* No empty slots */
{
return;
}
exploRec[i].isAvailable = false;
exploRec[i].x = tempPosX;
exploRec[i].y = tempPosY;
exploRec[i].explowidth = 2;
if(shottype != SHOT_INVALID)
{
tempExploSize = exploSize[shottype];
if (tempExploSize < 5)
JE_eSound(3);
else if (tempExploSize < 15)
JE_eSound(4);
else if (tempExploSize < 20)
JE_eSound(12);
else if (tempExploSize < 40)
JE_eSound(11);
else
{
JE_eSound(12);
JE_eSound(11);
}
exploRec[i].explomax = tempExploSize;
exploRec[i].explofill = exploDensity[shottype];
exploRec[i].exploType = (de_expl_t)shotDirt[shottype];
}
else
{
JE_eSound(4);
exploRec[i].explomax = (mt_rand() % 40) + 10;
exploRec[i].explofill = (mt_rand() % 60) + 20;
exploRec[i].exploType = EXPL_NORMAL;
}
}
void JE_eSound( unsigned int sound )
{
static int exploSoundChannel = 0;
if (++exploSoundChannel > 5)
{
exploSoundChannel = 1;
}
soundQueue[exploSoundChannel] = sound;
}
void JE_superPixel( unsigned int tempPosX, unsigned int tempPosY )
{
const unsigned int starPattern[5][5] = {
{ 0, 0, 246, 0, 0 },
{ 0, 247, 249, 247, 0 },
{ 246, 249, 252, 249, 246 },
{ 0, 247, 249, 247, 0 },
{ 0, 0, 246, 0, 0 }
};
const unsigned int starIntensity[5][5] = {
{ 0, 0, 1, 0, 0 },
{ 0, 1, 2, 1, 0 },
{ 1, 2, 4, 2, 1 },
{ 0, 1, 2, 1, 0 },
{ 0, 0, 1, 0, 0 }
};
int x, y, maxX, maxY;
unsigned int rowLen;
Uint8 *s;
maxX = destructTempScreen->pitch;
maxY = destructTempScreen->h;
rowLen = destructTempScreen->pitch;
s = (Uint8 *)destructTempScreen->pixels;
s += (rowLen * (tempPosY - 2)) + (tempPosX - 2);
for (y = 0; y < 5; y++, s += rowLen - 5)
{
if ((signed)tempPosY + y - 2 < 0 /* would be out of bounds */
|| (signed)tempPosY + y - 2 >= maxY) { continue; }
for (x = 0; x < 5; x++, s++)
{
if ((signed)tempPosX + x - 2 < 0
|| (signed)tempPosX + x - 2 >= maxX) { continue; }
if (starPattern[y][x] == 0) { continue; } /* this is just to speed it up */
/* at this point *s is our pixel. Our constant arrays tell us what
* to do with it. */
if (*s < starPattern[y][x])
{
*s = starPattern[y][x];
}
else if (*s + starIntensity[y][x] > 255)
{
*s = 255;
}
else
{
*s += starIntensity[y][x];
}
}
}
}
void JE_helpScreen( void )
{
unsigned int i, j;
//JE_getVGA(); didn't do anything anyway?
fade_black(15);
memcpy(VGAScreen2->pixels, VGAScreen->pixels, VGAScreen2->h * VGAScreen2->pitch);
JE_clr256(VGAScreen);
for(i = 0; i < 2; i++)
{
JE_outText(VGAScreen, 100, 5 + i * 90, destructHelp[i * 12 + 0], 2, 4);
JE_outText(VGAScreen, 100, 15 + i * 90, destructHelp[i * 12 + 1], 2, 1);
for (j = 3; j <= 12; j++)
{
JE_outText(VGAScreen, ((j - 1) % 2) * 160 + 10, 15 + ((j - 1) / 2) * 12 + i * 90, destructHelp[i * 12 + j-1], 1, 3);
}
}
JE_outText(VGAScreen, 30, 190, destructHelp[24], 3, 4);
JE_showVGA();
fade_palette(colors, 15, 0, 255);
do /* wait until user hits a key */
{
service_SDL_events(true);
SDL_Delay(16);
}
while (!newkey);
fade_black(15);
memcpy(VGAScreen->pixels, VGAScreen2->pixels, VGAScreen->h * VGAScreen->pitch);
JE_showVGA();
fade_palette(colors, 15, 0, 255);
}
void JE_pauseScreen( void )
{
set_volume(tyrMusicVolume / 2, fxVolume);
/* Save our current screen/game world. We don't want to screw it up while paused. */
memcpy(VGAScreen2->pixels, VGAScreen->pixels, VGAScreen2->h * VGAScreen2->pitch);
JE_outText(VGAScreen, JE_fontCenter(miscText[22], TINY_FONT), 90, miscText[22], 12, 5);
JE_showVGA();
do /* wait until user hits a key */
{
service_SDL_events(true);
SDL_Delay(16);
}
while (!newkey);
/* Restore current screen & volume*/
memcpy(VGAScreen->pixels, VGAScreen2->pixels, VGAScreen->h * VGAScreen->pitch);
JE_showVGA();
set_volume(tyrMusicVolume, fxVolume);
}
/* DE_ResetX
*
* The reset functions clear the state of whatefer they are assigned to.
*/
void DE_ResetUnits( void )
{
unsigned int p, u;
for (p = 0; p < MAX_PLAYERS; ++p)
for (u = 0; u < config.max_installations; ++u)
player[p].unit[u].health = 0;
}
void DE_ResetPlayers( void )
{
unsigned int i;
for (i = 0; i < MAX_PLAYERS; ++i)
{
player[i].is_cpu = false;
player[i].unitSelected = 0;
player[i].shotDelay = 0;
player[i].score = 0;
player[i].aiMemory.c_Angle = 0;
player[i].aiMemory.c_Power = 0;
player[i].aiMemory.c_Fire = 0;
player[i].aiMemory.c_noDown = 0;
memcpy(player[i].keys.Config, defaultKeyConfig[i], sizeof(player[i].keys.Config));
}
}
void DE_ResetWeapons( void )
{
unsigned int i;
for (i = 0; i < config.max_shots; i++)
shotRec[i].isAvailable = true;
for (i = 0; i < config.max_explosions; i++)
exploRec[i].isAvailable = true;
}
void DE_ResetLevel( void )
{
/* Okay, let's prep the arena */
DE_ResetWeapons();
JE_generateTerrain();
DE_ResetAI();
}
void DE_ResetAI( void )
{
unsigned int i, j;
struct destruct_unit_s * ptr;
for (i = PLAYER_LEFT; i < MAX_PLAYERS; i++)
{
if (player[i].is_cpu == false) { continue; }
ptr = player[i].unit;
for( j = 0; j < config.max_installations; j++, ptr++)
{
if(DE_isValidUnit(ptr) == false)
continue;
if (systemAngle[ptr->unitType] || ptr->unitType == UNIT_HELI)
ptr->angle = M_PI_4;
else
ptr->angle = 0;
ptr->power = (ptr->unitType == UNIT_LASER) ? 6 : 4;
if (world.mapFlags & MAP_WALLS)
ptr->shotType = (de_shot_t)defaultCpuWeaponB[ptr->unitType];
else
ptr->shotType = (de_shot_t)defaultCpuWeapon[ptr->unitType];
}
}
}
void DE_ResetActions( void )
{
unsigned int i;
for(i = 0; i < MAX_PLAYERS; i++)
{ /* Zero it all. A memset would do the trick */
memset(&(player[i].moves), 0, sizeof(player[i].moves));
}
}
/* DE_RunTick
*
* Runs one tick. One tick involves handling physics, drawing crap,
* moving projectiles and explosions, and getting input.
* Returns true while the game is running or false if the game is
* to be terminated.
*/
enum de_state_t DE_RunTick( void )
{
static unsigned int endDelay;
setjasondelay(1);
memset(soundQueue, 0, sizeof(soundQueue));
JE_tempScreenChecking();
DE_ResetActions();
DE_RunTickCycleDeadUnits();
DE_RunTickGravity();
DE_RunTickAnimate();
DE_RunTickDrawWalls();
DE_RunTickExplosions();
DE_RunTickShots();
DE_RunTickAI();
DE_RunTickDrawCrosshairs();
DE_RunTickDrawHUD();
JE_showVGA();
if (destructFirstTime)
{
fade_palette(colors, 25, 0, 255);
destructFirstTime = false;
endDelay = 0;
}
DE_RunTickGetInput();
DE_ProcessInput();
if (endDelay > 0)
{
if(--endDelay == 0)
{
return(STATE_RELOAD);
}
}
else if ( DE_RunTickCheckEndgame() == true)
{
endDelay = 80;
}
DE_RunTickPlaySounds();
/* The rest of this cruft needs to be put in appropriate sections */
if (keysactive[SDLK_F10])
{
player[PLAYER_LEFT].is_cpu = !player[PLAYER_LEFT].is_cpu;
keysactive[SDLK_F10] = false;
}
if (keysactive[SDLK_F11])
{
player[PLAYER_RIGHT].is_cpu = !player[PLAYER_RIGHT].is_cpu;
keysactive[SDLK_F11] = false;
}
if (keysactive[SDLK_p])
{
JE_pauseScreen();
keysactive[lastkey_sym] = false;
}
if (keysactive[SDLK_F1])
{
JE_helpScreen();
keysactive[lastkey_sym] = false;
}
wait_delay();
if (keysactive[SDLK_ESCAPE])
{
keysactive[SDLK_ESCAPE] = false;
return(STATE_INIT); /* STATE_INIT drops us to the mode select */
}
if (keysactive[SDLK_BACKSPACE])
{
keysactive[SDLK_BACKSPACE] = false;
return(STATE_RELOAD); /* STATE_RELOAD creates a new map */
}
return(STATE_CONTINUE);
}
/* DE_RunTickX
*
* Handles something that we do once per tick, such as
* track ammo and move asplosions.
*/
void DE_RunTickCycleDeadUnits( void )
{
unsigned int i;
struct destruct_unit_s * unit;
/* This code automatically switches the active unit if it is destroyed
* and skips over the useless satellite */
for (i = 0; i < MAX_PLAYERS; i++)
{
if (player[i].unitsRemaining == 0) { continue; }
unit = &(player[i].unit[player[i].unitSelected]);
while(DE_isValidUnit(unit) == false
|| unit->shotType == SHOT_INVALID)
{
player[i].unitSelected++;
unit++;
if (player[i].unitSelected >= config.max_installations)
{
player[i].unitSelected = 0;
unit = player[i].unit;
}
}
}
}
void DE_RunTickGravity( void )
{
unsigned int i, j;
struct destruct_unit_s * unit;
for (i = 0; i < MAX_PLAYERS; i++)
{
unit = player[i].unit;
for (j = 0; j < config.max_installations; j++, unit++)
{
if (DE_isValidUnit(unit) == false) /* invalid unit */
continue;
switch(unit->unitType)
{
case UNIT_SATELLITE: /* satellites don't fall down */
break;
case UNIT_HELI:
case UNIT_JUMPER:
if (unit->isYInAir == true) /* unit is falling down, at least in theory */
{
DE_GravityFlyUnit(unit);
break;
}
/* else fall through and treat as a normal unit */
default:
DE_GravityLowerUnit(unit);
}
/* Draw the unit. */
DE_GravityDrawUnit((de_player_t)i, unit);
}
}
}
void DE_GravityDrawUnit( enum de_player_t team, struct destruct_unit_s * unit )
{
unsigned int anim_index;
anim_index = GraphicBase[team][unit->unitType] + unit->ani_frame;
if (unit->unitType == UNIT_HELI)
{
/* Adjust animation index if we are travelling right or left. */
if (unit->lastMove < -2)
anim_index += 5;
else if (unit->lastMove > 2)
anim_index += 10;
}
else /* This handles our cannons and the like */
{
anim_index += floorf(unit->angle * 9.99f / M_PI);
}
blit_sprite2(VGAScreen, unit->unitX, roundf(unit->unitY) - 13, eShapes1, anim_index);
}
void DE_GravityLowerUnit( struct destruct_unit_s * unit )
{
/* units fall at a constant speed. The heli is an odd case though;
* we simply give it a downward velocity, but due to a buggy implementation
* the chopper didn't lower until you tried to fly it up. Tyrian 2000 fixes
* this by not making the chopper a special case. I've decided to actually
* mix both; the chopper is given a slight downward acceleration (simulating
* a 'rocky' takeoff), and it is lowered like a regular unit, but not as
* quickly.
*/
if(unit->unitY < 199) { /* checking takes time, don't check if it's at the bottom */
if (JE_stabilityCheck(unit->unitX, roundf(unit->unitY)))
{
switch(unit->unitType)
{
case UNIT_HELI:
unit->unitYMov = 1.5f;
unit->unitY += 0.2f;
break;
default:
unit->unitY += 1;
}
if (unit->unitY > 199) /* could be possible */
unit->unitY = 199;
}
}
}
void DE_GravityFlyUnit( struct destruct_unit_s * unit )
{
if (unit->unitY + unit->unitYMov > 199) /* would hit bottom of screen */
{
unit->unitY = 199;
unit->unitYMov = 0;
unit->isYInAir = false;
return;
}
/* move the unit and alter acceleration */
unit->unitY += unit->unitYMov;
if (unit->unitY < 24) /* This stops units from going above the screen */
{
unit->unitYMov = 0;
unit->unitY = 24;
}
if (unit->unitType == UNIT_HELI) /* helicopters fall more slowly */
unit->unitYMov += 0.0001f;
else
unit->unitYMov += 0.03f;
if (!JE_stabilityCheck(unit->unitX, roundf(unit->unitY)))
{
unit->unitYMov = 0;
unit->isYInAir = false;
}
}
void DE_RunTickAnimate( void )
{
unsigned int p, u;
struct destruct_unit_s * ptr;
for (p = 0; p < MAX_PLAYERS; ++p)
{
ptr = player[p].unit;
for (u = 0; u < config.max_installations; ++u, ++ptr)
{
/* Don't mess with any unit that is unallocated
* or doesn't animate and is set to frame 0 */
if(DE_isValidUnit(ptr) == false) { continue; }
if(systemAni[ptr->unitType] == false && ptr->ani_frame == 0) { continue; }
if (++(ptr->ani_frame) > 3)
{
ptr->ani_frame = 0;
}
}
}
}
void DE_RunTickDrawWalls( void )
{
unsigned int i;
for (i = 0; i < config.max_walls; i++)
{
if (world.mapWalls[i].wallExist)
{
blit_sprite2(VGAScreen, world.mapWalls[i].wallX, world.mapWalls[i].wallY, eShapes1, 42);
}
}
}
void DE_RunTickExplosions( void )
{
unsigned int i, j;
int tempPosX, tempPosY;
float tempRadian;
/* Run through all open explosions. They are not sorted in any way */
for (i = 0; i < config.max_explosions; i++)
{
if (exploRec[i].isAvailable == true) { continue; } /* Nothing to do */
for (j = 0; j < exploRec[i].explofill; j++)
{
/* An explosion is comprised of multiple 'flares' that fan out.
Calculate where this 'flare' will end up */
tempRadian = mt_rand_lt1() * (2 * M_PI);
tempPosY = exploRec[i].y + roundf(cosf(tempRadian) * mt_rand_lt1() * exploRec[i].explowidth);
tempPosX = exploRec[i].x + roundf(sinf(tempRadian) * mt_rand_lt1() * exploRec[i].explowidth);
/* Our game allows explosions to wrap around. This looks to have
* originally been a bug that was left in as being fun, but we are
* going to replicate it w/o risking out of bound arrays. */
while(tempPosX < 0) { tempPosX += 320; }
while(tempPosX > 320) { tempPosX -= 320; }
/* We don't draw our explosion if it's out of bounds vertically */
if (tempPosY >= 200 || tempPosY <= 15) { continue; }
/* And now the drawing. There are only two types of explosions
* right now; dirt and flares. Dirt simply draws a brown pixel;
* flares explode and have a star formation. */
switch(exploRec[i].exploType)
{
case EXPL_DIRT:
((Uint8 *)destructTempScreen->pixels)[tempPosX + tempPosY * destructTempScreen->pitch] = PIXEL_DIRT;
break;
case EXPL_NORMAL:
JE_superPixel(tempPosX, tempPosY);
DE_TestExplosionCollision(tempPosX, tempPosY);
break;
default:
assert(false);
break;
}
}
/* Widen the explosion and delete it if necessary. */
exploRec[i].explowidth++;
if (exploRec[i].explowidth == exploRec[i].explomax)
{
exploRec[i].isAvailable = true;
}
}
}
void DE_TestExplosionCollision( unsigned int PosX, unsigned int PosY)
{
unsigned int i, j;
struct destruct_unit_s * unit;
for (i = PLAYER_LEFT; i < MAX_PLAYERS; i++)
{
unit = player[i].unit;
for (j = 0; j < config.max_installations; j++, unit++)
{
if (DE_isValidUnit(unit) == true
&& PosX > unit->unitX && PosX < unit->unitX + 11
&& PosY < unit->unitY && PosY > unit->unitY - 11)
{
unit->health--;
if (unit->health <= 0)
{
DE_DestroyUnit((de_player_t)i, unit);
}
}
}
}
}
void DE_DestroyUnit( enum de_player_t playerID, struct destruct_unit_s * unit )
{
/* This function call was an evil evil piece of brilliance before. Go on.
* Look at the older revisions. It passed the result of a comparison.
* MULTIPLIED. This is at least a little clearer... */
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. */
if (unit->unitType != UNIT_SATELLITE) /* increment score */
{ /* todo: change when teams are created. Hacky kludge for now.*/
player[playerID].unitsRemaining--;
player[((playerID == PLAYER_LEFT) ? PLAYER_RIGHT : PLAYER_LEFT)].score++;
}
}
void DE_RunTickShots( void )
{
unsigned int i, j, k;
unsigned int tempTrails;
unsigned int tempPosX, tempPosY;
struct destruct_unit_s * unit;
for (i = 0; i < config.max_shots; i++)
{
if (shotRec[i].isAvailable == true) { continue; } /* Nothing to do */
/* Move the shot. Simple displacement */
shotRec[i].x += shotRec[i].xmov;
shotRec[i].y += shotRec[i].ymov;
/* If the shot can bounce off the map, bounce it */
if (shotBounce[shotRec[i].shottype])
{
if (shotRec[i].y > 199 || shotRec[i].y < 14)
{
shotRec[i].y -= shotRec[i].ymov;
shotRec[i].ymov = -shotRec[i].ymov;
}
if (shotRec[i].x < 1 || shotRec[i].x > 318)
{
shotRec[i].x -= shotRec[i].xmov;
shotRec[i].xmov = -shotRec[i].xmov;
}
}
else /* If it cannot, apply normal physics */
{
shotRec[i].ymov += 0.05f; /* add gravity */
if (shotRec[i].y > 199) /* We hit the floor */
{
shotRec[i].y -= shotRec[i].ymov;
shotRec[i].ymov = -shotRec[i].ymov * 0.8f; /* bounce at reduced velocity */
/* Don't allow a bouncing shot to bounce straight up and down */
if (shotRec[i].xmov == 0)
{
shotRec[i].xmov += mt_rand_lt1() - 0.5f;
}
}
}
/* Shot has gone out of bounds. Eliminate it. */
if (shotRec[i].x > 318 || shotRec[i].x < 1)
{
shotRec[i].isAvailable = true;
continue;
}
/* Now check for collisions. */
/* Don't bother checking for collisions above the map :) */
if (shotRec[i].y <= 14)
continue;
tempPosX = roundf(shotRec[i].x);
tempPosY = roundf(shotRec[i].y);
/*Check building hits*/
for(j = 0; j < MAX_PLAYERS; j++)
{
unit = player[j].unit;
for(k = 0; k < config.max_installations; k++, unit++)
{
if (DE_isValidUnit(unit) == false)
continue;
if (tempPosX > unit->unitX && tempPosX < unit->unitX + 11
&& tempPosY < unit->unitY && tempPosY > unit->unitY - 13)
{
shotRec[i].isAvailable = true;
JE_makeExplosion(tempPosX, tempPosY, (de_shot_t)shotRec[i].shottype);
}
}
}
tempTrails = (shotColor[shotRec[i].shottype] << 4) - 3;
JE_pixCool(tempPosX, tempPosY, tempTrails);
/*Draw the shot trail (if applicable) */
switch (shotTrail[shotRec[i].shottype])
{
case TRAILS_NONE:
break;
case TRAILS_NORMAL:
DE_DrawTrails( &(shotRec[i]), 2, 4, tempTrails - 3 );
break;
case TRAILS_FULL:
DE_DrawTrails( &(shotRec[i]), 4, 3, tempTrails - 1 );
break;
}
/* Bounce off of or destroy walls */
for (j = 0; j < config.max_walls; j++)
{
if (world.mapWalls[j].wallExist == true
&& tempPosX >= world.mapWalls[j].wallX && tempPosX <= world.mapWalls[j].wallX + 11
&& tempPosY >= world.mapWalls[j].wallY && tempPosY <= world.mapWalls[j].wallY + 14)
{
if (demolish[shotRec[i].shottype])
{
/* Blow up the wall and remove the shot. */
world.mapWalls[j].wallExist = false;
shotRec[i].isAvailable = true;
JE_makeExplosion(tempPosX, tempPosY, (de_shot_t)shotRec[i].shottype);
continue;
}
else
{
/* Otherwise, bounce. */
if (shotRec[i].x - shotRec[i].xmov < world.mapWalls[j].wallX
|| shotRec[i].x - shotRec[i].xmov > world.mapWalls[j].wallX + 11)
{
shotRec[i].xmov = -shotRec[i].xmov;
}
if (shotRec[i].y - shotRec[i].ymov < world.mapWalls[j].wallY
|| shotRec[i].y - shotRec[i].ymov > world.mapWalls[j].wallY + 14)
{
if (shotRec[i].ymov < 0)
shotRec[i].ymov = -shotRec[i].ymov;
else
shotRec[i].ymov = -shotRec[i].ymov * 0.8f;
}
tempPosX = roundf(shotRec[i].x);
tempPosY = roundf(shotRec[i].y);
}
}
}
/* Our last collision check, at least for now. We hit dirt. */
if((((Uint8 *)destructTempScreen->pixels)[tempPosX + tempPosY * destructTempScreen->pitch]) == PIXEL_DIRT)
{
shotRec[i].isAvailable = true;
JE_makeExplosion(tempPosX, tempPosY, (de_shot_t)shotRec[i].shottype);
continue;
}
}
}
void DE_DrawTrails( struct destruct_shot_s * shot, unsigned int count, unsigned int decay, unsigned int startColor )
{
int i;
for (i = count-1; i >= 0; i--) /* going in reverse is important as it affects how we draw */
{
if (shot->trailc[i] > 0 && shot->traily[i] > 12) /* If it exists and if it's not out of bounds, draw it. */
{
JE_pixCool(shot->trailx[i], shot->traily[i], shot->trailc[i]);
}
if (i == 0) /* The first trail we create. */
{
shot->trailx[i] = roundf(shot->x);
shot->traily[i] = roundf(shot->y);
shot->trailc[i] = startColor;
}
else /* The newer trails decay into the older trails.*/
{
shot->trailx[i] = shot->trailx[i-1];
shot->traily[i] = shot->traily[i-1];
if (shot->trailc[i-1] > 0)
{
shot->trailc[i] = shot->trailc[i-1] - decay;
}
}
}
}
void DE_RunTickAI( void )
{
unsigned int i, j;
struct destruct_player_s * ptrPlayer, * ptrTarget;
struct destruct_unit_s * ptrUnit, * ptrCurUnit;
for (i = 0; i < MAX_PLAYERS; i++)
{
ptrPlayer = &(player[i]);
if (ptrPlayer->is_cpu == false)
{
continue;
}
/* I've been thinking, purely hypothetically, about what it would take
* to have multiple computer opponents. The answer? A lot of crap
* and a 'target' variable in the player struct. */
j = i + 1;
if (j >= MAX_PLAYERS)
{
j = 0;
}
ptrTarget = &(player[j]);
ptrCurUnit = &(ptrPlayer->unit[ptrPlayer->unitSelected]);
/* This is the start of the original AI. Heh. AI. */
if (ptrPlayer->aiMemory.c_noDown > 0)
ptrPlayer->aiMemory.c_noDown--;
/* Until all structs are properly divvied up this must only apply to player1 */
if (mt_rand() % 100 > 80)
{
ptrPlayer->aiMemory.c_Angle += (mt_rand() % 3) - 1;
if (ptrPlayer->aiMemory.c_Angle > 1)
ptrPlayer->aiMemory.c_Angle = 1;
else
if (ptrPlayer->aiMemory.c_Angle < -1)
ptrPlayer->aiMemory.c_Angle = -1;
}
if (mt_rand() % 100 > 90)
{
if (ptrPlayer->aiMemory.c_Angle > 0 && ptrCurUnit->angle > (M_PI_2) - (M_PI / 9))
ptrPlayer->aiMemory.c_Angle = 0;
else
if (ptrPlayer->aiMemory.c_Angle < 0 && ptrCurUnit->angle < M_PI / 8)
ptrPlayer->aiMemory.c_Angle = 0;
}
if (mt_rand() % 100 > 93)
{
ptrPlayer->aiMemory.c_Power += (mt_rand() % 3) - 1;
if (ptrPlayer->aiMemory.c_Power > 1)
ptrPlayer->aiMemory.c_Power = 1;
else
if (ptrPlayer->aiMemory.c_Power < -1)
ptrPlayer->aiMemory.c_Power = -1;
}
if (mt_rand() % 100 > 90)
{
if (ptrPlayer->aiMemory.c_Power > 0 && ptrCurUnit->power > 4)
ptrPlayer->aiMemory.c_Power = 0;
else
if (ptrPlayer->aiMemory.c_Power < 0 && ptrCurUnit->power < 3)
ptrPlayer->aiMemory.c_Power = 0;
else
if (ptrCurUnit->power < 2)
ptrPlayer->aiMemory.c_Power = 1;
}
// prefer helicopter
ptrUnit = ptrPlayer->unit;
for (j = 0; j < config.max_installations; j++, ptrUnit++)
{
if (DE_isValidUnit(ptrUnit) && ptrUnit->unitType == UNIT_HELI)
{
ptrPlayer->unitSelected = j;
break;
}
}
if (ptrCurUnit->unitType == UNIT_HELI)
{
if (ptrCurUnit->isYInAir == false)
{
ptrPlayer->aiMemory.c_Power = 1;
}
if (mt_rand() % ptrCurUnit->unitX > 100)
{
ptrPlayer->aiMemory.c_Power = 1;
}
if (mt_rand() % 240 > ptrCurUnit->unitX)
{
ptrPlayer->moves.actions[MOVE_RIGHT] = true;
}
else if ((mt_rand() % 20) + 300 < ptrCurUnit->unitX)
{
ptrPlayer->moves.actions[MOVE_LEFT] = true;
}
else if (mt_rand() % 30 == 1)
{
ptrPlayer->aiMemory.c_Angle = (mt_rand() % 3) - 1;
}
if (ptrCurUnit->unitX > 295 && ptrCurUnit->lastMove > 1)
{
ptrPlayer->moves.actions[MOVE_LEFT] = true;
ptrPlayer->moves.actions[MOVE_RIGHT] = false;
}
if (ptrCurUnit->unitType != UNIT_HELI || ptrCurUnit->lastMove > 3 || (ptrCurUnit->unitX > 160 && ptrCurUnit->lastMove > -3))
{
if (mt_rand() % (int)roundf(ptrCurUnit->unitY) < 150 && ptrCurUnit->unitYMov < 0.01f && (ptrCurUnit->unitX < 160 || ptrCurUnit->lastMove < 2))
{
ptrPlayer->moves.actions[MOVE_FIRE] = true;
}
ptrPlayer->aiMemory.c_noDown = (5 - abs(ptrCurUnit->lastMove)) * (5 - abs(ptrCurUnit->lastMove)) + 3;
ptrPlayer->aiMemory.c_Power = 1;
} else {
ptrPlayer->moves.actions[MOVE_FIRE] = false;
}
ptrUnit = ptrTarget->unit;
for (j = 0; j < config.max_installations; j++, ptrUnit++)
{
if (abs(ptrUnit->unitX - ptrCurUnit->unitX) < 8)
{
/* I get it. This makes helicoptors hover over
* their enemies. */
if (ptrUnit->unitType == UNIT_SATELLITE)
{
ptrPlayer->moves.actions[MOVE_FIRE] = false;
}
else
{
ptrPlayer->moves.actions[MOVE_LEFT] = false;
ptrPlayer->moves.actions[MOVE_RIGHT] = false;
if (ptrCurUnit->lastMove < -1)
{
ptrCurUnit->lastMove++;
}
else if (ptrCurUnit->lastMove > 1)
{
ptrCurUnit->lastMove--;
}
}
}
}
} else {
ptrPlayer->moves.actions[MOVE_FIRE] = 1;
}
if (mt_rand() % 200 > 198)
{
ptrPlayer->moves.actions[MOVE_CHANGE] = true;
ptrPlayer->aiMemory.c_Angle = 0;
ptrPlayer->aiMemory.c_Power = 0;
ptrPlayer->aiMemory.c_Fire = 0;
}
if (mt_rand() % 100 > 98 || ptrCurUnit->shotType == SHOT_TRACER)
{ /* Clearly the CPU doesn't like the tracer :) */
ptrPlayer->moves.actions[MOVE_CYDN] = true;
}
if (ptrPlayer->aiMemory.c_Angle > 0)
{
ptrPlayer->moves.actions[MOVE_LEFT] = true;
}
if (ptrPlayer->aiMemory.c_Angle < 0)
{
ptrPlayer->moves.actions[MOVE_RIGHT] = true;
}
if (ptrPlayer->aiMemory.c_Power > 0)
{
ptrPlayer->moves.actions[MOVE_UP] = true;
}
if (ptrPlayer->aiMemory.c_Power < 0 && ptrPlayer->aiMemory.c_noDown == 0)
{
ptrPlayer->moves.actions[MOVE_DOWN] = true;
}
if (ptrPlayer->aiMemory.c_Fire > 0)
{
ptrPlayer->moves.actions[MOVE_FIRE] = true;
}
if (ptrCurUnit->unitYMov < -0.1f && ptrCurUnit->unitType == UNIT_HELI)
{
ptrPlayer->moves.actions[MOVE_FIRE] = false;
}
/* This last hack was down in the processing section.
* What exactly it was doing there I do not know */
if(ptrCurUnit->unitType == UNIT_LASER || ptrCurUnit->isYInAir == true) {
ptrPlayer->aiMemory.c_Power = 0;
}
}
}
void DE_RunTickDrawCrosshairs( void )
{
unsigned int i;
int tempPosX, tempPosY;
int direction;
struct destruct_unit_s * curUnit;
/* Draw the crosshairs. Most vehicles aim left or right. Helis can aim
* either way and this must be accounted for.
*/
for (i = 0; i < MAX_PLAYERS; i++)
{
direction = (i == PLAYER_LEFT) ? -1 : 1;
curUnit = &(player[i].unit[player[i].unitSelected]);
if (curUnit->unitType == UNIT_HELI)
{
tempPosX = curUnit->unitX + roundf(0.1f * curUnit->lastMove * curUnit->lastMove * curUnit->lastMove) + 5;
tempPosY = roundf(curUnit->unitY) + 1;
} else {
tempPosX = roundf(curUnit->unitX + 6 - cosf(curUnit->angle) * (curUnit->power * 8 + 7) * direction);
tempPosY = roundf(curUnit->unitY - 7 - sinf(curUnit->angle) * (curUnit->power * 8 + 7));
}
/* Draw it. Clip away from the HUD though. */
if(tempPosY > 9)
{
if(tempPosY > 11)
{
if(tempPosY > 13)
{
/* Top pixel */
JE_pix(VGAScreen, tempPosX, tempPosY - 2, 3);
}
/* Middle three pixels */
JE_pix(VGAScreen, tempPosX + 3, tempPosY, 3);
JE_pix(VGAScreen, tempPosX, tempPosY, 14);
JE_pix(VGAScreen, tempPosX - 3, tempPosY, 3);
}
/* Bottom pixel */
JE_pix(VGAScreen, tempPosX, tempPosY + 2, 3);
}
}
}
void DE_RunTickDrawHUD( void )
{
unsigned int i;
unsigned int startX;
char tempstr[16]; /* Max size needed: 16 assuming 10 digit int max. */
struct destruct_unit_s * curUnit;
for (i = 0; i < MAX_PLAYERS; i++)
{
curUnit = &(player[i].unit[player[i].unitSelected]);
startX = ((i == PLAYER_LEFT) ? 0 : 320 - 150);
fill_rectangle_xy(VGAScreen, startX + 5, 3, startX + 14, 8, 241);
JE_rectangle(VGAScreen, startX + 4, 2, startX + 15, 9, 242);
JE_rectangle(VGAScreen, startX + 3, 1, startX + 16, 10, 240);
fill_rectangle_xy(VGAScreen, startX + 18, 3, startX + 140, 8, 241);
JE_rectangle(VGAScreen, startX + 17, 2, startX + 143, 9, 242);
JE_rectangle(VGAScreen, startX + 16, 1, startX + 144, 10, 240);
blit_sprite2(VGAScreen, startX + 4, 0, eShapes1, 191 + curUnit->shotType);
JE_outText (VGAScreen, startX + 20, 3, weaponNames[curUnit->shotType], 15, 2);
sprintf (tempstr, "dmg~%d~", curUnit->health);
JE_outText (VGAScreen, startX + 75, 3, tempstr, 15, 0);
sprintf (tempstr, "pts~%d~", player[i].score);
JE_outText (VGAScreen, startX + 110, 3, tempstr, 15, 0);
}
}
void DE_RunTickGetInput( void )
{
unsigned int player_index, key_index, slot_index;
SDLKey key;
/* player.keys holds our key config. Players will eventually be allowed
* to can change their key mappings. player.moves and player.keys
* line up; rather than manually checking left and right we can
* just loop through the indexes and set the actions as needed. */
service_SDL_events(true);
#ifdef ANDROID
if( mouse_pressed[0] )
player[1].moves.actions[KEY_FIRE] = true;
#endif
for(player_index = 0; player_index < MAX_PLAYERS; player_index++)
{
for(key_index = 0; key_index < MAX_KEY; key_index++)
{
for(slot_index = 0; slot_index < MAX_KEY_OPTIONS; slot_index++)
{
key = player[player_index].keys.Config[key_index][slot_index];
if(key == SDLK_UNKNOWN) { break; }
if(keysactive[key] == true)
{
/* The right key was clearly pressed */
player[player_index].moves.actions[key_index] = true;
/* Some keys we want to toggle afterwards */
if(key_index == KEY_CHANGE ||
key_index == KEY_CYUP ||
key_index == KEY_CYDN)
{
keysactive[key] = false;
}
break;
}
}
}
}
}
void DE_ProcessInput( void )
{
int direction;
unsigned int player_index;
struct destruct_unit_s * curUnit;
for (player_index = 0; player_index < MAX_PLAYERS; player_index++)
{
if (player[player_index].unitsRemaining <= 0) { continue; }
direction = (player_index == PLAYER_LEFT) ? -1 : 1;
curUnit = &(player[player_index].unit[player[player_index].unitSelected]);
if (systemAngle[curUnit->unitType] == true) /* selected unit may change shot angle */
{
if (player[player_index].moves.actions[MOVE_LEFT] == true)
{
(player_index == PLAYER_LEFT) ? DE_RaiseAngle(curUnit) : DE_LowerAngle(curUnit);
}
if (player[player_index].moves.actions[MOVE_RIGHT] == true)
{
(player_index == PLAYER_LEFT) ? DE_LowerAngle(curUnit) : DE_RaiseAngle(curUnit);
}
} else if (curUnit->unitType == UNIT_HELI) {
if (player[player_index].moves.actions[MOVE_LEFT] == true && curUnit->unitX > 5)
if (JE_stabilityCheck(curUnit->unitX - 5, roundf(curUnit->unitY)))
{
if (curUnit->lastMove > -5)
{
curUnit->lastMove--;
}
curUnit->unitX--;
if (JE_stabilityCheck(curUnit->unitX, roundf(curUnit->unitY)))
{
curUnit->isYInAir = true;
}
}
if (player[player_index].moves.actions[MOVE_RIGHT] == true && curUnit->unitX < 305)
{
if (JE_stabilityCheck(curUnit->unitX + 5, roundf(curUnit->unitY)))
{
if (curUnit->lastMove < 5)
{
curUnit->lastMove++;
}
curUnit->unitX++;
if (JE_stabilityCheck(curUnit->unitX, roundf(curUnit->unitY)))
{
curUnit->isYInAir = true;
}
}
}
}
if (curUnit->unitType != UNIT_LASER)
{ /*increasepower*/
if (player[player_index].moves.actions[MOVE_UP] == true)
{
if (curUnit->unitType == UNIT_HELI)
{
curUnit->isYInAir = true;
curUnit->unitYMov -= 0.1f;
}
else if (curUnit->unitType == UNIT_JUMPER
&& curUnit->isYInAir == false) {
curUnit->unitYMov = -3;
curUnit->isYInAir = true;
}
else {
DE_RaisePower(curUnit);
}
}
/*decreasepower*/
if (player[player_index].moves.actions[MOVE_DOWN] == true)
{
if (curUnit->unitType == UNIT_HELI && curUnit->isYInAir == true)
{
curUnit->unitYMov += 0.1f;
} else {
DE_LowerPower(curUnit);
}
}
}
/*up/down weapon. These just cycle until a valid weapon is found */
if (player[player_index].moves.actions[MOVE_CYUP] == true)
{
DE_CycleWeaponUp(curUnit);
}
if (player[player_index].moves.actions[MOVE_CYDN] == true)
{
DE_CycleWeaponDown(curUnit);
}
/* Change. Since change would change out curUnit pointer, let's just do it last.
* Validity checking is performed at the beginning of the tick. */
if (player[player_index].moves.actions[MOVE_CHANGE] == true)
{
player[player_index].unitSelected++;
if (player[player_index].unitSelected >= config.max_installations)
{
player[player_index].unitSelected = 0;
}
}
/*Newshot*/
if (player[player_index].shotDelay > 0)
{
player[player_index].shotDelay--;
}
if (player[player_index].moves.actions[MOVE_FIRE] == true
&& (player[player_index].shotDelay == 0))
{
player[player_index].shotDelay = shotDelay[curUnit->shotType];
switch(shotDirt[curUnit->shotType])
{
case EXPL_NONE:
break;
case EXPL_MAGNET:
DE_RunMagnet((de_player_t)player_index, curUnit);
break;
case EXPL_DIRT:
case EXPL_NORMAL:
DE_MakeShot((de_player_t)player_index, curUnit, direction);
break;
default:
assert(false);
}
}
}
}
void DE_CycleWeaponUp( struct destruct_unit_s * unit )
{
do
{
unit->shotType = (de_shot_t)((int)unit->shotType + 1);
if (unit->shotType > SHOT_LAST)
{
unit->shotType = SHOT_FIRST;
}
} while (weaponSystems[unit->unitType][unit->shotType] == 0);
}
void DE_CycleWeaponDown( struct destruct_unit_s * unit )
{
do
{
unit->shotType = (de_shot_t)((int)unit->shotType - 1);
if (unit->shotType < SHOT_FIRST)
{
unit->shotType = SHOT_LAST;
}
} while (weaponSystems[unit->unitType][unit->shotType] == 0);
}
void DE_MakeShot( enum de_player_t curPlayer, const struct destruct_unit_s * curUnit, int direction )
{
unsigned int i;
unsigned int shotIndex;
/* First, find an empty shot struct we can use */
for (i = 0; ; i++)
{
if (i >= config.max_shots) { return; } /* no empty slots. Do nothing. */
if (shotRec[i].isAvailable)
{
shotIndex = i;
break;
}
}
if (curUnit->unitType == UNIT_HELI && curUnit->isYInAir == false)
{ /* Helis can't fire when they are on the ground. */
return;
}
/* Play the firing sound */
soundQueue[curPlayer] = shotSound[curUnit->shotType];
/* Create our shot. Some units have differing logic here */
switch (curUnit->unitType)
{
case UNIT_HELI:
shotRec[shotIndex].x = curUnit->unitX + curUnit->lastMove * 2 + 5;
shotRec[shotIndex].xmov = 0.02f * curUnit->lastMove * curUnit->lastMove * curUnit->lastMove;
/* If we are trying in vain to move up off the screen, act differently.*/
if (player[curPlayer].moves.actions[MOVE_UP] && curUnit->unitY < 30)
{
shotRec[shotIndex].y = curUnit->unitY;
shotRec[shotIndex].ymov = 0.1f;
if (shotRec[shotIndex].xmov < 0)
{
shotRec[shotIndex].xmov += 0.1f;
}
else if (shotRec[shotIndex].xmov > 0)
{
shotRec[shotIndex].xmov -= 0.1f;
}
}
else
{
shotRec[shotIndex].y = curUnit->unitY + 1;
shotRec[shotIndex].ymov = 0.5f + curUnit->unitYMov * 0.1f;
}
break;
case UNIT_JUMPER: /* Jumpers are normally only special for the left hand player. Bug? Or feature? */
if(config.jumper_straight[curPlayer])
{
/* This is identical to the default case.
* I considered letting the switch fall through
* but that's more confusing to people who aren't used
* to that quirk of switch. */
shotRec[shotIndex].x = curUnit->unitX + 6 - cosf(curUnit->angle) * 10 * direction;
shotRec[shotIndex].y = curUnit->unitY - 7 - sinf(curUnit->angle) * 10;
shotRec[shotIndex].xmov = -cosf(curUnit->angle) * curUnit->power * direction;
shotRec[shotIndex].ymov = -sinf(curUnit->angle) * curUnit->power;
}
else
{
/* This is not identical to the default case. */
shotRec[shotIndex].x = curUnit->unitX + 2;
shotRec[shotIndex].xmov = -cosf(curUnit->angle) * curUnit->power * direction;
if (curUnit->isYInAir == true)
{
shotRec[shotIndex].ymov = 1;
shotRec[shotIndex].y = curUnit->unitY + 2;
} else {
shotRec[shotIndex].ymov = -2;
shotRec[shotIndex].y = curUnit->unitY - 12;
}
}
break;
default:
shotRec[shotIndex].x = curUnit->unitX + 6 - cosf(curUnit->angle) * 10 * direction;
shotRec[shotIndex].y = curUnit->unitY - 7 - sinf(curUnit->angle) * 10;
shotRec[shotIndex].xmov = -cosf(curUnit->angle) * curUnit->power * direction;
shotRec[shotIndex].ymov = -sinf(curUnit->angle) * curUnit->power;
break;
}
/* Now set/clear out a few last details. */
shotRec[shotIndex].isAvailable = false;
shotRec[shotIndex].shottype = curUnit->shotType;
//shotRec[shotIndex].shotdur = shotFuse[shotRec[shotIndex].shottype];
shotRec[shotIndex].trailc[0] = 0;
shotRec[shotIndex].trailc[1] = 0;
shotRec[shotIndex].trailc[2] = 0;
shotRec[shotIndex].trailc[3] = 0;
}
void DE_RunMagnet( enum de_player_t curPlayer, struct destruct_unit_s * magnet )
{
unsigned int i;
enum de_player_t curEnemy;
int direction;
struct destruct_unit_s * enemyUnit;
curEnemy = (curPlayer == PLAYER_LEFT) ? PLAYER_RIGHT : PLAYER_LEFT;
direction = (curPlayer == PLAYER_LEFT) ? -1 : 1;
/* Push all shots that are in front of the magnet */
for (i = 0; i < config.max_shots; i++)
{
if (shotRec[i].isAvailable == false)
{
if ((curPlayer == PLAYER_LEFT && shotRec[i].x > magnet->unitX)
|| (curPlayer == PLAYER_RIGHT && shotRec[i].x < magnet->unitX))
{
shotRec[i].xmov += magnet->power * 0.1f * -direction;
}
}
}
enemyUnit = player[curEnemy].unit;
for (i = 0; i < config.max_installations; i++, enemyUnit++) /* magnets push coptors */
{
if (DE_isValidUnit(enemyUnit)
&& enemyUnit->unitType == UNIT_HELI
&& enemyUnit->isYInAir == true)
{
if ((curEnemy == PLAYER_RIGHT && player[curEnemy].unit[i].unitX + 11 < 318)
|| (curEnemy == PLAYER_LEFT && player[curEnemy].unit[i].unitX > 1))
{
enemyUnit->unitX -= 2 * direction;
}
}
}
magnet->ani_frame = 1;
}
void DE_RaiseAngle( struct destruct_unit_s * unit )
{
unit->angle += 0.01f;
if (unit->angle > M_PI_2 - 0.01f)
{
unit->angle = M_PI_2 - 0.01f;
}
}
void DE_LowerAngle( struct destruct_unit_s * unit )
{
unit->angle -= 0.01f;
if (unit->angle < 0)
{
unit->angle = 0;
}
}
void DE_RaisePower( struct destruct_unit_s * unit )
{
unit->power += 0.05f;
if (unit->power > 5)
{
unit->power = 5;
}
}
void DE_LowerPower( struct destruct_unit_s * unit )
{
unit->power -= 0.05f;
if (unit->power < 1)
{
unit->power = 1;
}
}
/* DE_isValidUnit
*
* Returns true if the unit's health is above 0 and false
* otherwise. This mainly exists because the 'health' var
* serves two roles and that can get confusing.
*/
static inline bool DE_isValidUnit( struct destruct_unit_s * unit )
{
return(unit->health > 0);
}
bool DE_RunTickCheckEndgame( void )
{
if (player[PLAYER_LEFT].unitsRemaining == 0)
{
player[PLAYER_RIGHT].score += ModeScore[PLAYER_LEFT][world.destructMode];
soundQueue[7] = V_CLEARED_PLATFORM;
return(true);
}
if (player[PLAYER_RIGHT].unitsRemaining == 0)
{
player[PLAYER_LEFT].score += ModeScore[PLAYER_RIGHT][world.destructMode];
soundQueue[7] = V_CLEARED_PLATFORM;
return(true);
}
return(false);
}
void DE_RunTickPlaySounds( void )
{
unsigned int i, tempSampleIndex, tempVolume;
for (i = 0; i < COUNTOF(soundQueue); i++)
{
if (soundQueue[i] != S_NONE)
{
tempSampleIndex = soundQueue[i];
if (i == 7)
{
tempVolume = fxPlayVol;
}
else
{
tempVolume = fxPlayVol / 2;
}
JE_multiSamplePlay(digiFx[tempSampleIndex-1], fxSize[tempSampleIndex-1], i, tempVolume);
soundQueue[i] = S_NONE;
}
}
}
void JE_pixCool( unsigned int x, unsigned int y, Uint8 c )
{
JE_pix(VGAScreen, x, y, c);
JE_pix(VGAScreen, x - 1, y, c - 2);
JE_pix(VGAScreen, x + 1, y, c - 2);
JE_pix(VGAScreen, x, y - 1, c - 2);
JE_pix(VGAScreen, x, y + 1, c - 2);
}
// kate: tab-width 4; vim: set noet: