diff --git a/CHANGELOG b/CHANGELOG index 82b2b75..8eb6e73 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,8 @@ Changelog 0.6 + * New Campaign missions + * Added new fighters: Bliizard, Razor, and Shale, part of the unstable Nation of Tzac * Added control remapping * New game mode: Challenges * Added i18n support diff --git a/README.md b/README.md index 94d9d73..b821f0e 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,8 @@ CC BY-NC-SA 3.0, with the following attribution: Copyright 2015-2016, Stephen J * 172591__timbre__zapitydooda.ogg - d1clsstf.wav, by wildweasel - https://freesound.org/people/wildweasel/sounds/39030/ * 39030__wildweasel__d1clsstf.ogg - push_button_switch_07.wav, by joedeshon - https://freesound.org/people/joedeshon/sounds/139061/ * 278142__ricemaster__effect-notify.ogg - effect_notify.wav, by ricemaster - https://freesound.org/people/ricemaster/sounds/278142/ +* 254174__kwahmah-02__s.ogg - s.wav, by kwahmah_02 - https://freesound.org/people/kwahmah_02/sounds/254174/ +* 172870__escortmarius__carbidexplosion.ogg - carbidexplosion.wav, by escortmarius - https://freesound.org/people/escortmarius/sounds/172870/ ### MUSIC diff --git a/README_DEV.md b/README_DEV.md index 14dc63f..d0b16fc 100644 --- a/README_DEV.md +++ b/README_DEV.md @@ -1,10 +1,10 @@ # DEVELOPMENT / DEBUG Stuff -You can run the game with +You can run the game with: `./tbftss -debug` -to access some (rather crude) debugging and development stuff, activated by pressing certain keys on the keyboard. Press the following keys to toggle the states (see the console for output). Note to porters - you shouldn't distribute the game in this state (either as a hard compile or run script), as it could result in unexpected behaviour. +to access some (rather crude) debugging and development stuff, activated by pressing certain keys on the keyboard. Press the following keys to toggle the states (see the console for output). Note to packagers - you shouldn't distribute the game in this state (either as a hard compile or run script), as it could result in unexpected behaviour. * [1] - Make the player immortal * [2] - Unlimited missiles @@ -15,3 +15,9 @@ to access some (rather crude) debugging and development stuff, activated by pres * [9] - Show the current frames per second * [0] - Take a screenshot once per second, saving to /tmp/tbftss on Linux. + +By default, the game will output WARN level messages, or greater. To see INFO level messages, you can use: + +`./tbftss -info` + +Note that using `-debug` will automatically output DEBUG level messages. diff --git a/common.mk b/common.mk index 84a8ad0..e79496f 100644 --- a/common.mk +++ b/common.mk @@ -19,7 +19,7 @@ OBJS += hud.o OBJS += i18n.o init.o input.o io.o items.o OBJS += jumpgate.o OBJS += load.o locations.o lookup.o -OBJS += main.o messageBox.o mission.o missionInfo.o modalDialog.o +OBJS += main.o messageBox.o mine.o mission.o missionInfo.o modalDialog.o OBJS += objectives.o options.o OBJS += player.o OBJS += quadtree.o diff --git a/data/fighters/blizzard.json b/data/fighters/blizzard.json new file mode 100644 index 0000000..3d7d2a6 --- /dev/null +++ b/data/fighters/blizzard.json @@ -0,0 +1,33 @@ +{ + "name" : "Blizzard", + "health" : 120, + "shield" : 70, + "speed" : 1.85, + "reloadTime" : 14, + "shieldRechargeRate" : 45, + "texture" : "gfx/fighters/blizzard.png", + "guns" : [ + { + "type" : "BT_PLASMA", + "x" : -14, + "y" : 0 + }, + { + "type" : "BT_PLASMA", + "x" : -14, + "y" : 0 + }, + { + "type" : "BT_PARTICLE", + "x" : 8, + "y" : 0 + }, + { + "type" : "BT_PARTICLE", + "x" : 8, + "y" : 0 + } + ], + "missiles" : 5, + "flags" : "EF_TAKES_DAMAGE" +} diff --git a/data/fighters/shale.json b/data/fighters/shale.json index fcf041f..f8f5c09 100644 --- a/data/fighters/shale.json +++ b/data/fighters/shale.json @@ -3,7 +3,7 @@ "health" : 50, "shield" : 50, "speed" : 2, - "reloadTime" : 8, + "reloadTime" : 18, "shieldRechargeRate" : 20, "texture" : "gfx/fighters/shale.png", "guns" : [ diff --git a/data/missions/aster/01 - return to aster #1.json b/data/missions/aster/01 - return to aster #1.json index e476c6e..9fb20a8 100644 --- a/data/missions/aster/01 - return to aster #1.json +++ b/data/missions/aster/01 - return to aster #1.json @@ -53,7 +53,7 @@ "x" : 25, "y" : 25, "number" : 6, - "aiFlags" : "+AIF_MOVES_TO_LEADER" + "aiFlags" : "AIF_MOVES_TO_LEADER+AIF_UNLIMITED_RANGE+AIF_DEFENSIVE" }, { "groupName" : "ShuttleTug", @@ -62,7 +62,7 @@ "x" : 25, "y" : 25, "number" : 6, - "aiFlags" : "+AIF_MOVES_TO_LEADER" + "aiFlags" : "AIF_MOVES_TO_LEADER+AIF_UNLIMITED_RANGE+AIF_DEFENSIVE" }, { "types" : "Thunderhead;Jackal;Sphinx", @@ -72,6 +72,28 @@ "number" : 4, "flags" : "+EF_AI_LEADER", "aiFlags" : "+AIF_UNLIMITED_RANGE" + }, + { + "groupName" : "Rebels", + "types" : "Razor", + "side" : "SIDE_REBEL", + "x" : 10, + "y" : 51, + "number" : 4, + "aiFlags" : "+AIF_UNLIMITED_RANGE", + "scatter" : 500, + "active" : 0 + }, + { + "groupName" : "Rebels", + "types" : "Khepri", + "side" : "SIDE_REBEL", + "x" : 10, + "y" : 51, + "number" : 8, + "aiFlags" : "+AIF_UNLIMITED_RANGE+AIF_TARGET_FOCUS", + "scatter" : 500, + "active" : 0 } ], "capitalShips" : [ @@ -80,7 +102,7 @@ "types" : "CSN Corvette 01", "side" : "SIDE_ALLIES", "x" : 10, - "y" : 40, + "y" : 37, "flags" : "+EF_AI_TARGET+EF_DISABLED+EF_MISSION_TARGET" } ], @@ -96,7 +118,7 @@ ] }, { - "function" : "ENEMIES_DESTROYED 16", + "function" : "ENEMIES_KILLED 16", "lines" : [ "WAIT 2", "MSG_BOX Black;Florin, Pandoran vessels have been dealt with.", @@ -108,12 +130,26 @@ "MSG_BOX CSN Florin;Negative. These appear to be non-aligned forces, fighters and bombers.", "WAIT_MSG_BOX", "WAIT 5", - "ACTIVATE_ENTITY_GROUP Rebels", + "ACTIVATE_ENTITY_GROUPS Rebels", "MSG_BOX CSN Florin;Incoming forces are on intercept vectors. We only need a few more minutes.", "MSG_BOX Black;On it.", "WAIT_MSG_BOX", "ACTIVATE_OBJECTIVES Defeat rebel forces" ] + }, + { + "function" : "OBJECTIVES_COMPLETE 2", + "lines" : [ + "WAIT 2", + "MSG_BOX Black;Looks like that's all of them.", + "MSG_BOX CSN Florin;Those were Nation of Tzac fighters. We've heard reports of them being active in some Confederation systems.", + "MSG_BOX Black;Would appear that Crystabelle is finally ready to make good on all her threats. Great, so now we have both her AND the Pandorans to deal with.", + "MSG_BOX CSN Florin;It never rains ...", + "MSG_BOX CSN Florin;We're starting systems up again. Once we've completed pre-jump checks we're heading for Temper. Thank you for your assistance.", + "MSG_BOX Black;We'll meet you there.", + "WAIT_MSG_BOX", + "COMPLETE_MISSION" + ] } ] } diff --git a/data/missions/aster/02 - return to aster #2.json b/data/missions/aster/02 - return to aster #2.json new file mode 100644 index 0000000..165bc57 --- /dev/null +++ b/data/missions/aster/02 - return to aster #2.json @@ -0,0 +1,74 @@ +{ + "name" : "Mine Sweeper", + "description" : "", + "requires" : 33, + "background" : "gfx/backgrounds/background04.jpg", + "planet" : "gfx/planets/bluePlanet.png", + "music" : "music/battle/track-3.mp3", + "manualComplete" : 1, + "objectives" : [ + { + "description" : "Destroy all enemy forces", + "targetName" : "Enemy", + "targetValue" : 1, + "targetType" : "TT_DESTROY", + "isEliminateAll" : 1 + } + ], + "player" : { + "type" : "Ray", + "side" : "SIDE_ALLIES", + "pilot" : "1st Lt. Cora Pope", + "squadron" : "Midnight Runners", + "x" : 25, + "y" : 45 + }, + "fighters" : [ + { + "types" : "TAF", + "side" : "SIDE_ALLIES", + "x" : 25, + "y" : 45, + "number" : 3, + "scatter" : 500 + }, + { + "types" : "Khepri", + "side" : "SIDE_REBEL", + "x" : 25, + "y" : 30, + "number" : 5, + "scatter" : 4000, + "flags" : "+EF_AI_LEADER", + "aiFlags" : "+AIF_DROPS_MINES+AIF_WANDERS" + }, + { + "types" : "Shale", + "side" : "SIDE_REBEL", + "x" : 25, + "y" : 35, + "number" : 3, + "scatter" : 2500, + "aiFlags" : "+AIF_MOVES_TO_LEADER" + } + ], + "script" : [ + { + "function" : "TIME 2", + "lines" : [ + "MSG_BOX Pope;Watch out for those mines. They're on proximity triggers and have a large area of effect.", + "MSG_BOX Pope;Take them out from a distance, otherwise you'll be in trouble." + ] + }, + { + "function" : "ALL_OBJECTIVES_COMPLETE", + "lines" : [ + "WAIT 1", + "MSG_BOX Pope;That's the bombers taken care of, but the area is still littered with mines.", + "MSG_BOX Pope;We'll need to get a team in here to take care of them.", + "WAIT_MSG_BOX", + "COMPLETE_MISSION" + ] + } + ] +} diff --git a/data/missions/granada/03 - suspect packages #3.json b/data/missions/granada/03 - suspect packages #3.json index ef8e8e5..893d39f 100644 --- a/data/missions/granada/03 - suspect packages #3.json +++ b/data/missions/granada/03 - suspect packages #3.json @@ -98,7 +98,7 @@ { "name" : "Tzac Commander", "groupName" : "skirmishGroup", - "types" : "Firefly", + "types" : "Razor", "side" : "SIDE_REBEL", "x" : 15, "y" : 40, @@ -107,7 +107,7 @@ }, { "groupName" : "skirmishGroup", - "types" : "Firefly;Nymph", + "types" : "Razor;Shale", "side" : "SIDE_REBEL", "number" : 8, "x" : 15, diff --git a/data/missions/rothan/02 - rothan defence #2.json b/data/missions/rothan/02 - rothan defence #2.json index 36120a7..a9fc104 100644 --- a/data/missions/rothan/02 - rothan defence #2.json +++ b/data/missions/rothan/02 - rothan defence #2.json @@ -59,7 +59,7 @@ { "name" : "Rebel", "groupName" : "Rebels", - "types" : "Nymph;Firefly", + "types" : "Razor;Shale", "side" : "SIDE_REBEL", "x" : -1, "y" : -1, @@ -70,7 +70,7 @@ { "name" : "Rebel", "groupName" : "Rebels", - "types" : "Nymph;Firefly", + "types" : "Razor;Shale", "side" : "SIDE_REBEL", "x" : 15, "y" : -1, @@ -81,7 +81,7 @@ { "name" : "Rebel", "groupName" : "Rebels", - "types" : "Nymph;Firefly", + "types" : "Razor;Shale", "side" : "SIDE_REBEL", "x" : -1, "y" : 15, diff --git a/gfx/entities/mine.png b/gfx/entities/mine.png new file mode 100644 index 0000000..c0135fc Binary files /dev/null and b/gfx/entities/mine.png differ diff --git a/gfx/entities/mineWarning.png b/gfx/entities/mineWarning.png new file mode 100644 index 0000000..8b78308 Binary files /dev/null and b/gfx/entities/mineWarning.png differ diff --git a/gfx/fighters/blizzard.png b/gfx/fighters/blizzard.png new file mode 100644 index 0000000..16dfe28 Binary files /dev/null and b/gfx/fighters/blizzard.png differ diff --git a/locale/tbftss.pot b/locale/tbftss.pot index e3de4ed..8c7afde 100644 --- a/locale/tbftss.pot +++ b/locale/tbftss.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: TBFTSS: The Pandoran War\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2016-03-27 11:09:14+0100\n" +"POT-Creation-Date: 2016-04-01 08:08:53+0100\n" "PO-Revision-Date: ???\n" "Last-Translator: ???\n" "Language-Team: ???\n" @@ -577,6 +577,54 @@ msgstr "" msgid "Okay, let's get home and report in." msgstr "" +msgid "Florin, this is Black of the Salty Wildcats, here to assist." +msgstr "" + +msgid "Glad to see you, Wildcats. The Pandorans arrived shortly before you did. We've got tugs and shuttles incoming." +msgstr "" + +msgid "Acknowledged, we'll secure the area while you get things up and running." +msgstr "" + +msgid "Florin, Pandoran vessels have been dealt with." +msgstr "" + +msgid "Many thanks, Wildcats. We've identified the cause of the power outage. Should be no more than a few minutes before we can restart all systems." +msgstr "" + +msgid "Wildcats, we're picking up an incoming squadron." +msgstr "" + +msgid "More Pandoran tugs?" +msgstr "" + +msgid "Negative. These appear to be non-aligned forces, fighters and bombers." +msgstr "" + +msgid "Incoming forces are on intercept vectors. We only need a few more minutes." +msgstr "" + +msgid "On it." +msgstr "" + +msgid "Looks like that's all of them." +msgstr "" + +msgid "Those were Nation of Tzac fighters. We've heard reports of them being active in some Confederation systems." +msgstr "" + +msgid "Would appear that Crystabelle is finally ready to make good on all her threats. Great, so now we have both her AND the Pandorans to deal with." +msgstr "" + +msgid "It never rains ..." +msgstr "" + +msgid "We're starting systems up again. Once we've completed pre-jump checks we're heading for Temper. Thank you for your assistance." +msgstr "" + +msgid "We'll meet you there." +msgstr "" + msgid "The day that we have feared and the events that we attempted to avert are now upon us - the Pandoran army has commenced its push beyond Mitikas space, and is beginning to assault neighbouring Independent star systems. We need to fight back, in order to protect Clarke from becoming underrun by this menace. Take heed: this will not be an easy battle, but we have little choice and must secure victory here today." msgstr "" @@ -661,9 +709,6 @@ msgstr "" msgid "Only way we'll find out is if we go to Kethlan, and I'm not keen on heading there anytime soon." msgstr "" -msgid "Looks like that's all of them." -msgstr "" - msgid "Chaz, anything?" msgstr "" @@ -910,9 +955,6 @@ msgstr "" msgid "Too early to speculate. We want you to disable and bring in one craft from each side. The rest, you can eliminate." msgstr "" -msgid "On it." -msgstr "" - msgid "This is Carr, reporting operation successful. Bagged us two squadron commanders." msgstr "" diff --git a/sound/172870__escortmarius__carbidexplosion.ogg b/sound/172870__escortmarius__carbidexplosion.ogg new file mode 100644 index 0000000..e65522f Binary files /dev/null and b/sound/172870__escortmarius__carbidexplosion.ogg differ diff --git a/sound/254174__kwahmah-02__s.ogg b/sound/254174__kwahmah-02__s.ogg new file mode 100644 index 0000000..8ef31a6 Binary files /dev/null and b/sound/254174__kwahmah-02__s.ogg differ diff --git a/src/battle/ai.c b/src/battle/ai.c index 145b48a..1f392ab 100644 --- a/src/battle/ai.c +++ b/src/battle/ai.c @@ -37,6 +37,7 @@ static int nearJumpgate(void); static void moveToJumpgate(void); static int nearEnemies(void); static int nearItems(void); +static int nearMines(void); static void moveToItem(void); static int nearTowableCraft(void); static void moveToTowableCraft(void); @@ -50,9 +51,16 @@ static void doGunAI(void); static void moveToLeader(void); static void wander(void); static void doWander(void); +static int selectWeaponForTarget(Entity *e); +static void deployMine(void); void doAI(void) { + if (self->aiFlags & AIF_DROPS_MINES) + { + deployMine(); + } + if ((self->aiFlags & (AIF_AVOIDS_COMBAT | AIF_EVADE)) && nearEnemies()) { return; @@ -63,6 +71,11 @@ void doAI(void) return; } + if (nearMines()) + { + return; + } + if ((self->aiFlags & AIF_GOAL_JUMPGATE) && nearJumpgate()) { /* near jumpgate, but you might decide to continue to fight, anyway */ @@ -302,7 +315,7 @@ static void findTarget(void) for (i = 0, e = candidates[i] ; e != NULL ; e = candidates[++i]) { - if (e->active && (e->flags & EF_TAKES_DAMAGE) && (!(e->flags & EF_DISABLED)) && e->side != self->side && e->health > 0 && canAttack(e)) + if (canAttack(e)) { dist = getDistance(self->x, self->y, e->x, e->y); @@ -317,19 +330,36 @@ static void findTarget(void) static int canAttack(Entity *e) { - self->selectedGunType = self->guns[0].type; + if (!e->active || e->side == self->side || e->health <= 0) + { + return 0; + } + + if (!(e->flags & EF_TAKES_DAMAGE)) + { + return 0; + } if (!(e->flags & EF_AI_TARGET)) { if (e->aiFlags & (AIF_AVOIDS_COMBAT | AIF_EVADE) || e->flags & EF_SECONDARY_TARGET) { - if (rand() % 5) - { - return 0; - } + return !(rand() % 5); } } + if ((self->aiFlags & AIF_TARGET_FOCUS) && (!(e->flags & EF_AI_TARGET))) + { + return 0; + } + + return selectWeaponForTarget(e); +} + +static int selectWeaponForTarget(Entity *e) +{ + self->selectedGunType = self->guns[0].type; + if (e->flags & EF_MUST_DISABLE) { if (e->systemPower > 0) @@ -437,7 +467,7 @@ static void preAttack(void) if (!(self->aiFlags & AIF_MISSILE_BOAT)) { /* force weapon selection, otherwise we'll keep using lasers / mag */ - canAttack(self->target); + selectWeaponForTarget(self->target); if (self->guns[0].type && (self->missiles == 0 || rand() % 50 > 0)) { @@ -562,6 +592,11 @@ static int nearEnemies(void) { if ((e->flags & EF_TAKES_DAMAGE) && e->side != self->side && !(e->flags & EF_DISABLED)) { + if ((self->aiFlags & AIF_TARGET_FOCUS) && (e->flags & EF_AI_TARGET)) + { + continue; + } + if (getDistance(e->x, e->y, self->x, self->y) < 1000) { self->targetLocation.x += e->x; @@ -589,6 +624,64 @@ static int nearEnemies(void) return 0; } +static void deployMine(void) +{ + Entity *mine; + + if (!self->reload && self->thrust > 0) + { + mine = spawnMine(); + mine->x = self->x; + mine->y = self->y; + mine->dx = rand() % 20 - rand() % 20; + mine->dx *= 0.1; + mine->dy = rand() % 20 - rand() % 20; + mine->dy *= 0.1; + mine->side = self->side; + + self->reload = rand() % (FPS * 3); + } +} + +static int nearMines(void) +{ + int i, numMines; + Entity *e, **candidates; + + candidates = getAllEntsWithin(self->x - 500, self->y - 500, 1000, 1000, self); + + self->targetLocation.x = self->targetLocation.y = 0; + + numMines = 0; + + for (i = 0, e = candidates[i] ; e != NULL ; e = candidates[++i]) + { + if (e->side != self->side && e->type == ET_MINE && getDistance(e->x, e->y, self->x, self->y) < 500) + { + self->targetLocation.x += e->x; + self->targetLocation.y += e->y; + numMines++; + } + } + + if (numMines) + { + self->targetLocation.x /= numMines; + self->targetLocation.y /= numMines; + + /* dodge slightly */ + self->targetLocation.x += (rand() % 100 - rand() % 100); + self->targetLocation.y += (rand() % 100 - rand() % 100); + + self->action = fleeEnemies; + self->aiActionTime = FPS * 2; + + return 1; + } + + return 0; +} + static void fleeEnemies(void) { int wantedAngle = 180 + getAngle(self->x, self->y, self->targetLocation.x, self->targetLocation.y); diff --git a/src/battle/ai.h b/src/battle/ai.h index 9038b97..628088b 100644 --- a/src/battle/ai.h +++ b/src/battle/ai.h @@ -38,6 +38,7 @@ extern void applyFighterBrakes(void); extern void addHudMessage(SDL_Color c, char *format, ...); extern Entity **getAllEntsWithin(int x, int y, int w, int h, Entity *ignore); extern char *getTranslatedString(char *string); +extern Entity *spawnMine(void); extern Battle battle; extern Colors colors; diff --git a/src/battle/effects.c b/src/battle/effects.c index 8b71890..330fc7a 100644 --- a/src/battle/effects.c +++ b/src/battle/effects.c @@ -284,6 +284,56 @@ void addSmallExplosion(void) } } +void addMineExplosion(void) +{ + int i; + Effect *e; + + for (i = 0 ; i < 16 ; i++) + { + e = malloc(sizeof(Effect)); + memset(e, 0, sizeof(Effect)); + battle.effectTail->next = e; + battle.effectTail = e; + + e->type = EFFECT_TEXTURE; + + e->x = self->x + rand() % 16 - rand() % 16; + e->y = self->y + rand() % 16 - rand() % 16; + e->texture = explosionTexture; + e->size = 32 + (rand() % 32); + e->r = 255; + + setRandomFlameHue(e); + + e->a = 32 + (rand() % 192); + e->health = e->a; + + e->x -= e->size / 2; + e->y -= e->size / 2; + } + + e = malloc(sizeof(Effect)); + + memset(e, 0, sizeof(Effect)); + battle.effectTail->next = e; + battle.effectTail = e; + + e->type = EFFECT_HALO; + e->x = self->x; + e->y = self->y; + e->size = 32; + e->scaleAmount = 2; + e->texture = haloTexture; + + e->r = 255; + e->g = 255; + e->b = 255; + e->a = 128; + + e->health = 128; +} + void addLargeExplosion(void) { int i; diff --git a/src/battle/hud.c b/src/battle/hud.c index eca800b..738a69d 100644 --- a/src/battle/hud.c +++ b/src/battle/hud.c @@ -409,7 +409,7 @@ static void drawObjectives(void) if (!game.currentMission->challengeData.isChallenge) { blit(objectives, (SCREEN_WIDTH / 2) - 50, 14, 0); - drawText(SCREEN_WIDTH / 2, 10, 16, TA_CENTER, colors.white, "%d / %d", battle.numObjectivesComplete, battle.numObjectivesTotal); + drawText(SCREEN_WIDTH / 2, 10, 16, TA_CENTER, colors.white, "%d / %d", battle.numObjectivesComplete, (battle.numObjectivesTotal + battle.numConditions)); } else { diff --git a/src/battle/jumpgate.c b/src/battle/jumpgate.c index 64c6365..8fd5ede 100644 --- a/src/battle/jumpgate.c +++ b/src/battle/jumpgate.c @@ -41,6 +41,8 @@ Entity *spawnJumpgate(void) portal = getTexture("gfx/entities/portal.png"); portalAngle = 0; + + SDL_QueryTexture(jumpgate->texture, NULL, NULL, &jumpgate->w, &jumpgate->h); return jumpgate; } diff --git a/src/battle/mine.c b/src/battle/mine.c new file mode 100644 index 0000000..d3080fa --- /dev/null +++ b/src/battle/mine.c @@ -0,0 +1,154 @@ +/* +Copyright (C) 2015-2016 Parallel Realities + +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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "mine.h" + +static void think(void); +static void die(void); +static void lookForFighters(void); +static void doSplashDamage(void); + +static SDL_Texture *mineWarning = NULL; +static SDL_Texture *mineNormal = NULL; + +Entity *spawnMine(void) +{ + Entity *mine = spawnEntity(); + + if (!mineNormal || !mineWarning) + { + mineNormal = getTexture("gfx/entities/mine.png"); + mineWarning = getTexture("gfx/entities/mineWarning.png"); + } + + mine->type = ET_MINE; + mine->health = mine->maxHealth = 1; + mine->speed = 1; + mine->systemPower = SYSTEM_POWER; + mine->texture = mineNormal; + mine->action = think; + mine->die = die; + mine->flags = EF_TAKES_DAMAGE+EF_NO_TARGET; + + SDL_QueryTexture(mine->texture, NULL, NULL, &mine->w, &mine->h); + + return mine; +} + +static void think(void) +{ + self->texture = mineNormal; + + self->angle += 0.1; + + if (self->angle >= 360) + { + self->angle -= 360; + } + + self->dx *= 0.99; + self->dy *= 0.99; + + lookForFighters(); + + if (self->systemPower < SYSTEM_POWER && battle.stats[STAT_TIME] % 8 < 4) + { + playBattleSound(SND_MINE_WARNING, self->x, self->y); + + self->texture = mineWarning; + } +} + +static void lookForFighters(void) +{ + Entity *e, **candidates; + int i; + + candidates = getAllEntsWithin(self->x - (self->w / 2) - DAMAGE_RANGE, self->y - (self->h / 2) - DAMAGE_RANGE, self->w + DAMAGE_RANGE, self->h + DAMAGE_RANGE, self); + + for (i = 0, e = candidates[i] ; e != NULL ; e = candidates[++i]) + { + if (e->side != self->side && e->health > 0 && e->type == ET_FIGHTER && getDistance(self->x, self->y, e->x, e->y) <= TRIGGER_RANGE) + { + self->systemPower--; + + if (self->systemPower <= 0) + { + self->health = 0; + } + + return; + } + } + + self->systemPower = SYSTEM_POWER; +} + + +static void die(void) +{ + addMineExplosion(); + + doSplashDamage(); + + playBattleSound(SND_EXPLOSION_5, self->x, self->y); + + self->alive = ALIVE_DEAD; +} + +static void doSplashDamage(void) +{ + Entity *e, **candidates; + int i, dist; + float damage, percent; + + candidates = getAllEntsWithin(self->x - (self->w / 2), self->y - (self->h / 2), self->w, self->h, self); + + for (i = 0, e = candidates[i] ; e != NULL ; e = candidates[++i]) + { + if (e->health > 0 && (e->type == ET_FIGHTER || e->type == ET_MINE) && !(e->flags & EF_IMMORTAL)) + { + dist = getDistance(self->x, self->y, e->x, e->y); + + if (dist <= DAMAGE_RANGE) + { + percent = dist; + percent /= DAMAGE_RANGE; + percent = 1 - percent; + + damage = 100; + damage *= percent; + + if (e->type == ET_FIGHTER) + { + damageFighter(e, damage, 0); + } + else if (e->type == ET_MINE) + { + e->dx = e->x - self->x; + e->dy = e->y - self->y; + + e->dx *= 0.01; + e->dy *= 0.01; + } + } + } + } +} diff --git a/src/battle/mine.h b/src/battle/mine.h new file mode 100644 index 0000000..6c613d9 --- /dev/null +++ b/src/battle/mine.h @@ -0,0 +1,36 @@ +/* +Copyright (C) 2015-2016 Parallel Realities + +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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include "../common.h" + +#define TRIGGER_RANGE 150 +#define DAMAGE_RANGE 255 +#define SYSTEM_POWER 50 + +extern Entity *spawnEntity(void); +extern SDL_Texture *getTexture(char *filename); +extern Entity **getAllEntsWithin(int x, int y, int w, int h, Entity *ignore); +extern int getDistance(int x1, int y1, int x2, int y2); +extern void addMineExplosion(void); +extern void damageFighter(Entity *e, int amount, long flags); +extern void playBattleSound(int id, int x, int y); + +extern Battle battle; +extern Entity *self; diff --git a/src/battle/objectives.c b/src/battle/objectives.c index 122b7e0..c4f46e6 100644 --- a/src/battle/objectives.c +++ b/src/battle/objectives.c @@ -26,7 +26,7 @@ void doObjectives(void) int numHiddenObjectives; Objective *o; - battle.numObjectivesComplete = battle.numObjectivesTotal = 0; + battle.numObjectivesComplete = battle.numObjectivesTotal = battle.numConditions = 0; objectiveFailed = 0; numHiddenObjectives = 0; @@ -38,6 +38,10 @@ void doObjectives(void) { battle.numObjectivesTotal++; } + else + { + battle.numConditions++; + } } else { diff --git a/src/battle/player.c b/src/battle/player.c index a47e192..221326a 100644 --- a/src/battle/player.c +++ b/src/battle/player.c @@ -480,7 +480,7 @@ static void selectTarget(void) for (e = battle.entityHead.next ; e != NULL ; e = e->next) { - if (e->active && e != player && (e->flags & EF_TAKES_DAMAGE) && e->side != player->side && e->alive == ALIVE_ALIVE && e->systemPower > 0 && i < MAX_SELECTABLE_TARGETS) + if (e->active && e != player && (e->flags & EF_TAKES_DAMAGE) && (!(e->flags & EF_NO_TARGET)) && e->side != player->side && e->alive == ALIVE_ALIVE && e->systemPower > 0 && i < MAX_SELECTABLE_TARGETS) { dist = getDistance(self->x, self->y, e->x, e->y); if (dist < closest) diff --git a/src/battle/radar.c b/src/battle/radar.c index cbe7fbf..4032b3b 100644 --- a/src/battle/radar.c +++ b/src/battle/radar.c @@ -34,6 +34,7 @@ void drawRadar(void) { SDL_Rect r; Entity *e; + int dist, inRange; blit(radarTexture, SCREEN_WIDTH - 85, SCREEN_HEIGHT - 85, 1); @@ -43,45 +44,57 @@ void drawRadar(void) for (e = battle.entityHead.next ; e != NULL ; e = e->next) { - if (e->active && getDistance(e->x, e->y, player->x, player->y) / radarRanges[battle.radarRange] < 70) + dist = getDistance(e->x, e->y, player->x, player->y); + + if (e->active) { - r.x = SCREEN_WIDTH - 85; - r.y = SCREEN_HEIGHT - 85; + inRange = (!(e->flags & EF_SHORT_RADAR_RANGE)) ? (dist / radarRanges[battle.radarRange]) < 70 : dist < 500; - r.x -= (player->x - e->x) / radarRanges[battle.radarRange]; - r.y -= (player->y - e->y) / radarRanges[battle.radarRange]; - - r.x--; - r.y--; - - switch (e->side) + if (inRange) { - case SIDE_ALLIES: - SDL_SetRenderDrawColor(app.renderer, 0, 255, 0, 255); - break; - - case SIDE_PIRATE: - case SIDE_PANDORAN: - case SIDE_REBEL: - SDL_SetRenderDrawColor(app.renderer, 255, 0, 0, 255); - break; - - case SIDE_NONE: + r.x = SCREEN_WIDTH - 85; + r.y = SCREEN_HEIGHT - 85; + + r.x -= (player->x - e->x) / radarRanges[battle.radarRange]; + r.y -= (player->y - e->y) / radarRanges[battle.radarRange]; + + r.x--; + r.y--; + + switch (e->side) + { + case SIDE_ALLIES: + SDL_SetRenderDrawColor(app.renderer, 0, 255, 0, 255); + break; + + case SIDE_PIRATE: + case SIDE_PANDORAN: + case SIDE_REBEL: + SDL_SetRenderDrawColor(app.renderer, 255, 0, 0, 255); + break; + + case SIDE_NONE: + SDL_SetRenderDrawColor(app.renderer, 255, 255, 255, 255); + break; + } + + if (e == player->target) + { + SDL_SetRenderDrawColor(app.renderer, 255, 255, 0, 255); + } + + if (e == battle.missionTarget) + { SDL_SetRenderDrawColor(app.renderer, 255, 255, 255, 255); - break; + } + + if (e->type == ET_MINE) + { + SDL_SetRenderDrawColor(app.renderer, 255, 255, 255, 255); + } + + SDL_RenderFillRect(app.renderer, &r); } - - if (e == player->target) - { - SDL_SetRenderDrawColor(app.renderer, 255, 255, 0, 255); - } - - if (e == battle.missionTarget) - { - SDL_SetRenderDrawColor(app.renderer, 255, 255, 255, 255); - } - - SDL_RenderFillRect(app.renderer, &r); } } } diff --git a/src/battle/waypoints.c b/src/battle/waypoints.c index 70226d4..7f95402 100644 --- a/src/battle/waypoints.c +++ b/src/battle/waypoints.c @@ -45,6 +45,8 @@ Entity *spawnWaypoint(void) waypoint->action = think; waypoint->flags |= EF_NO_MT_BOX; + SDL_QueryTexture(waypoint->texture, NULL, NULL, &waypoint->w, &waypoint->h); + return waypoint; } diff --git a/src/defs.h b/src/defs.h index 76884b2..8cb77d9 100644 --- a/src/defs.h +++ b/src/defs.h @@ -79,24 +79,26 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define BF_SHIELD_DAMAGE (2 << 2) #define BF_EXPLODES (2 << 3) -#define EF_NONE 0 -#define EF_NO_KILL (2 << 0) -#define EF_DISABLED (2 << 1) -#define EF_IMMORTAL (2 << 2) -#define EF_MISSION_TARGET (2 << 3) -#define EF_NO_MT_BOX (2 << 4) -#define EF_HAS_ROPE (2 << 5) -#define EF_COLLECTS_ITEMS (2 << 6) -#define EF_MUST_DISABLE (2 << 7) -#define EF_RETREATING (2 << 8) -#define EF_NO_EPIC (2 << 9) -#define EF_STATIC (2 << 10) -#define EF_TAKES_DAMAGE (2 << 11) -#define EF_SECONDARY_TARGET (2 << 12) -#define EF_AI_TARGET (2 << 13) -#define EF_AI_LEADER (2 << 14) -#define EF_ROPED_ATTACHED (2 << 15) -#define EF_NO_KILL_INC (2 << 16) +#define EF_NONE 0 +#define EF_NO_KILL (2 << 0) +#define EF_DISABLED (2 << 1) +#define EF_IMMORTAL (2 << 2) +#define EF_MISSION_TARGET (2 << 3) +#define EF_NO_MT_BOX (2 << 4) +#define EF_HAS_ROPE (2 << 5) +#define EF_COLLECTS_ITEMS (2 << 6) +#define EF_MUST_DISABLE (2 << 7) +#define EF_RETREATING (2 << 8) +#define EF_NO_EPIC (2 << 9) +#define EF_STATIC (2 << 10) +#define EF_TAKES_DAMAGE (2 << 11) +#define EF_SECONDARY_TARGET (2 << 12) +#define EF_AI_TARGET (2 << 13) +#define EF_AI_LEADER (2 << 14) +#define EF_ROPED_ATTACHED (2 << 15) +#define EF_NO_KILL_INC (2 << 16) +#define EF_SHORT_RADAR_RANGE (2 << 17) +#define EF_NO_TARGET (2 << 18) #define AIF_NONE 0 #define AIF_FOLLOWS_PLAYER (2 << 0) @@ -115,6 +117,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #define AIF_EVADE (2 << 13) #define AIF_WANDERS (2 << 14) #define AIF_COVERS_RETREAT (2 << 15) +#define AIF_TARGET_FOCUS (2 << 16) +#define AIF_DROPS_MINES (2 << 17) /* player abilities */ #define BOOST_RECHARGE_TIME (FPS * 7) @@ -150,6 +154,7 @@ enum ET_ITEM, ET_WAYPOINT, ET_JUMPGATE, + ET_MINE, ET_CAPITAL_SHIP_GUN, ET_CAPITAL_SHIP_COMPONENT, ET_CAPITAL_SHIP_ENGINE, @@ -225,6 +230,7 @@ enum SND_EXPLOSION_2, SND_EXPLOSION_3, SND_EXPLOSION_4, + SND_EXPLOSION_5, SND_GET_ITEM, SND_MISSILE, SND_INCOMING, @@ -236,6 +242,7 @@ enum SND_BOOST, SND_RADIO, SND_TROPHY, + SND_MINE_WARNING, SND_GUI_CLICK, SND_GUI_SELECT, SND_GUI_CLOSE, diff --git a/src/galaxy/mission.c b/src/galaxy/mission.c index e575e1c..44f47af 100644 --- a/src/galaxy/mission.c +++ b/src/galaxy/mission.c @@ -311,7 +311,7 @@ static void loadEntities(cJSON *node) { Entity *e; char *name, *groupName; - int i, type, scatter, number, active, addFlags; + int i, type, scatter, number, active, addFlags, side; float x, y; long flags; @@ -334,7 +334,8 @@ static void loadEntities(cJSON *node) number = getJSONValue(node, "number", 1); active = getJSONValue(node, "active", 1); scatter = getJSONValue(node, "scatter", 1); - + side = getJSONValue(node, "side", SIDE_NONE); + if (cJSON_GetObjectItem(node, "flags")) { flags = flagsToLong(cJSON_GetObjectItem(node, "flags")->valuestring, &addFlags); @@ -351,6 +352,10 @@ static void loadEntities(cJSON *node) case ET_JUMPGATE: e = spawnJumpgate(); break; + + case ET_MINE: + e = spawnMine(); + break; default: printf("Error: Unhandled entity type: %s\n", cJSON_GetObjectItem(node, "type")->valuestring); @@ -384,6 +389,8 @@ static void loadEntities(cJSON *node) e->x = x; e->y = y; + + e->side = side; if (scatter > 1) { @@ -392,8 +399,6 @@ static void loadEntities(cJSON *node) } e->active = active; - - SDL_QueryTexture(e->texture, NULL, NULL, &e->w, &e->h); } node = node->next; diff --git a/src/galaxy/mission.h b/src/galaxy/mission.h index 420bafd..aa13bd7 100644 --- a/src/galaxy/mission.h +++ b/src/galaxy/mission.h @@ -57,6 +57,7 @@ extern void loadFighters(cJSON *node); extern void loadItems(cJSON *node); extern void loadLocations(cJSON *node); extern void loadSpawners(cJSON *node); +extern Entity *spawnMine(void); extern Battle battle; extern Dev dev; diff --git a/src/plat/win32/win32Init.h b/src/plat/win32/win32Init.h index 8e12bef..565d1b6 100644 --- a/src/plat/win32/win32Init.h +++ b/src/plat/win32/win32Init.h @@ -25,3 +25,4 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "../../common.h" extern App app; +extern Dev dev; diff --git a/src/structs.h b/src/structs.h index f3cbbe2..aa65214 100644 --- a/src/structs.h +++ b/src/structs.h @@ -338,7 +338,7 @@ typedef struct { int ecmTimer; int radarRange; int numPlayerGuns; - int numObjectivesComplete, numObjectivesTotal; + int numObjectivesComplete, numObjectivesTotal, numConditions; Entity *missionTarget; Entity *jumpgate; SDL_Texture *background, *planetTexture; diff --git a/src/system/i18n.c b/src/system/i18n.c index 7283929..5328afa 100644 --- a/src/system/i18n.c +++ b/src/system/i18n.c @@ -54,9 +54,9 @@ void setLanguage(char *applicationName, char *languageCode) if (c[0] != '\0') { - strncat(language, "_", MAX_MESSAGE_LENGTH - strlen(language) - 1); + strncat(language, "_", MAX_DESCRIPTION_LENGTH - strlen(language) - 1); - strncat(language, c, MAX_MESSAGE_LENGTH - strlen(language) - 1); + strncat(language, c, MAX_DESCRIPTION_LENGTH - strlen(language) - 1); } } #else diff --git a/src/system/i18n.h b/src/system/i18n.h index a3263e1..d73d04f 100644 --- a/src/system/i18n.h +++ b/src/system/i18n.h @@ -19,6 +19,10 @@ Foundation, 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. #include "../common.h" +#ifdef _WIN32 + #include +#endif + #define TABLE_SIZE 255 char *getTranslatedString(char *); diff --git a/src/system/lookup.c b/src/system/lookup.c index d75351b..bf6fe7c 100644 --- a/src/system/lookup.c +++ b/src/system/lookup.c @@ -45,6 +45,7 @@ void initLookups(void) addLookup("ET_WAYPOINT", ET_WAYPOINT); addLookup("ET_JUMPGATE", ET_JUMPGATE); addLookup("ET_CAPITAL_SHIP", ET_CAPITAL_SHIP); + addLookup("ET_MINE", ET_MINE); addLookup("EF_NO_KILL", EF_NO_KILL); addLookup("EF_DISABLED", EF_DISABLED); @@ -79,6 +80,8 @@ void initLookups(void) addLookup("AIF_MOVES_TO_LEADER", AIF_MOVES_TO_LEADER); addLookup("AIF_WANDERS", AIF_WANDERS); addLookup("AIF_COVERS_RETREAT", AIF_COVERS_RETREAT); + addLookup("AIF_TARGET_FOCUS", AIF_TARGET_FOCUS); + addLookup("AIF_DROPS_MINES", AIF_DROPS_MINES); addLookup("DT_ANY", DT_ANY); addLookup("DT_NO_SPIN", DT_NO_SPIN); diff --git a/src/system/sound.c b/src/system/sound.c index 81d78f1..f540c21 100644 --- a/src/system/sound.c +++ b/src/system/sound.c @@ -112,12 +112,14 @@ static void loadSounds(void) sounds[SND_EXPLOSION_2] = loadSound("sound/207322__animationisaac__short-explosion.ogg"); sounds[SND_EXPLOSION_3] = loadSound("sound/254071__tb0y298__firework-explosion.ogg"); sounds[SND_EXPLOSION_4] = loadSound("sound/47252__nthompson__bad-explosion.ogg"); + sounds[SND_EXPLOSION_5] = loadSound("sound/172870__escortmarius__carbidexplosion.ogg"); sounds[SND_JUMP] = loadSound("sound/276912__pauldihor__transform.ogg"); sounds[SND_ECM] = loadSound("sound/251431__onlytheghosts__fusion-gun-flash0-by-onlytheghosts.ogg"); sounds[SND_MAG_HIT] = loadSound("sound/172591__timbre__zapitydooda.ogg"); sounds[SND_POWER_DOWN] = loadSound("sound/39030__wildweasel__d1clsstf.ogg"); sounds[SND_SELECT_WEAPON] = loadSound("sound/329359__bassoonrckr__reed-guillotine.ogg"); sounds[SND_TROPHY] = loadSound("sound/278142__ricemaster__effect-notify.ogg"); + sounds[SND_MINE_WARNING] = loadSound("sound/254174__kwahmah-02__s.ogg"); sounds[SND_GUI_CLICK] = loadSound("sound/257786__xtrgamr__mouse-click.ogg"); sounds[SND_GUI_SELECT] = loadSound("sound/321104__nsstudios__blip2.ogg");