mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-14 10:11:51 +02:00
767 lines
20 KiB
C++
767 lines
20 KiB
C++
#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("<pmgamestart>", NULL);
|
|
const idEventDef EV_PlayerRespawn("<pmplayerspawn>", NULL);
|
|
const idEventDef EV_MonsterRespawn("<pmmonsterspawn>", "d");
|
|
const idEventDef EV_ToggleFruit("<pmtogglefruit>", NULL);
|
|
const idEventDef EV_GameOver("<pmgameover>", NULL);
|
|
const idEventDef EV_NextMap("<pmnextmap>", 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; y<ARCADE_GRID_HEIGHT; y++) {
|
|
for (int x=0; x<ARCADE_GRID_WIDTH; x++) {
|
|
startingGrid[x][y] = maze[y*ARCADE_GRID_WIDTH+x] - '0';
|
|
}
|
|
}
|
|
}
|
|
|
|
void hhArcadeGame::Save(idSaveGame *savefile) const {
|
|
int i,j;
|
|
savefile->Write(startingGrid, ARCADE_GRID_WIDTH*ARCADE_GRID_HEIGHT);
|
|
for (i=0; i<ARCADE_GRID_WIDTH; i++) {
|
|
for (j=0; j<ARCADE_GRID_HEIGHT; j++) {
|
|
grid[i][j].Save(savefile);
|
|
}
|
|
}
|
|
for (i=0; i<4; i++) {
|
|
monsters[i].Save(savefile);
|
|
}
|
|
player.Save(savefile);
|
|
fruit.Save(savefile);
|
|
savefile->WriteVec2(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; i<ARCADE_GRID_WIDTH; i++) {
|
|
for (j=0; j<ARCADE_GRID_HEIGHT; j++) {
|
|
grid[i][j].Restore(savefile);
|
|
}
|
|
}
|
|
for (i=0; i<4; i++) {
|
|
monsters[i].Restore(savefile);
|
|
}
|
|
player.Restore(savefile);
|
|
fruit.Restore(savefile);
|
|
savefile->ReadVec2(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; x<ARCADE_GRID_WIDTH; x++) {
|
|
for (int y=0; y<ARCADE_GRID_HEIGHT; y++) {
|
|
if (x != skipX || y != skipY) {
|
|
if (grid[x][y].GetType() == type) {
|
|
xOut = x;
|
|
yOut = y;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void hhArcadeGame::ResetMap() {
|
|
int x, y;
|
|
|
|
// Make a copy of the grid that we can modify it, transposing in the process
|
|
for (x=0; x<ARCADE_GRID_WIDTH; x++) {
|
|
for (y=0; y<ARCADE_GRID_HEIGHT; y++) {
|
|
grid[x][y].SetType(startingGrid[x][y]);
|
|
grid[x][y].SetVisible(true);
|
|
}
|
|
}
|
|
|
|
FindType(ARCADE_FRUIT, x, y);
|
|
fruit.Set(x, y, false);
|
|
}
|
|
|
|
void hhArcadeGame::ResetPlayerAndMonsters() {
|
|
int x, y;
|
|
|
|
FindType(ARCADE_PLAYERSTART, x, y);
|
|
player.moveDelay = 200;
|
|
player.nextMoveTime = gameLocal.time;
|
|
player.isPlayer = true;
|
|
player.Set(x, y);
|
|
player.dying = false;
|
|
bPoweredUp = false;
|
|
StopSound(SND_CHANNEL_ITEM, true);
|
|
|
|
FindType(ARCADE_MONSTERSTART, x, y);
|
|
monsters[0].Set(x, y);
|
|
monsters[1].Set(x, y);
|
|
monsters[2].Set(x, y);
|
|
monsters[3].Set(x, y);
|
|
for (int ix=0; ix<4; ix++) {
|
|
monsters[ix].SetVisible(true);
|
|
monsters[ix].moveDelay = 300;
|
|
monsters[ix].nextMoveTime = gameLocal.time;
|
|
monsters[ix].vulnerableTimeRemaining = 0;
|
|
}
|
|
|
|
}
|
|
|
|
void hhArcadeGame::ResetGame() {
|
|
level = 1;
|
|
score = 0;
|
|
lives = 3;
|
|
nextScoreBoost = 10000;
|
|
bSimulating = false;
|
|
nextPelletTime = gameLocal.time;
|
|
bPoweredUp = false;
|
|
}
|
|
|
|
idVec2 hhArcadeGame::SmoothMovement(MovingGamePiece &piece) {
|
|
idVec2 curPos;
|
|
idVec2 lastGuiPos;
|
|
idVec2 lastGuiMove;
|
|
curPos.x = piece.x;
|
|
curPos.y = piece.y;
|
|
lastGuiPos.x = (curPos.x - piece.lastMove.x) * ARCADE_CELL_WIDTH;
|
|
lastGuiPos.y = (curPos.y - piece.lastMove.y) * ARCADE_CELL_HEIGHT;
|
|
lastGuiMove.x = piece.lastMove.x * ARCADE_CELL_WIDTH;
|
|
lastGuiMove.y = piece.lastMove.y * ARCADE_CELL_HEIGHT;
|
|
|
|
float alpha = (gameLocal.time - piece.lastMoveTime) / (float)piece.moveDelay;
|
|
alpha = idMath::ClampFloat(0.0f, 1.0f, alpha);
|
|
return lastGuiPos + lastGuiMove * alpha;
|
|
}
|
|
|
|
void hhArcadeGame::UpdateView() {
|
|
int ix;
|
|
idVec2 guiPos;
|
|
idUserInterface *gui = renderEntity.gui[0];
|
|
|
|
if (gui) {
|
|
|
|
if (player.Changed()) {
|
|
guiPos = SmoothMovement(player);
|
|
gui->SetStateInt("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; x<ARCADE_GRID_WIDTH; x++) {
|
|
for (int y=0; y<ARCADE_GRID_HEIGHT; y++) {
|
|
if (grid[x][y].Changed()) {
|
|
if (grid[x][y].GetType() == ARCADE_PELLET) {
|
|
gui->SetStateInt(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<ARCADE_GRID_WIDTH; i++) {
|
|
for (j=0; j<ARCADE_GRID_HEIGHT; j++) {
|
|
x = (startX + i) % ARCADE_GRID_WIDTH;
|
|
y = (startY + j) % ARCADE_GRID_HEIGHT;
|
|
// For each valid pellet, make all adjacent pellets visible
|
|
if (grid[x][y].GetType() == ARCADE_PELLET && grid[x][y].IsVisible()) {
|
|
if (grid[x-1][y].GetType() == ARCADE_PELLET && !grid[x-1][y].IsVisible()) {
|
|
grid[x-1][y].SetVisible(true);
|
|
growth++;
|
|
}
|
|
if (grid[x+1][y].GetType() == ARCADE_PELLET && !grid[x+1][y].IsVisible()) {
|
|
grid[x+1][y].SetVisible(true);
|
|
growth++;
|
|
}
|
|
if (grid[x][y-1].GetType() == ARCADE_PELLET && !grid[x][y-1].IsVisible()) {
|
|
grid[x][y-1].SetVisible(true);
|
|
growth++;
|
|
}
|
|
if (grid[x][y+1].GetType() == ARCADE_PELLET && !grid[x][y+1].IsVisible()) {
|
|
grid[x][y+1].SetVisible(true);
|
|
growth++;
|
|
}
|
|
|
|
if (growth > 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<ARCADE_GRID_WIDTH; x++) {
|
|
for (y=0; levelCompleted && y<ARCADE_GRID_HEIGHT; y++) {
|
|
type = grid[x][y].GetType();
|
|
if (grid[x][y].IsVisible() && (type == ARCADE_PELLET || type == ARCADE_POWERUP)) {
|
|
levelCompleted = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (levelCompleted) {
|
|
AddScore(2000);
|
|
bSimulating = false;
|
|
if (victoryAmount > 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<hhPlayer*>(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 );
|
|
}
|
|
}
|
|
|