home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
GameStar 2006 April
/
Gamestar_83_2006-04_dvd.iso
/
Dema
/
demowot_english.exe
/
Script
/
Source
/
pacman
/
pacman.sma
< prev
Wrap
Text File
|
2005-03-11
|
16KB
|
694 lines
#include "script.inc"
// --- Enums ---
enum eCellType
{
CT_UNDEFINED,
CT_EMPTY,
CT_GHOST, // empty cell in the ghost respawn area
CT_WALL,
CT_BORDER, // indestructible wall
CT_MINE, // yellow dot
CT_POWER, // power mode mine
CT_DEATH, // instant kill mine (to prevent the player from going into the ghost respawn area)
CT_RESPAWN, // ghost respawn point
CT_START, // player start point
};
// --- Defines & consts ---
new const playerTeam = 1;
new const ghostTeam = 2;
new const wallBase[] = "pacman_wall";
new const Float:wallScale[vec3] = {8.0, 8.0, 4.0};
new const borderBase[] = "pacman_border";
new const Float:borderScale[vec3] = {8.0, 8.0, 4.0};
new const fowBeaconBase[] = "fowbacon";
new const mineBase[] = "pacman_mine"
new const Float:mineScale[vec3] = {2.0, 2.0, 1.0};
new const powermineBase[] = "pacman_power_mine";
new const Float:powermineScale[vec3] = {4.0, 4.0, 1.0};
new const deathmineBase[] = "pacman_death_mine";
new const Float:deathmineScale[vec3] = {5.0, 5.0, 1.0};
new const playerTankBase[] = "pacman_player";
new const ghostTankBase[] = "pacman_ghost";
new const Float:blockSize = 8.0; // meters
#define MAP_SIZE 31 // must be 4 * k + 3
#define HALF_MAP_SIZE ((MAP_SIZE + 1) / 2)
new const nHoles = 12;
new const nPowerMines = 8;
new const playerStartX = 1;
new const playerStartY = 1;
#define GHOST_AREA_SIZE 7
new const eCellType:ghostArea[GHOST_AREA_SIZE][GHOST_AREA_SIZE] =
{
{CT_BORDER, CT_BORDER, CT_BORDER, CT_DEATH, CT_BORDER, CT_BORDER, CT_BORDER},
{CT_BORDER, CT_GHOST, CT_GHOST, CT_GHOST, CT_GHOST, CT_GHOST, CT_BORDER},
{CT_BORDER, CT_GHOST, CT_RESPAWN, CT_GHOST, CT_RESPAWN, CT_GHOST, CT_BORDER},
{CT_DEATH, CT_GHOST, CT_GHOST, CT_GHOST, CT_GHOST, CT_GHOST, CT_DEATH},
{CT_BORDER, CT_GHOST, CT_RESPAWN, CT_GHOST, CT_RESPAWN, CT_GHOST, CT_BORDER},
{CT_BORDER, CT_GHOST, CT_GHOST, CT_GHOST, CT_GHOST, CT_GHOST, CT_BORDER},
{CT_BORDER, CT_BORDER, CT_BORDER, CT_DEATH, CT_BORDER, CT_BORDER, CT_BORDER},
}
#define N_GHOSTS 4
#define N_GHOST_RESPAWN_POINTS 4
new const Float:ghostRespawnDelay = 5.0;
new const Float:powerModeTime = 60.0 // secs
new const scoreMine = 1000;
new const scorePowerMine = 2000;
new const scoreKill = 10000;
new const scorePowerKill = 20000;
new const Float:aiUpdateFrequency = 2.0;
new const Float:aiChaseDistMultiplier = 0.5;
new const aiChaseMaxIter = 4;
#define MAX_PROGRESS_STEPS_SHOWN 15
// --- Globals ---
new eCellType:map[MAP_SIZE][MAP_SIZE]; // [column][row]
new player;
new powerMode; // int instead of bool, so that it can store the number of active (exploded and not yet expired)
// power mines, in case the player gets one just before the previous one expires.
// (using a bool would turn off power mode when the time for the first mine expires.)
new score;
new nMines;
new ghosts[N_GHOSTS];
new Float:ghostRespawnPoints[N_GHOST_RESPAWN_POINTS][vec3];
new Float:ghostTargets[N_GHOSTS][vec3]; // current move targets
// --- Main ---
public main()
CommonAKMain();
public PostGameStart()
{
CommonAKInit();
AddAKEntityEvent("OnTestEvent", ANY_ENTITY, TEST_EVENT);
GenerateMap();
CreatePlayer();
FindGhostRespawnPoints();
CreateGhosts();
InitGhostAI();
AddObjective("ExplodeMines", OT_PRIMARY, "#iMSPM_PS01#", "#iMSPM_PL01#", OS_IN_PROGRESS);
}
public OnTestEvent()
{
}
// --- Support functions ---
stock CellToPos(col, row, Float:result[vec3])
{
result[vec3:x] = (col + 0.5) * blockSize;
result[vec3:y] = (row + 0.5) * blockSize;
result[vec3:z] = 0.0;
}
stock PosToCell(Float:pos[vec3], &col, &row)
{
col = floatround(pos[vec3:x] / blockSize, floatround_floor);
row = floatround(pos[vec3:y] / blockSize, floatround_floor);
if ((col < 0) || (col >= MAP_SIZE))
col = row = -1;
if ((row < 0) || (row >= MAP_SIZE))
col = row = -1;
}
stock Float:FRnd(Float:low, Float:high)
{
new i;
i = Rnd(0, 32767);
new Float:f = low + float(i) / 32767.0 * (high - low);
if (f < low)
f = low;
if (f > high)
f = high;
return f;
}
// --- Generate Map ---
stock ShowProgress(steps)
{
static sumSteps = 0;
sumSteps += steps;
while (sumSteps > MAX_PROGRESS_STEPS_SHOWN)
sumSteps -= MAX_PROGRESS_STEPS_SHOWN;
new str[MAX_PROGRESS_STEPS_SHOWN + 1];
new i;
for (i = 0; i < sumSteps; i++)
str[i] = '.';
str[i] = 0;
SetShortInfo(str);
}
stock InitMap()
{
new i;
for (i = 0; i < MAP_SIZE; i++)
{
new j;
for (j = 0; j < MAP_SIZE; j++)
map[i][j] = CT_EMPTY;
}
new Float:mapCenter[vec3];
mapCenter[vec3:x] = mapCenter[vec3:y] = (MAP_SIZE * blockSize) / 2.0;
new DrID:fowBeacon = CreateEntity(fowBeaconBase, mapCenter);
SetPropertyInt(fowBeacon, "Team", playerTeam);
SetPropertyFloat(fowBeacon, "FOWRangeRadius", MAP_SIZE * blockSize);
fowBeacon = CreateEntity(fowBeaconBase, mapCenter);
SetPropertyInt(fowBeacon, "Team", ghostTeam);
SetPropertyFloat(fowBeacon, "FOWRangeRadius", MAP_SIZE * blockSize);
}
// --- Generate walls ---
stock SetCell(col, row, eCellType:type, bool:symmetrically = true)
{
map[col][row] = type;
if (symmetrically)
{
map[MAP_SIZE - col - 1][row] = type;
map[col][MAP_SIZE - row - 1] = type;
map[MAP_SIZE - col - 1][MAP_SIZE - row - 1] = type;
}
}
stock GenerateBorder()
{
new i;
for (i = 0; i < HALF_MAP_SIZE; i++)
{
SetCell(i, 0, CT_BORDER);
SetCell(0, i, CT_BORDER);
}
}
stock RndLine(bool:processed[HALF_MAP_SIZE])
{
new line;
do
{
line = Rnd(1, HALF_MAP_SIZE / 2 - 1) * 2;
} while (processed[line]);
return line;
}
stock GenerateRow(bool:processedRows[HALF_MAP_SIZE])
{
new row = RndLine(processedRows);
new i;
new dir = Rnd(0, 1) * 2 - 1;
new col = (dir == 1) ? 1 : HALF_MAP_SIZE - 1;
for (i = 1; i < HALF_MAP_SIZE; i++, col += dir)
{
new eCellType:nextCell = (col + dir == HALF_MAP_SIZE) ? CT_WALL : map[col + dir][row];
if ((map[col][row] == CT_EMPTY) && (nextCell == CT_EMPTY))
SetCell(col, row, CT_WALL);
}
processedRows[row] = true;
}
stock GenerateColumn(bool:processedColumns[HALF_MAP_SIZE])
{
new col = RndLine(processedColumns);
new i;
new dir = Rnd(0, 1) * 2 - 1;
new row = (dir == 1) ? 1 : HALF_MAP_SIZE - 1;
for (i = 1; i < HALF_MAP_SIZE; i++, row += dir)
{
new eCellType:nextCell = (row + dir == HALF_MAP_SIZE) ? CT_WALL : map[col][row + dir];
if ((map[col][row] == CT_EMPTY) && (nextCell == CT_EMPTY))
SetCell(col, row, CT_WALL);
}
processedColumns[col] = true;
}
stock AddHoles()
{
new i;
for (i = 0; i < nHoles; i++)
{
new col, row;
do
{
col = Rnd(1, HALF_MAP_SIZE);
row = Rnd(1, HALF_MAP_SIZE);
} while (map[col][row] != CT_WALL);
SetCell(col, row, CT_EMPTY);
}
}
stock CreateGhostArea()
{
new i, j;
new start = (MAP_SIZE - GHOST_AREA_SIZE) / 2;
for (i = 0; i < GHOST_AREA_SIZE; i++)
{
for (j = 0; j < GHOST_AREA_SIZE; j++)
{
map[start + i][start + j] = ghostArea[i][j];
}
}
}
stock GenerateWalls()
{
GenerateBorder();
CreateGhostArea();
new bool:processedRows[HALF_MAP_SIZE];
new bool:processedColumns[HALF_MAP_SIZE];
new i;
for (i = 0; i < HALF_MAP_SIZE; i++)
{
processedRows[i] = false;
processedColumns[i] = false;
ShowProgress(1);
}
for (i = 0; i < HALF_MAP_SIZE / 2 - 1; i++)
{
GenerateRow(processedRows);
GenerateColumn(processedColumns);
ShowProgress(1);
}
AddHoles();
}
// --- Generate mines ---
stock GeneratePowerMines()
{
new i;
for (i = 0; i < nPowerMines / 4; i++)
{
new row, col;
do
{
row = Rnd(1, MAP_SIZE - 2);
col = Rnd(1, MAP_SIZE - 2);
} while (map[col][row] != CT_EMPTY);
SetCell(col, row, CT_POWER);
}
}
stock GenerateMines()
{
GeneratePowerMines();
nMines = 0;
new i, j;
for (i = 0; i < MAP_SIZE; i++)
{
for (j = 0; j < MAP_SIZE; j++)
{
if (map[i][j] == CT_EMPTY)
{
SetCell(i, j, CT_MINE, false);
nMines++;
}
}
}
}
// --- Create Entities ---
stock CreateEntities()
{
new col, row;
new Float:pos[vec3];
pos[vec3:z] = 0.0;
pos[vec3:x] = blockSize / 2.0;
for (col = 0; col < MAP_SIZE; col++, pos[vec3:x] += blockSize)
{
pos[vec3:y] = blockSize / 2.0;
for (row = 0; row < MAP_SIZE; row++, pos[vec3:y] += blockSize)
{
switch (map[col][row])
{
case CT_WALL:
{
new DrID:wall = CreateEntity(wallBase, pos, Rnd(0, 3) * 90.0);
SetPropertyVec3(wall, "Scale", wallScale);
}
case CT_BORDER:
{
new DrID:border = CreateEntity(borderBase, pos, Rnd(0, 3) * 90.0);
SetPropertyVec3(border, "Scale", borderScale);
}
case CT_MINE:
{
new DrID:mine = CreateEntity(mineBase, pos);
SetPropertyVec3(mine, "Scale", mineScale);
AddAKEntityEvent("OnMineExplode", mine, MINE_EXPLODE);
}
case CT_POWER:
{
new DrID:mine = CreateEntity(powermineBase, pos);
SetPropertyVec3(mine, "Scale", powermineScale);
AddAKEntityEvent("OnPowerMineExplode", mine, MINE_EXPLODE);
AddAKEntityEvent("OnPowerModeExpire", mine, MINE_EXPLODE, powerModeTime);
}
case CT_DEATH:
{
new DrID:mine = CreateEntity(deathmineBase, pos);
SetPropertyVec3(mine, "Scale", deathmineScale);
}
}
}
}
}
stock GenerateMap()
{
InitMap();
SetCell(playerStartX, playerStartY, CT_START, false);
GenerateWalls();
GenerateMines();
CreateEntities();
SetShortInfo("");
}
// --- Ghosts ---
stock FindGhostRespawnPoints()
{
new i, j, n = 0;
for (i = 0; i < MAP_SIZE; i++)
{
for (j = 0; j < MAP_SIZE; j++)
{
if (map[i][j] == CT_RESPAWN)
{
CellToPos(i, j, ghostRespawnPoints[n]);
n++;
}
}
}
assert(n >= N_GHOSTS);
}
stock GetGhostIndex(DrID:ghost)
{
new i;
for (i = 0; i < N_GHOSTS; i++)
{
if (ghosts[i] == GetEntityID(ghost))
return i;
}
return -1;
}
stock CreateGhost(index)
{
new Float:respawnPoint[vec3];
respawnPoint = ghostRespawnPoints[index];
new DrID:ghost = CreateEntity(ghostTankBase, respawnPoint, Rnd(0, 3) * 90);
ghosts[index] = GetEntityID(ghost);
SetPropertyInt(ghost, "Team", ghostTeam);
/*
// --- TIMBER ADDED THIS ---
MakeInvulnerable(ghost);
BurnEntity(ghost);
// --- TIMBER ADDED THIS ---
*/
AddAKEntityEvent("OnGhostDied", ghost, UNIT_DIED);
AddAKEntityEvent("OnGhostDiedDelayed", ghost, UNIT_DIED, ghostRespawnDelay);
ghostTargets[index][vec3:x] = -1.0; // invalidate
}
stock CreateGhosts()
{
new i;
for (i = 0; i < N_GHOSTS; i++)
CreateGhost(i);
}
public OnGhostDied(DrID:ghost)
{
if (powerMode)
AddToScore(scorePowerKill);
else
AddToScore(scoreKill);
}
public OnGhostDiedDelayed(DrID:ghost)
{
DamageEntity(ghost, 666.666, 49);
CreateGhost(GetGhostIndex(ghost));
}
// --- Ghost AI ---
stock MoveGhost(DrID:ghost, Float:targetPos[vec3])
{
CmdMove(ghost, _, targetPos);
ghostTargets[GetGhostIndex(ghost)] = targetPos;
}
// --- Ghost AI - chase ---
stock IsValidTarget(DrID:ghost, Float:target[vec3])
{
new Float:playerPos[vec3];
GetPropertyVec3(GetEntityByID(player), "Pos", playerPos);
new Float:ghostPos[vec3];
GetPropertyVec3(ghost, "Pos", ghostPos);
new Float:ghostDistToPlayer = Distance(playerPos, ghostPos);
new Float:targetDistToPlayer = Distance(playerPos, target);
if (targetDistToPlayer > aiChaseDistMultiplier * ghostDistToPlayer)
return false;
return true;
}
stock IsCurrentTargetValid(DrID:ghost)
{
new index = GetGhostIndex(ghost);
if (ghostTargets[index][vec3:x] == -1.0)
return false;
return IsValidTarget(ghost, ghostTargets[index]);
}
stock ChasePlayer(DrID:ghost)
{
if (IsCurrentTargetValid(ghost))
return;
new Float:playerPos[vec3];
GetPropertyVec3(GetEntityByID(player), "Pos", playerPos);
new Float:ghostPos[vec3];
GetPropertyVec3(ghost, "Pos", ghostPos);
new Float:ghostToPlayerDist = Distance(playerPos, ghostPos);
new Float:playerToGhostVec[vec3];
SubVec3(ghostPos, playerPos, playerToGhostVec);
NormalizeVec3(playerToGhostVec);
new Float:targetPos[vec3];
new col, row;
new iter;
do
{
/*
Get a circle around the player with r = aiChaseDistMultiplier * ghostToPlayerDist.
Get the half of this circle on the ghost's side.
Generate a random point inside the half-circle.
*/
new Float:targetToPlayerDist = FRnd(0, aiChaseDistMultiplier * ghostToPlayerDist);
new Float:rot = FRnd(-135.0, 135.0);
targetPos = playerToGhostVec;
RotateVec2(targetPos, rot);
targetPos[vec3:x] *= targetToPlayerDist;
targetPos[vec3:y] *= targetToPlayerDist;
AddVec3(targetPos, playerPos, targetPos);
PosToCell(targetPos, col, row);
iter++;
} while (((col < 0) || (map[col][row] == CT_WALL) || (map[col][row] == CT_BORDER)) && (iter < aiChaseMaxIter));
if (IsValidTarget(ghost, targetPos))
MoveGhost(ghost, targetPos);
}
// --- Ghost AI - flee ---
public Flee(DrID:ghost)
{
/*
Loop through the 9 cells around the ghost, find the one farthest from the player, and move there.
*/
new Float:playerPos[vec3];
GetPropertyVec3(GetEntityByID(player), "Pos", playerPos);
new Float:ghostPos[vec3];
GetPropertyVec3(ghost, "Pos", ghostPos);
new col, row;
PosToCell(ghostPos, col, row);
new c, r_;
new maxCol, maxRow;
new Float:maxDist = -1;
for (c = -1; c < 2; c++)
{
for (r_ = -1; r_ < 2; r_++)
{
new eCellType:cell = map[col + c][row + r_];
if ((cell == CT_WALL) || (cell == CT_BORDER))
continue;
new Float:cellPos[vec3];
CellToPos(col + c, row + r_, cellPos);
new Float:dist = Distance(playerPos, cellPos);
if (dist > maxDist)
{
maxDist = dist;
maxCol = col + c;
maxRow = row + r_;
}
}
}
new Float:targetPos[vec3];
CellToPos(maxCol, maxRow, targetPos);
if (!CmpVec3(targetPos, ghostTargets[GetGhostIndex(ghost)]))
MoveGhost(ghost, targetPos);
}
public RefreshGhosts(Float:time)
{
new i;
for (i = 0; i < N_GHOSTS; i++)
{
new DrID:ghost = GetEntityByID(ghosts[i]);
if (powerMode)
Flee(ghost);
else
ChasePlayer(ghost);
}
}
stock InitGhostAI()
{
AddTimerEvent("RefreshGhosts", 1.0 / aiUpdateFrequency);
}
// --- Player ---
stock CreatePlayer()
{
new Float:startPos[vec3];
startPos[vec3:x] = 1.5 * blockSize;
startPos[vec3:y] = 1.5 * blockSize;
new DrID:pl = CreateEntity(playerTankBase, startPos);
player = GetEntityID(pl);
SetPropertyInt(pl, "Team", playerTeam);
powerMode = 0;
EnablePowerWeapon(pl, false);
SetScore(0);
AddAKEntityEvent("OnPlayerDied", pl, UNIT_DIED);
}
public OnPlayerDied()
{
SetObjectiveState("ExplodeMines", OS_FAILED);
EndMission(MS_FAILED);
}
stock SetScore(aScore)
{
score = aScore;
new str[255];
Int2Str(score, str, 255, "Score: %d"); // TODO localize
SetShortInfo(str);
}
stock AddToScore(diff)
{
SetScore(score + diff);
}
// --- Mines ---
public OnMineExplode(DrID:mine)
{
AddToScore(scoreMine);
nMines--;
if (nMines == 0)
{
SetObjectiveState("ExplodeMines", OS_COMPLETED);
EndMission(MS_ACCOMPLISHED);
}
}
stock EnablePowerWeapon(DrID:tank, bool:enable)
{
///* Old CODE
new DrID:wm = GetComponent(tank, "cWeaponManager");
new DrID:weapon = GetArrayElement(wm, "Weapons", 1);
SetPropertyBool(weapon, "Enabled", enable);
//*/
/*
// --- TIMBER ADDED THIS ---
if(enable)
{
MakeInvulnerable(tank);
BurnEntity(tank);
}
else
{
StopBurning(tank);
MakeVulnerable(tank);
}
// --- TIMBER ADDED THIS ---
*/
}
stock UpdatePowerWeapons()
{
EnablePowerWeapon(GetEntityByID(player), powerMode > 0);
new i;
for (i = 0; i < N_GHOSTS; i++)
EnablePowerWeapon(GetEntityByID(ghosts[i]), powerMode == 0);
}
stock StartPowerMode()
{
UpdatePowerWeapons();
}
public OnPowerMineExplode(DrID:mine)
{
SetCountDown(floatround(powerModeTime));
powerMode++;
if (powerMode == 1)
StartPowerMode();
AddToScore(scorePowerMine);
}
stock EndPowerMode()
{
SetCountDown(-1);
UpdatePowerWeapons();
}
public OnPowerModeExpire(DrID:mine)
{
powerMode--;
if (powerMode == 0)
EndPowerMode();
}