Airport/Source/Game.c

4343 lines
131 KiB
C

/* *************************************
* Includes
* *************************************/
#include "Game.h"
#include "Timer.h"
#include "LoadMenu.h"
#include "System.h"
#include "Camera.h"
#include "Aircraft.h"
#include "GameGui.h"
#include "EndAnimation.h"
#include "Sfx.h"
#include "Pad.h"
#include "Message.h"
/* *************************************
* Defines
* *************************************/
#define GAME_MAX_RUNWAYS 16
#define GAME_MAX_AIRCRAFT_PER_TILE 4
#define FLIGHT_DATA_INVALID_IDX 0xFF
#define MIN_MAP_COLUMNS 8
#define MAX_MAP_COLUMNS 32
#define LEVEL_HEADER_SIZE 64
#define COLUMNS_PER_TILESET 4
#define ROWS_PER_TILESET COLUMNS_PER_TILESET
#define LEVEL_MAGIC_NUMBER_SIZE 3
#define LEVEL_MAGIC_NUMBER_STRING "ATC"
#define LEVEL_TITLE_SIZE 24
#define TILE_MIRROR_FLAG (0x80)
#define GAME_INVALID_TILE_SELECTION ( (uint16_t)0xFFFF )
#define GAME_MINIMUM_PARKING_SPAWN_TIME (2 * TIMER_PRESCALER_1_SECOND) // 2 seconds
/* **************************************
* Structs and enums *
* *************************************/
typedef struct t_rwyentrydata
{
DIRECTION Direction;
uint16_t rwyEntryTile;
int8_t rwyStep;
uint16_t rwyHeader;
}TYPE_RWY_ENTRY_DATA;
typedef struct t_GameLevelBuffer_UVData
{
short u;
short v;
}TYPE_TILE_UV_DATA;
enum
{
MOUSE_W = 8,
MOUSE_H = 8,
MOUSE_X = (X_SCREEN_RESOLUTION >> 1),
MOUSE_Y = (Y_SCREEN_RESOLUTION >> 1),
MOUSE_X_2PLAYER = (X_SCREEN_RESOLUTION >> 2),
MOUSE_Y_2PLAYER = (Y_SCREEN_RESOLUTION >> 1)
};
enum
{
LOST_FLIGHT_PENALTY = 4000,
SCORE_REWARD_TAXIING = 200,
SCORE_REWARD_FINAL = 400,
SCORE_REWARD_UNLOADING = 300,
SCORE_REWARD_TAKEOFF = 200,
SCORE_REWARD_FINISH_FLIGHT = 1000
};
enum
{
UNBOARDING_KEY_SEQUENCE_EASY = 4,
UNBOARDING_KEY_SEQUENCE_MEDIUM = 6,
UNBOARDING_KEY_SEQUENCE_HARD = GAME_MAX_SEQUENCE_KEYS,
UNBOARDING_PASSENGERS_PER_SEQUENCE = 100
};
enum
{
TILE_GRASS,
TILE_ASPHALT_WITH_BORDERS,
TILE_WATER,
TILE_ASPHALT,
TILE_RWY_MID,
TILE_RWY_START_1,
TILE_RWY_START_2,
TILE_PARKING,
TILE_PARKING_2,
TILE_TAXIWAY_INTERSECT_GRASS,
TILE_TAXIWAY_GRASS,
TILE_TAXIWAY_CORNER_GRASS,
TILE_HALF_WATER_1,
TILE_HALF_WATER_2,
TILE_RWY_HOLDING_POINT,
TILE_RWY_HOLDING_POINT_2,
TILE_RWY_EXIT,
TILE_TAXIWAY_CORNER_GRASS_2,
TILE_TAXIWAY_4WAY_CROSSING,
TILE_RWY_EXIT_2,
LAST_TILE_TILESET1 = TILE_RWY_EXIT_2,
TILE_UNUSED_1,
TILE_TAXIWAY_CORNER_GRASS_3,
FIRST_TILE_TILESET2 = TILE_UNUSED_1,
LAST_TILE_TILESET2 = TILE_TAXIWAY_CORNER_GRASS_3
};
enum
{
SOUND_M1_INDEX,
SOUND_W1_INDEX,
MAX_RADIO_CHATTER_SOUNDS
}RADIO_CHATTER_VOICE_NUMBERS;
/* *************************************
* Local Prototypes
* *************************************/
static void GameInit(const TYPE_GAME_CONFIGURATION* const pGameCfg);
static void GameInitTileUVTable(void);
static bool GameExit(void);
static void GameLoadLevel(const char* path);
static bool GamePause(void);
static void GameFinished(const uint8_t i);
static void GameEmergencyMode(void);
static void GameCalculations(void);
static void GamePlayerHandler(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData);
static void GamePlayerAddWaypoint(TYPE_PLAYER* const ptrPlayer);
static void GamePlayerAddWaypoint_Ex(TYPE_PLAYER* const ptrPlayer, uint16_t tile);
static void GameGraphics(void);
static void GameRenderTerrainPrecalculations(TYPE_PLAYER* const ptrPlayer, const TYPE_FLIGHT_DATA* const ptrFlightData);
static void GameRenderTerrain(TYPE_PLAYER* const ptrPlayer);
static void GameClock(void);
static void GameClockFlights(const uint8_t i);
static void GameAircraftState(const uint8_t i);
static void GameActiveAircraft(const uint8_t i);
static void GameStateShowAircraft(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData);
static void GameSelectAircraftFromList(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData);
static void GameStateSelectRunway(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData);
static void GameStateSelectTaxiwayRunway(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData);
static void GameStateSelectTaxiwayParking(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData);
static void GameStateLockTarget(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData);
static TYPE_ISOMETRIC_POS GameSelectAircraft(TYPE_PLAYER* const ptrPlayer);
static void GameSelectAircraftWaypoint(TYPE_PLAYER* const ptrPlayer);
static void GameGetRunwayArray(void);
static void GameGetSelectedRunwayArray(uint16_t rwyHeader, uint16_t* rwyArray, size_t sz);
static void GameAssignRunwaytoAircraft(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData);
static bool GamePathToTile(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData);
static void GameDrawMouse(TYPE_PLAYER* const ptrPlayer);
static void GameStateUnboarding(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData);
static void GameGenerateUnboardingSequence(TYPE_PLAYER* const ptrPlayer);
static void GameCreateTakeoffWaypoints(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData, uint8_t aircraftIdx);
static void GameGetRunwayEntryTile(uint8_t aircraftIdx, TYPE_RWY_ENTRY_DATA* ptrRwyEntry);
static void GameActiveAircraftList(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData);
static void GameRemainingAircraft(const uint8_t i);
static void GameMinimumSpawnTimeout(void);
static void GameRenderBuildingAircraft(TYPE_PLAYER* const ptrPlayer);
static void GameGetAircraftTilemap(const uint8_t i);
static bool GameWaypointCheckExisting(TYPE_PLAYER* const ptrPlayer, uint16_t temp_tile);
static void GameDrawBackground(void);
static DIRECTION GameGetRunwayDirection(uint16_t rwyHeader);
static DIRECTION GameGetParkingDirection(uint16_t parkingTile);
/* *************************************
* Global Variables
* *************************************/
bool GameStartupFlag;
uint32_t GameScore;
/* *************************************
* Local Variables
* *************************************/
// Sprites
static GsSprite GameTilesetSpr;
static GsSprite GameTileset2Spr;
static GsSprite GamePlaneSpr;
static GsSprite GameMouseSpr;
static GsSprite GameBuildingSpr;
static uint16_t GameRwy[GAME_MAX_RUNWAYS];
static TYPE_FLIGHT_DATA FlightData;
static uint16_t GameUsedRwy[GAME_MAX_RUNWAYS];
static uint16_t GameSelectedTile;
static TYPE_TIMER* GameSpawnMinTime;
static bool spawnMinTimeFlag;
static bool aircraftCreated;
static bool GameAircraftCollisionFlag;
static uint8_t GameAircraftCollisionIdx;
static uint8_t GameAircraftTilemap[GAME_MAX_MAP_SIZE][GAME_MAX_AIRCRAFT_PER_TILE];
static TYPE_TILE_UV_DATA GameLevelBuffer_UVData[GAME_MAX_MAP_SIZE];
// Radio chatter
static SsVag ApproachSnds[MAX_RADIO_CHATTER_SOUNDS];
static SsVag TowerFinalSnds[MAX_RADIO_CHATTER_SOUNDS];
// Takeoff sounds
static SsVag TakeoffSnd;
// Beep sounds (taxiway/parking accept)
static SsVag BeepSnd;
// Instances for player-specific data
static TYPE_PLAYER PlayerData[MAX_PLAYERS] =
{
[PLAYER_ONE] =
{
.PadKeyPressed_Callback = &PadOneKeyPressed,
.PadKeyReleased_Callback = &PadOneKeyReleased,
.PadKeySinglePress_Callback = &PadOneKeySinglePress,
.PadDirectionKeyPressed_Callback = &PadOneDirectionKeyPressed,
.PadLastKeySinglePressed_Callback = &PadOneGetLastKeySinglePressed
},
[PLAYER_TWO] =
{
.PadKeyPressed_Callback = &PadTwoKeyPressed,
.PadKeyReleased_Callback = &PadTwoKeyReleased,
.PadDirectionKeyPressed_Callback = &PadTwoDirectionKeyPressed,
.PadKeySinglePress_Callback = &PadTwoKeySinglePress,
.PadLastKeySinglePressed_Callback = &PadTwoGetLastKeySinglePressed
}
};
static void* GamePltDest[] = {(TYPE_FLIGHT_DATA*)&FlightData };
static uint16_t levelBuffer[GAME_MAX_MAP_SIZE];
static uint8_t GameLevelColumns;
static uint16_t GameLevelSize;
static char GameLevelTitle[LEVEL_TITLE_SIZE];
//Game local time
static uint8_t GameHour;
static uint8_t GameMinutes;
//Local flag for two-player game mode. Obtained from Menu
static bool twoPlayers;
// Determines whether game has finished or not.
bool levelFinished;
/* ***************************************************************************************
*
* @name: void Game(TYPE_GAME_CONFIGURATION* pGameCfg)
*
* @author: Xavier Del Campo
*
* @brief:
* Game main loop. Called by "Menu" module.
*
* @remarks:
*
* ***************************************************************************************/
void Game(const TYPE_GAME_CONFIGURATION* const pGameCfg)
{
twoPlayers = pGameCfg->TwoPlayers;
GameInit(pGameCfg);
while (1)
{
if (GameExit())
{
break;
}
GameEmergencyMode();
GameCalculations();
GameGraphics();
if (GameStartupFlag)
{
GameStartupFlag = false;
}
}
GfxDisableSplitScreen();
EndAnimation();
}
/* ***************************************************************************************
*
* @name: bool GameExit(void)
*
* @author: Xavier Del Campo
*
* @brief:
* Evaluates special conditions which end current game and return to main menu.
*
* @returns:
* True if game has to be exitted, false otherwise.
*
* ***************************************************************************************/
static bool GameExit(void)
{
if (levelFinished)
{
// Exit game on level finished.
if (GameGuiFinishedDialog(&PlayerData[PLAYER_ONE]))
{
return true;
}
}
if (GamePause())
{
// Exit game if player desires to exit.
return true;
}
if (GameAircraftCollisionFlag)
{
GameGuiAircraftCollision(&PlayerData[PLAYER_ONE]);
return true;
}
return false;
}
/* ***************************************************************************************
*
* @name: bool GamePause(void)
*
* @author: Xavier Del Campo
*
* @brief:
* When PAD_START is pressed, it draws a rectangle on top of the screen and the game halts.
*
* @remarks:
*
* ***************************************************************************************/
static bool GamePause(void)
{
uint8_t i;
if (GameStartupFlag)
{
return false;
}
for (i = 0 ; i < MAX_PLAYERS ; i++)
{
const TYPE_PLAYER* const ptrPlayer = &PlayerData[i];
// Run player-specific functions for each player
if (ptrPlayer->Active)
{
//Serial_printf("Released callback = 0x%08X\n", ptrPlayer->PadKeySinglePress_Callback);
if (ptrPlayer->PadKeySinglePress_Callback(PAD_START))
{
Serial_printf("Player %d set pause_flag to true!\n",i);
// Blocking function:
// * Returns true if player pointed to by ptrPlayer wants to exit game
// * Returns false if player pointed to by ptrPlayer wants to resume game
return GameGuiPauseDialog(ptrPlayer);
}
}
}
return false;
}
/* ***************************************************************************************
*
* @name: void GameInit(void)
*
* @author: Xavier Del Campo
*
* @brief:
* Game basic parameters initialization.
*
* @remarks:
* Tilesets and buildings are only loaded on first game. Then, only PLT is loaded.
*
* ***************************************************************************************/
void GameInit(const TYPE_GAME_CONFIGURATION* const pGameCfg)
{
static const char* const GameFileList[] =
{
"DATA\\SPRITES\\TILESET1.TIM",
"DATA\\SPRITES\\TILESET2.TIM",
"DATA\\SPRITES\\GAMEPLN.TIM",
"DATA\\SPRITES\\MOUSE.TIM",
"DATA\\SPRITES\\BLDNGS1.TIM",
"DATA\\SOUNDS\\RCPW1A1.VAG",
"DATA\\SOUNDS\\RCPM1A1.VAG",
"DATA\\SOUNDS\\RCTM1F1.VAG",
"DATA\\SOUNDS\\TAKEOFF1.VAG",
"DATA\\SOUNDS\\BEEP.VAG"
};
static void* GameFileDest[] =
{
&GameTilesetSpr,
&GameTileset2Spr,
&GamePlaneSpr,
&GameMouseSpr,
&GameBuildingSpr,
&ApproachSnds[SOUND_M1_INDEX],
&ApproachSnds[SOUND_W1_INDEX],
&TowerFinalSnds[SOUND_M1_INDEX],
&TakeoffSnd,
&BeepSnd
};
uint8_t i;
static bool loaded;
GameStartupFlag = true;
// Has to be initialized before loading *.PLT files inside LoadMenu().
MessageInit();
if (loaded == false)
{
loaded = true;
LOAD_FILES(GameFileList, GameFileDest);
GameSpawnMinTime = TimerCreate(GAME_MINIMUM_PARKING_SPAWN_TIME, false, GameMinimumSpawnTimeout);
}
LoadMenu( &pGameCfg->PLTPath,
GamePltDest,
sizeof (char),
ARRAY_SIZE(GamePltDest));
GameLoadLevel(pGameCfg->LVLPath);
GameGuiInit();
memset(GameRwy, 0, GAME_MAX_RUNWAYS * sizeof (uint16_t) );
memset(GameUsedRwy, 0, GAME_MAX_RUNWAYS * sizeof (uint16_t) );
PlayerData[PLAYER_ONE].Active = true;
PlayerData[PLAYER_ONE].FlightDataPage = 0;
PlayerData[PLAYER_ONE].UnboardingSequenceIdx = 0;
PlayerData[PLAYER_ONE].ShowAircraftData = false;
PlayerData[PLAYER_ONE].SelectRunway = false;
PlayerData[PLAYER_ONE].SelectTaxiwayRunway = false;
PlayerData[PLAYER_ONE].SelectTaxiwayParking = false;
PlayerData[PLAYER_ONE].InvalidPath = false;
PlayerData[PLAYER_ONE].LockTarget = false;
PlayerData[PLAYER_ONE].Unboarding = false;
memset(PlayerData[PLAYER_ONE].UnboardingSequence, 0, GAME_MAX_SEQUENCE_KEYS * sizeof (unsigned short) );
memset(PlayerData[PLAYER_ONE].TileData, 0, GAME_MAX_MAP_SIZE * sizeof (TYPE_TILE_DATA));
PlayerData[PLAYER_TWO].Active = twoPlayers? true : false;
if (PlayerData[PLAYER_TWO].Active)
{
PlayerData[PLAYER_TWO].FlightDataPage = 0;
PlayerData[PLAYER_TWO].UnboardingSequenceIdx = 0;
PlayerData[PLAYER_TWO].ShowAircraftData = false;
PlayerData[PLAYER_TWO].SelectRunway = false;
PlayerData[PLAYER_TWO].SelectTaxiwayRunway = false;
PlayerData[PLAYER_TWO].SelectTaxiwayParking = false;
PlayerData[PLAYER_TWO].InvalidPath = false;
PlayerData[PLAYER_TWO].LockTarget = false;
PlayerData[PLAYER_TWO].Unboarding = false;
memset(PlayerData[PLAYER_TWO].UnboardingSequence, 0, GAME_MAX_SEQUENCE_KEYS * sizeof (unsigned short) );
memset(PlayerData[PLAYER_TWO].TileData, 0, GAME_MAX_MAP_SIZE * sizeof (TYPE_TILE_DATA));
// On 2-player mode, one player controls departure flights and
// other player controls arrival flights.
PlayerData[PLAYER_ONE].FlightDirection = DEPARTURE;
PlayerData[PLAYER_TWO].FlightDirection = ARRIVAL;
}
else
{
PlayerData[PLAYER_ONE].FlightDirection = DEPARTURE | ARRIVAL;
}
for (i = 0; i < MAX_PLAYERS ; i++)
{
CameraInit(&PlayerData[i]);
PlayerData[i].ShowAircraftData = false;
PlayerData[i].SelectRunway = false;
PlayerData[i].SelectTaxiwayRunway = false;
PlayerData[i].LockTarget = false;
PlayerData[i].SelectedAircraft = 0;
PlayerData[i].FlightDataPage = 0;
memset(&PlayerData[i].Waypoints, 0, sizeof (uint16_t) * PLAYER_MAX_WAYPOINTS);
PlayerData[i].WaypointIdx = 0;
PlayerData[i].LastWaypointIdx = 0;
PlayerData[i].RemainingAircraft = 0;
}
aircraftCreated = false;
GameAircraftCollisionFlag = false;
GameAircraftCollisionIdx = 0;
if (GameTwoPlayersActive())
{
GameMouseSpr.x = MOUSE_X_2PLAYER;
GameMouseSpr.y = MOUSE_Y_2PLAYER;
}
else
{
GameMouseSpr.x = MOUSE_X;
GameMouseSpr.y = MOUSE_Y;
}
GameMouseSpr.w = MOUSE_W;
GameMouseSpr.h = MOUSE_H;
GameMouseSpr.attribute = COLORMODE(COLORMODE_16BPP);
GameMouseSpr.r = NORMAL_LUMINANCE;
GameMouseSpr.g = NORMAL_LUMINANCE;
GameMouseSpr.b = NORMAL_LUMINANCE;
spawnMinTimeFlag = false;
GameScore = 0;
GameGetRunwayArray();
GameSelectedTile = 0;
levelFinished = false;
GameInitTileUVTable();
AircraftInit();
LoadMenuEnd();
GfxSetGlobalLuminance(0);
{
const uint32_t track = SystemRand(GAMEPLAY_FIRST_TRACK, GAMEPLAY_LAST_TRACK);
SfxPlayTrack(track);
}
}
/* ***************************************************************************************
*
* @name: void GameEmergencyMode(void)
*
* @author: Xavier Del Campo
*
*
* @brief:
* Draws a blue rectangle on top of the screen whenever one of the two active controllers
* (e.g.: pad1 on single player mode, pad1 || pad2 on two player mode) is disconnected.
*
* @remarks:
* See PSX_PollPad(), defined on psx.h, and Pad module for further information.
*
* ***************************************************************************************/
void GameEmergencyMode(void)
{
uint8_t i;
uint8_t disconnected_players = 0x00;
static bool (*const PadXConnected[MAX_PLAYERS])(void) =
{
[PLAYER_ONE] = &PadOneConnected,
[PLAYER_TWO] = &PadTwoConnected
};
enum
{
PAD_DISCONNECTED_TEXT_X = 48,
PAD_DISCONNECTED_TEXT_Y = 48,
PAD_DISCONNECTED_TEXT_Y_OFFSET_BITSHIFT = 5
};
do
{
bool enabled = false;
if (SystemGetEmergencyMode())
{
enum
{
ERROR_RECT_X = 32,
ERROR_RECT_W = X_SCREEN_RESOLUTION - (ERROR_RECT_X << 1),
ERROR_RECT_Y = 16,
ERROR_RECT_H = Y_SCREEN_RESOLUTION - (ERROR_RECT_Y << 1),
ERROR_RECT_R = 0,
ERROR_RECT_G = 32,
ERROR_RECT_B = NORMAL_LUMINANCE
};
static const GsRectangle errorRct =
{
.x = ERROR_RECT_X,
.w = ERROR_RECT_W,
.y = ERROR_RECT_Y,
.h = ERROR_RECT_H,
.r = ERROR_RECT_R,
.g = ERROR_RECT_G,
.b = ERROR_RECT_B
};
// One of the pads has been disconnected during gameplay
// Show an error screen until it is disconnected again.
GsSortCls(0,0,0);
GsSortRectangle((GsRectangle*)&errorRct);
for (i = 0; i < MAX_PLAYERS; i++)
{
if (disconnected_players & (1 << i) )
{
FontPrintText( &SmallFont,
PAD_DISCONNECTED_TEXT_X,
PAD_DISCONNECTED_TEXT_Y + (i << PAD_DISCONNECTED_TEXT_Y_OFFSET_BITSHIFT),
"Pad %s disconnected", i? "right" : "left" );
}
}
GfxDrawScene_Slow();
}
for (i = 0; i < MAX_PLAYERS; i++)
{
TYPE_PLAYER* const ptrPlayer = &PlayerData[i];
if (ptrPlayer->Active)
{
if (PadXConnected[i]() == false)
{
enabled = true;
disconnected_players |= 1 << i;
}
else
{
disconnected_players &= ~(1 << i);
}
}
}
SystemSetEmergencyMode(enabled);
} while (SystemGetEmergencyMode());
}
/* ***************************************************************************************
*
* @name: void GameGetAircraftTilemap(uint8_t i)
*
* @author: Xavier Del Campo
*
* @param:
* uint8_t i:
* Index for FlightData table.
*
* @brief:
* On each cycle, it creates a 2-dimensional array relating aircraft indexes against
* tile numbers.
*
* @remarks:
*
* ***************************************************************************************/
static void GameGetAircraftTilemap(const uint8_t i)
{
if (i == 0)
{
memset(GameAircraftTilemap, FLIGHT_DATA_INVALID_IDX, sizeof (GameAircraftTilemap) );
}
if (FlightData.State[i] != STATE_IDLE)
{
const uint16_t tileNr = AircraftGetTileFromFlightDataIndex(i);
uint8_t j;
for (j = 0; j < GAME_MAX_AIRCRAFT_PER_TILE; j++)
{
if (GameAircraftTilemap[tileNr][j] == FLIGHT_DATA_INVALID_IDX)
{
break;
}
}
GameAircraftTilemap[tileNr][j] = i;
}
}
/* ***************************************************************************************
*
* @name: void GameCalculations(void)
*
* @author: Xavier Del Campo
*
* @brief:
* First half of game execution. Executed when GPU is still drawing previous frame.
* Calculates all new states and values.
*
* @remarks:
* Since the GPU takes a long time to draw a frame, GameCalculations() should be used
* for all CPU-intensive tasks.
*
* ***************************************************************************************/
void GameCalculations(void)
{
uint8_t i;
GameClock();
// Set level finished flag. It will
// reset if at least one flight is still pending.
levelFinished = true;
for (i = 0; i < FlightData.nAircraft; i++)
{
GameFinished(i);
GameClockFlights(i);
GameAircraftState(i);
GameActiveAircraft(i);
GameRemainingAircraft(i);
GameGetAircraftTilemap(i);
}
MessageHandler();
AircraftHandler();
GameGuiCalculateSlowScore();
for (i = 0 ; i < MAX_PLAYERS ; i++)
{
// Run player-specific functions for each player
if (PlayerData[i].Active)
{
GamePlayerHandler(&PlayerData[i], &FlightData);
}
}
}
/* ***************************************************************************************
*
* @name: void GamePlayerHandler(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData)
*
* @author: Xavier Del Campo
*
* @param:
* TYPE_PLAYER* const ptrPlayer:
* Pointer to a player structure
*
* TYPE_FLIGHT_DATA* const ptrFlightData:
* In the end, pointer to FlightData data table, which contains
* information about all available flights.
*
* @brief:
* Calls all routines attached to a player.
*
* @remarks:
*
* ***************************************************************************************/
void GamePlayerHandler(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData)
{
ptrPlayer->SelectedTile = 0; // Reset selected tile if no states
// which use this are currently active.
ptrPlayer->InvalidPath = false; // Do the same thing for "InvalidPath".
// Recalculate ptrPlayer->SelectedAircraft. In case new aircraft appear, we may be pointing
// to a incorrect instance.
GameActiveAircraftList(ptrPlayer, ptrFlightData);
if (GameAircraftCollisionFlag)
{
TYPE_ISOMETRIC_POS IsoPos = AircraftGetIsoPos(GameAircraftCollisionIdx);
CameraMoveToIsoPos(ptrPlayer, IsoPos);
}
if (System1SecondTick())
{
GameGuiCalculateNextAircraftTime(ptrPlayer, ptrFlightData);
}
GameStateUnboarding(ptrPlayer, ptrFlightData);
GameStateLockTarget(ptrPlayer, ptrFlightData);
GameStateSelectRunway(ptrPlayer, ptrFlightData);
GameStateSelectTaxiwayRunway(ptrPlayer, ptrFlightData);
GameStateSelectTaxiwayParking(ptrPlayer, ptrFlightData);
GameStateShowAircraft(ptrPlayer, ptrFlightData);
CameraHandler(ptrPlayer);
GameRenderTerrainPrecalculations(ptrPlayer, ptrFlightData);
GameGuiActiveAircraftPage(ptrPlayer, ptrFlightData);
GameSelectAircraftFromList(ptrPlayer, ptrFlightData);
}
/* *******************************************************************
*
* @name: void GameClock(void)
*
* @author: Xavier Del Campo
*
* @brief:
* Handles game clock later rendered on GameGui.
*
* @remarks:
*
* *******************************************************************/
void GameClock(void)
{
if (System1SecondTick())
{
if (++GameMinutes >= 60)
{
GameMinutes = 0;
if (++GameHour >= 24)
{
GameHour = 0;
}
}
}
}
/* *******************************************************************
*
* @name: void GameClockFlights(uint8_t i)
*
* @author: Xavier Del Campo
*
* @param:
* uint8_t i:
* Index for FlightData table.
*
* @brief:
* Handles hours/minutes values for all active aircraft.
*
* @remarks:
*
* *******************************************************************/
static void GameClockFlights(const uint8_t i)
{
if (System1SecondTick())
{
if (FlightData.State[i] != STATE_IDLE)
{
if (FlightData.RemainingTime[i] != 0)
{
FlightData.RemainingTime[i]--;
}
}
else
{
if ((FlightData.Minutes[i] == 0)
&&
FlightData.Hours[i])
{
FlightData.Minutes[i] = 60;
FlightData.Hours[i]--;
}
if (FlightData.Minutes[i])
{
FlightData.Minutes[i]--;
}
}
}
}
/* *******************************************************************
*
* @name: void GameGraphics(void)
*
* @author: Xavier Del Campo
*
* @brief:
* Second half of game execution. Once GameCalculations() has ended,
* states and new values have been calculated and all primitives are
* rendered depending on the obtained results.
*
* @remarks:
* It is advisable to keep CPU usage here, as once this function is
* entered, GPU is waiting for primitive data. Always try to move
* CPU-intensive operations to GameCalculations().
*
* *******************************************************************/
void GameGraphics(void)
{
uint8_t i;
bool split_screen = false;
// Caution: blocking function!
MessageRender();
if (twoPlayers)
{
split_screen = true;
}
if (GfxGetGlobalLuminance() < NORMAL_LUMINANCE)
{
// Fading from black effect on startup.
GfxIncreaseGlobalLuminance(1);
}
for (i = 0; i < MAX_PLAYERS ; i++)
{
TYPE_PLAYER* const ptrPlayer = &PlayerData[i];
if (ptrPlayer->Active)
{
if (split_screen)
{
GfxSetSplitScreen(i);
}
// Draw half split screen for each player
// only if 2-player mode is active. Else, render
// the whole screen as usual.
// Render background first.
GameDrawBackground();
// Then ground tiles must be rendered.
GameRenderTerrain(ptrPlayer);
// Ground tiles are now rendered. Now, depending on building/aircraft
// positions, determine in what order they should be rendered.
GameRenderBuildingAircraft(ptrPlayer);
GameGuiAircraftList(ptrPlayer, &FlightData);
GameGuiShowPassengersLeft(ptrPlayer);
GameDrawMouse(ptrPlayer);
GameGuiDrawUnboardingSequence(ptrPlayer);
if (split_screen)
{
GfxDrawScene_NoSwap();
while (GsIsDrawing());
}
}
}
// Avoid changing drawing environment twice on 1-player mode
// as it doesn't make any sense.
if (split_screen)
{
GfxDisableSplitScreen();
}
// Draw common elements for both players (messages, clock...)
GameGuiBubble(&FlightData);
GameGuiClock(GameHour,GameMinutes);
GameGuiShowScore();
if (split_screen)
{
GfxDrawScene_NoSwap();
}
GfxDrawScene();
}
/* *******************************************************************
*
* @name: void GameDrawBackground(void)
*
* @author: Xavier Del Campo
*
* @brief:
* Draws the background used for main gameplay.
*
* @remarks:
* Must be called before rendering anything else on screen!
*
* *******************************************************************/
static void GameDrawBackground(void)
{
enum
{
BG_POLY4_R0 = 0,
BG_POLY4_G0 = 0,
BG_POLY4_B0 = 16,
BG_POLY4_R1 = 0,
BG_POLY4_G1 = 0,
BG_POLY4_B1 = 16,
BG_POLY4_R2 = 0,
BG_POLY4_G2 = 0,
BG_POLY4_B2 = 80,
BG_POLY4_R3 = 0,
BG_POLY4_G3 = 0,
BG_POLY4_B3 = 80
};
static GsGPoly4 BgPoly4 =
{
.x[0] = 0,
.x[2] = 0,
.y[0] = 0,
.y[1] = 0,
.r[0] = BG_POLY4_R0,
.g[0] = BG_POLY4_G0,
.b[0] = BG_POLY4_B0,
.r[1] = BG_POLY4_R1,
.g[1] = BG_POLY4_G1,
.b[1] = BG_POLY4_B1,
.r[2] = BG_POLY4_R2,
.g[2] = BG_POLY4_G2,
.b[2] = BG_POLY4_B2,
.r[3] = BG_POLY4_R3,
.g[3] = BG_POLY4_G3,
.b[3] = BG_POLY4_B3
};
BgPoly4.x[1] = GfxGetDrawEnvWidth();
BgPoly4.x[3] = BgPoly4.x[1];
BgPoly4.y[2] = GfxGetDrawEnvHeight();
BgPoly4.y[3] = BgPoly4.y[2];
GsSortGPoly4((GsGPoly4*)&BgPoly4);
}
/* *******************************************************************
*
* @name: void GameRenderBuildingAircraft(TYPE_PLAYER* const ptrPlayer)
*
* @author: Xavier Del Campo
*
* @param:
* TYPE_PLAYER* const ptrPlayer:
* Pointer to player data structure.
*
* @brief:
* Determines rendering order depending on building/aircraft
* isometric position data.
*
* @remarks:
*
* *******************************************************************/
void GameRenderBuildingAircraft(TYPE_PLAYER* const ptrPlayer)
{
enum
{
BUILDING_NONE,
BUILDING_HANGAR,
BUILDING_ILS,
BUILDING_ATC_TOWER,
BUILDING_ATC_LOC,
BUILDING_TERMINAL,
BUILDING_TERMINAL_2,
BUILDING_GATE,
LAST_BUILDING = BUILDING_GATE,
MAX_BUILDING_ID
};
enum
{
BUILDING_ATC_LOC_OFFSET_X = TILE_SIZE >> 1,
BUILDING_ATC_LOC_OFFSET_Y = TILE_SIZE >> 1,
BUILDING_ILS_OFFSET_X = 0,
BUILDING_ILS_OFFSET_Y = 0,
BUILDING_GATE_OFFSET_X = (TILE_SIZE >> 1) - 4,
BUILDING_GATE_OFFSET_Y = 0,
BUILDING_HANGAR_OFFSET_X = 4,
BUILDING_HANGAR_OFFSET_Y = TILE_SIZE >> 1,
BUILDING_TERMINAL_OFFSET_X = 0,
BUILDING_TERMINAL_OFFSET_Y = TILE_SIZE >> 1,
BUILDING_TERMINAL_2_OFFSET_X = BUILDING_TERMINAL_OFFSET_X,
BUILDING_TERMINAL_2_OFFSET_Y = BUILDING_TERMINAL_OFFSET_Y,
BUILDING_ATC_TOWER_OFFSET_X = TILE_SIZE >> 2,
BUILDING_ATC_TOWER_OFFSET_Y = TILE_SIZE >> 1,
};
enum
{
BUILDING_ILS_U = 34,
BUILDING_ILS_V = 0,
BUILDING_ILS_W = 24,
BUILDING_ILS_H = 34,
BUILDING_GATE_U = 0,
BUILDING_GATE_V = 70,
BUILDING_GATE_W = 28,
BUILDING_GATE_H = 25,
BUILDING_HANGAR_U = 0,
BUILDING_HANGAR_V = 0,
BUILDING_HANGAR_W = 34,
BUILDING_HANGAR_H = 28,
BUILDING_TERMINAL_U = 0,
BUILDING_TERMINAL_V = 34,
BUILDING_TERMINAL_W = 51,
BUILDING_TERMINAL_H = 36,
BUILDING_TERMINAL_2_U = 51,
BUILDING_TERMINAL_2_V = BUILDING_TERMINAL_V,
BUILDING_TERMINAL_2_W = BUILDING_TERMINAL_W,
BUILDING_TERMINAL_2_H = BUILDING_TERMINAL_H,
BUILDING_ATC_TOWER_U = 58,
BUILDING_ATC_TOWER_V = 0,
BUILDING_ATC_TOWER_W = 29,
BUILDING_ATC_TOWER_H = 34,
BUILDING_ATC_LOC_U = 87,
BUILDING_ATC_LOC_V = 0,
BUILDING_ATC_LOC_W = 10,
BUILDING_ATC_LOC_H = 34
};
enum
{
BUILDING_ILS_ORIGIN_X = 10,
BUILDING_ILS_ORIGIN_Y = 22,
BUILDING_GATE_ORIGIN_X = 20,
BUILDING_GATE_ORIGIN_Y = 8,
BUILDING_TERMINAL_ORIGIN_X = 20,
BUILDING_TERMINAL_ORIGIN_Y = 11,
BUILDING_TERMINAL_2_ORIGIN_X = BUILDING_TERMINAL_ORIGIN_X,
BUILDING_TERMINAL_2_ORIGIN_Y = BUILDING_TERMINAL_ORIGIN_Y,
BUILDING_HANGAR_ORIGIN_X = 16,
BUILDING_HANGAR_ORIGIN_Y = 12,
BUILDING_ATC_TOWER_ORIGIN_X = 12,
BUILDING_ATC_TOWER_ORIGIN_Y = 20,
BUILDING_ATC_LOC_ORIGIN_X = 6,
BUILDING_ATC_LOC_ORIGIN_Y = 32
};
static const struct
{
TYPE_ISOMETRIC_POS IsoPos; // Offset inside tile
short orig_x; // Coordinate X origin inside building sprite
short orig_y; // Coordinate Y origin inside building sprite
short w; // Building width
short h; // Building height
short u; // Building X offset inside texture page
short v; // Building Y offset inside texture page
} GameBuildingData[MAX_BUILDING_ID] =
{
[BUILDING_GATE] =
{
.IsoPos.x = BUILDING_GATE_OFFSET_X,
.IsoPos.y = BUILDING_GATE_OFFSET_Y,
.orig_x = BUILDING_GATE_ORIGIN_X,
.orig_y = BUILDING_GATE_ORIGIN_Y,
.u = BUILDING_GATE_U,
.v = BUILDING_GATE_V,
.w = BUILDING_GATE_W,
.h = BUILDING_GATE_H,
// z coordinate set to 0 by default.
},
[BUILDING_ATC_LOC] =
{
.IsoPos.x = BUILDING_ATC_LOC_OFFSET_X,
.IsoPos.y = BUILDING_ATC_LOC_OFFSET_Y,
.orig_x = BUILDING_ATC_LOC_ORIGIN_X,
.orig_y = BUILDING_ATC_LOC_ORIGIN_Y,
.u = BUILDING_ATC_LOC_U,
.v = BUILDING_ATC_LOC_V,
.w = BUILDING_ATC_LOC_W,
.h = BUILDING_ATC_LOC_H,
// z coordinate set to 0 by default.
},
[BUILDING_ILS] =
{
.IsoPos.x = BUILDING_ILS_OFFSET_X,
.IsoPos.y = BUILDING_ILS_OFFSET_Y,
// z coordinate set to 0 by default.
.orig_x = BUILDING_ILS_ORIGIN_X,
.orig_y = BUILDING_ILS_ORIGIN_Y,
.u = BUILDING_ILS_U,
.v = BUILDING_ILS_V,
.w = BUILDING_ILS_W,
.h = BUILDING_ILS_H,
},
[BUILDING_HANGAR] =
{
// BUILDING_HANGAR coordinates inside tile.
.IsoPos.x = BUILDING_HANGAR_OFFSET_X,
.IsoPos.y = BUILDING_HANGAR_OFFSET_Y,
// z coordinate set to 0 by default.
.orig_x = BUILDING_HANGAR_ORIGIN_X,
.orig_y = BUILDING_HANGAR_ORIGIN_Y,
.u = BUILDING_HANGAR_U,
.v = BUILDING_HANGAR_V,
.w = BUILDING_HANGAR_W,
.h = BUILDING_HANGAR_H,
},
[BUILDING_TERMINAL] =
{
// BUILDING_TERMINAL coordinates inside tile.
.IsoPos.x = BUILDING_TERMINAL_OFFSET_X,
.IsoPos.y = BUILDING_TERMINAL_OFFSET_Y,
// z coordinate set to 0 by default.
.orig_x = BUILDING_TERMINAL_ORIGIN_X,
.orig_y = BUILDING_TERMINAL_ORIGIN_Y,
.u = BUILDING_TERMINAL_U,
.v = BUILDING_TERMINAL_V,
.w = BUILDING_TERMINAL_W,
.h = BUILDING_TERMINAL_H,
},
[BUILDING_TERMINAL_2] =
{
// BUILDING_TERMINAL_2 coordinates inside tile.
.IsoPos.x = BUILDING_TERMINAL_2_OFFSET_X,
.IsoPos.y = BUILDING_TERMINAL_2_OFFSET_Y,
// z coordinate set to 0 by default.
.orig_x = BUILDING_TERMINAL_2_ORIGIN_X,
.orig_y = BUILDING_TERMINAL_2_ORIGIN_Y,
.u = BUILDING_TERMINAL_2_U,
.v = BUILDING_TERMINAL_2_V,
.w = BUILDING_TERMINAL_2_W,
.h = BUILDING_TERMINAL_2_H,
},
[BUILDING_ATC_TOWER] =
{
// BUILDING_ATC_TOWER coordinates inside tile.
.IsoPos.x = BUILDING_ATC_TOWER_OFFSET_X,
.IsoPos.y = BUILDING_ATC_TOWER_OFFSET_Y,
// z coordinate set to 0 by default.
.orig_x = BUILDING_ATC_TOWER_ORIGIN_X,
.orig_y = BUILDING_ATC_TOWER_ORIGIN_Y,
.u = BUILDING_ATC_TOWER_U,
.v = BUILDING_ATC_TOWER_V,
.w = BUILDING_ATC_TOWER_W,
.h = BUILDING_ATC_TOWER_H,
},
[BUILDING_GATE] =
{
// BUILDING_GATE coordinates inside tile.
.IsoPos.x = BUILDING_GATE_OFFSET_X,
.IsoPos.y = BUILDING_GATE_OFFSET_Y,
// z coordinate set to 0 by default.
.orig_x = BUILDING_GATE_ORIGIN_X,
.orig_y = BUILDING_GATE_ORIGIN_Y,
.u = BUILDING_GATE_U,
.v = BUILDING_GATE_V,
.w = BUILDING_GATE_W,
.h = BUILDING_GATE_H,
},
};
uint16_t tileNr;
uint8_t rows = 0;
uint8_t columns = 0;
for (tileNr = 0; tileNr < GameLevelSize; tileNr++)
{
// Building data is stored in levelBuffer MSB. LSB is dedicated to tile data.
uint8_t CurrentBuilding = (uint8_t)(levelBuffer[tileNr] >> 8);
uint8_t j;
uint8_t k;
uint8_t AircraftRenderOrder[GAME_MAX_AIRCRAFT_PER_TILE];
short Aircraft_Y_Data[GAME_MAX_AIRCRAFT_PER_TILE];
memset(AircraftRenderOrder, FLIGHT_DATA_INVALID_IDX, sizeof (AircraftRenderOrder) );
for (j = 0; j < GAME_MAX_AIRCRAFT_PER_TILE; j++)
{
// Fill with 0x7FFF (maximum 16-bit positive value).
Aircraft_Y_Data[j] = 0x7FFF;
}
for (j = 0; j < GAME_MAX_AIRCRAFT_PER_TILE; j++)
{
uint8_t AircraftIdx = GameAircraftTilemap[tileNr][j];
TYPE_ISOMETRIC_POS aircraftIsoPos = AircraftGetIsoPos(AircraftIdx);
if (AircraftIdx == FLIGHT_DATA_INVALID_IDX)
{
// No more aircraft on this tile.
break;
}
//DEBUG_PRINT_VAR(aircraftIsoPos.y);
for (k = 0; k < GAME_MAX_AIRCRAFT_PER_TILE; k++)
{
if (aircraftIsoPos.y < Aircraft_Y_Data[k])
{
uint8_t idx;
for (idx = k; idx < (GAME_MAX_AIRCRAFT_PER_TILE - 1); idx++)
{
// Move previous Y values to the right.
Aircraft_Y_Data[idx + 1] = Aircraft_Y_Data[idx];
AircraftRenderOrder[idx + 1] = AircraftRenderOrder[idx];
}
Aircraft_Y_Data[k] = aircraftIsoPos.y;
AircraftRenderOrder[k] = AircraftIdx;
break;
}
}
}
if (CurrentBuilding == BUILDING_NONE)
{
for (k = 0; k < GAME_MAX_AIRCRAFT_PER_TILE; k++)
{
AircraftRender(ptrPlayer, AircraftRenderOrder[k]);
}
}
else
{
// Determine rendering order depending on Y value.
short x_bldg_offset = GameBuildingData[CurrentBuilding].IsoPos.x;
short y_bldg_offset = GameBuildingData[CurrentBuilding].IsoPos.y;
short z_bldg_offset = GameBuildingData[CurrentBuilding].IsoPos.z;
short orig_u = GameBuildingSpr.u;
short orig_v = GameBuildingSpr.v;
TYPE_ISOMETRIC_POS buildingIsoPos =
{
.x = (columns << (TILE_SIZE_BIT_SHIFT)) + x_bldg_offset,
.y = (rows << (TILE_SIZE_BIT_SHIFT)) + y_bldg_offset,
.z = z_bldg_offset
};
// Isometric -> Cartesian conversion
TYPE_CARTESIAN_POS buildingCartPos = GfxIsometricToCartesian(&buildingIsoPos);
bool buildingDrawn = false;
// Define new coordinates for building.
GameBuildingSpr.x = buildingCartPos.x - GameBuildingData[CurrentBuilding].orig_x;
GameBuildingSpr.y = buildingCartPos.y - GameBuildingData[CurrentBuilding].orig_y;
GameBuildingSpr.u = orig_u + GameBuildingData[CurrentBuilding].u;
GameBuildingSpr.v = orig_v + GameBuildingData[CurrentBuilding].v;
GameBuildingSpr.w = GameBuildingData[CurrentBuilding].w;
GameBuildingSpr.h = GameBuildingData[CurrentBuilding].h;
CameraApplyCoordinatesToSprite(ptrPlayer, &GameBuildingSpr);
for (k = 0; k < GAME_MAX_AIRCRAFT_PER_TILE; k++)
{
if (AircraftRenderOrder[k] == FLIGHT_DATA_INVALID_IDX)
{
if (buildingDrawn == false)
{
GfxSortSprite(&GameBuildingSpr);
GameBuildingSpr.u = orig_u;
GameBuildingSpr.v = orig_v;
buildingDrawn = true;
}
break;
}
if (Aircraft_Y_Data[k] < buildingIsoPos.y)
{
AircraftRender(ptrPlayer, AircraftRenderOrder[k]);
}
else
{
if (buildingDrawn == false)
{
GfxSortSprite(&GameBuildingSpr);
GameBuildingSpr.u = orig_u;
GameBuildingSpr.v = orig_v;
buildingDrawn = true;
}
AircraftRender(ptrPlayer, AircraftRenderOrder[k]);
}
}
}
if (columns < (GameLevelColumns - 1) )
{
columns++;
}
else
{
rows++;
columns = 0;
}
}
}
/* *******************************************************************
*
* @name: void GameLoadLevel(void)
*
* @author: Xavier Del Campo
*
* @brief:
* Loads and parses *.LVL data.
*
*
* @remarks:
* Filepath for *.LVL is given by GameLevelList[0]. Do NOT ever move
* it from there to avoid problems!
*
* *******************************************************************/
static void GameLoadLevel(const char* path)
{
uint8_t i = 0;
uint8_t* ptrBuffer;
char LevelHeader[LEVEL_MAGIC_NUMBER_SIZE + 1];
/* TODO - Very important */
// Map contents (that means, without header) should be copied to levelBuffer
// Header treatment (magic number, map size, map title...) should be done
// using System's file buffer.
if (SystemLoadFile((char*)path) == false)
{
return;
}
ptrBuffer = SystemGetBufferAddress();
//SystemLoadFileToBuffer(GameLevelList[0],levelBuffer,GAME_MAX_MAP_SIZE);
memset(LevelHeader,0, LEVEL_MAGIC_NUMBER_SIZE + 1);
memmove(LevelHeader,ptrBuffer,LEVEL_MAGIC_NUMBER_SIZE);
LevelHeader[LEVEL_MAGIC_NUMBER_SIZE] = '\0';
Serial_printf("Level header: %s\n",LevelHeader);
if (strncmp(LevelHeader,LEVEL_MAGIC_NUMBER_STRING,LEVEL_MAGIC_NUMBER_SIZE) != 0)
{
Serial_printf("Invalid level header! Read \"%s\" instead of " LEVEL_MAGIC_NUMBER_STRING "\n", LevelHeader);
return;
}
i += LEVEL_MAGIC_NUMBER_SIZE;
GameLevelColumns = ptrBuffer[i++];
Serial_printf("Level size: %d\n",GameLevelColumns);
if ( (GameLevelColumns < MIN_MAP_COLUMNS)
||
(GameLevelColumns > MAX_MAP_COLUMNS) )
{
Serial_printf("Invalid map size! Value: %d\n",GameLevelColumns);
return;
}
GameLevelSize = GameLevelColumns * GameLevelColumns;
memset(GameLevelTitle,0,LEVEL_TITLE_SIZE);
memmove(GameLevelTitle,&ptrBuffer[i],LEVEL_TITLE_SIZE);
Serial_printf("Game level title: %s\n",GameLevelTitle);
i += LEVEL_TITLE_SIZE;
memset(levelBuffer, 0, GAME_MAX_MAP_SIZE);
i = LEVEL_HEADER_SIZE;
{
size_t j;
size_t k;
for (j = LEVEL_HEADER_SIZE, k = 0; k < GameLevelSize; j += sizeof (uint16_t), k++)
{
levelBuffer[k] = ptrBuffer[j + 1];
levelBuffer[k] |= (ptrBuffer[j] << 8);
}
}
}
/* ******************************************************************************************
*
* @name: void GameAircraftState(uint8_t i)
*
* @author: Xavier Del Campo
*
* @param:
* uint8_t i:
* Index for FlightData table.
*
* @brief:
* It determines what state should be applied to aircraft when spawn timer expires.
*
* @remarks:
* This is where TYPE_FLIGHT_DATA is transferred to TYPE_AIRCRAFT on departure.
*
* ******************************************************************************************/
static void GameAircraftState(const uint8_t i)
{
if (FlightData.Finished[i] == false)
{
if ((FlightData.Hours[i] == 0)
&&
(FlightData.Minutes[i] == 0)
&&
(FlightData.State[i] == STATE_IDLE)
&&
(FlightData.RemainingTime[i] != 0))
{
if (spawnMinTimeFlag == false)
{
if ((FlightData.FlightDirection[i] == DEPARTURE)
&&
FlightData.Parking[i])
{
uint8_t j;
bool bParkingBusy = false;
for (j = 0; j < FlightData.nAircraft; j++)
{
TYPE_AIRCRAFT_DATA* ptrAircraft = AircraftFromFlightDataIndex(j);
if (ptrAircraft != NULL)
{
if (ptrAircraft->State != STATE_IDLE)
{
const uint16_t* const targets = AircraftGetTargets(j);
if (targets != NULL)
{
const uint16_t tile = AircraftGetTileFromFlightDataIndex(j);
if (tile == FlightData.Parking[i])
{
bParkingBusy = true;
}
else if (SystemContains_u16(FlightData.Parking[i], targets, AIRCRAFT_MAX_TARGETS))
{
bParkingBusy = true;
}
}
}
}
}
if (bParkingBusy == false)
{
uint16_t target[2] = {0};
// Arrays are copied to AircraftAddNew, so we create a first and only
// target which is the parking tile itself, and the second element
// is just the NULL character.
// Not an ideal solution, but the best one currently available.
FlightData.State[i] = STATE_PARKED;
aircraftCreated = true;
// Create notification request for incoming aircraft
GameGuiBubbleShow();
target[0] = FlightData.Parking[i];
if (AircraftAddNew(&FlightData, i, target, GameGetParkingDirection(levelBuffer[target[0]])) == false)
{
Serial_printf("Exceeded maximum aircraft number!\n");
return;
}
}
}
else if (FlightData.FlightDirection[i] == ARRIVAL)
{
const uint32_t idx = SystemRand(SOUND_M1_INDEX, MAX_RADIO_CHATTER_SOUNDS - 1);
FlightData.State[i] = STATE_APPROACH;
aircraftCreated = true;
// Play chatter sound.
SfxPlaySound(&ApproachSnds[idx]);
// Create notification request for incoming aircraft
GameGuiBubbleShow();
}
}
}
else if ( (FlightData.State[i] != STATE_IDLE)
&&
(FlightData.RemainingTime[i] == 0))
{
// Player(s) lost a flight!
GameRemoveFlight(i, false);
}
}
}
static void GameInitTileUVTable(void)
{
uint16_t i;
memset(GameLevelBuffer_UVData, 0, sizeof (GameLevelBuffer_UVData));
for (i = 0 ; i < GameLevelSize; i++)
{
uint8_t CurrentTile = (uint8_t)(levelBuffer[i] & 0x007F); // Remove building data
// and mirror flag.
if (CurrentTile >= FIRST_TILE_TILESET2)
{
CurrentTile -= FIRST_TILE_TILESET2;
}
GameLevelBuffer_UVData[i].u = (short)(CurrentTile % COLUMNS_PER_TILESET) << TILE_SIZE_BIT_SHIFT;
GameLevelBuffer_UVData[i].v = (short)(CurrentTile / COLUMNS_PER_TILESET) * TILE_SIZE_H;
}
}
/* ******************************************************************************************
*
* @name: void GameRenderTerrainPrecalculations(TYPE_PLAYER* const ptrPlayer)
*
* @author: Xavier Del Campo
*
* @param:
* TYPE_PLAYER* const ptrPlayer:
* Pointer to a player structure
*
* TYPE_FLIGHT_DATA* const ptrFlightData:
* In the end, pointer to FlightData data table, which contains
* information about all available flights.
*
* @brief:
* Reads current player states, precalculates RGB/XY/visibilty data and saves it into
* lookup tables which will be then used on GameRenderTerrain().
*
* @remarks:
* Tiles are usually rendered with normal RGB values unless parking/runway is busy
* or ptrPlayer->InvalidPath.
*
* ******************************************************************************************/
static void GameRenderTerrainPrecalculations(TYPE_PLAYER* const ptrPlayer, const TYPE_FLIGHT_DATA* const ptrFlightData)
{
uint16_t i;
uint8_t rows = 0;
uint8_t columns = 0;
unsigned char rwy_sine = SystemGetSineValue();
bool used_rwy = SystemContains_u16(ptrPlayer->RwyArray[0], GameUsedRwy, GAME_MAX_RUNWAYS);
for (i = 0 ; i < GameLevelSize; i++)
{
TYPE_ISOMETRIC_POS tileIsoPos;
// levelBuffer bits explanation:
// X X X X X X X X X X X X X X X X
// | | | | | | | | | | | | | | | |
// | | | | | | | | | | | | | | | V
// | | | | | | | | | | | | | | V Tile, bit 0
// | | | | | | | | | | | | | V Tile, bit 1
// | | | | | | | | | | | | V Tile, bit 2
// | | | | | | | | | | | V Tile, bit 3
// | | | | | | | | | | V Tile, bit 4
// | | | | | | | | | V Tile, bit 5
// | | | | | | | | V Tile, bit 6
// | | | | | | | | Tile mirror flag
// | | | | | | | V
// | | | | | | V Building, bit 0
// | | | | | V Building, bit 1
// | | | | V Building, bit 2
// | | | V Building, bit 3
// | | V Building, bit 4
// | V Building, bit 5
// V Building, bit 6
// Building, bit 7
uint8_t CurrentTile = (uint8_t)(levelBuffer[i] & 0x007F); // Remove building data
// and mirror flag.
// Isometric -> Cartesian conversion
tileIsoPos.x = columns << (TILE_SIZE_BIT_SHIFT);
tileIsoPos.y = rows << (TILE_SIZE_BIT_SHIFT);
tileIsoPos.z = 0;
TYPE_TILE_DATA* const tileData = &ptrPlayer->TileData[i];
tileData->CartPos = GfxIsometricToCartesian(&tileIsoPos);
if (columns < (GameLevelColumns - 1) )
{
columns++;
}
else
{
rows++;
columns = 0;
}
// Set coordinate origin to left upper corner.
tileData->CartPos.x -= TILE_SIZE >> 1;
CameraApplyCoordinatesToCartesianPos(ptrPlayer, &tileData->CartPos);
if (GfxIsInsideScreenArea( tileData->CartPos.x,
tileData->CartPos.y,
TILE_SIZE,
TILE_SIZE_H ))
{
tileData->ShowTile = true;
tileData->r = NORMAL_LUMINANCE;
tileData->g = NORMAL_LUMINANCE;
tileData->b = NORMAL_LUMINANCE;
if (i != 0)
{
if (ptrPlayer->SelectRunway)
{
if (SystemContains_u16(i, ptrPlayer->RwyArray, GAME_MAX_RWY_LENGTH))
{
if (used_rwy)
{
tileData->r = rwy_sine;
tileData->b = NORMAL_LUMINANCE >> 2;
tileData->g = NORMAL_LUMINANCE >> 2;
}
else
{
tileData->r = NORMAL_LUMINANCE >> 2;
tileData->g = NORMAL_LUMINANCE >> 2;
tileData->b = rwy_sine;
}
}
}
else if ( (ptrPlayer->SelectTaxiwayParking)
||
(ptrPlayer->SelectTaxiwayRunway) )
{
if (( (SystemContains_u16(i, ptrPlayer->Waypoints, ptrPlayer->WaypointIdx))
||
(i == ptrPlayer->SelectedTile) )
&&
(ptrPlayer->SelectedTile != GAME_INVALID_TILE_SELECTION) )
{
if (ptrPlayer->InvalidPath)
{
tileData->r = rwy_sine;
tileData->b = NORMAL_LUMINANCE >> 2;
tileData->g = NORMAL_LUMINANCE >> 2;
}
else
{
tileData->r = NORMAL_LUMINANCE >> 2;
tileData->g = NORMAL_LUMINANCE >> 2;
tileData->b = rwy_sine;
}
}
else if ( (ptrPlayer->SelectTaxiwayRunway)
&&
( (CurrentTile == TILE_RWY_HOLDING_POINT)
||
(CurrentTile == TILE_RWY_HOLDING_POINT_2) ) )
{
tileData->r = NORMAL_LUMINANCE >> 2;
tileData->g = rwy_sine;
tileData->b = NORMAL_LUMINANCE >> 2;
}
else if ( (ptrPlayer->SelectTaxiwayParking)
&&
( (CurrentTile == TILE_PARKING)
||
(CurrentTile == TILE_PARKING_2) ) )
{
bool parkingBusy = false;
uint8_t aircraftIndex;
for (aircraftIndex = 0; aircraftIndex < GAME_MAX_AIRCRAFT; aircraftIndex++)
{
const TYPE_AIRCRAFT_DATA* const ptrAircraft = AircraftFromFlightDataIndex(aircraftIndex);
if (ptrAircraft->State == STATE_PARKED)
{
const uint16_t tile = AircraftGetTileFromFlightDataIndex(aircraftIndex);
if (i == tile)
{
parkingBusy = true;
break;
}
}
}
if (parkingBusy)
{
tileData->r = rwy_sine;
tileData->g = NORMAL_LUMINANCE >> 2;
tileData->b = NORMAL_LUMINANCE >> 2;
}
else
{
tileData->r = NORMAL_LUMINANCE >> 2;
tileData->g = rwy_sine;
tileData->b = NORMAL_LUMINANCE >> 2;
}
}
}
else if (ptrPlayer->ShowAircraftData)
{
const uint8_t aircraftIndex = ptrPlayer->FlightDataSelectedAircraft;
switch (ptrFlightData->State[aircraftIndex])
{
case STATE_TAXIING:
// Fall through.
case STATE_USER_STOPPED:
// Fall through.
case STATE_AUTO_STOPPED:
// Fall through.
{
const uint16_t* const targets = AircraftGetTargets(aircraftIndex);
if (targets != NULL)
{
if (SystemContains_u16(i, targets, AIRCRAFT_MAX_TARGETS))
{
if (SystemIndexOf_U16(i, targets, AIRCRAFT_MAX_TARGETS) >=
AircraftGetTargetIdx(aircraftIndex))
{
tileData->r = NORMAL_LUMINANCE >> 2;
tileData->g = NORMAL_LUMINANCE >> 2;
tileData->b = rwy_sine;
}
}
}
}
break;
default:
break;
}
}
}
}
}
}
/* ******************************************************************************************
*
* @name: void GameRenderTerrain(TYPE_PLAYER* const ptrPlayer)
*
* @author: Xavier Del Campo
*
* @param:
* TYPE_PLAYER* const ptrPlayer:
* Pointer to a player structure
*
*
* @brief:
* Draws all tiles depending on levelBuffer configuration.
*
* @remarks:
* Tiles are usually rendered with normal RGB values unless parking/runway is busy
* or ptrPlayer->InvalidPath.
*
* ******************************************************************************************/
void GameRenderTerrain(TYPE_PLAYER* const ptrPlayer)
{
uint16_t i;
for (i = 0 ; i < GameLevelSize; i++)
{
if (ptrPlayer->TileData[i].ShowTile)
{
bool flip_id;
GsSprite* ptrTileset;
uint8_t aux_id;
uint8_t CurrentTile = (uint8_t)(levelBuffer[i] & 0x00FF);
// Flipped tiles have bit 7 set.
if (CurrentTile & TILE_MIRROR_FLAG)
{
flip_id = true;
aux_id = CurrentTile;
CurrentTile &= ~(TILE_MIRROR_FLAG);
}
else
{
flip_id = false;
}
if (CurrentTile <= LAST_TILE_TILESET1)
{
// Draw using GameTilesetSpr
ptrTileset = &GameTilesetSpr;
}
else if ( (CurrentTile > LAST_TILE_TILESET1)
&&
(CurrentTile <= LAST_TILE_TILESET2) )
{
// Draw using GameTileset2Spr
ptrTileset = &GameTileset2Spr;
}
else
{
ptrTileset = NULL;
continue;
}
// Apply {X, Y} data from precalculated lookup tables.
ptrTileset->x = ptrPlayer->TileData[i].CartPos.x;
ptrTileset->y = ptrPlayer->TileData[i].CartPos.y;
// Apply RGB data from precalculated lookup tables.
ptrTileset->r = ptrPlayer->TileData[i].r;
ptrTileset->g = ptrPlayer->TileData[i].g;
ptrTileset->b = ptrPlayer->TileData[i].b;
if (flip_id)
{
ptrTileset->attribute |= H_FLIP;
}
ptrTileset->w = TILE_SIZE;
ptrTileset->h = TILE_SIZE_H;
ptrTileset->u = GameLevelBuffer_UVData[i].u;
ptrTileset->v = GameLevelBuffer_UVData[i].v;
ptrTileset->mx = ptrTileset->u + (TILE_SIZE >> 1);
ptrTileset->my = ptrTileset->v + (TILE_SIZE_H >> 1);
if (flip_id)
{
flip_id = false;
CurrentTile = aux_id;
}
GfxSortSprite(ptrTileset);
if (ptrTileset->attribute & H_FLIP)
{
ptrTileset->attribute &= ~(H_FLIP);
}
}
}
}
/* *******************************************************************
*
* @name: void GameSetTime(uint8_t hour, uint8_t minutes)
*
* @author: Xavier Del Campo
*
* @brief:
* Reportedly, it sets game time to specified hour and minutes.
*
*
* @remarks:
* To be used on GameInit() after PLT file parsing.
*
* *******************************************************************/
void GameSetTime(uint8_t hour, uint8_t minutes)
{
GameHour = hour;
GameMinutes = minutes;
}
/* *******************************************************************
*
* @name: void GameActiveAircraft(uint8_t i)
*
* @author: Xavier Del Campo
*
* @param:
* uint8_t i:
* Index from FlightData array.
*
* @brief:
* On each game cycle, FlightData.ActiveAircraft is set to 0 and
* number of active aircraft is recalculated.
*
* @remarks:
* Called ciclically from GameCalculations(). This function is
* executed GAME_MAX_AIRCRAFT times on each cycle.
*
* *******************************************************************/
static void GameActiveAircraft(const uint8_t i)
{
// Reset iterator when i == 0.
if (i == 0)
{
FlightData.ActiveAircraft = 0;
}
if (FlightData.State[i] != STATE_IDLE)
{
FlightData.ActiveAircraft++;
}
}
/* ******************************************************************************************
*
* @name: void GameStateShowAircraft(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData)
*
* @author: Xavier Del Campo
*
* @param:
* TYPE_PLAYER* const ptrPlayer:
* Pointer to a player structure
*
* TYPE_FLIGHT_DATA* const ptrFlightData:
* In the end, pointer to FlightData data table, which contains
* information about all available flights.
*
* @brief:
* Handles ptrPlayer->ShowAircraftData state.
*
*
* @remarks:
* Called ciclically from GamePlayerHandler().
*
* ******************************************************************************************/
static void GameStateShowAircraft(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData)
{
if (ptrPlayer->ShowAircraftData)
{
if (ptrPlayer->PadKeySinglePress_Callback(PAD_TRIANGLE))
{
ptrPlayer->ShowAircraftData = false;
}
}
if (ptrPlayer->PadKeySinglePress_Callback(PAD_CIRCLE))
{
if (GameGuiShowAircraftDataSpecialConditions(ptrPlayer) == false)
{
//Invert ptrPlayer->ShowAircraftData value
ptrPlayer->ShowAircraftData = ptrPlayer->ShowAircraftData ? false : true;
}
}
}
/* ******************************************************************************************
*
* @name: void GameStateLockTarget(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData)
*
* @author: Xavier Del Campo
*
* @param:
* TYPE_PLAYER* const ptrPlayer:
* Pointer to a player structure
*
* TYPE_FLIGHT_DATA* const ptrFlightData:
* In the end, pointer to FlightData data table, which contains
* information about all available flights.
*
* @brief:
* Handles ptrPlayer->LockTarget state.
*
*
* @remarks:
* Called ciclically from GamePlayerHandler().
*
******************************************************************************************/
static void GameStateLockTarget(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData)
{
uint8_t AircraftIdx = ptrPlayer->FlightDataSelectedAircraft;
if (ptrPlayer->LockTarget)
{
if ((ptrPlayer->LockedAircraft != FLIGHT_DATA_INVALID_IDX)
&&
(ptrPlayer->LockedAircraft <= ptrPlayer->FlightDataSelectedAircraft))
{
CameraMoveToIsoPos(ptrPlayer, AircraftGetIsoPos(ptrPlayer->LockedAircraft) );
}
}
if (ptrPlayer->ShowAircraftData)
{
if ( (ptrFlightData->State[AircraftIdx] != STATE_IDLE)
&&
(ptrFlightData->State[AircraftIdx] != STATE_APPROACH) )
{
ptrPlayer->LockTarget = true;
ptrPlayer->LockedAircraft = AircraftIdx;
}
}
else
{
ptrPlayer->LockTarget = false;
ptrPlayer->LockedAircraft = FLIGHT_DATA_INVALID_IDX;
}
}
/* ******************************************************************************************
*
* @name: void GameStateSelectTaxiwayRunway(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData)
*
* @author: Xavier Del Campo
*
* @param:
* TYPE_PLAYER* const ptrPlayer:
* Pointer to a player structure
*
* TYPE_FLIGHT_DATA* const ptrFlightData:
* In the end, pointer to FlightData data table, which contains
* information about all available flights.
*
* @brief:
* Handler for ptrPlayer->SelectTaxiwayRunway.
*
*
* @remarks:
* Called ciclically from GamePlayerHandler().
*
* ******************************************************************************************/
static void GameStateSelectTaxiwayRunway(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData)
{
uint8_t i;
uint16_t target_tile;
/*Serial_printf("Camera is pointing to {%d,%d}\n",IsoPos.x, IsoPos.y);*/
if (ptrPlayer->SelectTaxiwayRunway)
{
// Under this mode, always reset locking target.
ptrPlayer->LockTarget = false;
ptrPlayer->LockedAircraft = FLIGHT_DATA_INVALID_IDX;
if (GamePathToTile(ptrPlayer, ptrFlightData) == false)
{
ptrPlayer->InvalidPath = true;
}
if (ptrPlayer->PadKeySinglePress_Callback(PAD_TRIANGLE))
{
// State exit.
ptrPlayer->SelectTaxiwayRunway = false;
// Clear waypoints array.
memset(ptrPlayer->Waypoints, 0, sizeof (uint16_t) * PLAYER_MAX_WAYPOINTS);
ptrPlayer->WaypointIdx = 0;
ptrPlayer->LastWaypointIdx = 0;
}
else if (ptrPlayer->PadKeySinglePress_Callback(PAD_CROSS))
{
if (ptrPlayer->InvalidPath == false)
{
for (i = 0; i < PLAYER_MAX_WAYPOINTS; i++)
{
if (ptrPlayer->Waypoints[i] == 0)
{
break;
}
ptrPlayer->LastWaypointIdx = i;
}
target_tile = levelBuffer[ptrPlayer->Waypoints[ptrPlayer->LastWaypointIdx]];
SfxPlaySound(&BeepSnd);
switch(target_tile)
{
case TILE_RWY_HOLDING_POINT:
// Fall through
case TILE_RWY_HOLDING_POINT | TILE_MIRROR_FLAG:
// Fall through
case TILE_RWY_HOLDING_POINT_2:
// Fall through
case TILE_RWY_HOLDING_POINT_2 | TILE_MIRROR_FLAG:
AircraftFromFlightDataIndexAddTargets(ptrPlayer->FlightDataSelectedAircraft, ptrPlayer->Waypoints);
Serial_printf("Added these targets to aircraft %d:\n", ptrPlayer->FlightDataSelectedAircraft);
for (i = 0; i < PLAYER_MAX_WAYPOINTS; i++)
{
Serial_printf("%d ",ptrPlayer->Waypoints[i]);
}
Serial_printf("\n");
// Clear waypoints array.
memset(ptrPlayer->Waypoints, 0, sizeof (uint16_t) * PLAYER_MAX_WAYPOINTS);
// Reset state and auxiliar variables
ptrPlayer->WaypointIdx = 0;
ptrPlayer->LastWaypointIdx = 0;
ptrPlayer->LockedAircraft = FLIGHT_DATA_INVALID_IDX;
ptrPlayer->LockTarget = false;
ptrPlayer->SelectTaxiwayRunway = false;
ptrFlightData->State[ptrPlayer->FlightDataSelectedAircraft] = STATE_TAXIING;
GameScore += SCORE_REWARD_TAXIING;
break;
default:
break;
}
}
}
}
}
/* **************************************************************************************************
*
* @name: void GameStateSelectTaxiwayParking(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData)
*
* @author: Xavier Del Campo
*
* @param:
* TYPE_PLAYER* const ptrPlayer:
* Pointer to a player structure
*
* TYPE_FLIGHT_DATA* const ptrFlightData:
* In the end, pointer to FlightData data table, which contains
* information about all available flights.
*
* @brief:
* Handler for ptrPlayer->SelectTaxiwayParking.
*
* @remarks:
*
* **************************************************************************************************/
static void GameStateSelectTaxiwayParking(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData)
{
uint8_t i;
uint16_t target_tile;
if (ptrPlayer->SelectTaxiwayParking)
{
// Under this mode, always reset locking target.
ptrPlayer->LockTarget = false;
ptrPlayer->LockedAircraft = FLIGHT_DATA_INVALID_IDX;
if (GamePathToTile(ptrPlayer, ptrFlightData) == false)
{
ptrPlayer->InvalidPath = true;
}
for (i = 0; i < GAME_MAX_AIRCRAFT; i++)
{
if (ptrPlayer->InvalidPath == false)
{
const TYPE_AIRCRAFT_DATA* const ptrAircraft = AircraftFromFlightDataIndex(i);
if (ptrAircraft != NULL)
{
if (ptrAircraft->State == STATE_PARKED)
{
const uint16_t tile = AircraftGetTileFromFlightDataIndex(i);
if (ptrPlayer->SelectedTile == tile)
{
ptrPlayer->InvalidPath = true;
break;
}
}
}
}
}
if (ptrPlayer->PadKeySinglePress_Callback(PAD_TRIANGLE))
{
// State exit.
ptrPlayer->SelectTaxiwayParking = false;
// Clear waypoints array.
memset(ptrPlayer->Waypoints, 0, sizeof (uint16_t) * PLAYER_MAX_WAYPOINTS);
ptrPlayer->WaypointIdx = 0;
ptrPlayer->LastWaypointIdx = 0;
}
else if (ptrPlayer->PadKeySinglePress_Callback(PAD_CROSS))
{
if (ptrPlayer->InvalidPath == false)
{
for (i = 0; i < PLAYER_MAX_WAYPOINTS; i++)
{
if (ptrPlayer->Waypoints[i] == 0)
{
break;
}
ptrPlayer->LastWaypointIdx = i;
}
target_tile = levelBuffer[ptrPlayer->Waypoints[ptrPlayer->LastWaypointIdx]] & ~(TILE_MIRROR_FLAG);
SfxPlaySound(&BeepSnd);
if ( (target_tile == TILE_PARKING)
||
(target_tile == TILE_PARKING_2) )
{
// TODO: Assign path to aircraft
AircraftFromFlightDataIndexAddTargets(ptrPlayer->FlightDataSelectedAircraft, ptrPlayer->Waypoints);
Serial_printf("Added these targets to aircraft %d:\n", ptrPlayer->FlightDataSelectedAircraft);
for (i = 0; i < PLAYER_MAX_WAYPOINTS; i++)
{
Serial_printf("%d ",ptrPlayer->Waypoints[i]);
}
Serial_printf("\n");
ptrPlayer->SelectTaxiwayParking = false;
// Clear waypoints array.
memset(ptrPlayer->Waypoints, 0, sizeof (uint16_t) * PLAYER_MAX_WAYPOINTS);
ptrPlayer->WaypointIdx = 0;
ptrPlayer->LastWaypointIdx = 0;
ptrFlightData->State[ptrPlayer->FlightDataSelectedAircraft] = STATE_TAXIING;
GameScore += SCORE_REWARD_TAXIING;
}
else
{
Serial_printf("Tile %d cannot be used as end point.\n", target_tile);
}
}
}
}
}
/* **************************************************************************************************
*
* @name: void GameStateSelectRunway(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData)
*
* @author: Xavier Del Campo
*
* @param:
* TYPE_PLAYER* const ptrPlayer:
* Pointer to a player structure
*
* TYPE_FLIGHT_DATA* const ptrFlightData:
* In the end, pointer to FlightData data table, which contains
* information about all available flights.
*
* @brief:
* Handler for ptrPlayer->SelectRunway.
*
* @remarks:
*
* **************************************************************************************************/
static void GameStateSelectRunway(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData)
{
uint8_t i;
TYPE_ISOMETRIC_POS IsoPos = { GameGetXFromTile_short(GameRwy[ptrPlayer->SelectedRunway]),
GameGetYFromTile_short(GameRwy[ptrPlayer->SelectedRunway]),
0 };
if (ptrPlayer->SelectRunway)
{
// Under this mode, always reset locking target.
ptrPlayer->LockTarget = false;
ptrPlayer->LockedAircraft = FLIGHT_DATA_INVALID_IDX;
GameGetSelectedRunwayArray(GameRwy[ptrPlayer->SelectedRunway], ptrPlayer->RwyArray, sizeof (ptrPlayer->RwyArray));
CameraMoveToIsoPos(ptrPlayer, IsoPos);
if (ptrPlayer->PadKeySinglePress_Callback(PAD_TRIANGLE))
{
ptrPlayer->SelectRunway = false;
}
else if (ptrPlayer->PadKeySinglePress_Callback(PAD_CROSS))
{
bool success = false;
if (SystemContains_u16(GameRwy[ptrPlayer->SelectedRunway], GameUsedRwy, GAME_MAX_RUNWAYS) == false)
{
ptrPlayer->SelectRunway = false;
Serial_printf("Player selected runway %d!\n",GameRwy[ptrPlayer->SelectedRunway]);
for (i = 0; i < GAME_MAX_RUNWAYS; i++)
{
if (GameUsedRwy[i] == 0)
{
GameAssignRunwaytoAircraft(ptrPlayer, ptrFlightData);
success = true;
GameUsedRwy[i] = GameRwy[ptrPlayer->SelectedRunway];
break;
}
}
if (success == false)
{
Serial_printf("No available runways!\n");
}
}
}
else if (ptrPlayer->PadKeySinglePress_Callback(PAD_LEFT))
{
if (ptrFlightData->State[ptrPlayer->FlightDataSelectedAircraft] == STATE_APPROACH)
{
if (ptrPlayer->SelectedRunway != 0)
{
ptrPlayer->SelectedRunway--;
}
}
}
else if (ptrPlayer->PadKeySinglePress_Callback(PAD_RIGHT))
{
if (ptrFlightData->State[ptrPlayer->FlightDataSelectedAircraft] == STATE_APPROACH)
{
if (ptrPlayer->SelectedRunway < (GAME_MAX_RUNWAYS - 1))
{
if (GameRwy[ptrPlayer->SelectedRunway + 1] != 0)
{
ptrPlayer->SelectedRunway++;
}
}
}
}
}
}
/* **************************************************************************************************
*
* @name: void GameGetRunwayArray(void)
*
* @author: Xavier Del Campo
*
*
* @brief:
* On startup, an array of runway headers is created from levelBuffer once *.LVL is parsed.
*
* @remarks:
* Do not confuse GameRwy with GameRwyArray, which are used for completely different purposes.
*
* **************************************************************************************************/
void GameGetRunwayArray(void)
{
uint16_t i;
uint8_t j = 0;
for (i = 0; i < GameLevelSize; i++)
{
uint8_t tileNr = levelBuffer[i] & ~TILE_MIRROR_FLAG;
if (tileNr == TILE_RWY_START_1)
{
if (SystemContains_u16(i, levelBuffer, GAME_MAX_RUNWAYS) == false)
{
GameRwy[j++] = i;
}
}
}
Serial_printf("GameRwy = ");
for (i = 0; i < GAME_MAX_RUNWAYS; i++)
{
if (GameRwy[i] == 0)
{
break;
}
Serial_printf("%d ", GameRwy[i]);
}
Serial_printf("\n");
}
/* **************************************************************************************************
*
* @name: void GameSelectAircraftFromList(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData)
*
* @author: Xavier Del Campo
*
* @param:
* TYPE_PLAYER* const ptrPlayer:
* Pointer to a player structure
*
* TYPE_FLIGHT_DATA* const ptrFlightData:
* In the end, pointer to FlightData data table, which contains
* information about all available flights.
*
* @brief:
* Actions for ptrPlayer->ShowAircraftData.
*
* @remarks:
*
* **************************************************************************************************/
static void GameSelectAircraftFromList(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData)
{
uint8_t AircraftIdx = ptrPlayer->FlightDataSelectedAircraft;
FL_STATE aircraftState = ptrFlightData->State[AircraftIdx];
if (ptrPlayer->ShowAircraftData)
{
if (ptrPlayer->PadKeySinglePress_Callback(PAD_CROSS))
{
if (ptrPlayer->ActiveAircraft != 0)
{
ptrPlayer->ShowAircraftData = false;
switch(aircraftState)
{
case STATE_APPROACH:
ptrPlayer->SelectRunway = true;
break;
case STATE_PARKED:
ptrPlayer->SelectTaxiwayRunway = true;
// Move camera to selected aircraft and add first waypoint.
GameSelectAircraftWaypoint(ptrPlayer);
break;
case STATE_LANDED:
ptrPlayer->SelectTaxiwayParking = true;
// Move camera to selected aircraft and add first waypoint.
GameSelectAircraftWaypoint(ptrPlayer);
break;
case STATE_UNBOARDING:
ptrPlayer->Unboarding = true;
// Move camera to selected aircraft.
GameSelectAircraft(ptrPlayer);
// Generate first unboarding key sequence
GameGenerateUnboardingSequence(ptrPlayer);
break;
case STATE_READY_FOR_TAKEOFF:
ptrFlightData->State[AircraftIdx] = STATE_TAKEOFF;
GameCreateTakeoffWaypoints(ptrPlayer, ptrFlightData, AircraftIdx);
SfxPlaySound(&TakeoffSnd);
break;
case STATE_HOLDING_RWY:
{
TYPE_RWY_ENTRY_DATA rwyEntryData = {0};
uint8_t i;
ptrPlayer->SelectRunway = true;
GameGetRunwayEntryTile(AircraftIdx, &rwyEntryData);
for (i = 0; GameRwy[i] != 0 && (i < (sizeof (GameRwy) / sizeof (GameRwy[0]))); i++)
{
if (GameRwy[i] == rwyEntryData.rwyHeader)
{
break;
}
}
ptrPlayer->SelectedRunway = i;
}
break;
default:
Serial_printf("Incompatible state %d!\n",aircraftState);
// States remain unchanged
ptrPlayer->SelectRunway = false;
ptrPlayer->SelectTaxiwayRunway = false;
ptrPlayer->ShowAircraftData = true;
ptrPlayer->Unboarding = false;
break;
}
}
}
else if (ptrPlayer->PadKeySinglePress_Callback(PAD_L1))
{
FL_STATE* const ptrAircraftState = &FlightData.State[ptrPlayer->FlightDataSelectedAircraft];
switch (*ptrAircraftState)
{
case STATE_TAXIING:
*ptrAircraftState = STATE_USER_STOPPED;
break;
case STATE_USER_STOPPED:
// Fall through.
case STATE_AUTO_STOPPED:
*ptrAircraftState = STATE_TAXIING;
break;
default:
break;
}
}
}
}
/* **************************************************************************************************
*
* @name: DIRECTION GameGetParkingDirection(uint16_t parkingTile)
*
* @author: Xavier Del Campo
*
* @return:
* Depending on tile number, parking direction is returned.
*
* **************************************************************************************************/
DIRECTION GameGetParkingDirection(uint16_t parkingTile)
{
switch (parkingTile)
{
case TILE_PARKING:
return DIR_WEST;
case TILE_PARKING | TILE_MIRROR_FLAG:
return DIR_NORTH;
case TILE_PARKING_2:
return DIR_EAST;
case TILE_PARKING_2 | TILE_MIRROR_FLAG:
return DIR_SOUTH;
default:
break;
}
return NO_DIRECTION;
}
/* **************************************************************************************************
*
* @name: DIRECTION GameGetRunwayDirection(uint16_t rwyHeader)
*
* @author: Xavier Del Campo
*
* @return:
* Depending on tile number, runway direction is returned.
*
* **************************************************************************************************/
DIRECTION GameGetRunwayDirection(uint16_t rwyHeader)
{
switch(levelBuffer[rwyHeader])
{
case TILE_RWY_START_1:
return DIR_EAST;
case TILE_RWY_START_2:
return DIR_WEST;
case TILE_RWY_START_1 | TILE_MIRROR_FLAG:
return DIR_SOUTH;
case TILE_RWY_START_2 | TILE_MIRROR_FLAG:
return DIR_NORTH;
default:
Serial_printf("Unknown direction for tile %d\n",rwyHeader);
break;
}
return NO_DIRECTION;
}
/* **************************************************************************************************
*
* @name: void GameGetSelectedRunwayArray(uint16_t rwyHeader, uint16_t* rwyArray, size_t sz)
*
* @author: Xavier Del Campo
*
* @param:
* uint16_t rwyHeader:
* Level tile number (located inside levelBuffer) pointing to runway header.
* Only TILE_RWY_START_1 and TILE_RWY_START_2 (with or without TILE_MIRROR_FLAG)
* can be used for runway headers!
*
* uint16_t* rwyArray:
* Pointer to an array which will be filled with all the tiles belonging to a runway
* with header pointed to by rwyHeader.
*
* size_t sz:
* Maximum size of the array.
*
* @brief:
* Fills rwyArray with all the tile numbers (included in levelBuffer) belonging to a
* runway with header pointed to by rwyHeader.
*
* @remarks:
*
* **************************************************************************************************/
void GameGetSelectedRunwayArray(uint16_t rwyHeader, uint16_t* rwyArray, size_t sz)
{
static uint16_t last_tile = 0;
static uint8_t i = 0;
static DIRECTION dir;
if (sz != (GAME_MAX_RWY_LENGTH * sizeof (uint16_t) ))
{
Serial_printf( "GameGetSelectedRunwayArray: size %d is different"
" than expected (%d bytes). Returning...\n",
sz,
(GAME_MAX_RWY_LENGTH * sizeof (uint16_t) ) );
return;
}
if (rwyHeader != 0)
{
// This function is called recursively.
// Since 0 is not a valid value (it's not allowed to place
// a runway header on first tile), it is used to determine
// when to start creating the array.
// Part one: determine runway direction and call the function again with rwyHeader == 0.
memset(rwyArray, 0, sz);
last_tile = rwyHeader;
i = 0;
dir = GameGetRunwayDirection(rwyHeader);
if (dir == NO_DIRECTION)
{
Serial_printf("rwyHeader = %d returned NO_DIRECTION\n", rwyHeader);
return;
}
}
else
{
// Part two: append tiles to array until runway end is found.
if ( (levelBuffer[last_tile] == TILE_RWY_START_1)
||
(levelBuffer[last_tile] == TILE_RWY_START_2)
||
(levelBuffer[last_tile] == (TILE_RWY_START_1 | TILE_MIRROR_FLAG) )
||
(levelBuffer[last_tile] == (TILE_RWY_START_2 | TILE_MIRROR_FLAG) ) )
{
// Runway end found
rwyArray[i++] = last_tile;
return;
}
}
rwyArray[i++] = last_tile;
switch(dir)
{
case DIR_EAST:
last_tile++;
break;
case DIR_WEST:
last_tile--;
break;
case DIR_NORTH:
last_tile -= GameLevelColumns;
break;
case DIR_SOUTH:
last_tile += GameLevelColumns;
break;
case NO_DIRECTION:
// Fall through
default:
Serial_printf("Invalid runway direction.\n");
return;
}
GameGetSelectedRunwayArray(0, rwyArray, sz);
}
/* **************************************************************************************************
*
* @name: void GameAssignRunwaytoAircraft(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData)
*
* @author: Xavier Del Campo
*
* @param:
* TYPE_PLAYER* const ptrPlayer:
* Pointer to a player structure
*
* TYPE_FLIGHT_DATA* const ptrFlightData:
* In the end, pointer to FlightData data table, which contains
* information about all available flights.
*
* @brief:
* Assigns a runway to an incoming aircraft (FlightDirection == ARRIVAL) depending on
* player selection.
*
* @remarks:
*
* **************************************************************************************************/
void GameAssignRunwaytoAircraft(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData)
{
uint16_t assignedRwy = GameRwy[ptrPlayer->SelectedRunway];
uint8_t aircraftIndex = ptrPlayer->FlightDataSelectedAircraft;
uint16_t rwyExit = 0;
uint8_t i;
uint16_t targets[AIRCRAFT_MAX_TARGETS] = {0};
uint8_t rwyTiles[GAME_MAX_RWY_LENGTH] = {0};
// Remember that ptrPlayer->SelectedAircraft contains an index to
// be used with ptrFlightData.
if (ptrFlightData->State[aircraftIndex] == STATE_APPROACH)
{
uint8_t j;
bool firstEntryPointFound = false;
uint16_t rwyArray[GAME_MAX_RWY_LENGTH];
// TODO: Algorithm is not correct. If TILE_RWY_EXIT is placed further,
// but returns a match earlier than other rwyExitTiles[], invalid targets
// are returned to aircraft. We should check this before proceeding.
static const uint8_t rwyExitTiles[] =
{
TILE_RWY_EXIT,
TILE_RWY_EXIT | TILE_MIRROR_FLAG,
TILE_RWY_EXIT_2,
TILE_RWY_EXIT_2 | TILE_MIRROR_FLAG
};
ptrFlightData->State[aircraftIndex] = STATE_FINAL;
GameScore += SCORE_REWARD_FINAL;
GameGetSelectedRunwayArray(assignedRwy, rwyArray, sizeof (rwyArray));
for (i = 0; i < GAME_MAX_RWY_LENGTH; i++)
{
rwyTiles[i] = levelBuffer[rwyArray[i]];
}
for (i = 0; (i < GAME_MAX_RWY_LENGTH) && (rwyExit == 0); i++)
{
for (j = 0; j < ARRAY_SIZE(rwyExitTiles); j++)
{
if (rwyTiles[i] == rwyExitTiles[j])
{
if (firstEntryPointFound == false)
{
firstEntryPointFound = true;
}
else
{
rwyExit = rwyArray[i];
}
break;
}
}
}
if (rwyExit == 0)
{
Serial_printf("ERROR: Could not find TILE_RWY_EXIT or TILE_RWY_EXIT_2 for runway header %d.\n", assignedRwy);
return;
}
// Create two new targets for the recently created aircraft.
targets[0] = assignedRwy;
targets[1] = rwyExit;
if (AircraftAddNew( ptrFlightData,
aircraftIndex,
targets,
GameGetRunwayDirection(assignedRwy) ) == false)
{
Serial_printf("Exceeded maximum aircraft number!\n");
return;
}
SfxPlaySound(&TowerFinalSnds[SystemRand(SOUND_M1_INDEX, MAX_RADIO_CHATTER_SOUNDS - 1)]);
}
else if (ptrFlightData->State[aircraftIndex] == STATE_HOLDING_RWY)
{
TYPE_RWY_ENTRY_DATA rwyEntryData = {0};
GameGetRunwayEntryTile(aircraftIndex, &rwyEntryData);
targets[0] = rwyEntryData.rwyEntryTile;
targets[1] = targets[0] + rwyEntryData.rwyStep;
AircraftAddTargets(AircraftFromFlightDataIndex(aircraftIndex), targets);
ptrFlightData->State[aircraftIndex] = STATE_ENTERING_RWY;
}
}
/* *******************************************************************
*
* @name: short GameGetXFromTile_short(uint16_t tile)
*
* @author: Xavier Del Campo
*
* @param:
* uint16_t tile:
* Tile number from levelBuffer.
*
* @return:
* Returns relative X position (no fixed-point arithmetic) given
* a tile number from levelBuffer.
*
* @remarks:
*
* *******************************************************************/
short GameGetXFromTile_short(uint16_t tile)
{
short retVal;
tile %= GameLevelColumns;
retVal = (tile << TILE_SIZE_BIT_SHIFT);
// Always point to tile center
retVal += TILE_SIZE >> 1;
return retVal;
}
/* *******************************************************************
*
* @name: short GameGetYFromTile_short(uint16_t tile)
*
* @author: Xavier Del Campo
*
* @param:
* uint16_t tile:
* Tile number from levelBuffer.
*
* @return:
* Returns relative Y position (no fixed-point arithmetic) given
* a tile number from levelBuffer.
*
* @remarks:
*
* *******************************************************************/
short GameGetYFromTile_short(uint16_t tile)
{
short retVal;
tile /= GameLevelColumns;
retVal = (tile << TILE_SIZE_BIT_SHIFT);
// Always point to tile center
retVal += TILE_SIZE >> 1;
return retVal;
}
/* *******************************************************************
*
* @name: fix16_t GameGetXFromTile(uint16_t tile)
*
* @author: Xavier Del Campo
*
* @param:
* uint16_t tile:
* Tile number from levelBuffer.
*
* @return:
* Returns relative X position in 16.16 (fix16_t) fixed-point format
* given a tile number from levelBuffer.
*
* @remarks:
*
* *******************************************************************/
fix16_t GameGetXFromTile(uint16_t tile)
{
return fix16_from_int(GameGetXFromTile_short(tile));
}
/* *******************************************************************
*
* @name: fix16_t GameGetYFromTile(uint16_t tile)
*
* @author: Xavier Del Campo
*
* @param:
* uint16_t tile:
* Tile number from levelBuffer.
*
* @return:
* Returns relative Y position in 16.16 (fix16_t) fixed-point format
* given a tile number from levelBuffer.
*
* @remarks:
*
* *******************************************************************/
fix16_t GameGetYFromTile(uint16_t tile)
{
return fix16_from_int(GameGetYFromTile_short(tile));
}
/* ****************************************************************************
*
* @name: FL_STATE GameTargetsReached(uint16_t firstTarget, uint8_t index)
*
* @author: Xavier Del Campo
*
* @param:
* uint16_t firstTarget:
* First waypoint of TYPE_AIRCRAFT instance.
*
* uint8_t index:
* Index of FlightData.
*
* @brief:
* Calculates new state for aircraft once all waypoints have been reached.
*
* @return:
* New state for aircraft once waypoints have been reached.
*
* @remarks:
*
* ****************************************************************************/
FL_STATE GameTargetsReached(uint16_t firstTarget, uint8_t index)
{
FL_STATE retState = STATE_IDLE;
uint8_t i;
switch(FlightData.State[index])
{
case STATE_FINAL:
FlightData.State[index] = STATE_LANDED;
for (i = 0; i < GAME_MAX_RUNWAYS; i++)
{
if (GameUsedRwy[i] == firstTarget)
{
GameUsedRwy[i] = 0;
}
}
break;
case STATE_TAXIING:
if (FlightData.FlightDirection[index] == DEPARTURE)
{
FlightData.State[index] = STATE_HOLDING_RWY;
}
else if (FlightData.FlightDirection[index] == ARRIVAL)
{
FlightData.State[index] = STATE_UNBOARDING;
}
break;
case STATE_TAKEOFF:
FlightData.State[index] = STATE_CLIMBING;
break;
case STATE_ENTERING_RWY:
FlightData.State[index] = STATE_READY_FOR_TAKEOFF;
break;
default:
break;
}
return retState;
}
/* ****************************************************************************
*
* @name: uint16_t GameGetTileFromIsoPosition(TYPE_ISOMETRIC_POS* IsoPos)
*
* @author: Xavier Del Campo
*
* @param:
* TYPE_ISOMETRIC_POS* IsoPos:
* (x, y, z) position data.
*
* @brief:
* Calculates new state for aircraft once all waypoints have been reached.
*
* @return:
* Tile number to be used against levelBuffer.
*
* @remarks:
* GameLevelColumns is used to determine tile number.
*
* ****************************************************************************/
uint16_t GameGetTileFromIsoPosition(const TYPE_ISOMETRIC_POS* const IsoPos)
{
uint16_t tile;
if (IsoPos == NULL)
{
return 0;
}
if ( (IsoPos->x < 0) || (IsoPos->y < 0) )
{
return GAME_INVALID_TILE_SELECTION; // Invalid XYZ position
}
tile = IsoPos->x >> TILE_SIZE_BIT_SHIFT;
tile += (IsoPos->y >> TILE_SIZE_BIT_SHIFT) * GameLevelColumns;
/*Serial_printf("Returning tile %d from position {%d, %d, %d}\n",
tile,
IsoPos->x,
IsoPos->y,
IsoPos->z );*/
return tile;
}
/* ****************************************************************************
*
* @name: uint8_t GameGetLevelColumns(void)
*
* @author: Xavier Del Campo
*
* @return:
* GameLevelColumns. Used for other modules without declaring GameLevelColumns
* as a global variable.
*
* ****************************************************************************/
uint8_t GameGetLevelColumns(void)
{
return GameLevelColumns;
}
/* ****************************************************************************
*
* @name: void GamePlayerAddWaypoint(TYPE_PLAYER* const ptrPlayer)
*
* @author: Xavier Del Campo
*
* @param:
* TYPE_PLAYER* const ptrPlayer:
* Pointer to a player structure
*
* @brief:
* Wrapper for GamePlayerAddWaypoint_Ex().
*
* ****************************************************************************/
void GamePlayerAddWaypoint(TYPE_PLAYER* const ptrPlayer)
{
GamePlayerAddWaypoint_Ex(ptrPlayer, ptrPlayer->SelectedTile);
}
/* ****************************************************************************
*
* @name: void GamePlayerAddWaypoint(TYPE_PLAYER* const ptrPlayer)
*
* @author: Xavier Del Campo
*
* @param:
* TYPE_PLAYER* const ptrPlayer:
* Pointer to a player structure.
*
* uint16_t tile:
* Tile number from levelBuffer.
*
* @brief:
* It allows adding a tile number to ptrPlayer.
*
* @remark:
* To be used together with GamePathToTile().
*
* ****************************************************************************/
void GamePlayerAddWaypoint_Ex(TYPE_PLAYER* const ptrPlayer, uint16_t tile)
{
// "_Ex" function allow selecting a certain tile, whereas the other one
// is a particulare case of "_Ex" for tile = ptrPlayer->SelectedTIle.
if (ptrPlayer->WaypointIdx >= PLAYER_MAX_WAYPOINTS)
{
Serial_printf("No available waypoints for this player!\n");
return;
}
/*Serial_printf("Added tile %d to ptrPlayer->Waypoints[%d]\n",
tile,
ptrPlayer->WaypointIdx);*/
ptrPlayer->Waypoints[ptrPlayer->WaypointIdx++] = tile;
}
/* **************************************************************************************
*
* @name: bool GameWaypointCheckExisting(TYPE_PLAYER* const ptrPlayer, uint16_t temp_tile)
*
* @author: Xavier Del Campo
*
* @param:
* TYPE_PLAYER* const ptrPlayer:
* Pointer to a player structure.
*
* uint16_t tile:
* Tile number from levelBuffer.
*
* @brief:
* Checks if tile number temp_tile is already included on player's waypoint list.
*
* @return:
* True if waypoint is already included on waypoint list, false otherwise.
*
* **************************************************************************************/
bool GameWaypointCheckExisting(TYPE_PLAYER* const ptrPlayer, uint16_t temp_tile)
{
if (SystemContains_u16(temp_tile, ptrPlayer->Waypoints, PLAYER_MAX_WAYPOINTS) == false)
{
/*for (i = 0; i < FlightData.nAircraft; i++)
{
if ( (ptrFlightData->State[i] != STATE_IDLE)
&&
(AircraftMoving(i) == false) )
{
if (temp_tile == AircraftGetTileFromFlightDataIndex(i))
{
return false; // Check pending!
}
}
}*/
GamePlayerAddWaypoint_Ex(ptrPlayer, temp_tile);
return false;
}
// temp_tile is already included on ptrPlayer->Waypoints!
return true;
}
/* ****************************************************************************************
*
* @name: bool GamePathToTile(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData)
*
* @author: Xavier Del Campo
*
* @param:
* TYPE_PLAYER* const ptrPlayer:
* Pointer to a player structure
*
* TYPE_FLIGHT_DATA* const ptrFlightData:
* In the end, pointer to FlightData data table, which contains
* information about all available flights.
*
* @brief:
* Given an input TYPE_PLAYER structure and a selected tile,
* it updates current Waypoints array with all tiles between two points.
* If one of these tiles do not belong to desired tiles (i.e.: grass,
* water, buildings...), then false is returned.
*
* @return:
* Returns false on invalid path or invalid tile number selected. True otherwise.
*
* ****************************************************************************************/
bool GamePathToTile(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData)
{
static const uint8_t AcceptedTiles[] =
{
TILE_ASPHALT_WITH_BORDERS,
TILE_PARKING,
TILE_RWY_MID,
TILE_RWY_EXIT,
TILE_RWY_EXIT_2,
TILE_TAXIWAY_CORNER_GRASS,
TILE_TAXIWAY_CORNER_GRASS_2,
TILE_TAXIWAY_GRASS,
TILE_TAXIWAY_INTERSECT_GRASS,
TILE_TAXIWAY_4WAY_CROSSING,
TILE_PARKING_2,
TILE_RWY_HOLDING_POINT,
TILE_RWY_HOLDING_POINT_2,
TILE_TAXIWAY_CORNER_GRASS_3
};
uint8_t i;
uint16_t x_diff;
uint16_t y_diff;
uint16_t temp_tile;
const TYPE_ISOMETRIC_POS IsoPos = CameraGetIsoPos(ptrPlayer);
ptrPlayer->SelectedTile = GameGetTileFromIsoPosition(&IsoPos);
if (ptrPlayer->SelectedTile == GAME_INVALID_TILE_SELECTION)
{
return false;
}
for (i = (ptrPlayer->LastWaypointIdx + 1); i < PLAYER_MAX_WAYPOINTS; i++)
{
ptrPlayer->Waypoints[i] = 0;
}
ptrPlayer->WaypointIdx = ptrPlayer->LastWaypointIdx + 1;
x_diff = abs((ptrPlayer->SelectedTile % GameLevelColumns) -
(ptrPlayer->Waypoints[ptrPlayer->LastWaypointIdx] % GameLevelColumns));
y_diff = abs((ptrPlayer->SelectedTile / GameLevelColumns) -
(ptrPlayer->Waypoints[ptrPlayer->LastWaypointIdx] / GameLevelColumns));
// At this point, we have to update current waypoints list.
// ptrPlayer->Waypoints[ptrPlayer->WaypointIdx - 1] points to the last inserted point,
// so now we have to determine how many points need to be created.
temp_tile = ptrPlayer->Waypoints[ptrPlayer->LastWaypointIdx];
if (x_diff >= y_diff)
{
while ( (x_diff--) > 0)
{
if ( (ptrPlayer->SelectedTile % GameLevelColumns) >
(ptrPlayer->Waypoints[ptrPlayer->LastWaypointIdx] % GameLevelColumns) )
{
temp_tile++;
}
else
{
temp_tile--;
}
if (GameWaypointCheckExisting(ptrPlayer, temp_tile))
{
return false; // Tile is already included in the list of temporary tiles?
}
}
while ( (y_diff--) > 0)
{
if ( (ptrPlayer->SelectedTile / GameLevelColumns) >
(ptrPlayer->Waypoints[ptrPlayer->LastWaypointIdx] / GameLevelColumns) )
{
temp_tile += GameLevelColumns;
}
else
{
temp_tile -= GameLevelColumns;
}
if (GameWaypointCheckExisting(ptrPlayer, temp_tile))
{
return false; // Tile is already included in the list of temporary tiles?
}
}
}
else
{
while ( (y_diff--) > 0)
{
if ( (ptrPlayer->SelectedTile / GameLevelColumns) >
(ptrPlayer->Waypoints[ptrPlayer->LastWaypointIdx] / GameLevelColumns) )
{
temp_tile += GameLevelColumns;
}
else
{
temp_tile -= GameLevelColumns;
}
if (GameWaypointCheckExisting(ptrPlayer, temp_tile))
{
return false; // Tile is already included in the list of temporary tiles?
}
}
while ( (x_diff--) > 0)
{
if ( (ptrPlayer->SelectedTile % GameLevelColumns) >
(ptrPlayer->Waypoints[ptrPlayer->LastWaypointIdx] % GameLevelColumns) )
{
temp_tile++;
}
else
{
temp_tile--;
}
if (GameWaypointCheckExisting(ptrPlayer, temp_tile))
{
return false; // Tile is already included in the list of temporary tiles?
}
}
}
// Now at this point, we have prepared our array.
for (i = 0; i < PLAYER_MAX_WAYPOINTS; i++)
{
if (ptrPlayer->Waypoints[i] == 0)
{
// We have found empty waypoints. Exit loop
break;
}
if (SystemContains_u8( levelBuffer[ptrPlayer->Waypoints[i]],
AcceptedTiles,
sizeof (AcceptedTiles) ) == false)
{
// Now try again with mirrored tiles, just in case!
static const uint8_t AcceptedMirroredTiles[] =
{
TILE_ASPHALT_WITH_BORDERS | TILE_MIRROR_FLAG,
TILE_PARKING | TILE_MIRROR_FLAG,
TILE_RWY_MID | TILE_MIRROR_FLAG,
TILE_RWY_EXIT | TILE_MIRROR_FLAG,
TILE_RWY_EXIT_2 | TILE_MIRROR_FLAG,
TILE_TAXIWAY_CORNER_GRASS | TILE_MIRROR_FLAG,
TILE_TAXIWAY_CORNER_GRASS_2 | TILE_MIRROR_FLAG,
TILE_TAXIWAY_GRASS | TILE_MIRROR_FLAG,
TILE_TAXIWAY_INTERSECT_GRASS | TILE_MIRROR_FLAG,
TILE_TAXIWAY_4WAY_CROSSING | TILE_MIRROR_FLAG,
TILE_PARKING_2 | TILE_MIRROR_FLAG,
TILE_RWY_HOLDING_POINT | TILE_MIRROR_FLAG,
TILE_RWY_HOLDING_POINT_2 | TILE_MIRROR_FLAG,
TILE_TAXIWAY_CORNER_GRASS_3 | TILE_MIRROR_FLAG
};
if (SystemContains_u8( levelBuffer[ptrPlayer->Waypoints[i]],
AcceptedMirroredTiles,
sizeof (AcceptedTiles) ) == false)
{
// Both cases have failed. Return from function.
return false;
}
}
}
return true;
}
/* ****************************************************************************************
*
* @name: TYPE_ISOMETRIC_POS GameSelectAircraft(TYPE_PLAYER* const ptrPlayer)
*
* @author: Xavier Del Campo
*
* @param:
* TYPE_PLAYER* const ptrPlayer:
* Pointer to a player structure
*
* @brief:
* Moves player camera position to selected aircraft.
*
* @return:
* Isometric position of selected aircraft.
*
* ****************************************************************************************/
static TYPE_ISOMETRIC_POS GameSelectAircraft(TYPE_PLAYER* const ptrPlayer)
{
const uint8_t AircraftIdx = ptrPlayer->FlightDataSelectedAircraft;
const TYPE_ISOMETRIC_POS IsoPos = AircraftGetIsoPos(AircraftIdx);
CameraMoveToIsoPos(ptrPlayer, IsoPos);
return IsoPos;
}
/* ********************************************************************************
*
* @name: void GameSelectAircraftWaypoint(TYPE_PLAYER* const ptrPlayer)
*
* @author: Xavier Del Campo
*
* @param:
* TYPE_PLAYER* const ptrPlayer:
* Pointer to a player structure
*
* @brief:
* Moves player camera to selected aircraft and adds first waypoint.
*
* ********************************************************************************/
void GameSelectAircraftWaypoint(TYPE_PLAYER* const ptrPlayer)
{
TYPE_ISOMETRIC_POS IsoPos = GameSelectAircraft(ptrPlayer);
ptrPlayer->SelectedTile = GameGetTileFromIsoPosition(&IsoPos);
GamePlayerAddWaypoint(ptrPlayer);
}
/* ********************************************************************************
*
* @name: bool GameTwoPlayersActive(void)
*
* @author: Xavier Del Campo
*
* @return:
* Returns if a second player is active. To be used with other modules without
* declaring twoPlayers as a global variable.
*
* ********************************************************************************/
bool GameTwoPlayersActive(void)
{
return twoPlayers;
}
/* *****************************************************************
*
* @name: void GameDrawMouse(TYPE_PLAYER* const ptrPlayer)
*
* @author: Xavier Del Campo
*
* @param:
* TYPE_PLAYER* const ptrPlayer:
* Pointer to a player structure
*
* @brief:
* Draws GameMouseSpr under determined player states.
*
* *****************************************************************/
static void GameDrawMouse(TYPE_PLAYER* const ptrPlayer)
{
if ((ptrPlayer->SelectTaxiwayParking)
||
(ptrPlayer->SelectTaxiwayRunway) )
{
GfxSortSprite(&GameMouseSpr);
}
}
/* ********************************************************************************
*
* @name: FL_STATE GameGetFlightDataStateFromIdx(uint8_t FlightDataIdx)
*
* @author: Xavier Del Campo
*
* @param:
* uint8_t FlightDataIdx:
* Index from FlightData.
*
* @return:
* Returns .State variable given offset from FlightData.
*
* ********************************************************************************/
FL_STATE GameGetFlightDataStateFromIdx(uint8_t FlightDataIdx)
{
if (FlightDataIdx >= FlightData.nAircraft)
{
return STATE_IDLE; // Error: could cause buffer overrun
}
return FlightData.State[FlightDataIdx];
}
/* ********************************************************************************
*
* @name: uint32_t GameGetScore(void)
*
* @author: Xavier Del Campo
*
* @return:
* Returns game score to other modules.
*
* ********************************************************************************/
uint32_t GameGetScore(void)
{
return GameScore;
}
/* *******************************************************************************************
*
* @name: void GameStateUnboarding(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData)
*
* @author: Xavier Del Campo
*
* @param:
* TYPE_PLAYER* const ptrPlayer:
* Pointer to a player structure
*
* TYPE_FLIGHT_DATA* const ptrFlightData:
* In the end, pointer to FlightData data table, which contains
* information about all available flights.
*
* @brief:
* Handler for StateUnboarding.
*
* *******************************************************************************************/
static void GameStateUnboarding(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData)
{
if (ptrPlayer->Unboarding)
{
if (ptrPlayer->PadKeySinglePress_Callback(PAD_CIRCLE))
{
ptrPlayer->Unboarding = false;
ptrPlayer->UnboardingSequenceIdx = 0; // Player will need to repeat sequence
// if he/she decides to leave without finishing
}
ptrPlayer->LockTarget = true;
ptrPlayer->LockedAircraft = ptrPlayer->FlightDataSelectedAircraft;
if (ptrPlayer->PadLastKeySinglePressed_Callback() == ptrPlayer->UnboardingSequence[ptrPlayer->UnboardingSequenceIdx])
{
if (++ptrPlayer->UnboardingSequenceIdx >= UNBOARDING_KEY_SEQUENCE_MEDIUM)
{
if (ptrFlightData->Passengers[ptrPlayer->FlightDataSelectedAircraft] > UNBOARDING_PASSENGERS_PER_SEQUENCE)
{
// Player has entered correct sequence. Unboard UNBOARDING_PASSENGERS_PER_SEQUENCE passengers.
ptrFlightData->Passengers[ptrPlayer->FlightDataSelectedAircraft] -= UNBOARDING_PASSENGERS_PER_SEQUENCE;
GameScore += SCORE_REWARD_UNLOADING;
ptrPlayer->PassengersLeftSelectedAircraft = ptrFlightData->Passengers[ptrPlayer->FlightDataSelectedAircraft];
GameGenerateUnboardingSequence(ptrPlayer);
}
else
{
// Flight has finished. Remove aircraft and set finished flag
ptrPlayer->Unboarding = false;
GameRemoveFlight(ptrPlayer->FlightDataSelectedAircraft, true);
}
ptrPlayer->UnboardingSequenceIdx = 0;
}
Serial_printf("ptrPlayer->UnboardingSequenceIdx = %d\n", ptrPlayer->UnboardingSequenceIdx);
SfxPlaySound(&BeepSnd);
}
else if (ptrPlayer->PadLastKeySinglePressed_Callback() != 0)
{
ptrPlayer->UnboardingSequenceIdx = 0; // Player has committed a mistake while entering the sequence. Repeat it!
}
}
}
/* *******************************************************************************************
*
* @name: void GameStateUnboarding(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData)
*
* @author: Xavier Del Campo
*
* @param:
* TYPE_PLAYER* const ptrPlayer:
* Pointer to a player structure
*
* @brief:
* Modifies ptrPlayer->UnboardingSequence creating a random sequence of numbers
* using SystemRand().
*
* @todo:
* Maximum number of sequence keys should be adjusted according to difficulty.
*
* *******************************************************************************************/
static void GameGenerateUnboardingSequence(TYPE_PLAYER* const ptrPlayer)
{
uint8_t i;
unsigned short keyTable[] = { PAD_CROSS, PAD_SQUARE, PAD_TRIANGLE };
memset(ptrPlayer->UnboardingSequence, 0, sizeof (ptrPlayer->UnboardingSequence) );
ptrPlayer->UnboardingSequenceIdx = 0;
Serial_printf("Key sequence generated: ");
// Only medium level implemented. TODO: Implement other levels
for (i = 0; i < UNBOARDING_KEY_SEQUENCE_MEDIUM; i++)
{
uint8_t randIdx = SystemRand(0, (sizeof (keyTable) / sizeof (keyTable[0])) - 1);
ptrPlayer->UnboardingSequence[i] = keyTable[randIdx];
Serial_printf("idx = %d, 0x%04X ", randIdx, ptrPlayer->UnboardingSequence[i]);
}
Serial_printf("\n");
}
/* *********************************************************************************************************************
*
* @name: void GameCreateTakeoffWaypoints(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData, uint8_t aircraftIdx)
*
* @author: Xavier Del Campo
*
* @param:
* TYPE_PLAYER* const ptrPlayer:
* Pointer to a player structure
*
* TYPE_FLIGHT_DATA* const ptrFlightData:
* In the end, pointer to FlightData data table, which contains
* information about all available flights.
*
* uint8_t aircraftIdx:
* Index from FlightData.
*
* @brief:
* Given input aircraft from FlightData, it automatically looks for selected runway and creates an array
* of waypoints to be then executed by corresponding TYPE_AIRCRAFT_DATA instance.
*
* *********************************************************************************************************************/
static void GameCreateTakeoffWaypoints(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData, uint8_t aircraftIdx)
{
TYPE_AIRCRAFT_DATA* const ptrAircraft = AircraftFromFlightDataIndex(aircraftIdx);
if (ptrAircraft != NULL)
{
// Look for aircraft direction by searching TILE_RWY_EXIT
//uint16_t currentTile = AircraftGetTileFromFlightDataIndex(aircraftIdx);
//uint8_t targetsIdx = 0;
DIRECTION aircraftDir = AircraftGetDirection(ptrAircraft);
int8_t rwyStep = 0;
uint16_t currentTile = 0;
uint16_t targets[AIRCRAFT_MAX_TARGETS] = {0};
uint8_t i;
switch(aircraftDir)
{
case DIR_EAST:
rwyStep = 1;
break;
case DIR_WEST:
rwyStep = -1;
break;
case DIR_NORTH:
rwyStep = -GameLevelColumns;
break;
case DIR_SOUTH:
rwyStep = GameLevelColumns;
break;
default:
return;
}
DEBUG_PRINT_VAR(AircraftGetTileFromFlightDataIndex(aircraftIdx));
for (currentTile = (AircraftGetTileFromFlightDataIndex(aircraftIdx) + rwyStep);
((levelBuffer[currentTile] & ~(TILE_MIRROR_FLAG)) != TILE_RWY_START_1)
&&
((levelBuffer[currentTile] & ~(TILE_MIRROR_FLAG)) != TILE_RWY_START_2);
currentTile -= rwyStep )
{
// Calculate new currentTile value until conditions are invalid.
}
for (i = 0; i < GAME_MAX_RUNWAYS; i++)
{
if (GameUsedRwy[i] == currentTile)
{
GameUsedRwy[i] = 0;
break;
}
}
for ( currentTile = (AircraftGetTileFromFlightDataIndex(aircraftIdx) + rwyStep);
((levelBuffer[currentTile] & ~(TILE_MIRROR_FLAG)) != TILE_RWY_EXIT)
&&
((levelBuffer[currentTile] & ~(TILE_MIRROR_FLAG)) != TILE_RWY_EXIT_2);
currentTile += rwyStep )
{
}
targets[0] = currentTile;
AircraftAddTargets(AircraftFromFlightDataIndex(aircraftIdx), targets);
}
}
/* *******************************************************************************************
*
* @name: void GameGetRunwayEntryTile(uint8_t aircraftIdx, TYPE_RWY_ENTRY_DATA* ptrRwyEntry)
*
* @author: Xavier Del Campo
*
* @param:
*
* uint8_t aircraftIdx:
* Index from FlightData. Used to determine target tile.
*
* TYPE_RWY_ENTRY_DATA* ptrRwyEntry:
* Instance to be filled with runway data.
*
* @brief:
* Fills a TYPE_RWY_ENTRY_DATA instance with information about runway.
*
* *******************************************************************************************/
static void GameGetRunwayEntryTile(uint8_t aircraftIdx, TYPE_RWY_ENTRY_DATA* ptrRwyEntry)
{
// Look for aircraft direction by searching TILE_RWY_EXIT
const uint16_t currentTile = AircraftGetTileFromFlightDataIndex(aircraftIdx);
int16_t step = 0;
uint16_t i;
if ( (currentTile >= GameLevelColumns)
&&
( (currentTile + GameLevelColumns) < GameLevelSize) )
{
if ( ((levelBuffer[currentTile + 1] & ~(TILE_MIRROR_FLAG)) == TILE_RWY_EXIT)
||
((levelBuffer[currentTile + 1] & ~(TILE_MIRROR_FLAG)) == TILE_RWY_EXIT_2) )
{
ptrRwyEntry->Direction = DIR_EAST;
ptrRwyEntry->rwyStep = GameLevelColumns;
step = 1;
}
else if ( ((levelBuffer[currentTile - 1] & ~(TILE_MIRROR_FLAG) ) == TILE_RWY_EXIT)
||
((levelBuffer[currentTile - 1] & ~(TILE_MIRROR_FLAG)) == TILE_RWY_EXIT_2) )
{
ptrRwyEntry->Direction = DIR_WEST;
ptrRwyEntry->rwyStep = GameLevelColumns;
step = -1;
}
else if ( ((levelBuffer[currentTile + GameLevelColumns] & ~(TILE_MIRROR_FLAG)) == TILE_RWY_EXIT)
||
((levelBuffer[currentTile + GameLevelColumns] & ~(TILE_MIRROR_FLAG)) == TILE_RWY_EXIT_2) )
{
ptrRwyEntry->Direction = DIR_SOUTH;
ptrRwyEntry->rwyStep = 1;
step = GameLevelColumns;
}
else if ( ((levelBuffer[currentTile - GameLevelColumns] & ~(TILE_MIRROR_FLAG)) == TILE_RWY_EXIT)
||
((levelBuffer[currentTile - GameLevelColumns] & ~(TILE_MIRROR_FLAG)) == TILE_RWY_EXIT_2) )
{
ptrRwyEntry->Direction = DIR_NORTH;
ptrRwyEntry->rwyStep = 1;
step = -GameLevelColumns;
}
else
{
ptrRwyEntry->rwyEntryTile = 0;
ptrRwyEntry->Direction = NO_DIRECTION;
ptrRwyEntry->rwyStep = 0;
return;
}
ptrRwyEntry->rwyEntryTile = currentTile + step;
i = ptrRwyEntry->rwyEntryTile;
while ( ((levelBuffer[i] & ~TILE_MIRROR_FLAG) != TILE_RWY_START_1)
&&
((levelBuffer[i] & ~TILE_MIRROR_FLAG) != TILE_RWY_START_2)
&&
(i > ptrRwyEntry->rwyStep)
&&
((i - ptrRwyEntry->rwyStep) < GameLevelSize ) )
{
i -= ptrRwyEntry->rwyStep;
}
ptrRwyEntry->rwyHeader = i;
}
else
{
Serial_printf("GameGetRunwayEntryTile(): Invalid index for tile.\n");
}
}
/* *******************************************************************************************
*
* @name: bool GameInsideLevelFromIsoPos(TYPE_ISOMETRIC_FIX16_POS* ptrIsoPos)
*
* @author: Xavier Del Campo
*
* @param:
*
* TYPE_ISOMETRIC_FIX16_POS* ptrIsoPos:
* (x, y, z) coordinate data in an isometric system.
*
* @return:
* Returns true if a (x, y, z) coordinate is inside level coordinates. False otherwise.
*
* *******************************************************************************************/
bool GameInsideLevelFromIsoPos(TYPE_ISOMETRIC_FIX16_POS* ptrIsoPos)
{
short x = (short)fix16_to_int(ptrIsoPos->x);
short y = (short)fix16_to_int(ptrIsoPos->y);
if (x < 0)
{
return false;
}
if (x > (GameLevelColumns << TILE_SIZE_BIT_SHIFT))
{
return false;
}
if (y < 0)
{
return false;
}
if (y > (GameLevelColumns << TILE_SIZE_BIT_SHIFT) )
{
return false;
}
return true;
}
/* *******************************************************************************************
*
* @name: void GameRemoveFlight(uint8_t idx, bool successful)
*
* @author: Xavier Del Campo
*
* @param:
*
* uint8_t idx:
* Index from FlightData.
*
* bool successful:
* False if flight was lost on timeout, true otherwise.
*
* @brief:
* Actions to be performed when a flight ends, both successfully or not (lost flight).
*
* @remarks:
* GameScore is updated here depending on player actions.
*
* *******************************************************************************************/
void GameRemoveFlight(const uint8_t idx, const bool successful)
{
uint8_t i;
for (i = PLAYER_ONE; i < MAX_PLAYERS; i++)
{
TYPE_PLAYER* const ptrPlayer = &PlayerData[i];
uint8_t j;
if (ptrPlayer->Active == false)
{
continue;
}
if (idx >= FlightData.nAircraft)
{
Serial_printf("GameRemoveFlight: index %d exceeds max index %d!\n", idx, FlightData.nAircraft);
return;
}
if ((FlightData.FlightDirection[idx] & ptrPlayer->FlightDirection) == 0)
{
continue;
}
for (j = 0; j < ptrPlayer->ActiveAircraft; j++)
{
if (ptrPlayer->ActiveAircraftList[j] == idx)
{
if (FlightData.State[idx] != STATE_IDLE)
{
uint8_t k;
for (k = 0; k < GAME_MAX_RUNWAYS; k++)
{
const uint16_t* const targets = AircraftGetTargets(idx);
uint16_t rwyArray[GAME_MAX_RWY_LENGTH] = {0};
if (SystemContains_u16(GameUsedRwy[k], targets, AIRCRAFT_MAX_TARGETS))
{
GameUsedRwy[k] = 0;
}
else
{
// GameRwyArray is filled with runway tiles.
GameGetSelectedRunwayArray(GameUsedRwy[k], rwyArray, GAME_MAX_RWY_LENGTH * sizeof (uint16_t) );
if (SystemContains_u16( AircraftGetTileFromFlightDataIndex(idx),
rwyArray,
sizeof (rwyArray) / sizeof (rwyArray[0]) ))
{
GameUsedRwy[k] = 0;
}
}
}
if (FlightData.State[idx] != STATE_APPROACH)
{
if (FlightData.State[idx] == STATE_UNBOARDING)
{
memset(ptrPlayer->UnboardingSequence, 0, GAME_MAX_SEQUENCE_KEYS);
ptrPlayer->UnboardingSequenceIdx = 0;
ptrPlayer->Unboarding = false;
ptrPlayer->LockTarget = false;
ptrPlayer->LockedAircraft = FLIGHT_DATA_INVALID_IDX;
}
if (AircraftRemove(idx) == false)
{
Serial_printf("Something went wrong when removing aircraft!\n");
return;
}
}
else
{
// STATE_APPROACH is the only state which is not linked to a TYPE_AIRCRAFT_DATA instance.
}
if (ptrPlayer->LockedAircraft == idx)
{
ptrPlayer->LockTarget = false;
ptrPlayer->LockedAircraft = FLIGHT_DATA_INVALID_IDX;
}
if (successful)
{
GameScore += SCORE_REWARD_FINISH_FLIGHT;
// Add punctuation
GameScore += FlightData.RemainingTime[idx] << 1;
}
else
{
GameScore = (GameScore < LOST_FLIGHT_PENALTY)? 0 : (GameScore - LOST_FLIGHT_PENALTY);
}
if (ptrPlayer->SelectedAircraft != 0)
{
if (ptrPlayer->SelectedAircraft >= j)
{
ptrPlayer->SelectedAircraft--;
}
}
FlightData.Passengers[idx] = 0;
FlightData.State[idx] = STATE_IDLE;
FlightData.Finished[idx] = true;
spawnMinTimeFlag = true;
TimerRestart(GameSpawnMinTime);
return;
}
}
}
// Usually called in PlayerHandler(), but now
// force active aircraft list update.
GameActiveAircraftList(ptrPlayer, &FlightData);
}
}
/* *******************************************************************************************
*
* @name: void GameActiveAircraftList(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData)
*
* @author: Xavier Del Campo
*
* @param:
*
* TYPE_PLAYER* const ptrPlayer:
* Pointer to a player structure
*
* TYPE_FLIGHT_DATA* const ptrFlightData:
* In the end, pointer to FlightData data table, which contains
* information about all available flights.
*
* @brief:
* Rebuilds flight data arrray for a specific player.
*
* *******************************************************************************************/
void GameActiveAircraftList(TYPE_PLAYER* const ptrPlayer, TYPE_FLIGHT_DATA* const ptrFlightData)
{
uint8_t i;
uint8_t j = 0;
uint8_t currentFlightDataIdx;
const uint8_t lastFlightDataIdx = ptrPlayer->ActiveAircraftList[ptrPlayer->SelectedAircraft];
// Clear all pointers for aircraft data first.
// Then, rebuild aircraft list for current player.
memset(ptrPlayer->ActiveAircraftList, 0, GAME_MAX_AIRCRAFT);
ptrPlayer->ActiveAircraft = 0;
ptrPlayer->RemainingAircraft = 0;
for (i = 0; i < FlightData.nAircraft; i++)
{
if ((ptrFlightData->State[i] != STATE_IDLE)
&&
(ptrFlightData->FlightDirection[i] & ptrPlayer->FlightDirection) )
{
ptrPlayer->ActiveAircraftList[j++] = i;
ptrPlayer->ActiveAircraft++;
}
else if (ptrFlightData->State[i] == STATE_IDLE)
{
ptrPlayer->RemainingAircraft++;
}
}
currentFlightDataIdx = ptrPlayer->ActiveAircraftList[ptrPlayer->SelectedAircraft];
if (aircraftCreated)
{
aircraftCreated = false;
if (ptrPlayer->ActiveAircraft > 1)
{
if (currentFlightDataIdx != lastFlightDataIdx)
{
for (ptrPlayer->SelectedAircraft = 0; ptrPlayer->SelectedAircraft < FlightData.nAircraft; ptrPlayer->SelectedAircraft++)
{
if (ptrPlayer->ActiveAircraftList[ptrPlayer->SelectedAircraft] == lastFlightDataIdx)
{
break;
}
}
}
}
}
ptrPlayer->FlightDataSelectedAircraft = ptrPlayer->ActiveAircraftList[ptrPlayer->SelectedAircraft];
}
/* *******************************************************************************************
*
* @name: void GameRemainingAircraft(uint8_t i)
*
* @author: Xavier Del Campo
*
* @param:
*
* uint8_t i:
* Index from FlightData.
*
* @brief:
* Reportedly, it updates FlightData.nRemainingAircraft depending on game status.
*
* @remarks:
* This function is called nActiveAircraft times. See loop inside GameCalculations()
* for further reference.
*
* *******************************************************************************************/
static void GameRemainingAircraft(const uint8_t i)
{
// Reset iterator when starting from first element.
if (i == 0)
{
FlightData.nRemainingAircraft = FlightData.nAircraft;
}
if ((FlightData.State[i] != STATE_IDLE)
||
FlightData.Finished[i])
{
FlightData.nRemainingAircraft--;
}
}
/* *******************************************************************************************
*
* @name: void GameFinished(uint8_t i)
*
* @author: Xavier Del Campo
*
* @param:
*
* uint8_t i:
* Index from FlightData.
*
* @brief:
* Sets levelFinished if there are no more active aircraft.
*
* @remarks:
* This function is called nActiveAircraft times. See loop inside GameCalculations()
* for further reference.
*
* *******************************************************************************************/
static void GameFinished(const uint8_t i)
{
if (FlightData.Finished[i] == false)
{
levelFinished = false;
}
}
/* *******************************************************************************************
*
* @name: void GameMinimumSpawnTimeout(void)
*
* @author: Xavier Del Campo
*
* @param:
*
* uint8_t i:
* Index from FlightData.
*
* @brief:
* Callback automatically executed on GameSpawnMinTime expired. spawnMinTimeFlag is used
* to set a minimum time between flight ended and flight spawn.
*
* *******************************************************************************************/
void GameMinimumSpawnTimeout(void)
{
spawnMinTimeFlag = false;
}
/* *******************************************************************************************
*
* @name: void GameAircraftCollision(uint8_t AircraftIdx)
*
* @author: Xavier Del Campo
*
* @param:
*
* uint8_t AircraftIdx:
* Index from FlightData.
*
* @brief:
* Sets GameAircraftCollisionFlag when two or more aircraft collide. This flag is then
* checked by Game().
*
* *******************************************************************************************/
void GameAircraftCollision(uint8_t AircraftIdx)
{
GameAircraftCollisionFlag = true;
GameAircraftCollisionIdx = AircraftIdx;
}
/* *******************************************************************************************
*
* @name: void GameAircraftCollision(uint8_t AircraftIdx)
*
* @author: Xavier Del Campo
*
* @param:
*
* uint8_t AircraftIdx:
* Index from FlightData.
*
* @brief:
* Event triggered by Aircraft when aircraft A is getting close to non-moving aircraft B
* and a security stop must be done in order to avoid collision.
*
* *******************************************************************************************/
void GameStopFlight(uint8_t AircraftIdx)
{
FL_STATE* ptrState = &FlightData.State[AircraftIdx];
if (*ptrState == STATE_TAXIING)
{
// Only allow auto stop under taxi
*ptrState = STATE_AUTO_STOPPED;
}
}
/* *******************************************************************************************
*
* @name: void GameAircraftCollision(uint8_t AircraftIdx)
*
* @author: Xavier Del Campo
*
* @param:
*
* uint8_t AircraftIdx:
* Index from FlightData.
*
* @brief:
* Event triggered by Aircraft when aircraft A is no longer getting close to aircraft B, so
* that taxiing can be resumed.
*
* *******************************************************************************************/
void GameResumeFlightFromAutoStop(uint8_t AircraftIdx)
{
FL_STATE* ptrState = &FlightData.State[AircraftIdx];
if (*ptrState == STATE_AUTO_STOPPED)
{
// Only recovery to STATE_TAXIING is allowed.
*ptrState = STATE_TAXIING;
}
}