/* * 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 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: