#include "precompiled.h" #pragma hdrstop #include "prey_local.h" #define DEFAULT_HIGH_SCORE 76535 #define DEFAULT_HIGH_SCORE_NAME "Teacher" #define FRUIT_DELAY 15000 const idEventDef EV_GameStart("", NULL); const idEventDef EV_PlayerRespawn("", NULL); const idEventDef EV_MonsterRespawn("", "d"); const idEventDef EV_ToggleFruit("", NULL); const idEventDef EV_GameOver("", NULL); const idEventDef EV_NextMap("", NULL); CLASS_DECLARATION(hhConsole, hhArcadeGame) EVENT( EV_GameStart, hhArcadeGame::Event_GameStart) EVENT( EV_PlayerRespawn, hhArcadeGame::Event_PlayerRespawn) EVENT( EV_MonsterRespawn, hhArcadeGame::Event_MonsterRespawn) EVENT( EV_ToggleFruit, hhArcadeGame::Event_ToggleFruit) EVENT( EV_GameOver, hhArcadeGame::Event_GameOver) EVENT( EV_NextMap, hhArcadeGame::Event_NextMap) EVENT( EV_CallGuiEvent, hhArcadeGame::Event_CallGuiEvent) END_CLASS hhArcadeGame::hhArcadeGame() { } hhArcadeGame::~hhArcadeGame() { } void hhArcadeGame::Spawn() { highscore = DEFAULT_HIGH_SCORE; highscoreName = DEFAULT_HIGH_SCORE_NAME; GetMazeForLevel(1); victoryAmount = spawnArgs.GetInt("victory"); bPlayerMoving = false; playerMove.Set(1,0); bGameOver = true; ResetMap(); ResetPlayerAndMonsters(); ResetGame(); UpdateView(); } void hhArcadeGame::GetMazeForLevel(int lev) { numMazes = idMath::ClampInt(1, 999, spawnArgs.GetInt("numMazes")); const char *maze = spawnArgs.GetString(va("maze%d", (lev-1)%numMazes+1)); assert(idStr::Length(maze) == (ARCADE_GRID_WIDTH*ARCADE_GRID_HEIGHT)); memset(startingGrid, 0, ARCADE_GRID_WIDTH*ARCADE_GRID_HEIGHT); for (int y=0; yWrite(startingGrid, ARCADE_GRID_WIDTH*ARCADE_GRID_HEIGHT); for (i=0; iWriteVec2(playerMove); savefile->WriteBool(bPlayerMoving); savefile->WriteBool(bSimulating); savefile->WriteBool(bGameOver); savefile->WriteBool(bPoweredUp); savefile->WriteInt(score); savefile->WriteInt(lives); savefile->WriteInt(level); savefile->WriteInt(nextScoreBoost); savefile->WriteInt(nextPelletTime); savefile->WriteInt(victoryAmount); savefile->WriteInt(numMazes); savefile->WriteInt(highscore); savefile->WriteString(highscoreName); } void hhArcadeGame::Restore( idRestoreGame *savefile ) { int i,j; savefile->Read(startingGrid, ARCADE_GRID_WIDTH*ARCADE_GRID_HEIGHT); for (i=0; iReadVec2(playerMove); savefile->ReadBool(bPlayerMoving); savefile->ReadBool(bSimulating); savefile->ReadBool(bGameOver); savefile->ReadBool(bPoweredUp); savefile->ReadInt(score); savefile->ReadInt(lives); savefile->ReadInt(level); savefile->ReadInt(nextScoreBoost); savefile->ReadInt(nextPelletTime); savefile->ReadInt(victoryAmount); savefile->ReadInt(numMazes); savefile->ReadInt(highscore); savefile->ReadString(highscoreName); } void hhArcadeGame::ConsoleActivated() { BecomeActive(TH_MISC3); } void hhArcadeGame::StartGame() { if (bGameOver) { ResetMap(); ResetPlayerAndMonsters(); StartSound("snd_intro", SND_CHANNEL_ANY, 0, true, NULL); ResetGame(); StartSound("snd_coin", SND_CHANNEL_ANY, 0, true, NULL); PostEventMS(&EV_GameStart, 2000); bGameOver = false; UpdateView(); } } void hhArcadeGame::FindType(int type, int &xOut, int &yOut, int skipX, int skipY) { xOut = 0; yOut = 0; for (int x=0; xSetStateInt("player_x", guiPos.x); gui->SetStateInt("player_y", guiPos.y); gui->SetStateBool("player_vis", player.IsVisible()); gui->SetStateBool("player_moving", bPlayerMoving); gui->SetStateFloat("player_angle", player.angle); gui->SetStateBool("player_dying", player.dying); } for (ix=0; ix<4; ix++) { if (monsters[ix].Changed()) { guiPos = SmoothMovement(monsters[ix]); gui->SetStateInt(va("monster%d_x", ix+1), guiPos.x); gui->SetStateInt(va("monster%d_y", ix+1), guiPos.y); gui->SetStateBool(va("monster%d_vis", ix+1), monsters[ix].IsVisible()); gui->SetStateInt(va("monster%d_pu", ix+1), monsters[ix].vulnerableTimeRemaining); gui->SetStateFloat(va("monster%d_angle", ix+1), monsters[ix].angle); } } // Traverse grid, telling gui about state of the pellets and powerups int curPowerup = 1; int curPellet = 1; for (int x=0; xSetStateInt(va("pellet%d_x", curPellet), x * ARCADE_CELL_WIDTH); gui->SetStateInt(va("pellet%d_y", curPellet), y * ARCADE_CELL_HEIGHT); gui->SetStateBool(va("pellet%d_vis", curPellet), grid[x][y].IsVisible()); curPellet++; } else if (grid[x][y].GetType() == ARCADE_POWERUP) { gui->SetStateInt(va("powerup%d_x", curPowerup), x * ARCADE_CELL_WIDTH); gui->SetStateInt(va("powerup%d_y", curPowerup), y * ARCADE_CELL_HEIGHT); gui->SetStateBool(va("powerup%d_vis", curPowerup), grid[x][y].IsVisible()); curPowerup++; } } } } if (fruit.Changed()) { gui->SetStateInt("fruit_x", fruit.x * ARCADE_CELL_WIDTH); gui->SetStateInt("fruit_y", fruit.y * ARCADE_CELL_HEIGHT); gui->SetStateBool("fruit_vis", fruit.IsVisible()); } if (score > highscore) { highscore = score; highscoreName = cvarSystem->GetCVarString("ui_name"); } gui->SetStateInt("maze", (level-1)%numMazes+1); gui->SetStateInt("level", level); gui->SetStateInt("score", score); gui->SetStateInt("lives", lives); gui->SetStateInt("highscore", highscore); gui->SetStateString("highscorename", highscoreName.c_str()); gui->SetStateBool("gameover", bGameOver); gui->StateChanged(gameLocal.time, true); } } bool hhArcadeGame::HandleSingleGuiCommand(idEntity *entityGui, idLexer *src) { idToken token; if (!src->ReadToken(&token)) { return false; } if (token == ";") { return false; } if (token.Icmp("startgame") == 0) { // Trigger this entity, used for tip system idEntity *agTrig = gameLocal.FindEntity( "arcadegame_used" ); if( agTrig ) { agTrig->PostEventMS( &EV_Activate, 0, this ); } BecomeActive(TH_MISC3); StartGame(); } else if (token.Icmp("reset") == 0) { BecomeInactive(TH_MISC3); bSimulating = false; bGameOver = true; bPlayerMoving = false; playerMove.Set(1,0); ResetMap(); ResetPlayerAndMonsters(); ResetGame(); highscore = 1000; highscoreName = DEFAULT_HIGH_SCORE_NAME; UpdateView(); } else { src->UnreadToken(&token); return false; } return true; } void hhArcadeGame::PlayerControls(usercmd_t *cmd) { if (bGameOver && (cmd->buttons & BUTTON_ATTACK)) { StartGame(); } // These come from the player every tick if (cmd->rightmove != 0 || cmd->forwardmove != 0) { playerMove.x = cmd->rightmove > 0 ? 1 : cmd->rightmove < 0 ? -1 : 0; playerMove.y = cmd->forwardmove < 0 ? 1 : cmd->forwardmove > 0 ? -1 : 0; } } idVec2 MoveForDirection(int dir) { switch(dir) { case PAC_MOVE_LEFT: return idVec2(-1, 0); case PAC_MOVE_RIGHT: return idVec2(1, 0); case PAC_MOVE_UP: return idVec2(0, -1); case PAC_MOVE_DOWN: return idVec2(0, 1); } return idVec2(0,0); } bool hhArcadeGame::MoveIsValid(idVec2 &move, MovingGamePiece &piece) { int newX = piece.x + move.x; int newY = piece.y + move.y; int cell = grid[newX][newY].GetType(); if (cell == ARCADE_CLIPPLAYER) { // Hack: disallow reentry into ghost cage. Assumes the monster start is adjacent to the playerclip int sourceCell = grid[piece.x][piece.y].GetType(); if (sourceCell != ARCADE_MONSTERSTART) { return false; } } return (cell != ARCADE_SOLID && (cell != ARCADE_CLIPPLAYER || !piece.isPlayer ) ); } void hhArcadeGame::GrowPellets() { if (!bSimulating) { return; } int growth = 0; int i, j; int x, y; int startX = gameLocal.random.RandomInt(ARCADE_GRID_WIDTH); int startY = gameLocal.random.RandomInt(ARCADE_GRID_HEIGHT); StartSound("snd_grow", SND_CHANNEL_ANY, 0, true, NULL); for (i=0; i 4) { return; } } } } } void hhArcadeGame::CheckForCollisions(MovingGamePiece &piece) { int x = piece.x; int y = piece.y; int ix; int type; int destX, destY; switch(grid[x][y].GetType()) { case ARCADE_TELEPORT: FindType(ARCADE_TELEPORT, destX, destY, x, y); piece.Set(destX, destY); break; case ARCADE_PELLET: if (piece.isPlayer && grid[x][y].IsVisible()) { grid[x][y].SetVisible(false); AddScore(1); StartSound("snd_pellet", SND_CHANNEL_ANY, 0, true, NULL); } break; case ARCADE_POWERUP: if (piece.isPlayer && grid[x][y].IsVisible()) { AddScore(50); grid[x][y].SetVisible(false); monsters[0].vulnerableTimeRemaining += 5000 / level; monsters[1].vulnerableTimeRemaining += 5000 / level; monsters[2].vulnerableTimeRemaining += 5000 / level; monsters[3].vulnerableTimeRemaining += 5000 / level; bPoweredUp = true; StopSound(SND_CHANNEL_ITEM, true); StartSound("snd_powerup", SND_CHANNEL_ANY, 0, true, NULL); StartSound("snd_poweruploop", SND_CHANNEL_ITEM, 0, true, NULL); } break; } if (piece.isPlayer) { if (fruit.IsVisible() && piece.x==fruit.x && piece.y==fruit.y) { fruit.SetVisible(false); AddScore(1000*level); StartSound("snd_fruit", SND_CHANNEL_ANY, 0, true, NULL); CancelEvents(&EV_ToggleFruit); PostEventMS(&EV_ToggleFruit, FRUIT_DELAY); } } // player / monster collisions for (ix=0; ix<4; ix++) { if (monsters[ix].IsVisible() && player.x==monsters[ix].x && player.y==monsters[ix].y) { if (monsters[ix].vulnerableTimeRemaining > 0) { monsters[ix].SetVisible(false); StartSound("snd_eatmonster", SND_CHANNEL_ANY, 0, true, NULL); AddScore(500); PostEventMS(&EV_MonsterRespawn, 3000, ix); } else if (!gameLocal.GetLocalPlayer()->godmode) { bSimulating = false; player.SetVisible(false); player.dying = true; StartSound("snd_die", SND_CHANNEL_ANY, 0, true, NULL); if (lives <= 0) { PostEventMS(&EV_GameOver, 0); } else { lives--; PostEventMS(&EV_PlayerRespawn, 2000); } return; } } } // Check for life bonus if (score >= nextScoreBoost) { lives++; nextScoreBoost *= 2; StartSound("snd_lifeboost", SND_CHANNEL_ANY, 0, true, NULL); } // Check for level completion if (piece.isPlayer) { bool levelCompleted = true; for (x=0; levelCompleted && x 0 && (level+1 >= victoryAmount)) { ActivateTargets( gameLocal.GetLocalPlayer() ); StartSound( "snd_victory", SND_CHANNEL_ANY ); victoryAmount = 0; } StartSound("snd_leveldone", SND_CHANNEL_ANY, 0, true, NULL); PostEventMS(&EV_NextMap, 2000); } } } bool hhArcadeGame::DoMove(idVec2 &move, MovingGamePiece &piece) { bool bMoved = move.x || move.y; piece.Move(move); if (piece.isPlayer) { // Acount for player movement direction bPlayerMoving = bMoved; } CheckForCollisions(piece); return bMoved; } void hhArcadeGame::DoPlayerMove() { bPlayerMoving = false; if (!bSimulating) { return; } bool bValidXMove = playerMove.x && MoveIsValid(idVec2(playerMove.x, 0), player); bool bValidYMove = playerMove.y && MoveIsValid(idVec2(0, playerMove.y), player); // Allow move direction to alternate between X and Y if (player.lastMove.x) { if (bValidYMove) { DoMove(idVec2(0, playerMove.y), player); } else if (bValidXMove) { DoMove(idVec2(playerMove.x, 0), player); } else if (MoveIsValid(player.lastMove, player)) { DoMove(player.lastMove, player); } } else { if (bValidXMove) { DoMove(idVec2(playerMove.x, 0), player); } else if (bValidYMove) { DoMove(idVec2(0, playerMove.y), player); } else if (MoveIsValid(player.lastMove, player)) { DoMove(player.lastMove, player); } } } void hhArcadeGame::DoMonsterAI(MovingGamePiece &monster, int index) { if (!bSimulating) { return; } if (monster.vulnerableTimeRemaining > 0) { monster.vulnerableTimeRemaining -= monster.moveDelay; monster.vulnerableTimeRemaining = idMath::ClampInt(0, 100000, monster.vulnerableTimeRemaining); } idVec2 move; idVec2 toPlayer; idVec2 toPlayerMove; int playerDirX, playerDirY; toPlayer.Set(player.x - monster.x, player.y - monster.y); playerDirX = toPlayer.x < 0 ? PAC_MOVE_LEFT : PAC_MOVE_RIGHT; playerDirY = toPlayer.y < 0 ? PAC_MOVE_UP : PAC_MOVE_DOWN; toPlayerMove.Set( toPlayer.x > 0 ? 1 : toPlayer.x < 0 ? -1 : 0, toPlayer.y > 0 ? 1 : toPlayer.y < 0 ? -1 : 0 ); idVec2 illegalMove = -monster.lastMove; bool bGoTowardPlayer = gameLocal.random.RandomInt(4) >= index; int choice; int startingChoice = index; // Makes each monster unique int j; // Keep moving in direction of last move until there is an intersection // At intersection, choose between one of the directions, favoring one going towards // the player. Never go back in the direction you came from unless at a dead end. // Go towards player if possible if (bGoTowardPlayer) { move = MoveForDirection(playerDirX); if (MoveIsValid(move, monster) && move != illegalMove) { DoMove(move, monster); return; } move = MoveForDirection(playerDirY); if (MoveIsValid(move, monster) && move != illegalMove) { DoMove(move, monster); return; } } // Otherwise move in one of the other valid directions for (j=0; j<4; j++) { choice = (startingChoice + j) % 4; move = MoveForDirection(choice); if (MoveIsValid(move, monster) && move != illegalMove) { DoMove(move, monster); return; } } // If still haven't found a valid move, must be at a dead end, now allow illegalMove for (j=0; j<4; j++) { choice = (startingChoice + j) % 4; move = MoveForDirection(choice); if (MoveIsValid(move, monster)) { DoMove(move, monster); return; } } } void hhArcadeGame::Think() { hhConsole::Think(); if (thinkFlags & TH_MISC3) { if (bSimulating) { // Player if (gameLocal.time > player.nextMoveTime) { player.nextMoveTime = gameLocal.time + player.moveDelay; if (playerMove.LengthSqr() > 0.0f) { DoPlayerMove(); } } // AI for (int ix=0; ix<4; ix++) { if (gameLocal.time > monsters[ix].nextMoveTime) { monsters[ix].nextMoveTime = gameLocal.time + monsters[ix].moveDelay; DoMonsterAI(monsters[ix], ix); } } // See if power up sound should end if (bPoweredUp) { bPoweredUp = false; for (int ix=0; ix<4; ix++) { if (monsters[ix].vulnerableTimeRemaining > 0) { bPoweredUp = true; break; } } if (!bPoweredUp) { StopSound(SND_CHANNEL_ITEM, true); } } if (gameLocal.time > nextPelletTime) { nextPelletTime = gameLocal.time + 3500; //GrowPellets(); } UpdateView(); } } } void hhArcadeGame::AddScore(int amount) { if (!gameLocal.GetLocalPlayer()->godmode) { score += amount; } } void hhArcadeGame::Event_GameStart() { bSimulating = true; PostEventMS(&EV_ToggleFruit, FRUIT_DELAY); } void hhArcadeGame::Event_GameOver() { // Player ran out of lives CancelEvents(&EV_ToggleFruit); StopSound(SND_CHANNEL_ITEM, true); bSimulating = false; bGameOver = true; CallNamedEvent("gameover"); UpdateView(); } void hhArcadeGame::Event_NextMap() { // Transition to next map CancelEvents(&EV_ToggleFruit); StopSound(SND_CHANNEL_ITEM, true); GetMazeForLevel(++level); ResetMap(); ResetPlayerAndMonsters(); StartSound("snd_intro", SND_CHANNEL_ANY, 0, true, NULL); PostEventMS(&EV_GameStart, 2000); UpdateView(); } void hhArcadeGame::Event_PlayerRespawn() { // Player died, respawn CancelEvents(&EV_ToggleFruit); ResetPlayerAndMonsters(); StartSound("snd_intro", SND_CHANNEL_ANY, 0, true, NULL); player.SetVisible(true); player.dying = false; PostEventMS(&EV_GameStart, 3000); UpdateView(); } void hhArcadeGame::Event_MonsterRespawn(int index) { // Monster died, respawn int x,y; FindType(ARCADE_MONSTERSTART, x, y); monsters[index].SetVisible(true); monsters[index].vulnerableTimeRemaining = 0; monsters[index].Set(x,y); monsters[index].nextMoveTime = gameLocal.time + 3000; } void hhArcadeGame::Event_ToggleFruit() { if (fruit.IsVisible()) { fruit.SetVisible(false); PostEventMS(&EV_ToggleFruit, FRUIT_DELAY); } else { fruit.SetVisible(true); PostEventMS(&EV_ToggleFruit, 5000); } } void hhArcadeGame::Event_CallGuiEvent(const char *eventName) { if (!idStr::Icmp(eventName, "turnoff")) { // Release any players routing commands to us for( int i = 0; i < gameLocal.numClients; i++ ) { if ( gameLocal.entities[i] && gameLocal.entities[i]->IsType(hhPlayer::Type) ) { hhPlayer *player = static_cast(gameLocal.entities[i]); if (player->guiWantsControls.IsValid() && player->guiWantsControls.GetEntity() == this) { player->guiWantsControls = NULL; } } } } CallNamedEvent(eventName); } void hhArcadeGame::LockedGuiReleased(hhPlayer *player) { idEntity *agTrig = gameLocal.FindEntity( "arcadegame_released" ); if( agTrig ) { agTrig->PostEventMS( &EV_Activate, 0, this ); } }