3272 lines
84 KiB
C++
3272 lines
84 KiB
C++
/*
|
|
This file is part of Warzone 2100.
|
|
Copyright (C) 1999-2004 Eidos Interactive
|
|
Copyright (C) 2005-2013 Warzone 2100 Project
|
|
|
|
Warzone 2100 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.
|
|
|
|
Warzone 2100 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 Warzone 2100; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
/**
|
|
* @file mission.c
|
|
*
|
|
* All the stuff relevant to a mission.
|
|
*/
|
|
#include <time.h>
|
|
|
|
#include "mission.h"
|
|
|
|
#include "lib/framework/frame.h"
|
|
#include "lib/framework/wzapp.h"
|
|
#include "lib/framework/math_ext.h"
|
|
#include "lib/ivis_opengl/bitimage.h"
|
|
#include "lib/ivis_opengl/pieblitfunc.h"
|
|
#include "lib/gamelib/gtime.h"
|
|
#include "lib/script/script.h"
|
|
#include "lib/sound/audio.h"
|
|
#include "lib/sound/audio_id.h"
|
|
#include "lib/sound/cdaudio.h"
|
|
#include "lib/widget/label.h"
|
|
#include "lib/widget/widget.h"
|
|
|
|
#include "game.h"
|
|
#include "challenge.h"
|
|
#include "projectile.h"
|
|
#include "power.h"
|
|
#include "structure.h"
|
|
#include "message.h"
|
|
#include "research.h"
|
|
#include "hci.h"
|
|
#include "order.h"
|
|
#include "action.h"
|
|
#include "display3d.h"
|
|
#include "effects.h"
|
|
#include "radar.h"
|
|
#include "transporter.h"
|
|
#include "group.h"
|
|
#include "frontend.h" // for displaytextoption.
|
|
#include "intdisplay.h"
|
|
#include "main.h"
|
|
#include "display.h"
|
|
#include "loadsave.h"
|
|
#include "scripttabs.h"
|
|
#include "cmddroid.h"
|
|
#include "warcam.h"
|
|
#include "wrappers.h"
|
|
#include "console.h"
|
|
#include "droid.h"
|
|
#include "data.h"
|
|
#include "multiplay.h"
|
|
#include "loop.h"
|
|
#include "visibility.h"
|
|
#include "mapgrid.h"
|
|
#include "cluster.h"
|
|
#include "gateway.h"
|
|
#include "selection.h"
|
|
#include "scores.h"
|
|
#include "keymap.h"
|
|
#include "texture.h"
|
|
#include "warzoneconfig.h"
|
|
#include "combat.h"
|
|
#include "qtscript.h"
|
|
|
|
#define IDMISSIONRES_TXT 11004
|
|
#define IDMISSIONRES_LOAD 11005
|
|
#define IDMISSIONRES_CONTINUE 11008
|
|
#define IDMISSIONRES_BACKFORM 11013
|
|
#define IDMISSIONRES_TITLE 11014
|
|
|
|
/* Mission timer label position */
|
|
#define TIMER_LABELX 15
|
|
#define TIMER_LABELY 0
|
|
|
|
/* Transporter Timer form position */
|
|
#define TRAN_FORM_X STAT_X
|
|
#define TRAN_FORM_Y TIMER_Y
|
|
|
|
/* Transporter Timer position */
|
|
#define TRAN_TIMER_X 4
|
|
#define TRAN_TIMER_Y TIMER_LABELY
|
|
#define TRAN_TIMER_WIDTH 25
|
|
|
|
#define MISSION_1_X 5 // pos of text options in box.
|
|
#define MISSION_1_Y 15
|
|
#define MISSION_2_X 5
|
|
#define MISSION_2_Y 35
|
|
#define MISSION_3_X 5
|
|
#define MISSION_3_Y 55
|
|
|
|
#define MISSION_TEXT_W MISSIONRES_W-10
|
|
#define MISSION_TEXT_H 16
|
|
|
|
// These are used for mission countdown
|
|
#define TEN_MINUTES (10 * 60 * GAME_TICKS_PER_SEC)
|
|
#define FIVE_MINUTES (5 * 60 * GAME_TICKS_PER_SEC)
|
|
#define FOUR_MINUTES (4 * 60 * GAME_TICKS_PER_SEC)
|
|
#define THREE_MINUTES (3 * 60 * GAME_TICKS_PER_SEC)
|
|
#define TWO_MINUTES (2 * 60 * GAME_TICKS_PER_SEC)
|
|
#define ONE_MINUTE (1 * 60 * GAME_TICKS_PER_SEC)
|
|
#define NOT_PLAYED_ONE 0x01
|
|
#define NOT_PLAYED_TWO 0x02
|
|
#define NOT_PLAYED_THREE 0x04
|
|
#define NOT_PLAYED_FIVE 0x08
|
|
#define NOT_PLAYED_TEN 0x10
|
|
#define NOT_PLAYED_ACTIVATED 0x20
|
|
|
|
MISSION mission;
|
|
|
|
bool offWorldKeepLists;
|
|
|
|
/*lists of droids that are held seperate over several missions. There should
|
|
only be selectedPlayer's droids but have possibility for MAX_PLAYERS -
|
|
also saves writing out list functions to cater for just one player*/
|
|
DROID *apsLimboDroids[MAX_PLAYERS];
|
|
|
|
//Where the Transporter lands for player 0 (sLandingZone[0]), and the rest are
|
|
//a list of areas that cannot be built on, used for landing the enemy transporters
|
|
static LANDING_ZONE sLandingZone[MAX_NOGO_AREAS];
|
|
|
|
//flag to indicate when the droids in a Transporter are flown to safety and not the next mission
|
|
static bool bDroidsToSafety;
|
|
|
|
// return positions for vtols
|
|
Vector2i asVTOLReturnPos[MAX_PLAYERS];
|
|
|
|
static UBYTE missionCountDown;
|
|
//flag to indicate whether the coded mission countdown is played
|
|
static UBYTE bPlayCountDown;
|
|
|
|
//FUNCTIONS**************
|
|
static void addLandingLights(UDWORD x, UDWORD y);
|
|
static bool startMissionOffClear(char *pGame);
|
|
static bool startMissionOffKeep(char *pGame);
|
|
static bool startMissionCampaignStart(char *pGame);
|
|
static bool startMissionCampaignChange(char *pGame);
|
|
static bool startMissionCampaignExpand(char *pGame);
|
|
static bool startMissionCampaignExpandLimbo(char *pGame);
|
|
static bool startMissionBetween(void);
|
|
static void endMissionCamChange(void);
|
|
static void endMissionOffClear(void);
|
|
static void endMissionOffKeep(void);
|
|
static void endMissionOffKeepLimbo(void);
|
|
static void endMissionExpandLimbo(void);
|
|
|
|
static void saveMissionData(void);
|
|
static void restoreMissionData(void);
|
|
static void saveCampaignData(void);
|
|
static void missionResetDroids(void);
|
|
static void saveMissionLimboData(void);
|
|
static void restoreMissionLimboData(void);
|
|
static void processMissionLimbo(void);
|
|
|
|
static void intUpdateMissionTimer(WIDGET *psWidget, W_CONTEXT *psContext);
|
|
static bool intAddMissionTimer(void);
|
|
static void intUpdateTransporterTimer(WIDGET *psWidget, W_CONTEXT *psContext);
|
|
static void adjustMissionPower(void);
|
|
static void saveMissionPower(void);
|
|
static UDWORD getHomeLandingX(void);
|
|
static UDWORD getHomeLandingY(void);
|
|
static void processPreviousCampDroids(void);
|
|
static bool intAddTransporterTimer(void);
|
|
static void clearCampaignUnits(void);
|
|
static void emptyTransporters(bool bOffWorld);
|
|
|
|
bool MissionResUp = false;
|
|
|
|
static SDWORD g_iReinforceTime = 0;
|
|
|
|
/* Which campaign are we dealing with? */
|
|
static UDWORD camNumber = 1;
|
|
|
|
|
|
//returns true if on an off world mission
|
|
bool missionIsOffworld(void)
|
|
{
|
|
return ((mission.type == LDS_MKEEP)
|
|
|| (mission.type == LDS_MCLEAR)
|
|
|| (mission.type == LDS_MKEEP_LIMBO)
|
|
);
|
|
}
|
|
|
|
//returns true if the correct type of mission for reinforcements
|
|
bool missionForReInforcements(void)
|
|
{
|
|
if (mission.type == LDS_CAMSTART || missionIsOffworld() || mission.type == LDS_CAMCHANGE)
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//returns true if the correct type of mission and a reinforcement time has been set
|
|
bool missionCanReEnforce(void)
|
|
{
|
|
if (mission.ETA >= 0)
|
|
{
|
|
if (missionForReInforcements())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//returns true if the mission is a Limbo Expand mission
|
|
bool missionLimboExpand(void)
|
|
{
|
|
return (mission.type == LDS_EXPAND_LIMBO);
|
|
}
|
|
|
|
// mission initialisation game code
|
|
void initMission(void)
|
|
{
|
|
debug(LOG_SAVE, "*** Init Mission ***");
|
|
mission.type = LDS_NONE;
|
|
for (int inc = 0; inc < MAX_PLAYERS; inc++)
|
|
{
|
|
mission.apsStructLists[inc] = NULL;
|
|
mission.apsDroidLists[inc] = NULL;
|
|
mission.apsFeatureLists[inc] = NULL;
|
|
mission.apsFlagPosLists[inc] = NULL;
|
|
mission.apsExtractorLists[inc] = NULL;
|
|
apsLimboDroids[inc] = NULL;
|
|
}
|
|
mission.apsSensorList[0] = NULL;
|
|
mission.apsOilList[0] = NULL;
|
|
offWorldKeepLists = false;
|
|
mission.time = -1;
|
|
setMissionCountDown();
|
|
|
|
mission.ETA = -1;
|
|
mission.startTime = 0;
|
|
mission.psGateways = NULL;
|
|
mission.mapHeight = 0;
|
|
mission.mapWidth = 0;
|
|
for (int i = 0; i < ARRAY_SIZE(mission.psBlockMap); ++i)
|
|
{
|
|
mission.psBlockMap[i] = NULL;
|
|
}
|
|
for (int i = 0; i < ARRAY_SIZE(mission.psAuxMap); ++i)
|
|
{
|
|
mission.psAuxMap[i] = NULL;
|
|
}
|
|
|
|
//init all the landing zones
|
|
for (int inc = 0; inc < MAX_NOGO_AREAS; inc++)
|
|
{
|
|
sLandingZone[inc].x1 = sLandingZone[inc].y1 = sLandingZone[inc].x2 = sLandingZone[inc].y2 = 0;
|
|
}
|
|
|
|
// init the vtol return pos
|
|
memset(asVTOLReturnPos, 0, sizeof(Vector2i) * MAX_PLAYERS);
|
|
|
|
bDroidsToSafety = false;
|
|
setPlayCountDown(true);
|
|
|
|
//start as not cheating!
|
|
mission.cheatTime = 0;
|
|
}
|
|
|
|
// reset the vtol landing pos
|
|
void resetVTOLLandingPos(void)
|
|
{
|
|
memset(asVTOLReturnPos, 0, sizeof(Vector2i)*MAX_PLAYERS);
|
|
}
|
|
|
|
//this is called everytime the game is quit
|
|
void releaseMission(void)
|
|
{
|
|
/* mission.apsDroidLists may contain some droids that have been transferred from one campaign to the next */
|
|
freeAllMissionDroids();
|
|
|
|
/* apsLimboDroids may contain some droids that have been saved at the end of one mission and not yet used */
|
|
freeAllLimboDroids();
|
|
}
|
|
|
|
//called to shut down when mid-mission on an offWorld map
|
|
bool missionShutDown(void)
|
|
{
|
|
debug(LOG_SAVE, "called, mission is %s", missionIsOffworld() ? "off-world" : "main map");
|
|
if (missionIsOffworld())
|
|
{
|
|
//clear out the audio
|
|
audio_StopAll();
|
|
|
|
freeAllDroids();
|
|
freeAllStructs();
|
|
freeAllFeatures();
|
|
releaseAllProxDisp();
|
|
gwShutDown();
|
|
|
|
for (int inc = 0; inc < MAX_PLAYERS; inc++)
|
|
{
|
|
apsDroidLists[inc] = mission.apsDroidLists[inc];
|
|
mission.apsDroidLists[inc] = NULL;
|
|
apsStructLists[inc] = mission.apsStructLists[inc];
|
|
mission.apsStructLists[inc] = NULL;
|
|
apsFeatureLists[inc] = mission.apsFeatureLists[inc];
|
|
mission.apsFeatureLists[inc] = NULL;
|
|
apsFlagPosLists[inc] = mission.apsFlagPosLists[inc];
|
|
mission.apsFlagPosLists[inc] = NULL;
|
|
apsExtractorLists[inc] = mission.apsExtractorLists[inc];
|
|
mission.apsExtractorLists[inc] = NULL;
|
|
}
|
|
apsSensorList[0] = mission.apsSensorList[0];
|
|
apsOilList[0] = mission.apsOilList[0];
|
|
mission.apsSensorList[0] = NULL;
|
|
mission.apsOilList[0] = NULL;
|
|
|
|
psMapTiles = mission.psMapTiles;
|
|
mapWidth = mission.mapWidth;
|
|
mapHeight = mission.mapHeight;
|
|
for (int i = 0; i < ARRAY_SIZE(mission.psBlockMap); ++i)
|
|
{
|
|
free(psBlockMap[i]);
|
|
psBlockMap[i] = mission.psBlockMap[i];
|
|
mission.psBlockMap[i] = NULL;
|
|
}
|
|
for (int i = 0; i < ARRAY_SIZE(mission.psAuxMap); ++i)
|
|
{
|
|
free(psAuxMap[i]);
|
|
psAuxMap[i] = mission.psAuxMap[i];
|
|
mission.psAuxMap[i] = NULL;
|
|
}
|
|
gwSetGateways(mission.psGateways);
|
|
}
|
|
|
|
// sorry if this breaks something - but it looks like it's what should happen - John
|
|
mission.type = LDS_NONE;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/*on the PC - sets the countdown played flag*/
|
|
void setMissionCountDown(void)
|
|
{
|
|
SDWORD timeRemaining;
|
|
|
|
timeRemaining = mission.time - (gameTime - mission.startTime);
|
|
if (timeRemaining < 0)
|
|
{
|
|
timeRemaining = 0;
|
|
}
|
|
|
|
// Need to init the countdown played each time the mission time is changed
|
|
missionCountDown = NOT_PLAYED_ONE | NOT_PLAYED_TWO | NOT_PLAYED_THREE | NOT_PLAYED_FIVE | NOT_PLAYED_TEN | NOT_PLAYED_ACTIVATED;
|
|
|
|
if (timeRemaining < TEN_MINUTES - 1)
|
|
{
|
|
missionCountDown &= ~NOT_PLAYED_TEN;
|
|
}
|
|
if (timeRemaining < FIVE_MINUTES - 1)
|
|
{
|
|
missionCountDown &= ~NOT_PLAYED_FIVE;
|
|
}
|
|
if (timeRemaining < THREE_MINUTES - 1)
|
|
{
|
|
missionCountDown &= ~NOT_PLAYED_THREE;
|
|
}
|
|
if (timeRemaining < TWO_MINUTES - 1)
|
|
{
|
|
missionCountDown &= ~NOT_PLAYED_TWO;
|
|
}
|
|
if (timeRemaining < ONE_MINUTE - 1)
|
|
{
|
|
missionCountDown &= ~NOT_PLAYED_ONE;
|
|
}
|
|
}
|
|
|
|
|
|
bool startMission(LEVEL_TYPE missionType, char *pGame)
|
|
{
|
|
bool loaded = true;
|
|
|
|
debug(LOG_SAVE, "type %d", (int)missionType);
|
|
|
|
/* Player has (obviously) not failed at the start */
|
|
setPlayerHasLost(false);
|
|
setPlayerHasWon(false);
|
|
|
|
/* and win/lose video is obvioulsy not playing */
|
|
setScriptWinLoseVideo(PLAY_NONE);
|
|
|
|
// this inits the flag so that 'reinforcements have arrived' message isn't played for the first transporter load
|
|
initFirstTransporterFlag();
|
|
|
|
if (mission.type != LDS_NONE)
|
|
{
|
|
/*mission type gets set to none when you have returned from a mission
|
|
so don't want to go another mission when already on one! - so ignore*/
|
|
debug(LOG_SAVE, "Already on a mission");
|
|
return true;
|
|
}
|
|
|
|
// reset the cluster stuff
|
|
clustInitialise();
|
|
initEffectsSystem();
|
|
|
|
//load the game file for all types of mission except a Between Mission
|
|
if (missionType != LDS_BETWEEN)
|
|
{
|
|
loadGameInit(pGame);
|
|
}
|
|
|
|
//all proximity messages are removed between missions now
|
|
releaseAllProxDisp();
|
|
|
|
switch (missionType)
|
|
{
|
|
case LDS_CAMSTART:
|
|
if (!startMissionCampaignStart(pGame))
|
|
{
|
|
loaded = false;
|
|
}
|
|
break;
|
|
case LDS_MKEEP:
|
|
case LDS_MKEEP_LIMBO:
|
|
if (!startMissionOffKeep(pGame))
|
|
{
|
|
loaded = false;
|
|
}
|
|
break;
|
|
case LDS_BETWEEN:
|
|
//do anything?
|
|
if (!startMissionBetween())
|
|
{
|
|
loaded = false;
|
|
}
|
|
break;
|
|
case LDS_CAMCHANGE:
|
|
if (!startMissionCampaignChange(pGame))
|
|
{
|
|
loaded = false;
|
|
}
|
|
break;
|
|
case LDS_EXPAND:
|
|
if (!startMissionCampaignExpand(pGame))
|
|
{
|
|
loaded = false;
|
|
}
|
|
break;
|
|
case LDS_EXPAND_LIMBO:
|
|
if (!startMissionCampaignExpandLimbo(pGame))
|
|
{
|
|
loaded = false;
|
|
}
|
|
break;
|
|
case LDS_MCLEAR:
|
|
if (!startMissionOffClear(pGame))
|
|
{
|
|
loaded = false;
|
|
}
|
|
break;
|
|
default:
|
|
//error!
|
|
debug(LOG_ERROR, "Unknown Mission Type");
|
|
|
|
loaded = false;
|
|
}
|
|
|
|
if (!loaded)
|
|
{
|
|
debug(LOG_ERROR, "Failed to start mission, missiontype = %d, game, %s", (int)missionType, pGame);
|
|
return false;
|
|
}
|
|
|
|
mission.type = missionType;
|
|
|
|
if (missionIsOffworld())
|
|
{
|
|
//add what power have got from the home base
|
|
adjustMissionPower();
|
|
}
|
|
|
|
if (missionCanReEnforce())
|
|
{
|
|
//add mission timer - if necessary
|
|
addMissionTimerInterface();
|
|
|
|
//add Transporter Timer
|
|
addTransporterTimerInterface();
|
|
}
|
|
|
|
scoreInitSystem();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// initialise the mission stuff for a save game
|
|
bool startMissionSave(SDWORD missionType)
|
|
{
|
|
mission.type = missionType;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/*checks the time has been set and then adds the timer if not already on
|
|
the display*/
|
|
void addMissionTimerInterface(void)
|
|
{
|
|
//don't add if the timer hasn't been set
|
|
if (mission.time < 0 && !challengeActive)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//check timer is not already on the screen
|
|
if (!widgGetFromID(psWScreen, IDTIMER_FORM))
|
|
{
|
|
intAddMissionTimer();
|
|
}
|
|
}
|
|
|
|
/*checks that the timer has been set and that a Transporter exists before
|
|
adding the timer button*/
|
|
void addTransporterTimerInterface(void)
|
|
{
|
|
DROID *psTransporter = NULL;
|
|
bool bAddInterface = false;
|
|
W_CLICKFORM *psForm;
|
|
|
|
//check if reinforcements are allowed
|
|
if (mission.ETA >= 0)
|
|
{
|
|
//check the player has at least one Transporter back at base
|
|
for (DROID *psDroid = mission.apsDroidLists[selectedPlayer]; psDroid != NULL; psDroid = psDroid->psNext)
|
|
{
|
|
if (psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER)
|
|
{
|
|
psTransporter = psDroid;
|
|
break;
|
|
}
|
|
}
|
|
if (psTransporter)
|
|
{
|
|
bAddInterface = true;
|
|
|
|
//check timer is not already on the screen
|
|
if (!widgGetFromID(psWScreen, IDTRANTIMER_BUTTON))
|
|
{
|
|
intAddTransporterTimer();
|
|
}
|
|
|
|
//set the data for the transporter timer
|
|
widgSetUserData(psWScreen, IDTRANTIMER_DISPLAY, (void *)psTransporter);
|
|
|
|
// lock the button if necessary
|
|
if (transporterFlying(psTransporter))
|
|
{
|
|
// disable the form so can't add any more droids into the transporter
|
|
psForm = (W_CLICKFORM *)widgGetFromID(psWScreen, IDTRANTIMER_BUTTON);
|
|
if (psForm)
|
|
{
|
|
psForm->setState(WBUT_LOCK);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// if criteria not met
|
|
if (!bAddInterface)
|
|
{
|
|
// make sure its not there!
|
|
intRemoveTransporterTimer();
|
|
}
|
|
}
|
|
|
|
#define OFFSCREEN_HEIGHT 600
|
|
#define EDGE_SIZE 1
|
|
|
|
/* pick nearest map edge to point */
|
|
void missionGetNearestCorner(UWORD iX, UWORD iY, UWORD *piOffX, UWORD *piOffY)
|
|
{
|
|
UDWORD iMidX = (scrollMinX + scrollMaxX) / 2,
|
|
iMidY = (scrollMinY + scrollMaxY) / 2;
|
|
|
|
if (map_coord(iX) < iMidX)
|
|
{
|
|
*piOffX = (UWORD)(world_coord(scrollMinX) + (EDGE_SIZE * TILE_UNITS));
|
|
}
|
|
else
|
|
{
|
|
*piOffX = (UWORD)(world_coord(scrollMaxX) - (EDGE_SIZE * TILE_UNITS));
|
|
}
|
|
|
|
if (map_coord(iY) < iMidY)
|
|
{
|
|
*piOffY = (UWORD)(world_coord(scrollMinY) + (EDGE_SIZE * TILE_UNITS));
|
|
}
|
|
else
|
|
{
|
|
*piOffY = (UWORD)(world_coord(scrollMaxY) - (EDGE_SIZE * TILE_UNITS));
|
|
}
|
|
}
|
|
|
|
/* fly in transporters at start of level */
|
|
void missionFlyTransportersIn(SDWORD iPlayer, bool bTrackTransporter)
|
|
{
|
|
DROID *psTransporter, *psNext;
|
|
UWORD iX, iY, iZ;
|
|
SDWORD iLandX, iLandY, iDx, iDy;
|
|
|
|
ASSERT_OR_RETURN(, iPlayer < MAX_PLAYERS, "Flying nonexistent player %d's transporters in", iPlayer);
|
|
|
|
iLandX = getLandingX(iPlayer);
|
|
iLandY = getLandingY(iPlayer);
|
|
missionGetTransporterEntry(iPlayer, &iX, &iY);
|
|
iZ = (UWORD)(map_Height(iX, iY) + OFFSCREEN_HEIGHT);
|
|
|
|
psNext = NULL;
|
|
//get the droids for the mission
|
|
for (psTransporter = mission.apsDroidLists[iPlayer]; psTransporter != NULL; psTransporter = psNext)
|
|
{
|
|
psNext = psTransporter->psNext;
|
|
// FIXME: When we convert campaign scripts to use DROID_SUPERTRANSPORTER
|
|
if (psTransporter->droidType == DROID_TRANSPORTER)
|
|
{
|
|
// Check that this transporter actually contains some droids
|
|
if (psTransporter->psGroup && psTransporter->psGroup->refCount > 1)
|
|
{
|
|
// Remove map information from previous map
|
|
free(psTransporter->watchedTiles);
|
|
psTransporter->watchedTiles = NULL;
|
|
psTransporter->numWatchedTiles = 0;
|
|
|
|
// Remove out of stored list and add to current Droid list
|
|
if (droidRemove(psTransporter, mission.apsDroidLists))
|
|
{
|
|
// Do not want to add it unless managed to remove it from the previous list
|
|
addDroid(psTransporter, apsDroidLists);
|
|
}
|
|
|
|
/* set start position */
|
|
psTransporter->pos.x = iX;
|
|
psTransporter->pos.y = iY;
|
|
psTransporter->pos.z = iZ;
|
|
|
|
/* set start direction */
|
|
iDx = iLandX - iX;
|
|
iDy = iLandY - iY;
|
|
|
|
psTransporter->rot.direction = iAtan2(iDx, iDy);
|
|
|
|
// Camera track requested and it's the selected player.
|
|
if ((bTrackTransporter == true) && (iPlayer == (SDWORD)selectedPlayer))
|
|
{
|
|
/* deselect all droids */
|
|
selDroidDeselect(selectedPlayer);
|
|
|
|
if (getWarCamStatus())
|
|
{
|
|
camToggleStatus();
|
|
}
|
|
|
|
/* select transporter */
|
|
psTransporter->selected = true;
|
|
camToggleStatus();
|
|
}
|
|
|
|
//little hack to ensure all Transporters are fully repaired by time enter world
|
|
psTransporter->body = psTransporter->originalBody;
|
|
|
|
/* set fly-in order */
|
|
orderDroidLoc(psTransporter, DORDER_TRANSPORTIN, iLandX, iLandY, ModeImmediate);
|
|
|
|
audio_PlayObjDynamicTrack(psTransporter, ID_SOUND_BLIMP_FLIGHT, moveCheckDroidMovingAndVisible);
|
|
|
|
//only want to fly one transporter in at a time now - AB 14/01/99
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Saves the necessary data when moving from a home base Mission to an OffWorld mission */
|
|
static void saveMissionData(void)
|
|
{
|
|
UDWORD inc;
|
|
DROID *psDroid;
|
|
STRUCTURE *psStruct, *psStructBeingBuilt;
|
|
bool bRepairExists;
|
|
|
|
debug(LOG_SAVE, "called");
|
|
|
|
//clear out the audio
|
|
audio_StopAll();
|
|
|
|
//save the mission data
|
|
mission.psMapTiles = psMapTiles;
|
|
mission.mapWidth = mapWidth;
|
|
mission.mapHeight = mapHeight;
|
|
for (int i = 0; i < ARRAY_SIZE(mission.psBlockMap); ++i)
|
|
{
|
|
mission.psBlockMap[i] = psBlockMap[i];
|
|
}
|
|
for (int i = 0; i < ARRAY_SIZE(mission.psAuxMap); ++i)
|
|
{
|
|
mission.psAuxMap[i] = psAuxMap[i];
|
|
}
|
|
mission.scrollMinX = scrollMinX;
|
|
mission.scrollMinY = scrollMinY;
|
|
mission.scrollMaxX = scrollMaxX;
|
|
mission.scrollMaxY = scrollMaxY;
|
|
mission.psGateways = gwGetGateways();
|
|
gwSetGateways(NULL);
|
|
// save the selectedPlayer's LZ
|
|
mission.homeLZ_X = getLandingX(selectedPlayer);
|
|
mission.homeLZ_Y = getLandingY(selectedPlayer);
|
|
|
|
bRepairExists = false;
|
|
//set any structures currently being built to completed for the selected player
|
|
for (psStruct = apsStructLists[selectedPlayer]; psStruct; psStruct = psStruct->psNext)
|
|
{
|
|
if (psStruct->status == SS_BEING_BUILT)
|
|
{
|
|
//find a droid working on it
|
|
for (psDroid = apsDroidLists[selectedPlayer]; psDroid != NULL; psDroid = psDroid->psNext)
|
|
{
|
|
if ((psStructBeingBuilt = (STRUCTURE *)orderStateObj(psDroid, DORDER_BUILD))
|
|
&& psStructBeingBuilt == psStruct)
|
|
{
|
|
// just give it all its build points
|
|
structureBuild(psStruct, NULL, psStruct->pStructureType->buildPoints);
|
|
//don't bother looking for any other droids working on it
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
//check if have a completed repair facility on home world
|
|
if (psStruct->pStructureType->type == REF_REPAIR_FACILITY && psStruct->status == SS_BUILT)
|
|
{
|
|
bRepairExists = true;
|
|
}
|
|
}
|
|
|
|
//repair all droids back at home base if have a repair facility
|
|
if (bRepairExists)
|
|
{
|
|
for (psDroid = apsDroidLists[selectedPlayer]; psDroid != NULL; psDroid = psDroid->psNext)
|
|
{
|
|
if (droidIsDamaged(psDroid))
|
|
{
|
|
psDroid->body = psDroid->originalBody;
|
|
}
|
|
}
|
|
}
|
|
|
|
//clear droid orders for all droids except constructors still building
|
|
for (psDroid = apsDroidLists[selectedPlayer]; psDroid != NULL; psDroid = psDroid->psNext)
|
|
{
|
|
if ((psStructBeingBuilt = (STRUCTURE *)orderStateObj(psDroid, DORDER_BUILD)))
|
|
{
|
|
if (psStructBeingBuilt->status == SS_BUILT)
|
|
{
|
|
orderDroid(psDroid, DORDER_STOP, ModeImmediate);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
orderDroid(psDroid, DORDER_STOP, ModeImmediate);
|
|
}
|
|
}
|
|
|
|
for (inc = 0; inc < MAX_PLAYERS; inc++)
|
|
{
|
|
mission.apsStructLists[inc] = apsStructLists[inc];
|
|
mission.apsDroidLists[inc] = apsDroidLists[inc];
|
|
mission.apsFeatureLists[inc] = apsFeatureLists[inc];
|
|
mission.apsFlagPosLists[inc] = apsFlagPosLists[inc];
|
|
mission.apsExtractorLists[inc] = apsExtractorLists[inc];
|
|
}
|
|
mission.apsSensorList[0] = apsSensorList[0];
|
|
mission.apsOilList[0] = apsOilList[0];
|
|
|
|
mission.playerX = player.p.x;
|
|
mission.playerY = player.p.z;
|
|
|
|
//save the power settings
|
|
saveMissionPower();
|
|
|
|
//init before loading in the new game
|
|
initFactoryNumFlag();
|
|
|
|
//clear all the effects from the map
|
|
initEffectsSystem();
|
|
|
|
resizeRadar();
|
|
}
|
|
|
|
/*
|
|
This routine frees the memory for the offworld mission map (in the call to mapShutdown)
|
|
|
|
- so when this routine is called we must still be set to the offworld map data
|
|
i.e. We shoudn't have called SwapMissionPointers()
|
|
|
|
*/
|
|
void restoreMissionData(void)
|
|
{
|
|
UDWORD inc;
|
|
BASE_OBJECT *psObj;
|
|
|
|
debug(LOG_SAVE, "called");
|
|
|
|
//clear out the audio
|
|
audio_StopAll();
|
|
|
|
//clear all the lists
|
|
proj_FreeAllProjectiles();
|
|
freeAllDroids();
|
|
freeAllStructs();
|
|
freeAllFeatures();
|
|
gwShutDown();
|
|
if (game.type != CAMPAIGN)
|
|
{
|
|
ASSERT(false, "game type isn't campaign, but we are in a campaign game!");
|
|
game.type = CAMPAIGN; // fix the issue, since it is obviously a bug
|
|
}
|
|
//restore the game pointers
|
|
for (inc = 0; inc < MAX_PLAYERS; inc++)
|
|
{
|
|
apsDroidLists[inc] = mission.apsDroidLists[inc];
|
|
mission.apsDroidLists[inc] = NULL;
|
|
for (psObj = (BASE_OBJECT *)apsDroidLists[inc]; psObj; psObj = psObj->psNext)
|
|
{
|
|
psObj->died = false; //make sure the died flag is not set
|
|
}
|
|
|
|
apsStructLists[inc] = mission.apsStructLists[inc];
|
|
mission.apsStructLists[inc] = NULL;
|
|
|
|
apsFeatureLists[inc] = mission.apsFeatureLists[inc];
|
|
mission.apsFeatureLists[inc] = NULL;
|
|
|
|
apsFlagPosLists[inc] = mission.apsFlagPosLists[inc];
|
|
mission.apsFlagPosLists[inc] = NULL;
|
|
|
|
apsExtractorLists[inc] = mission.apsExtractorLists[inc];
|
|
mission.apsExtractorLists[inc] = NULL;
|
|
}
|
|
apsSensorList[0] = mission.apsSensorList[0];
|
|
apsOilList[0] = mission.apsOilList[0];
|
|
mission.apsSensorList[0] = NULL;
|
|
//swap mission data over
|
|
|
|
psMapTiles = mission.psMapTiles;
|
|
|
|
mapWidth = mission.mapWidth;
|
|
mapHeight = mission.mapHeight;
|
|
for (int i = 0; i < ARRAY_SIZE(mission.psBlockMap); ++i)
|
|
{
|
|
psBlockMap[i] = mission.psBlockMap[i];
|
|
mission.psBlockMap[i] = NULL;
|
|
}
|
|
for (int i = 0; i < ARRAY_SIZE(mission.psAuxMap); ++i)
|
|
{
|
|
psAuxMap[i] = mission.psAuxMap[i];
|
|
mission.psAuxMap[i] = NULL;
|
|
}
|
|
scrollMinX = mission.scrollMinX;
|
|
scrollMinY = mission.scrollMinY;
|
|
scrollMaxX = mission.scrollMaxX;
|
|
scrollMaxY = mission.scrollMaxY;
|
|
gwSetGateways(mission.psGateways);
|
|
//and clear the mission pointers
|
|
mission.psMapTiles = NULL;
|
|
mission.mapWidth = 0;
|
|
mission.mapHeight = 0;
|
|
mission.scrollMinX = 0;
|
|
mission.scrollMinY = 0;
|
|
mission.scrollMaxX = 0;
|
|
mission.scrollMaxY = 0;
|
|
mission.psGateways = NULL;
|
|
|
|
//reset the current structure lists
|
|
setCurrentStructQuantity(false);
|
|
|
|
initFactoryNumFlag();
|
|
resetFactoryNumFlag();
|
|
|
|
offWorldKeepLists = false;
|
|
|
|
resizeRadar();
|
|
}
|
|
|
|
/*Saves the necessary data when moving from one mission to a limbo expand Mission*/
|
|
void saveMissionLimboData(void)
|
|
{
|
|
DROID *psDroid, *psNext;
|
|
STRUCTURE *psStruct;
|
|
|
|
debug(LOG_SAVE, "called");
|
|
|
|
//clear out the audio
|
|
audio_StopAll();
|
|
|
|
// before copy the pointers over check selectedPlayer's mission.droids since
|
|
// there might be some from the previous camapign
|
|
processPreviousCampDroids();
|
|
|
|
// move droids properly - does all the clean up code
|
|
for (psDroid = apsDroidLists[selectedPlayer]; psDroid != NULL; psDroid = psNext)
|
|
{
|
|
psNext = psDroid->psNext;
|
|
if (droidRemove(psDroid, apsDroidLists))
|
|
{
|
|
addDroid(psDroid, mission.apsDroidLists);
|
|
}
|
|
}
|
|
apsDroidLists[selectedPlayer] = NULL;
|
|
|
|
// any selectedPlayer's factories/research need to be put on holdProduction/holdresearch
|
|
for (psStruct = apsStructLists[selectedPlayer]; psStruct != NULL; psStruct = psStruct->psNext)
|
|
{
|
|
if (StructIsFactory(psStruct))
|
|
{
|
|
holdProduction(psStruct, ModeImmediate);
|
|
}
|
|
else if (psStruct->pStructureType->type == REF_RESEARCH)
|
|
{
|
|
holdResearch(psStruct, ModeImmediate);
|
|
}
|
|
}
|
|
}
|
|
|
|
//this is called via a script function to place the Limbo droids once the mission has started
|
|
void placeLimboDroids(void)
|
|
{
|
|
DROID *psDroid, *psNext;
|
|
UDWORD droidX, droidY;
|
|
PICKTILE pickRes;
|
|
|
|
debug(LOG_SAVE, "called");
|
|
|
|
// Copy the droids across for the selected Player
|
|
for (psDroid = apsLimboDroids[selectedPlayer]; psDroid != NULL; psDroid = psNext)
|
|
{
|
|
psNext = psDroid->psNext;
|
|
if (droidRemove(psDroid, apsLimboDroids))
|
|
{
|
|
addDroid(psDroid, apsDroidLists);
|
|
//KILL OFF TRANSPORTER - should never be one but....
|
|
if (psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER)
|
|
{
|
|
vanishDroid(psDroid);
|
|
continue;
|
|
}
|
|
//set up location for each of the droids
|
|
droidX = map_coord(getLandingX(LIMBO_LANDING));
|
|
droidY = map_coord(getLandingY(LIMBO_LANDING));
|
|
pickRes = pickHalfATile(&droidX, &droidY, LOOK_FOR_EMPTY_TILE);
|
|
if (pickRes == NO_FREE_TILE)
|
|
{
|
|
ASSERT(false, "placeLimboUnits: Unable to find a free location");
|
|
}
|
|
psDroid->pos.x = (UWORD)world_coord(droidX);
|
|
psDroid->pos.y = (UWORD)world_coord(droidY);
|
|
ASSERT(worldOnMap(psDroid->pos.x, psDroid->pos.y), "limbo droid is not on the map");
|
|
psDroid->pos.z = map_Height(psDroid->pos.x, psDroid->pos.y);
|
|
updateDroidOrientation(psDroid);
|
|
psDroid->selected = false;
|
|
//this is mainly for VTOLs
|
|
setDroidBase(psDroid, NULL);
|
|
psDroid->cluster = 0;
|
|
//initialise the movement data
|
|
initDroidMovement(psDroid);
|
|
//make sure the died flag is not set
|
|
psDroid->died = false;
|
|
}
|
|
else
|
|
{
|
|
ASSERT(false, "placeLimboUnits: Unable to remove unit from Limbo list");
|
|
}
|
|
}
|
|
}
|
|
|
|
/*restores the necessary data on completion of a Limbo Expand mission*/
|
|
void restoreMissionLimboData(void)
|
|
{
|
|
DROID *psDroid, *psNext;
|
|
|
|
debug(LOG_SAVE, "called");
|
|
|
|
/*the droids stored in the mission droid list need to be added back
|
|
into the current droid list*/
|
|
for (psDroid = mission.apsDroidLists[selectedPlayer]; psDroid; psDroid = psNext)
|
|
{
|
|
psNext = psDroid->psNext;
|
|
//remove out of stored list and add to current Droid list
|
|
if (droidRemove(psDroid, mission.apsDroidLists))
|
|
{
|
|
addDroid(psDroid, apsDroidLists);
|
|
psDroid->cluster = 0;
|
|
//reset droid orders
|
|
orderDroid(psDroid, DORDER_STOP, ModeImmediate);
|
|
//the location of the droid should be valid!
|
|
}
|
|
}
|
|
ASSERT(mission.apsDroidLists[selectedPlayer] == NULL, "list should be empty");
|
|
}
|
|
|
|
/*Saves the necessary data when moving from one campaign to the start of the
|
|
next - saves out the list of droids for the selected player*/
|
|
void saveCampaignData(void)
|
|
{
|
|
DROID *psDroid, *psNext, *psSafeDroid, *psNextSafe, *psCurr, *psCurrNext;
|
|
|
|
debug(LOG_SAVE, "called");
|
|
|
|
// If the droids have been moved to safety then get any Transporters that exist
|
|
if (getDroidsToSafetyFlag())
|
|
{
|
|
// Move any Transporters into the mission list
|
|
psDroid = apsDroidLists[selectedPlayer];
|
|
while (psDroid != NULL)
|
|
{
|
|
psNext = psDroid->psNext;
|
|
if (psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER)
|
|
{
|
|
// Empty the transporter into the mission list
|
|
ASSERT(psDroid->psGroup != NULL, "saveCampaignData: Transporter does not have a group");
|
|
|
|
for (psCurr = psDroid->psGroup->psList; psCurr != NULL && psCurr != psDroid; psCurr = psCurrNext)
|
|
{
|
|
psCurrNext = psCurr->psGrpNext;
|
|
// Remove it from the transporter group
|
|
psDroid->psGroup->remove(psCurr);
|
|
// Cam change add droid
|
|
psCurr->pos.x = INVALID_XY;
|
|
psCurr->pos.y = INVALID_XY;
|
|
// Add it back into current droid lists
|
|
addDroid(psCurr, mission.apsDroidLists);
|
|
}
|
|
// Remove the transporter from the current list
|
|
if (droidRemove(psDroid, apsDroidLists))
|
|
{
|
|
//cam change add droid
|
|
psDroid->pos.x = INVALID_XY;
|
|
psDroid->pos.y = INVALID_XY;
|
|
addDroid(psDroid, mission.apsDroidLists);
|
|
}
|
|
}
|
|
psDroid = psNext;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Reserve the droids for selected player for start of next campaign
|
|
mission.apsDroidLists[selectedPlayer] = apsDroidLists[selectedPlayer];
|
|
apsDroidLists[selectedPlayer] = NULL;
|
|
psDroid = mission.apsDroidLists[selectedPlayer];
|
|
while (psDroid != NULL)
|
|
{
|
|
//cam change add droid
|
|
psDroid->pos.x = INVALID_XY;
|
|
psDroid->pos.y = INVALID_XY;
|
|
psDroid = psDroid->psNext;
|
|
}
|
|
}
|
|
|
|
//if the droids have been moved to safety then get any Transporters that exist
|
|
if (getDroidsToSafetyFlag())
|
|
{
|
|
/*now that every unit for the selected player has been moved into the
|
|
mission list - reverse it and fill the transporter with the first ten units*/
|
|
reverseObjectList(&mission.apsDroidLists[selectedPlayer]);
|
|
|
|
//find the *first* transporter
|
|
for (psDroid = mission.apsDroidLists[selectedPlayer]; psDroid; psDroid = psDroid->psNext)
|
|
{
|
|
if (psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER)
|
|
{
|
|
//fill it with droids from the mission list
|
|
for (psSafeDroid = mission.apsDroidLists[selectedPlayer]; psSafeDroid; psSafeDroid = psNextSafe)
|
|
{
|
|
psNextSafe = psSafeDroid->psNext;
|
|
if (psSafeDroid != psDroid)
|
|
{
|
|
//add to the Transporter, checking for when full
|
|
if (checkTransporterSpace(psDroid, psSafeDroid))
|
|
{
|
|
if (droidRemove(psSafeDroid, mission.apsDroidLists))
|
|
{
|
|
psDroid->psGroup->add(psSafeDroid);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//setting this will cause the loop to end
|
|
psNextSafe = NULL;
|
|
}
|
|
}
|
|
}
|
|
//only want to fill one transporter
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//clear all other droids
|
|
for (int inc = 0; inc < MAX_PLAYERS; inc++)
|
|
{
|
|
psDroid = apsDroidLists[inc];
|
|
|
|
while (psDroid != NULL)
|
|
{
|
|
psNext = psDroid->psNext;
|
|
vanishDroid(psDroid);
|
|
psDroid = psNext;
|
|
}
|
|
}
|
|
|
|
//clear out the audio
|
|
audio_StopAll();
|
|
|
|
//clear all other memory
|
|
freeAllStructs();
|
|
freeAllFeatures();
|
|
}
|
|
|
|
|
|
//start an off world mission - clearing the object lists
|
|
bool startMissionOffClear(char *pGame)
|
|
{
|
|
debug(LOG_SAVE, "called for %s", pGame);
|
|
|
|
saveMissionData();
|
|
|
|
//load in the new game clearing the lists
|
|
if (!loadGame(pGame, !KEEPOBJECTS, !FREEMEM, false))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
offWorldKeepLists = false;
|
|
intResetPreviousObj();
|
|
|
|
// The message should have been played at the between stage
|
|
missionCountDown &= ~NOT_PLAYED_ACTIVATED;
|
|
|
|
return true;
|
|
}
|
|
|
|
//start an off world mission - keeping the object lists
|
|
bool startMissionOffKeep(char *pGame)
|
|
{
|
|
debug(LOG_SAVE, "called for %s", pGame);
|
|
saveMissionData();
|
|
|
|
//load in the new game clearing the lists
|
|
if (!loadGame(pGame, !KEEPOBJECTS, !FREEMEM, false))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
offWorldKeepLists = true;
|
|
intResetPreviousObj();
|
|
|
|
// The message should have been played at the between stage
|
|
missionCountDown &= ~NOT_PLAYED_ACTIVATED;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool startMissionCampaignStart(char *pGame)
|
|
{
|
|
debug(LOG_SAVE, "called for %s", pGame);
|
|
|
|
// Clear out all intelligence screen messages
|
|
freeMessages();
|
|
|
|
// Check no units left with any settings that are invalid
|
|
clearCampaignUnits();
|
|
|
|
// Load in the new game details
|
|
if (!loadGame(pGame, !KEEPOBJECTS, FREEMEM, false))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
offWorldKeepLists = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool startMissionCampaignChange(char *pGame)
|
|
{
|
|
// Clear out all intelligence screen messages
|
|
freeMessages();
|
|
|
|
// Check no units left with any settings that are invalid
|
|
clearCampaignUnits();
|
|
|
|
// Clear out the production run between campaigns
|
|
changeProductionPlayer((UBYTE)selectedPlayer);
|
|
|
|
saveCampaignData();
|
|
|
|
//load in the new game details
|
|
if (!loadGame(pGame, !KEEPOBJECTS, !FREEMEM, false))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
offWorldKeepLists = false;
|
|
intResetPreviousObj();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool startMissionCampaignExpand(char *pGame)
|
|
{
|
|
//load in the new game details
|
|
if (!loadGame(pGame, KEEPOBJECTS, !FREEMEM, false))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
offWorldKeepLists = false;
|
|
return true;
|
|
}
|
|
|
|
bool startMissionCampaignExpandLimbo(char *pGame)
|
|
{
|
|
saveMissionLimboData();
|
|
|
|
//load in the new game details
|
|
if (!loadGame(pGame, KEEPOBJECTS, !FREEMEM, false))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
offWorldKeepLists = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool startMissionBetween(void)
|
|
{
|
|
offWorldKeepLists = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
//check no units left with any settings that are invalid
|
|
static void clearCampaignUnits(void)
|
|
{
|
|
for (DROID *psDroid = apsDroidLists[selectedPlayer]; psDroid; psDroid = psDroid->psNext)
|
|
{
|
|
orderDroid(psDroid, DORDER_STOP, ModeImmediate);
|
|
setDroidBase(psDroid, NULL);
|
|
visRemoveVisibilityOffWorld((BASE_OBJECT *)psDroid);
|
|
CHECK_DROID(psDroid);
|
|
}
|
|
}
|
|
|
|
/*This deals with droids at the end of an offworld mission*/
|
|
static void processMission(void)
|
|
{
|
|
DROID *psNext;
|
|
DROID *psDroid;
|
|
UDWORD droidX, droidY;
|
|
PICKTILE pickRes;
|
|
|
|
//and the rest on the mission map - for now?
|
|
for (psDroid = apsDroidLists[selectedPlayer]; psDroid != NULL; psDroid = psNext)
|
|
{
|
|
psNext = psDroid->psNext;
|
|
//reset order - do this to all the droids that are returning from offWorld
|
|
orderDroid(psDroid, DORDER_STOP, ModeImmediate);
|
|
// clean up visibility
|
|
visRemoveVisibility((BASE_OBJECT *)psDroid);
|
|
//remove out of stored list and add to current Droid list
|
|
if (droidRemove(psDroid, apsDroidLists))
|
|
{
|
|
int x, y;
|
|
|
|
addDroid(psDroid, mission.apsDroidLists);
|
|
droidX = getHomeLandingX();
|
|
droidY = getHomeLandingY();
|
|
// Swap the droid and map pointers
|
|
swapMissionPointers();
|
|
|
|
pickRes = pickHalfATile(&droidX, &droidY, LOOK_FOR_EMPTY_TILE);
|
|
ASSERT(pickRes != NO_FREE_TILE, "processMission: Unable to find a free location");
|
|
x = (UWORD)world_coord(droidX);
|
|
y = (UWORD)world_coord(droidY);
|
|
droidSetPosition(psDroid, x, y);
|
|
ASSERT(worldOnMap(psDroid->pos.x, psDroid->pos.y), "the droid is not on the map");
|
|
updateDroidOrientation(psDroid);
|
|
// Swap the droid and map pointers back again
|
|
swapMissionPointers();
|
|
psDroid->selected = false;
|
|
// This is mainly for VTOLs
|
|
setDroidBase(psDroid, NULL);
|
|
psDroid->cluster = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#define MAXLIMBODROIDS (999)
|
|
|
|
/*This deals with droids at the end of an offworld Limbo mission*/
|
|
void processMissionLimbo(void)
|
|
{
|
|
DROID *psNext, *psDroid;
|
|
UDWORD numDroidsAddedToLimboList = 0;
|
|
|
|
//all droids (for selectedPlayer only) are placed into the limbo list
|
|
for (psDroid = apsDroidLists[selectedPlayer]; psDroid != NULL; psDroid = psNext)
|
|
{
|
|
psNext = psDroid->psNext;
|
|
//KILL OFF TRANSPORTER - should never be one but....
|
|
if (psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER)
|
|
{
|
|
vanishDroid(psDroid);
|
|
}
|
|
else
|
|
{
|
|
if (numDroidsAddedToLimboList >= MAXLIMBODROIDS) // any room in limbo list
|
|
{
|
|
vanishDroid(psDroid);
|
|
}
|
|
else
|
|
{
|
|
// Remove out of stored list and add to current Droid list
|
|
if (droidRemove(psDroid, apsDroidLists))
|
|
{
|
|
// Limbo list invalidate XY
|
|
psDroid->pos.x = INVALID_XY;
|
|
psDroid->pos.y = INVALID_XY;
|
|
addDroid(psDroid, apsLimboDroids);
|
|
// This is mainly for VTOLs
|
|
setDroidBase(psDroid, NULL);
|
|
psDroid->cluster = 0;
|
|
orderDroid(psDroid, DORDER_STOP, ModeImmediate);
|
|
numDroidsAddedToLimboList++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*switch the pointers for the map and droid lists so that droid placement
|
|
and orientation can occur on the map they will appear on*/
|
|
// NOTE: This is one huge hack for campaign games!
|
|
// Pay special attention on what is getting swapped!
|
|
void swapMissionPointers(void)
|
|
{
|
|
debug(LOG_SAVE, "called");
|
|
|
|
std::swap(psMapTiles, mission.psMapTiles);
|
|
std::swap(mapWidth, mission.mapWidth);
|
|
std::swap(mapHeight, mission.mapHeight);
|
|
for (int i = 0; i < ARRAY_SIZE(mission.psBlockMap); ++i)
|
|
{
|
|
std::swap(psBlockMap[i], mission.psBlockMap[i]);
|
|
}
|
|
for (int i = 0; i < ARRAY_SIZE(mission.psAuxMap); ++i)
|
|
{
|
|
std::swap(psAuxMap[i], mission.psAuxMap[i]);
|
|
}
|
|
//swap gateway zones
|
|
GATEWAY *gateway = gwGetGateways();
|
|
gwSetGateways(mission.psGateways);
|
|
mission.psGateways = gateway;
|
|
std::swap(scrollMinX, mission.scrollMinX);
|
|
std::swap(scrollMinY, mission.scrollMinY);
|
|
std::swap(scrollMaxX, mission.scrollMaxX);
|
|
std::swap(scrollMaxY, mission.scrollMaxY);
|
|
for (unsigned inc = 0; inc < MAX_PLAYERS; inc++)
|
|
{
|
|
std::swap(apsDroidLists[inc], mission.apsDroidLists[inc]);
|
|
std::swap(apsStructLists[inc], mission.apsStructLists[inc]);
|
|
std::swap(apsFeatureLists[inc], mission.apsFeatureLists[inc]);
|
|
std::swap(apsFlagPosLists[inc], mission.apsFlagPosLists[inc]);
|
|
std::swap(apsExtractorLists[inc], mission.apsExtractorLists[inc]);
|
|
}
|
|
std::swap(apsSensorList[0], mission.apsSensorList[0]);
|
|
std::swap(apsOilList[0], mission.apsOilList[0]);
|
|
}
|
|
|
|
void endMission(void)
|
|
{
|
|
if (mission.type == LDS_NONE)
|
|
{
|
|
//can't go back any further!!
|
|
debug(LOG_SAVE, "Already returned from mission");
|
|
return;
|
|
}
|
|
|
|
switch (mission.type)
|
|
{
|
|
case LDS_CAMSTART:
|
|
//any transporters that are flying in need to be emptied
|
|
emptyTransporters(false);
|
|
//when loading in a save game mid cam2a or cam3a it is loaded as a camstart
|
|
endMissionCamChange();
|
|
/*
|
|
This is called at the end of every campaign mission
|
|
*/
|
|
break;
|
|
case LDS_MKEEP:
|
|
//any transporters that are flying in need to be emptied
|
|
emptyTransporters(true);
|
|
endMissionOffKeep();
|
|
break;
|
|
case LDS_EXPAND:
|
|
case LDS_BETWEEN:
|
|
/*
|
|
This is called at the end of every campaign mission
|
|
*/
|
|
|
|
break;
|
|
case LDS_CAMCHANGE:
|
|
//any transporters that are flying in need to be emptied
|
|
emptyTransporters(false);
|
|
endMissionCamChange();
|
|
break;
|
|
/* left in so can skip the mission for testing...*/
|
|
case LDS_EXPAND_LIMBO:
|
|
//shouldn't be any transporters on this mission but...who knows?
|
|
endMissionExpandLimbo();
|
|
break;
|
|
case LDS_MCLEAR:
|
|
//any transporters that are flying in need to be emptied
|
|
emptyTransporters(true);
|
|
endMissionOffClear();
|
|
break;
|
|
case LDS_MKEEP_LIMBO:
|
|
//any transporters that are flying in need to be emptied
|
|
emptyTransporters(true);
|
|
endMissionOffKeepLimbo();
|
|
break;
|
|
default:
|
|
//error!
|
|
debug(LOG_FATAL, "Unknown Mission Type");
|
|
abort();
|
|
}
|
|
|
|
if (missionCanReEnforce()) //mission.type == LDS_MCLEAR || mission.type == LDS_MKEEP)
|
|
{
|
|
intRemoveMissionTimer();
|
|
intRemoveTransporterTimer();
|
|
}
|
|
|
|
//at end of mission always do this
|
|
intRemoveTransporterLaunch();
|
|
|
|
//and this...
|
|
//make sure the cheat time is not set for the next mission
|
|
mission.cheatTime = 0;
|
|
|
|
|
|
//reset the bSetPlayCountDown flag
|
|
setPlayCountDown(true);
|
|
|
|
|
|
//mission.type = MISSION_NONE;
|
|
mission.type = LDS_NONE;
|
|
|
|
// reset the transporters
|
|
initTransporters();
|
|
}
|
|
|
|
void endMissionCamChange(void)
|
|
{
|
|
//get any droids remaining from the previous campaign
|
|
processPreviousCampDroids();
|
|
}
|
|
|
|
void endMissionOffClear(void)
|
|
{
|
|
processMission();
|
|
restoreMissionData();
|
|
|
|
//reset variables in Droids
|
|
missionResetDroids();
|
|
}
|
|
|
|
void endMissionOffKeep(void)
|
|
{
|
|
processMission();
|
|
restoreMissionData();
|
|
|
|
//reset variables in Droids
|
|
missionResetDroids();
|
|
}
|
|
|
|
/*In this case any droids remaining (for selectedPlayer) go into a limbo list
|
|
for use in a future mission (expand type) */
|
|
void endMissionOffKeepLimbo(void)
|
|
{
|
|
// Save any droids left 'alive'
|
|
processMissionLimbo();
|
|
|
|
// Set the lists back to the home base lists
|
|
restoreMissionData();
|
|
|
|
//reset variables in Droids
|
|
missionResetDroids();
|
|
}
|
|
|
|
//This happens MID_MISSION now! but is left here in case the scripts fail but somehow get here...?
|
|
/*The selectedPlayer's droids which were separated at the start of the
|
|
mission need to merged back into the list*/
|
|
void endMissionExpandLimbo(void)
|
|
{
|
|
restoreMissionLimboData();
|
|
}
|
|
|
|
|
|
//this is called mid Limbo mission via the script
|
|
void resetLimboMission(void)
|
|
{
|
|
//add the units that were moved into the mission list at the start of the mission
|
|
restoreMissionLimboData();
|
|
//set the mission type to plain old expand...
|
|
mission.type = LDS_EXPAND;
|
|
}
|
|
|
|
/* The update routine for all droids left back at home base
|
|
Only interested in Transporters at present*/
|
|
void missionDroidUpdate(DROID *psDroid)
|
|
{
|
|
ASSERT(psDroid != NULL, "unitUpdate: Invalid unit pointer");
|
|
|
|
/*This is required for Transporters that are moved offWorld so the
|
|
saveGame doesn't try to set their position in the map - especially important
|
|
for endCam2 where there isn't a valid map!*/
|
|
if (psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER)
|
|
{
|
|
psDroid->pos.x = INVALID_XY;
|
|
psDroid->pos.y = INVALID_XY;
|
|
}
|
|
|
|
//ignore all droids except Transporters
|
|
if ((psDroid->droidType != DROID_TRANSPORTER && psDroid->droidType != DROID_SUPERTRANSPORTER)
|
|
|| !(orderState(psDroid, DORDER_TRANSPORTOUT) ||
|
|
orderState(psDroid, DORDER_TRANSPORTIN) ||
|
|
orderState(psDroid, DORDER_TRANSPORTRETURN)))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// NO ai update droid
|
|
|
|
// update the droids order
|
|
orderUpdateDroid(psDroid);
|
|
|
|
// update the action of the droid
|
|
actionUpdateDroid(psDroid);
|
|
|
|
//NO move update
|
|
}
|
|
|
|
// Reset variables in Droids such as order and position
|
|
static void missionResetDroids(void)
|
|
{
|
|
debug(LOG_SAVE, "called");
|
|
|
|
for (unsigned int player = 0; player < MAX_PLAYERS; player++)
|
|
{
|
|
for (DROID *psDroid = apsDroidLists[player]; psDroid != NULL; psDroid = psDroid->psNext)
|
|
{
|
|
// Reset order - unless constructor droid that is mid-build
|
|
if ((psDroid->droidType == DROID_CONSTRUCT
|
|
|| psDroid->droidType == DROID_CYBORG_CONSTRUCT)
|
|
&& orderStateObj(psDroid, DORDER_BUILD))
|
|
{
|
|
// Need to set the action time to ignore the previous mission time
|
|
psDroid->actionStarted = gameTime;
|
|
}
|
|
else
|
|
{
|
|
orderDroid(psDroid, DORDER_STOP, ModeImmediate);
|
|
}
|
|
|
|
//KILL OFF TRANSPORTER
|
|
if (psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER)
|
|
{
|
|
vanishDroid(psDroid);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (DROID *psDroid = apsDroidLists[selectedPlayer]; psDroid != NULL; psDroid = psDroid->psNext)
|
|
{
|
|
bool placed = false;
|
|
|
|
//for all droids that have never left home base
|
|
if (psDroid->pos.x == INVALID_XY && psDroid->pos.y == INVALID_XY)
|
|
{
|
|
STRUCTURE *psStruct = psDroid->psBaseStruct;
|
|
FACTORY *psFactory = NULL;
|
|
|
|
if (psStruct && StructIsFactory(psStruct))
|
|
{
|
|
psFactory = (FACTORY *)psStruct->pFunctionality;
|
|
}
|
|
//find a location next to the factory
|
|
if (psFactory)
|
|
{
|
|
PICKTILE pickRes;
|
|
UDWORD x, y;
|
|
|
|
// Use factory DP if one
|
|
if (psFactory->psAssemblyPoint)
|
|
{
|
|
x = map_coord(psFactory->psAssemblyPoint->coords.x);
|
|
y = map_coord(psFactory->psAssemblyPoint->coords.y);
|
|
}
|
|
else
|
|
{
|
|
x = map_coord(psStruct->pos.x);
|
|
y = map_coord(psStruct->pos.y);
|
|
}
|
|
pickRes = pickHalfATile(&x, &y, LOOK_FOR_EMPTY_TILE);
|
|
if (pickRes == NO_FREE_TILE)
|
|
{
|
|
ASSERT(false, "missionResetUnits: Unable to find a free location");
|
|
psStruct = NULL;
|
|
}
|
|
else
|
|
{
|
|
int wx = world_coord(x);
|
|
int wy = world_coord(y);
|
|
|
|
droidSetPosition(psDroid, wx, wy);
|
|
placed = true;
|
|
}
|
|
}
|
|
else // if couldn't find the factory - try to place near HQ instead
|
|
{
|
|
for (psStruct = apsStructLists[psDroid->player]; psStruct != NULL; psStruct = psStruct->psNext)
|
|
{
|
|
if (psStruct->pStructureType->type == REF_HQ)
|
|
{
|
|
UDWORD x = map_coord(psStruct->pos.x);
|
|
UDWORD y = map_coord(psStruct->pos.y);
|
|
PICKTILE pickRes = pickHalfATile(&x, &y, LOOK_FOR_EMPTY_TILE);
|
|
|
|
if (pickRes == NO_FREE_TILE)
|
|
{
|
|
ASSERT(false, "missionResetUnits: Unable to find a free location");
|
|
psStruct = NULL;
|
|
}
|
|
else
|
|
{
|
|
int wx = world_coord(x);
|
|
int wy = world_coord(y);
|
|
|
|
droidSetPosition(psDroid, wx, wy);
|
|
placed = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (placed)
|
|
{
|
|
// Do all the things in build droid that never did when it was built!
|
|
// check the droid is a reasonable distance from the edge of the map
|
|
if (psDroid->pos.x <= world_coord(EDGE_SIZE) ||
|
|
psDroid->pos.y <= world_coord(EDGE_SIZE) ||
|
|
psDroid->pos.x >= world_coord(mapWidth - EDGE_SIZE) ||
|
|
psDroid->pos.y >= world_coord(mapHeight - EDGE_SIZE))
|
|
{
|
|
debug(LOG_ERROR, "missionResetUnits: unit too close to edge of map - removing");
|
|
vanishDroid(psDroid);
|
|
continue;
|
|
}
|
|
|
|
// People always stand upright
|
|
if (psDroid->droidType != DROID_PERSON && !cyborgDroid(psDroid))
|
|
{
|
|
updateDroidOrientation(psDroid);
|
|
}
|
|
// Reset the selected flag
|
|
psDroid->selected = false;
|
|
}
|
|
else
|
|
{
|
|
//can't put it down so get rid of this droid!!
|
|
ASSERT(false, "missionResetUnits: can't place unit - cancel to continue");
|
|
vanishDroid(psDroid);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*unloads the Transporter passed into the mission at the specified x/y
|
|
goingHome = true when returning from an off World mission*/
|
|
void unloadTransporter(DROID *psTransporter, UDWORD x, UDWORD y, bool goingHome)
|
|
{
|
|
DROID *psDroid, *psNext;
|
|
DROID **ppCurrentList;
|
|
UDWORD droidX, droidY;
|
|
UDWORD iX, iY;
|
|
DROID_GROUP *psGroup;
|
|
|
|
if (goingHome)
|
|
{
|
|
ppCurrentList = mission.apsDroidLists;
|
|
}
|
|
else
|
|
{
|
|
ppCurrentList = apsDroidLists;
|
|
}
|
|
|
|
//unload all the droids from within the current Transporter
|
|
if (psTransporter->droidType == DROID_TRANSPORTER || psTransporter->droidType == DROID_SUPERTRANSPORTER)
|
|
{
|
|
// reset the transporter cluster
|
|
psTransporter->cluster = 0;
|
|
for (psDroid = psTransporter->psGroup->psList; psDroid != NULL && psDroid != psTransporter; psDroid = psNext)
|
|
{
|
|
psNext = psDroid->psGrpNext;
|
|
//add it back into current droid lists
|
|
addDroid(psDroid, ppCurrentList);
|
|
|
|
//starting point...based around the value passed in
|
|
droidX = map_coord(x);
|
|
droidY = map_coord(y);
|
|
if (goingHome)
|
|
{
|
|
//swap the droid and map pointers
|
|
swapMissionPointers();
|
|
}
|
|
if (!pickATileGen(&droidX, &droidY, LOOK_FOR_EMPTY_TILE, zonedPAT))
|
|
{
|
|
ASSERT(false, "unloadTransporter: Unable to find a valid location");
|
|
}
|
|
droidSetPosition(psDroid, world_coord(droidX), world_coord(droidY));
|
|
updateDroidOrientation(psDroid);
|
|
// a commander needs to get it's group back
|
|
if (psDroid->droidType == DROID_COMMAND)
|
|
{
|
|
psGroup = grpCreate();
|
|
psGroup->add(psDroid);
|
|
clearCommandDroidFactory(psDroid);
|
|
}
|
|
|
|
//reset droid orders
|
|
orderDroid(psDroid, DORDER_STOP, ModeImmediate);
|
|
psDroid->selected = false;
|
|
if (!bMultiPlayer)
|
|
{
|
|
// So VTOLs don't try to rearm on another map
|
|
setDroidBase(psDroid, NULL);
|
|
}
|
|
psDroid->cluster = 0;
|
|
if (goingHome)
|
|
{
|
|
//swap the droid and map pointers
|
|
swapMissionPointers();
|
|
}
|
|
}
|
|
|
|
/* trigger script callback detailing group about to disembark */
|
|
transporterSetScriptCurrent(psTransporter);
|
|
eventFireCallbackTrigger((TRIGGER_TYPE)CALL_TRANSPORTER_LANDED);
|
|
triggerEvent(TRIGGER_TRANSPORTER_LANDED, psTransporter);
|
|
transporterSetScriptCurrent(NULL);
|
|
|
|
/* remove droids from transporter group if not already transferred to script group */
|
|
for (psDroid = psTransporter->psGroup->psList; psDroid != NULL
|
|
&& psDroid != psTransporter; psDroid = psNext)
|
|
{
|
|
psNext = psDroid->psGrpNext;
|
|
psTransporter->psGroup->remove(psDroid);
|
|
}
|
|
}
|
|
|
|
// Don't do this in multiPlayer
|
|
if (!bMultiPlayer)
|
|
{
|
|
// Send all transporter Droids back to home base if off world
|
|
if (!goingHome)
|
|
{
|
|
/* Stop the camera following the transporter */
|
|
psTransporter->selected = false;
|
|
|
|
/* Send transporter offworld */
|
|
missionGetTransporterExit(psTransporter->player, &iX, &iY);
|
|
orderDroidLoc(psTransporter, DORDER_TRANSPORTRETURN, iX, iY, ModeImmediate);
|
|
|
|
// Set the launch time so the transporter doesn't just disappear for CAMSTART/CAMCHANGE
|
|
transporterSetLaunchTime(gameTime);
|
|
}
|
|
}
|
|
}
|
|
|
|
void missionMoveTransporterOffWorld(DROID *psTransporter)
|
|
{
|
|
W_CLICKFORM *psForm;
|
|
DROID *psDroid;
|
|
// FIXME: When we convert campaign scripts, use DROID_SUPERTRANSPORTER
|
|
if (psTransporter->droidType == DROID_TRANSPORTER)
|
|
{
|
|
/* trigger script callback */
|
|
transporterSetScriptCurrent(psTransporter);
|
|
eventFireCallbackTrigger((TRIGGER_TYPE)CALL_TRANSPORTER_OFFMAP);
|
|
triggerEvent(TRIGGER_TRANSPORTER_EXIT, psTransporter);
|
|
transporterSetScriptCurrent(NULL);
|
|
|
|
if (droidRemove(psTransporter, apsDroidLists))
|
|
{
|
|
addDroid(psTransporter, mission.apsDroidLists);
|
|
}
|
|
|
|
//stop the droid moving - the moveUpdate happens AFTER the orderUpdate and can cause problems if the droid moves from one tile to another
|
|
moveReallyStopDroid(psTransporter);
|
|
|
|
//if offworld mission, then add the timer
|
|
//if (mission.type == LDS_MKEEP || mission.type == LDS_MCLEAR)
|
|
if (missionCanReEnforce() && psTransporter->player == selectedPlayer)
|
|
{
|
|
addTransporterTimerInterface();
|
|
//set the data for the transporter timer label
|
|
widgSetUserData(psWScreen, IDTRANTIMER_DISPLAY, (void *)psTransporter);
|
|
|
|
//make sure the button is enabled
|
|
psForm = (W_CLICKFORM *)widgGetFromID(psWScreen, IDTRANTIMER_BUTTON);
|
|
if (psForm)
|
|
{
|
|
psForm->setState(WBUT_PLAIN);
|
|
}
|
|
}
|
|
//need a callback for when all the selectedPlayers' reinforcements have been delivered
|
|
if (psTransporter->player == selectedPlayer)
|
|
{
|
|
psDroid = NULL;
|
|
for (psDroid = mission.apsDroidLists[selectedPlayer]; psDroid != NULL; psDroid = psDroid->psNext)
|
|
{
|
|
if (psDroid->droidType != DROID_TRANSPORTER && psDroid->droidType != DROID_SUPERTRANSPORTER)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (psDroid == NULL)
|
|
{
|
|
eventFireCallbackTrigger((TRIGGER_TYPE)CALL_NO_REINFORCEMENTS_LEFT);
|
|
triggerEvent(TRIGGER_TRANSPORTER_DONE, psTransporter);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
debug(LOG_SAVE, "droid type not transporter!");
|
|
}
|
|
}
|
|
|
|
|
|
//add the Mission timer into the top right hand corner of the screen
|
|
bool intAddMissionTimer(void)
|
|
{
|
|
//check to see if it exists already
|
|
if (widgGetFromID(psWScreen, IDTIMER_FORM) != NULL)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Add the background
|
|
W_FORMINIT sFormInit;
|
|
|
|
sFormInit.formID = 0;
|
|
sFormInit.id = IDTIMER_FORM;
|
|
sFormInit.style = WFORM_PLAIN;
|
|
|
|
sFormInit.width = iV_GetImageWidth(IntImages, IMAGE_MISSION_CLOCK); //TIMER_WIDTH;
|
|
sFormInit.height = iV_GetImageHeight(IntImages, IMAGE_MISSION_CLOCK); //TIMER_HEIGHT;
|
|
sFormInit.x = (SWORD)(RADTLX + RADWIDTH - sFormInit.width);
|
|
sFormInit.y = (SWORD)TIMER_Y;
|
|
sFormInit.UserData = PACKDWORD_TRI(0, IMAGE_MISSION_CLOCK, IMAGE_MISSION_CLOCK_UP);
|
|
sFormInit.pDisplay = intDisplayMissionClock;
|
|
|
|
if (!widgAddForm(psWScreen, &sFormInit))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//add labels for the time display
|
|
W_LABINIT sLabInit;
|
|
sLabInit.formID = IDTIMER_FORM;
|
|
sLabInit.id = IDTIMER_DISPLAY;
|
|
sLabInit.style = WLAB_PLAIN | WIDG_HIDDEN;
|
|
sLabInit.x = TIMER_LABELX;
|
|
sLabInit.y = TIMER_LABELY;
|
|
sLabInit.width = sFormInit.width;//TIMER_WIDTH;
|
|
sLabInit.height = sFormInit.height;//TIMER_HEIGHT;
|
|
sLabInit.pText = "00:00:00";
|
|
sLabInit.pCallback = intUpdateMissionTimer;
|
|
|
|
if (!widgAddLabel(psWScreen, &sLabInit))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//add the Transporter timer into the top left hand corner of the screen
|
|
bool intAddTransporterTimer(void)
|
|
{
|
|
// Make sure that Transporter Launch button isn't up as well
|
|
intRemoveTransporterLaunch();
|
|
|
|
//check to see if it exists already
|
|
if (widgGetFromID(psWScreen, IDTRANTIMER_BUTTON) != NULL)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Add the button form - clickable
|
|
W_FORMINIT sFormInit;
|
|
sFormInit.formID = 0;
|
|
sFormInit.id = IDTRANTIMER_BUTTON;
|
|
sFormInit.style = WFORM_CLICKABLE | WFORM_NOCLICKMOVE;
|
|
sFormInit.x = TRAN_FORM_X;
|
|
sFormInit.y = TRAN_FORM_Y;
|
|
sFormInit.width = iV_GetImageWidth(IntImages, IMAGE_TRANSETA_UP);
|
|
sFormInit.height = iV_GetImageHeight(IntImages, IMAGE_TRANSETA_UP);
|
|
sFormInit.pTip = _("Load Transport");
|
|
sFormInit.pDisplay = intDisplayImageHilight;
|
|
sFormInit.UserData = PACKDWORD_TRI(0, IMAGE_TRANSETA_DOWN, IMAGE_TRANSETA_UP);
|
|
|
|
if (!widgAddForm(psWScreen, &sFormInit))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//add labels for the time display
|
|
W_LABINIT sLabInit;
|
|
sLabInit.formID = IDTRANTIMER_BUTTON;
|
|
sLabInit.id = IDTRANTIMER_DISPLAY;
|
|
sLabInit.style = WIDG_HIDDEN;
|
|
sLabInit.x = TRAN_TIMER_X;
|
|
sLabInit.y = TRAN_TIMER_Y;
|
|
sLabInit.width = TRAN_TIMER_WIDTH;
|
|
sLabInit.height = sFormInit.height;
|
|
sLabInit.pCallback = intUpdateTransporterTimer;
|
|
if (!widgAddLabel(psWScreen, &sLabInit))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//add the capacity label
|
|
sLabInit = W_LABINIT();
|
|
sLabInit.formID = IDTRANTIMER_BUTTON;
|
|
sLabInit.id = IDTRANS_CAPACITY;
|
|
sLabInit.x = 65;
|
|
sLabInit.y = 1;
|
|
sLabInit.width = 16;
|
|
sLabInit.height = 16;
|
|
sLabInit.pText = "00/10";
|
|
sLabInit.pCallback = intUpdateTransCapacity;
|
|
if (!widgAddLabel(psWScreen, &sLabInit))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void missionSetReinforcementTime(UDWORD iTime)
|
|
{
|
|
g_iReinforceTime = iTime;
|
|
}
|
|
|
|
UDWORD missionGetReinforcementTime(void)
|
|
{
|
|
return g_iReinforceTime;
|
|
}
|
|
|
|
//fills in a hours(if bHours = true), minutes and seconds display for a given time in 1000th sec
|
|
static void fillTimeDisplay(QString &text, UDWORD time, bool bHours)
|
|
{
|
|
char psText[100];
|
|
//this is only for the transporter timer - never have hours!
|
|
if (time == LZ_COMPROMISED_TIME)
|
|
{
|
|
strcpy(psText, "--:--");
|
|
}
|
|
else
|
|
{
|
|
//hardcode conversion from time to string to avoid strftie malfunction
|
|
long secs = time / GAME_TICKS_PER_SEC;
|
|
long hh = secs / 3600;
|
|
long mm = (secs / 60) % 60;
|
|
long ss = secs % 60;
|
|
if (bHours)
|
|
{
|
|
if (hh==0) psText[0] = psText[1] = '0';
|
|
else if (hh<10)
|
|
{
|
|
psText[0] = '0';
|
|
psText[1] = '0' + hh;
|
|
}
|
|
else
|
|
{
|
|
psText[0] = '0' + hh / 10;
|
|
psText[1] = '0' + hh % 10;
|
|
}
|
|
psText[2] = ':';
|
|
|
|
if (mm==0) psText[3] = psText[4] = '0';
|
|
else if (mm<10)
|
|
{
|
|
psText[3] = '0';
|
|
psText[4] = '0' + mm;
|
|
}
|
|
else
|
|
{
|
|
psText[3] = '0' + mm / 10;
|
|
psText[4] = '0' + mm % 10;
|
|
}
|
|
psText[5] = ':';
|
|
|
|
if (ss==0) psText[6] = psText[7] = '0';
|
|
else if (ss<10)
|
|
{
|
|
psText[6] = '0';
|
|
psText[7] = '0' + ss;
|
|
}
|
|
else
|
|
{
|
|
psText[6] = '0' + ss / 10;
|
|
psText[7] = '0' + ss % 10;
|
|
}
|
|
psText[8]='\0';
|
|
}
|
|
else
|
|
{
|
|
if (mm==0) psText[0] = psText[1] = '0';
|
|
else if (mm<10)
|
|
{
|
|
psText[0] = '0';
|
|
psText[1] = '0' + mm;
|
|
}
|
|
else
|
|
{
|
|
psText[0] = '0' + mm / 10;
|
|
psText[1] = '0' + mm % 10;
|
|
}
|
|
psText[2] = ':';
|
|
|
|
if (ss==0) psText[3] = psText[4] = '0';
|
|
else if (ss<10)
|
|
{
|
|
psText[3] = '0';
|
|
psText[4] = '0' + ss;
|
|
}
|
|
else
|
|
{
|
|
psText[3] = '0' + ss / 10;
|
|
psText[4] = '0' + ss % 10;
|
|
}
|
|
psText[5]='\0';
|
|
}
|
|
|
|
}
|
|
text = QString::fromUtf8(psText);
|
|
}
|
|
|
|
|
|
//update function for the mission timer
|
|
void intUpdateMissionTimer(WIDGET *psWidget, W_CONTEXT *psContext)
|
|
{
|
|
W_LABEL *Label = (W_LABEL *)psWidget;
|
|
UDWORD timeElapsed;
|
|
SDWORD timeRemaining;
|
|
|
|
// If the cheatTime has been set, then don't want the timer to countdown until stop cheating
|
|
if (mission.cheatTime)
|
|
{
|
|
timeElapsed = mission.cheatTime - mission.startTime;
|
|
}
|
|
else
|
|
{
|
|
timeElapsed = gameTime - mission.startTime;
|
|
}
|
|
|
|
if (!challengeActive)
|
|
{
|
|
timeRemaining = mission.time - timeElapsed;
|
|
if (timeRemaining < 0)
|
|
{
|
|
timeRemaining = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
timeRemaining = timeElapsed;
|
|
}
|
|
|
|
fillTimeDisplay(Label->aText, timeRemaining, true);
|
|
Label->show(); // Make sure its visible
|
|
|
|
if (challengeActive)
|
|
{
|
|
return; // all done
|
|
}
|
|
|
|
//make timer flash if time remaining < 5 minutes
|
|
if (timeRemaining < FIVE_MINUTES)
|
|
{
|
|
flashMissionButton(IDTIMER_FORM);
|
|
}
|
|
//stop timer from flashing when gets to < 4 minutes
|
|
if (timeRemaining < FOUR_MINUTES)
|
|
{
|
|
stopMissionButtonFlash(IDTIMER_FORM);
|
|
}
|
|
//play audio the first time the timed mission is started
|
|
if (timeRemaining && (missionCountDown & NOT_PLAYED_ACTIVATED))
|
|
{
|
|
audio_QueueTrack(ID_SOUND_MISSION_TIMER_ACTIVATED);
|
|
missionCountDown &= ~NOT_PLAYED_ACTIVATED;
|
|
}
|
|
//play some audio for mission countdown - start at 10 minutes remaining
|
|
if (getPlayCountDown() && timeRemaining < TEN_MINUTES)
|
|
{
|
|
if (timeRemaining < TEN_MINUTES && (missionCountDown & NOT_PLAYED_TEN))
|
|
{
|
|
audio_QueueTrack(ID_SOUND_10_MINUTES_REMAINING);
|
|
missionCountDown &= ~NOT_PLAYED_TEN;
|
|
}
|
|
else if (timeRemaining < FIVE_MINUTES && (missionCountDown & NOT_PLAYED_FIVE))
|
|
{
|
|
audio_QueueTrack(ID_SOUND_5_MINUTES_REMAINING);
|
|
missionCountDown &= ~NOT_PLAYED_FIVE;
|
|
}
|
|
else if (timeRemaining < THREE_MINUTES && (missionCountDown & NOT_PLAYED_THREE))
|
|
{
|
|
audio_QueueTrack(ID_SOUND_3_MINUTES_REMAINING);
|
|
missionCountDown &= ~NOT_PLAYED_THREE;
|
|
}
|
|
else if (timeRemaining < TWO_MINUTES && (missionCountDown & NOT_PLAYED_TWO))
|
|
{
|
|
audio_QueueTrack(ID_SOUND_2_MINUTES_REMAINING);
|
|
missionCountDown &= ~NOT_PLAYED_TWO;
|
|
}
|
|
else if (timeRemaining < ONE_MINUTE && (missionCountDown & NOT_PLAYED_ONE))
|
|
{
|
|
audio_QueueTrack(ID_SOUND_1_MINUTE_REMAINING);
|
|
missionCountDown &= ~NOT_PLAYED_ONE;
|
|
}
|
|
}
|
|
}
|
|
|
|
#define TRANSPORTER_REINFORCE_LEADIN 10*GAME_TICKS_PER_SEC
|
|
|
|
//update function for the transporter timer
|
|
void intUpdateTransporterTimer(WIDGET *psWidget, W_CONTEXT *psContext)
|
|
{
|
|
W_LABEL *Label = (W_LABEL *)psWidget;
|
|
DROID *psTransporter;
|
|
SDWORD timeRemaining;
|
|
SDWORD ETA;
|
|
|
|
ETA = mission.ETA;
|
|
if (ETA < 0)
|
|
{
|
|
ETA = 0;
|
|
}
|
|
|
|
// Get the object associated with this widget.
|
|
psTransporter = (DROID *)Label->pUserData;
|
|
if (psTransporter != NULL)
|
|
{
|
|
ASSERT(psTransporter != NULL,
|
|
"intUpdateTransporterTimer: invalid Droid pointer");
|
|
|
|
if (psTransporter->action == DACTION_TRANSPORTIN ||
|
|
psTransporter->action == DACTION_TRANSPORTWAITTOFLYIN)
|
|
{
|
|
if (mission.ETA == LZ_COMPROMISED_TIME)
|
|
{
|
|
timeRemaining = LZ_COMPROMISED_TIME;
|
|
}
|
|
else
|
|
{
|
|
timeRemaining = mission.ETA - (gameTime - g_iReinforceTime);
|
|
if (timeRemaining < 0)
|
|
{
|
|
timeRemaining = 0;
|
|
}
|
|
if (timeRemaining < TRANSPORTER_REINFORCE_LEADIN)
|
|
{
|
|
// arrived: tell the transporter to move to the new onworld
|
|
// location if not already doing so
|
|
if (psTransporter->action == DACTION_TRANSPORTWAITTOFLYIN)
|
|
{
|
|
missionFlyTransportersIn(selectedPlayer, false);
|
|
eventFireCallbackTrigger((TRIGGER_TYPE)CALL_TRANSPORTER_REINFORCE);
|
|
triggerEvent(TRIGGER_TRANSPORTER_ARRIVED, psTransporter);
|
|
}
|
|
}
|
|
}
|
|
fillTimeDisplay(Label->aText, timeRemaining, false);
|
|
}
|
|
else
|
|
{
|
|
fillTimeDisplay(Label->aText, ETA, false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (missionCanReEnforce()) // ((mission.type == LDS_MKEEP) || (mission.type == LDS_MCLEAR)) & (mission.ETA >= 0) ) {
|
|
{
|
|
fillTimeDisplay(Label->aText, ETA, false);
|
|
}
|
|
else
|
|
{
|
|
|
|
fillTimeDisplay(Label->aText, 0, false);
|
|
|
|
}
|
|
}
|
|
|
|
//minutes
|
|
/*calcTime = timeRemaining / (60*GAME_TICKS_PER_SEC);
|
|
Label->aText[0] = (UBYTE)('0'+ calcTime / 10);
|
|
Label->aText[1] = (UBYTE)('0'+ calcTime % 10);
|
|
timeElapsed -= calcTime * (60*GAME_TICKS_PER_SEC);
|
|
//seperator
|
|
Label->aText[3] = (UBYTE)(':');
|
|
//seconds
|
|
calcTime = timeRemaining / GAME_TICKS_PER_SEC;
|
|
Label->aText[3] = (UBYTE)('0'+ calcTime / 10);
|
|
Label->aText[4] = (UBYTE)('0'+ calcTime % 10);*/
|
|
|
|
Label->show();
|
|
}
|
|
|
|
/* Remove the Mission Timer widgets from the screen*/
|
|
void intRemoveMissionTimer(void)
|
|
{
|
|
// Check it's up.
|
|
if (widgGetFromID(psWScreen, IDTIMER_FORM) != NULL)
|
|
{
|
|
//and remove it.
|
|
widgDelete(psWScreen, IDTIMER_FORM);
|
|
}
|
|
}
|
|
|
|
/* Remove the Transporter Timer widgets from the screen*/
|
|
void intRemoveTransporterTimer(void)
|
|
{
|
|
|
|
//remove main screen
|
|
if (widgGetFromID(psWScreen, IDTRANTIMER_BUTTON) != NULL)
|
|
{
|
|
widgDelete(psWScreen, IDTRANTIMER_BUTTON);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// ////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////
|
|
// mission result functions for the interface.
|
|
|
|
|
|
|
|
static void intDisplayMissionBackDrop(WIDGET *psWidget, UDWORD xOffset, UDWORD yOffset)
|
|
{
|
|
scoreDataToScreen();
|
|
}
|
|
|
|
static void missionResetInGameState(void)
|
|
{
|
|
//stop the game if in single player mode
|
|
setMissionPauseState();
|
|
|
|
// reset the input state
|
|
resetInput();
|
|
|
|
// Add the background
|
|
// get rid of reticule etc..
|
|
intResetScreen(false);
|
|
forceHidePowerBar();
|
|
intRemoveReticule();
|
|
intRemoveMissionTimer();
|
|
}
|
|
|
|
static bool _intAddMissionResult(bool result, bool bPlaySuccess)
|
|
{
|
|
missionResetInGameState();
|
|
|
|
W_FORMINIT sFormInit;
|
|
|
|
// add some funky beats
|
|
cdAudio_PlayTrack(SONG_FRONTEND);
|
|
|
|
pie_LoadBackDrop(SCREEN_MISSIONEND);
|
|
|
|
sFormInit.formID = 0;
|
|
sFormInit.id = IDMISSIONRES_BACKFORM;
|
|
sFormInit.style = WFORM_PLAIN;
|
|
sFormInit.pDisplay = intDisplayMissionBackDrop;
|
|
W_FORM *missionResBackForm = widgAddForm(psWScreen, &sFormInit);
|
|
missionResBackForm->setGeometry(0 + D_W, 0 + D_H, 640, 480);
|
|
|
|
// TITLE
|
|
IntFormAnimated *missionResTitle = new IntFormAnimated(missionResBackForm);
|
|
missionResTitle->id = IDMISSIONRES_TITLE;
|
|
missionResTitle->setGeometry(MISSIONRES_TITLE_X, MISSIONRES_TITLE_Y, MISSIONRES_TITLE_W, MISSIONRES_TITLE_H);
|
|
|
|
// add form
|
|
IntFormAnimated *missionResForm = new IntFormAnimated(missionResBackForm);
|
|
missionResForm->id = IDMISSIONRES_FORM;
|
|
missionResForm->setGeometry(MISSIONRES_X, MISSIONRES_Y, MISSIONRES_W, MISSIONRES_H);
|
|
|
|
// description of success/fail
|
|
W_LABINIT sLabInit;
|
|
sLabInit.formID = IDMISSIONRES_TITLE;
|
|
sLabInit.id = IDMISSIONRES_TXT;
|
|
sLabInit.style = WLAB_ALIGNCENTRE;
|
|
sLabInit.x = 0;
|
|
sLabInit.y = 12;
|
|
sLabInit.width = MISSIONRES_TITLE_W;
|
|
sLabInit.height = 16;
|
|
if (result)
|
|
{
|
|
|
|
//don't bother adding the text if haven't played the audio
|
|
if (bPlaySuccess)
|
|
{
|
|
sLabInit.pText = Cheated ? _("OBJECTIVE ACHIEVED by cheating!") : _("OBJECTIVE ACHIEVED");//"Objective Achieved";
|
|
}
|
|
|
|
}
|
|
else
|
|
{
|
|
sLabInit.pText = Cheated ? _("OBJECTIVE FAILED--and you cheated!") : _("OBJECTIVE FAILED"); //"Objective Failed;
|
|
}
|
|
sLabInit.FontID = font_regular;
|
|
if (!widgAddLabel(psWScreen, &sLabInit))
|
|
{
|
|
return false;
|
|
}
|
|
// options.
|
|
W_BUTINIT sButInit;
|
|
sButInit.formID = IDMISSIONRES_FORM;
|
|
sButInit.style = WBUT_TXTCENTRE;
|
|
sButInit.width = MISSION_TEXT_W;
|
|
sButInit.height = MISSION_TEXT_H;
|
|
sButInit.pDisplay = displayTextOption;
|
|
// If won or in debug mode
|
|
if (result || getDebugMappingStatus() || bMultiPlayer)
|
|
{
|
|
//continue
|
|
sButInit.x = MISSION_2_X;
|
|
// Won the game, so display "Quit to main menu"
|
|
if (testPlayerHasWon() && !bMultiPlayer)
|
|
{
|
|
sButInit.id = IDMISSIONRES_QUIT;
|
|
sButInit.y = MISSION_2_Y - 8;
|
|
sButInit.pText = _("Quit To Main Menu");
|
|
widgAddButton(psWScreen, &sButInit);
|
|
}
|
|
else
|
|
{
|
|
// Finished the mission, so display "Continue Game"
|
|
sButInit.y = MISSION_2_Y;
|
|
sButInit.id = IDMISSIONRES_CONTINUE;
|
|
sButInit.pText = _("Continue Game");//"Continue Game";
|
|
widgAddButton(psWScreen, &sButInit);
|
|
}
|
|
|
|
// FIXME, We got serious issues with savegames at the *END* of some missions, and while they
|
|
// will load, they don't have the correct state information or other settings.
|
|
// See transition from CAM2->CAM3 for a example.
|
|
/* Only add save option if in the game for real, ie, not fastplay.
|
|
* And the player hasn't just completed the whole game
|
|
* Don't add save option if just lost and in debug mode.
|
|
if (!bMultiPlayer && !testPlayerHasWon() && !(testPlayerHasLost() && getDebugMappingStatus()))
|
|
{
|
|
//save
|
|
sButInit.id = IDMISSIONRES_SAVE;
|
|
sButInit.x = MISSION_1_X;
|
|
sButInit.y = MISSION_1_Y;
|
|
sButInit.pText = _("Save Game");//"Save Game";
|
|
widgAddButton(psWScreen, &sButInit);
|
|
|
|
// automatically save the game to be able to restart a mission
|
|
saveGame((char *)"savegames/Autosave.gam", GTYPE_SAVE_START);
|
|
}
|
|
*/
|
|
}
|
|
else
|
|
{
|
|
//load
|
|
sButInit.id = IDMISSIONRES_LOAD;
|
|
sButInit.x = MISSION_1_X;
|
|
sButInit.y = MISSION_1_Y;
|
|
sButInit.pText = _("Load Saved Game");//"Load Saved Game";
|
|
widgAddButton(psWScreen, &sButInit);
|
|
//quit
|
|
sButInit.id = IDMISSIONRES_QUIT;
|
|
sButInit.x = MISSION_2_X;
|
|
sButInit.y = MISSION_2_Y;
|
|
sButInit.pText = _("Quit To Main Menu");//"Quit to Main Menu";
|
|
widgAddButton(psWScreen, &sButInit);
|
|
}
|
|
|
|
intMode = INT_MISSIONRES;
|
|
MissionResUp = true;
|
|
|
|
/* play result audio */
|
|
if (result == true && bPlaySuccess)
|
|
{
|
|
audio_QueueTrack(ID_SOUND_OBJECTIVE_ACCOMPLISHED);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool intAddMissionResult(bool result, bool bPlaySuccess)
|
|
{
|
|
return _intAddMissionResult(result, bPlaySuccess);
|
|
}
|
|
|
|
void intRemoveMissionResultNoAnim(void)
|
|
{
|
|
widgDelete(psWScreen, IDMISSIONRES_TITLE);
|
|
widgDelete(psWScreen, IDMISSIONRES_FORM);
|
|
widgDelete(psWScreen, IDMISSIONRES_BACKFORM);
|
|
|
|
cdAudio_Stop();
|
|
|
|
MissionResUp = false;
|
|
intMode = INT_NORMAL;
|
|
|
|
//reset the pauses
|
|
resetMissionPauseState();
|
|
|
|
// add back the reticule and power bar.
|
|
intAddReticule();
|
|
|
|
intShowPowerBar();
|
|
}
|
|
|
|
void intRunMissionResult(void)
|
|
{
|
|
wzSetCursor(CURSOR_DEFAULT);
|
|
|
|
if (bLoadSaveUp)
|
|
{
|
|
if (runLoadSave(false)) // check for file name.
|
|
{
|
|
if (strlen(sRequestResult))
|
|
{
|
|
debug(LOG_SAVE, "Returned %s", sRequestResult);
|
|
|
|
if (!bRequestLoad)
|
|
{
|
|
char msg[256] = {'\0'};
|
|
|
|
saveGame(sRequestResult, GTYPE_SAVE_START);
|
|
sstrcpy(msg, _("GAME SAVED :"));
|
|
sstrcat(msg, sRequestResult);
|
|
addConsoleMessage(msg, LEFT_JUSTIFY, NOTIFY_MESSAGE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void missionContineButtonPressed(void)
|
|
{
|
|
if (nextMissionType == LDS_CAMSTART
|
|
|| nextMissionType == LDS_BETWEEN
|
|
|| nextMissionType == LDS_EXPAND
|
|
|| nextMissionType == LDS_EXPAND_LIMBO)
|
|
{
|
|
launchMission();
|
|
}
|
|
widgDelete(psWScreen, IDMISSIONRES_FORM); //close option box.
|
|
|
|
if (bMultiPlayer)
|
|
{
|
|
intRemoveMissionResultNoAnim();
|
|
}
|
|
}
|
|
|
|
void intProcessMissionResult(UDWORD id)
|
|
{
|
|
switch (id)
|
|
{
|
|
case IDMISSIONRES_LOAD:
|
|
// throw up some filerequester
|
|
addLoadSave(LOAD_MISSIONEND, _("Load Saved Game"));
|
|
break;
|
|
case IDMISSIONRES_SAVE:
|
|
addLoadSave(SAVE_MISSIONEND, _("Save Game"));
|
|
if (widgGetFromID(psWScreen, IDMISSIONRES_QUIT) == NULL)
|
|
{
|
|
//Add Quit Button now save has been pressed
|
|
W_BUTINIT sButInit;
|
|
sButInit.formID = IDMISSIONRES_FORM;
|
|
sButInit.style = WBUT_TXTCENTRE;
|
|
sButInit.width = MISSION_TEXT_W;
|
|
sButInit.height = MISSION_TEXT_H;
|
|
sButInit.pDisplay = displayTextOption;
|
|
sButInit.id = IDMISSIONRES_QUIT;
|
|
sButInit.x = MISSION_3_X;
|
|
sButInit.y = MISSION_3_Y;
|
|
sButInit.pText = _("Quit To Main Menu");
|
|
widgAddButton(psWScreen, &sButInit);
|
|
}
|
|
break;
|
|
case IDMISSIONRES_QUIT:
|
|
// catered for by hci.c.
|
|
break;
|
|
case IDMISSIONRES_CONTINUE:
|
|
if (bLoadSaveUp)
|
|
{
|
|
closeLoadSave(); // close save interface if it's up.
|
|
}
|
|
missionContineButtonPressed();
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// end of interface stuff.
|
|
// ////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////
|
|
|
|
/*builds a droid back at the home base whilst on a mission - stored in a list made
|
|
available to the transporter interface*/
|
|
DROID *buildMissionDroid(DROID_TEMPLATE *psTempl, UDWORD x, UDWORD y,
|
|
UDWORD player)
|
|
{
|
|
DROID *psNewDroid;
|
|
|
|
psNewDroid = buildDroid(psTempl, world_coord(x), world_coord(y), player, true, NULL);
|
|
if (!psNewDroid)
|
|
{
|
|
return NULL;
|
|
}
|
|
addDroid(psNewDroid, mission.apsDroidLists);
|
|
//set its x/y to impossible values so can detect when return from mission
|
|
psNewDroid->pos.x = INVALID_XY;
|
|
psNewDroid->pos.y = INVALID_XY;
|
|
|
|
//set all the droids to selected from when return
|
|
psNewDroid->selected = true;
|
|
|
|
// Set died parameter correctly
|
|
psNewDroid->died = NOT_CURRENT_LIST;
|
|
|
|
return psNewDroid;
|
|
}
|
|
|
|
//this causes the new mission data to be loaded up - only if startMission has been called
|
|
void launchMission(void)
|
|
{
|
|
//if (mission.type == MISSION_NONE)
|
|
if (mission.type == LDS_NONE)
|
|
{
|
|
// tell the loop that a new level has to be loaded up
|
|
loopMissionState = LMS_NEWLEVEL;
|
|
}
|
|
else
|
|
{
|
|
debug(LOG_SAVE, "Start Mission has not been called");
|
|
}
|
|
}
|
|
|
|
|
|
//sets up the game to start a new mission
|
|
bool setUpMission(UDWORD type)
|
|
{
|
|
// Close the interface
|
|
intResetScreen(true);
|
|
|
|
/* The last mission must have been successful otherwise endgame would have been called */
|
|
endMission();
|
|
|
|
//release the level data for the previous mission
|
|
if (!levReleaseMissionData())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (type == LDS_CAMSTART)
|
|
{
|
|
// Another one of those lovely hacks!!
|
|
bool bPlaySuccess = true;
|
|
|
|
// We don't want the 'mission accomplished' audio/text message at end of cam1
|
|
if (getCampaignNumber() == 2)
|
|
{
|
|
bPlaySuccess = false;
|
|
}
|
|
// Give the option of save/continue
|
|
if (!intAddMissionResult(true, bPlaySuccess))
|
|
{
|
|
return false;
|
|
}
|
|
loopMissionState = LMS_SAVECONTINUE;
|
|
}
|
|
else if (type == LDS_MKEEP
|
|
|| type == LDS_MCLEAR
|
|
|| type == LDS_MKEEP_LIMBO)
|
|
{
|
|
launchMission();
|
|
}
|
|
else
|
|
{
|
|
if (!getWidgetsStatus())
|
|
{
|
|
setWidgetsStatus(true);
|
|
intResetScreen(false);
|
|
}
|
|
|
|
// Give the option of save / continue
|
|
if (!intAddMissionResult(true, true))
|
|
{
|
|
return false;
|
|
}
|
|
loopMissionState = LMS_SAVECONTINUE;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//save the power settings before loading in the new map data
|
|
void saveMissionPower(void)
|
|
{
|
|
UDWORD inc;
|
|
|
|
for (inc = 0; inc < MAX_PLAYERS; inc++)
|
|
{
|
|
mission.asCurrentPower[inc] = getPower(inc);
|
|
}
|
|
}
|
|
|
|
//add the power from the home base to the current power levels for the mission map
|
|
void adjustMissionPower(void)
|
|
{
|
|
UDWORD inc;
|
|
|
|
for (inc = 0; inc < MAX_PLAYERS; inc++)
|
|
{
|
|
addPower(inc, mission.asCurrentPower[inc]);
|
|
}
|
|
}
|
|
|
|
/*sets the appropriate pause states for when the interface is up but the
|
|
game needs to be paused*/
|
|
void setMissionPauseState(void)
|
|
{
|
|
if (!bMultiPlayer)
|
|
{
|
|
gameTimeStop();
|
|
setGameUpdatePause(true);
|
|
setAudioPause(true);
|
|
setScriptPause(true);
|
|
setConsolePause(true);
|
|
}
|
|
}
|
|
|
|
/*resets the pause states */
|
|
void resetMissionPauseState(void)
|
|
{
|
|
if (!bMultiPlayer)
|
|
{
|
|
setGameUpdatePause(false);
|
|
setAudioPause(false);
|
|
setScriptPause(false);
|
|
setConsolePause(false);
|
|
gameTimeStart();
|
|
}
|
|
}
|
|
|
|
//gets the coords for a no go area
|
|
LANDING_ZONE *getLandingZone(SDWORD i)
|
|
{
|
|
ASSERT((i >= 0) && (i < MAX_NOGO_AREAS), "getLandingZone out of range.");
|
|
return &sLandingZone[i];
|
|
}
|
|
|
|
/*Initialises all the nogo areas to 0 - DOESN'T INIT THE LIMBO AREA because we
|
|
have to set this up in the mission BEFORE*/
|
|
void initNoGoAreas(void)
|
|
{
|
|
UBYTE i;
|
|
|
|
for (i = 0; i < MAX_NOGO_AREAS; i++)
|
|
{
|
|
if (i != LIMBO_LANDING)
|
|
{
|
|
sLandingZone[i].x1 = sLandingZone[i].y1 = sLandingZone[i].x2 =
|
|
sLandingZone[i].y2 = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
//sets the coords for a no go area
|
|
void setNoGoArea(UBYTE x1, UBYTE y1, UBYTE x2, UBYTE y2, UBYTE area)
|
|
{
|
|
// make sure that x2 > x1 and y2 > y1
|
|
if (x2 < x1)
|
|
{
|
|
std::swap(x1, x2);
|
|
}
|
|
if (y2 < y1)
|
|
{
|
|
std::swap(y1, y2);
|
|
}
|
|
|
|
sLandingZone[area].x1 = x1;
|
|
sLandingZone[area].x2 = x2;
|
|
sLandingZone[area].y1 = y1;
|
|
sLandingZone[area].y2 = y2;
|
|
|
|
if (area == 0 && x1 && y1)
|
|
{
|
|
addLandingLights(getLandingX(area) + 64, getLandingY(area) + 64);
|
|
}
|
|
}
|
|
|
|
static inline void addLandingLight(int x, int y, LAND_LIGHT_SPEC spec, bool lit)
|
|
{
|
|
// The height the landing lights should be above the ground
|
|
static const unsigned int AboveGround = 16;
|
|
Vector3i pos;
|
|
|
|
if (x < 0 || y < 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
pos.x = x;
|
|
pos.z = y;
|
|
pos.y = map_Height(x, y) + AboveGround;
|
|
|
|
effectSetLandLightSpec(spec);
|
|
|
|
addEffect(&pos, EFFECT_EXPLOSION, EXPLOSION_TYPE_LAND_LIGHT, false, NULL, lit);
|
|
}
|
|
|
|
static void addLandingLights(UDWORD x, UDWORD y)
|
|
{
|
|
addLandingLight(x, y, LL_MIDDLE, true); // middle
|
|
|
|
addLandingLight(x + 128, y + 128, LL_OUTER, false); // outer
|
|
addLandingLight(x + 128, y - 128, LL_OUTER, false);
|
|
addLandingLight(x - 128, y + 128, LL_OUTER, false);
|
|
addLandingLight(x - 128, y - 128, LL_OUTER, false);
|
|
|
|
addLandingLight(x + 64, y + 64, LL_INNER, false); // inner
|
|
addLandingLight(x + 64, y - 64, LL_INNER, false);
|
|
addLandingLight(x - 64, y + 64, LL_INNER, false);
|
|
addLandingLight(x - 64, y - 64, LL_INNER, false);
|
|
}
|
|
|
|
/* checks the x,y passed in are not within the boundary of any Landing Zone
|
|
x and y in tile coords*/
|
|
bool withinLandingZone(UDWORD x, UDWORD y)
|
|
{
|
|
UDWORD inc;
|
|
|
|
ASSERT(x < mapWidth, "withinLandingZone: x coord bigger than mapWidth");
|
|
ASSERT(y < mapHeight, "withinLandingZone: y coord bigger than mapHeight");
|
|
|
|
|
|
for (inc = 0; inc < MAX_NOGO_AREAS; inc++)
|
|
{
|
|
if ((x >= (UDWORD)sLandingZone[inc].x1 && x <= (UDWORD)sLandingZone[inc].x2) &&
|
|
(y >= (UDWORD)sLandingZone[inc].y1 && y <= (UDWORD)sLandingZone[inc].y2))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//returns the x coord for where the Transporter can land (for player 0)
|
|
UWORD getLandingX(SDWORD iPlayer)
|
|
{
|
|
ASSERT_OR_RETURN(0, iPlayer < MAX_NOGO_AREAS, "getLandingX: player %d out of range", iPlayer);
|
|
return (UWORD)world_coord((sLandingZone[iPlayer].x1 + (sLandingZone[iPlayer].x2 -
|
|
sLandingZone[iPlayer].x1) / 2));
|
|
}
|
|
|
|
//returns the y coord for where the Transporter can land
|
|
UWORD getLandingY(SDWORD iPlayer)
|
|
{
|
|
ASSERT_OR_RETURN(0, iPlayer < MAX_NOGO_AREAS, "getLandingY: player %d out of range", iPlayer);
|
|
return (UWORD)world_coord((sLandingZone[iPlayer].y1 + (sLandingZone[iPlayer].y2 -
|
|
sLandingZone[iPlayer].y1) / 2));
|
|
}
|
|
|
|
//returns the x coord for where the Transporter can land back at home base
|
|
UDWORD getHomeLandingX(void)
|
|
{
|
|
return map_coord(mission.homeLZ_X);
|
|
}
|
|
|
|
//returns the y coord for where the Transporter can land back at home base
|
|
UDWORD getHomeLandingY(void)
|
|
{
|
|
return map_coord(mission.homeLZ_Y);
|
|
}
|
|
|
|
void missionSetTransporterEntry(SDWORD iPlayer, SDWORD iEntryTileX, SDWORD iEntryTileY)
|
|
{
|
|
ASSERT(iPlayer < MAX_PLAYERS, "missionSetTransporterEntry: player %i too high", iPlayer);
|
|
|
|
if ((iEntryTileX > scrollMinX) && (iEntryTileX < scrollMaxX))
|
|
{
|
|
mission.iTranspEntryTileX[iPlayer] = (UWORD) iEntryTileX;
|
|
}
|
|
else
|
|
{
|
|
debug(LOG_SAVE, "entry point x %i outside scroll limits %i->%i", iEntryTileX, scrollMinX, scrollMaxX);
|
|
mission.iTranspEntryTileX[iPlayer] = (UWORD)(scrollMinX + EDGE_SIZE);
|
|
}
|
|
|
|
if ((iEntryTileY > scrollMinY) && (iEntryTileY < scrollMaxY))
|
|
{
|
|
mission.iTranspEntryTileY[iPlayer] = (UWORD) iEntryTileY;
|
|
}
|
|
else
|
|
{
|
|
debug(LOG_SAVE, "entry point y %i outside scroll limits %i->%i", iEntryTileY, scrollMinY, scrollMaxY);
|
|
mission.iTranspEntryTileY[iPlayer] = (UWORD)(scrollMinY + EDGE_SIZE);
|
|
}
|
|
}
|
|
|
|
void missionSetTransporterExit(SDWORD iPlayer, SDWORD iExitTileX, SDWORD iExitTileY)
|
|
{
|
|
ASSERT(iPlayer < MAX_PLAYERS, "missionSetTransporterExit: player %i too high", iPlayer);
|
|
|
|
if ((iExitTileX > scrollMinX) && (iExitTileX < scrollMaxX))
|
|
{
|
|
mission.iTranspExitTileX[iPlayer] = (UWORD) iExitTileX;
|
|
}
|
|
else
|
|
{
|
|
debug(LOG_SAVE, "entry point x %i outside scroll limits %i->%i", iExitTileX, scrollMinX, scrollMaxX);
|
|
mission.iTranspExitTileX[iPlayer] = (UWORD)(scrollMinX + EDGE_SIZE);
|
|
}
|
|
|
|
if ((iExitTileY > scrollMinY) && (iExitTileY < scrollMaxY))
|
|
{
|
|
mission.iTranspExitTileY[iPlayer] = (UWORD) iExitTileY;
|
|
}
|
|
else
|
|
{
|
|
debug(LOG_SAVE, "entry point y %i outside scroll limits %i->%i", iExitTileY, scrollMinY, scrollMaxY);
|
|
mission.iTranspExitTileY[iPlayer] = (UWORD)(scrollMinY + EDGE_SIZE);
|
|
}
|
|
}
|
|
|
|
void missionGetTransporterEntry(SDWORD iPlayer, UWORD *iX, UWORD *iY)
|
|
{
|
|
ASSERT(iPlayer < MAX_PLAYERS, "missionGetTransporterEntry: player %i too high", iPlayer);
|
|
|
|
*iX = (UWORD) world_coord(mission.iTranspEntryTileX[iPlayer]);
|
|
*iY = (UWORD) world_coord(mission.iTranspEntryTileY[iPlayer]);
|
|
}
|
|
|
|
void missionGetTransporterExit(SDWORD iPlayer, UDWORD *iX, UDWORD *iY)
|
|
{
|
|
ASSERT(iPlayer < MAX_PLAYERS, "missionGetTransporterExit: player %i too high", iPlayer);
|
|
|
|
*iX = world_coord(mission.iTranspExitTileX[iPlayer]);
|
|
*iY = world_coord(mission.iTranspExitTileY[iPlayer]);
|
|
}
|
|
|
|
/*update routine for mission details */
|
|
void missionTimerUpdate(void)
|
|
{
|
|
//don't bother with the time check if have 'cheated'
|
|
if (!mission.cheatTime)
|
|
{
|
|
//Want a mission timer on all types of missions now - AB 26/01/99
|
|
//only interested in off world missions (so far!) and if timer has been set
|
|
if (mission.time >= 0) //&& (
|
|
//mission.type == LDS_MKEEP || mission.type == LDS_MKEEP_LIMBO ||
|
|
//mission.type == LDS_MCLEAR || mission.type == LDS_BETWEEN))
|
|
{
|
|
//check if time is up
|
|
if ((SDWORD)(gameTime - mission.startTime) > mission.time)
|
|
{
|
|
//the script can call the end game cos have failed!
|
|
eventFireCallbackTrigger((TRIGGER_TYPE)CALL_MISSION_TIME);
|
|
triggerEvent(TRIGGER_MISSION_TIMEOUT);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Remove any objects left ie walls,structures and droids that are not the selected player.
|
|
//
|
|
void missionDestroyObjects(void)
|
|
{
|
|
DROID *psDroid;
|
|
STRUCTURE *psStruct;
|
|
UBYTE Player, i;
|
|
|
|
debug(LOG_SAVE, "called");
|
|
proj_FreeAllProjectiles();
|
|
for (Player = 0; Player < MAX_PLAYERS; Player++)
|
|
{
|
|
if (Player != selectedPlayer)
|
|
{
|
|
// AI player, clear out old data
|
|
|
|
psDroid = apsDroidLists[Player];
|
|
|
|
while (psDroid != NULL)
|
|
{
|
|
DROID *psNext = psDroid->psNext;
|
|
removeDroidBase(psDroid);
|
|
psDroid = psNext;
|
|
}
|
|
|
|
//clear out the mission lists as well to make sure no Tranporters exist
|
|
apsDroidLists[Player] = mission.apsDroidLists[Player];
|
|
psDroid = apsDroidLists[Player];
|
|
|
|
while (psDroid != NULL)
|
|
{
|
|
DROID *psNext = psDroid->psNext;
|
|
|
|
//make sure its died flag is not set since we've swapped the apsDroidList pointers over
|
|
psDroid->died = false;
|
|
removeDroidBase(psDroid);
|
|
psDroid = psNext;
|
|
}
|
|
mission.apsDroidLists[Player] = NULL;
|
|
|
|
psStruct = apsStructLists[Player];
|
|
|
|
while (psStruct != NULL)
|
|
{
|
|
STRUCTURE *psNext = psStruct->psNext;
|
|
removeStruct(psStruct, true);
|
|
psStruct = psNext;
|
|
}
|
|
}
|
|
}
|
|
|
|
// human player, check that we do not reference the cleared out data
|
|
Player = selectedPlayer;
|
|
|
|
psDroid = apsDroidLists[Player];
|
|
while (psDroid != NULL)
|
|
{
|
|
|
|
if (psDroid->psBaseStruct && psDroid->psBaseStruct->died)
|
|
{
|
|
setDroidBase(psDroid, NULL);
|
|
}
|
|
for (i = 0; i < DROID_MAXWEAPS; i++)
|
|
{
|
|
if (psDroid->psActionTarget[i] && psDroid->psActionTarget[i]->died)
|
|
{
|
|
setDroidActionTarget(psDroid, NULL, i);
|
|
// Clear action too if this requires a valid first action target
|
|
if (i == 0
|
|
&& psDroid->action != DACTION_MOVEFIRE
|
|
&& psDroid->action != DACTION_TRANSPORTIN
|
|
&& psDroid->action != DACTION_TRANSPORTOUT)
|
|
{
|
|
psDroid->action = DACTION_NONE;
|
|
}
|
|
}
|
|
}
|
|
if (psDroid->order.psObj && psDroid->order.psObj->died)
|
|
{
|
|
setDroidTarget(psDroid, NULL);
|
|
}
|
|
psDroid = psDroid->psNext;
|
|
}
|
|
|
|
psStruct = apsStructLists[Player];
|
|
while (psStruct != NULL)
|
|
{
|
|
for (i = 0; i < STRUCT_MAXWEAPS; i++)
|
|
{
|
|
if (psStruct->psTarget[i] && psStruct->psTarget[i]->died)
|
|
{
|
|
setStructureTarget(psStruct, NULL, i, ORIGIN_UNKNOWN);
|
|
}
|
|
}
|
|
psStruct = psStruct->psNext;
|
|
}
|
|
|
|
// FIXME: check that orders do not reference anything bad?
|
|
|
|
gameTime++; // Wonderful hack to ensure objects destroyed above get free'ed up by objmemUpdate.
|
|
objmemUpdate(); // Actually free objects removed above
|
|
}
|
|
|
|
void processPreviousCampDroids(void)
|
|
{
|
|
DROID *psDroid, *psNext;
|
|
|
|
// See if any are left
|
|
if (mission.apsDroidLists[selectedPlayer])
|
|
{
|
|
for (psDroid = mission.apsDroidLists[selectedPlayer]; psDroid != NULL; psDroid = psNext)
|
|
{
|
|
psNext = psDroid->psNext;
|
|
// We want to kill off all droids now! - AB 27/01/99
|
|
// KILL OFF TRANSPORTER
|
|
if (droidRemove(psDroid, mission.apsDroidLists))
|
|
{
|
|
addDroid(psDroid, apsDroidLists);
|
|
vanishDroid(psDroid);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//access functions for droidsToSafety flag - so we don't have to end the mission when a Transporter fly's off world
|
|
void setDroidsToSafetyFlag(bool set)
|
|
{
|
|
bDroidsToSafety = set;
|
|
}
|
|
|
|
bool getDroidsToSafetyFlag(void)
|
|
{
|
|
return bDroidsToSafety;
|
|
}
|
|
|
|
|
|
//access functions for bPlayCountDown flag - true = play coded mission count down
|
|
void setPlayCountDown(UBYTE set)
|
|
{
|
|
bPlayCountDown = set;
|
|
}
|
|
|
|
bool getPlayCountDown(void)
|
|
{
|
|
return bPlayCountDown;
|
|
}
|
|
|
|
|
|
//checks to see if the player has any droids (except Transporters left)
|
|
bool missionDroidsRemaining(UDWORD player)
|
|
{
|
|
for (DROID *psDroid = apsDroidLists[player]; psDroid != NULL; psDroid = psDroid->psNext)
|
|
{
|
|
if (psDroid->droidType != DROID_TRANSPORTER && psDroid->droidType != DROID_SUPERTRANSPORTER)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*called when a Transporter gets to the edge of the world and the droids are
|
|
being flown to safety. The droids inside the Transporter are placed into the
|
|
mission list for later use*/
|
|
void moveDroidsToSafety(DROID *psTransporter)
|
|
{
|
|
DROID *psDroid, *psNext;
|
|
|
|
ASSERT((psTransporter->droidType == DROID_TRANSPORTER || psTransporter->droidType == DROID_SUPERTRANSPORTER), "unit not a Transporter");
|
|
|
|
//move droids out of Transporter into mission list
|
|
for (psDroid = psTransporter->psGroup->psList; psDroid != NULL && psDroid != psTransporter; psDroid = psNext)
|
|
{
|
|
psNext = psDroid->psGrpNext;
|
|
psTransporter->psGroup->remove(psDroid);
|
|
//cam change add droid
|
|
psDroid->pos.x = INVALID_XY;
|
|
psDroid->pos.y = INVALID_XY;
|
|
addDroid(psDroid, mission.apsDroidLists);
|
|
}
|
|
//move the transporter into the mission list also
|
|
if (droidRemove(psTransporter, apsDroidLists))
|
|
{
|
|
addDroid(psTransporter, mission.apsDroidLists);
|
|
}
|
|
}
|
|
|
|
void clearMissionWidgets(void)
|
|
{
|
|
//remove any widgets that are up due to the missions
|
|
if (mission.time > 0)
|
|
{
|
|
intRemoveMissionTimer();
|
|
}
|
|
|
|
if (missionCanReEnforce())
|
|
{
|
|
intRemoveTransporterTimer();
|
|
}
|
|
|
|
intRemoveTransporterLaunch();
|
|
}
|
|
|
|
void resetMissionWidgets(void)
|
|
{
|
|
DROID *psDroid;
|
|
|
|
//add back any widgets that should be up due to the missions
|
|
if (mission.time > 0)
|
|
{
|
|
intAddMissionTimer();
|
|
//make sure its not flashing when added
|
|
stopMissionButtonFlash(IDTIMER_FORM);
|
|
}
|
|
|
|
if (missionCanReEnforce())
|
|
{
|
|
addTransporterTimerInterface();
|
|
}
|
|
//check not a typical reinforcement mission
|
|
else if (!missionForReInforcements())
|
|
{
|
|
for (psDroid = apsDroidLists[selectedPlayer]; psDroid != NULL; psDroid = psDroid->psNext)
|
|
{
|
|
if (psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER)
|
|
{
|
|
intAddTransporterLaunch(psDroid);
|
|
break;
|
|
}
|
|
}
|
|
/*if we got to the end without adding a transporter - there might be
|
|
one sitting in the mission list which is waiting to come back in*/
|
|
if (!psDroid)
|
|
{
|
|
for (psDroid = mission.apsDroidLists[selectedPlayer]; psDroid != NULL; psDroid = psDroid->psNext)
|
|
{
|
|
if ((psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER) &&
|
|
psDroid->action == DACTION_TRANSPORTWAITTOFLYIN)
|
|
{
|
|
intAddTransporterLaunch(psDroid);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void setCampaignNumber(UDWORD number)
|
|
{
|
|
ASSERT(number < 4, "Campaign Number too high!");
|
|
camNumber = number;
|
|
}
|
|
|
|
UDWORD getCampaignNumber(void)
|
|
{
|
|
return(camNumber);
|
|
}
|
|
|
|
/*deals with any selectedPlayer's transporters that are flying in when the
|
|
mission ends. bOffWorld is true if the Mission is currenly offWorld*/
|
|
void emptyTransporters(bool bOffWorld)
|
|
{
|
|
DROID *psTransporter, *psDroid, *psNext, *psNextTrans;
|
|
|
|
//see if there are any Transporters in the world
|
|
for (psTransporter = apsDroidLists[selectedPlayer]; psTransporter != NULL; psTransporter = psNextTrans)
|
|
{
|
|
psNextTrans = psTransporter->psNext;
|
|
if (psTransporter->droidType == DROID_TRANSPORTER || psTransporter->droidType == DROID_SUPERTRANSPORTER)
|
|
{
|
|
//if flying in, empty the contents
|
|
if (orderState(psTransporter, DORDER_TRANSPORTIN))
|
|
{
|
|
/* if we're offWorld, all we need to do is put the droids into the apsDroidList
|
|
and processMission() will assign them a location etc */
|
|
if (bOffWorld)
|
|
{
|
|
for (psDroid = psTransporter->psGroup->psList; psDroid != NULL
|
|
&& psDroid != psTransporter; psDroid = psNext)
|
|
{
|
|
psNext = psDroid->psGrpNext;
|
|
//take it out of the Transporter group
|
|
psTransporter->psGroup->remove(psDroid);
|
|
//add it back into current droid lists
|
|
addDroid(psDroid, apsDroidLists);
|
|
}
|
|
}
|
|
/* we're not offWorld so add to mission.apsDroidList to be
|
|
processed by the endMission function */
|
|
else
|
|
{
|
|
for (psDroid = psTransporter->psGroup->psList; psDroid != NULL
|
|
&& psDroid != psTransporter; psDroid = psNext)
|
|
{
|
|
psNext = psDroid->psGrpNext;
|
|
//take it out of the Transporter group
|
|
psTransporter->psGroup->remove(psDroid);
|
|
//add it back into current droid lists
|
|
addDroid(psDroid, mission.apsDroidLists);
|
|
}
|
|
}
|
|
//now kill off the Transporter
|
|
vanishDroid(psDroid);
|
|
}
|
|
}
|
|
}
|
|
//deal with any transporters that are waiting to come over
|
|
for (psTransporter = mission.apsDroidLists[selectedPlayer]; psTransporter !=
|
|
NULL; psTransporter = psTransporter->psNext)
|
|
{
|
|
if (psTransporter->droidType == DROID_TRANSPORTER || psTransporter->droidType == DROID_SUPERTRANSPORTER)
|
|
{
|
|
//for each droid within the transporter...
|
|
for (psDroid = psTransporter->psGroup->psList; psDroid != NULL
|
|
&& psDroid != psTransporter; psDroid = psNext)
|
|
{
|
|
psNext = psDroid->psGrpNext;
|
|
//take it out of the Transporter group
|
|
psTransporter->psGroup->remove(psDroid);
|
|
//add it back into mission droid lists
|
|
addDroid(psDroid, mission.apsDroidLists);
|
|
}
|
|
}
|
|
//don't need to destory the transporter here - it is dealt with by the endMission process
|
|
}
|
|
}
|
|
|
|
/*bCheating = true == start of cheat
|
|
bCheating = false == end of cheat */
|
|
void setMissionCheatTime(bool bCheating)
|
|
{
|
|
if (bCheating)
|
|
{
|
|
mission.cheatTime = gameTime;
|
|
}
|
|
else
|
|
{
|
|
//adjust the mission start time for the duration of the cheat!
|
|
mission.startTime += gameTime - mission.cheatTime;
|
|
mission.cheatTime = 0;
|
|
}
|
|
}
|