/*-- Temple Pushing --*/ #strict 2 static const MODE_Classic = 0, MODE_Magic = 1, MODE_Festive = 2, MODE_Apocalyptic = 3, MODE_Knightly = 4; static const SBRD_RelaunchesCol = 0; static const MaxTeamCount = 2; static SBRD_ScoreCol; static section, mode; static numRelaunches; static deathmatchEnabled, deathmatchWinScore; static playerDeaths, playerScore, teamScore; static ambienceEnabled; static suddendeathEnabled; static loadingSection; static gameStartMessage, countdown, gameStarted, gameOver; func Initialize() { ShowLobby(); // Create Thrones for recreational purposes CreateObject(THRN, 590, 420, NO_OWNER); CreateObject(THRN, 410, 210, NO_OWNER); // Initial values deathmatchWinScore = 20; playerDeaths = CreateArray(); playerScore = CreateArray(); teamScore = CreateArray(); countdown = 3; // Create setup menu var menu = CreateObject(SPMU, 0, 0, NO_OWNER); menu->LocalN("extinguisherEnabled") = true; menu->LocalN("rotateInJumpEnabled") = true; menu->LocalN("numRelaunches") = 10; menu->LocalN("deathmatchWinScore") = 20; menu->LocalN("ambienceEnabled") = true; ScriptGo(true); } /*func TeamRow(int team) { return 1 + team * 1000; } func PlayerRow(int player) { return TeamRow(GetPlayerTeam(player)) + GetPlayerID(player); }*/ func TeamRow(int team) { return team; } func PlayerRow(int player) { return MaxTeamCount + 1 + GetPlayerID(player); } func InitializePlayer(int player) { SetFoW(false, player); if (LobbyActive()) return; playerDeaths[GetPlayerID(player)] = 0; if (deathmatchEnabled) { var team = GetPlayerTeam(player); if (team) { SetScoreboardData(TeamRow(team), SBRD_Caption, Format("Team %s", GetTeamColor(team), GetTeamName(team)), 0); } } SetScoreboardData(PlayerRow(player), SBRD_Caption, GetTaggedPlayerName(player), 1); UpdateScoreboard(player); LaunchClonk(player, GetCrew(player)); } func GetRelaunchesLeft(int player) { return numRelaunches - playerDeaths[GetPlayerID(player)]; } func RemovePlayer(int player) { // Leaver? if (!LobbyActive() && (!deathmatchEnabled && GetRelaunchesLeft(player) >= 0 || deathmatchEnabled && !gameOver)) { UpdateScoreboard(player, true); } } func UpdateScoreboard(int player, bool leaver) { var playerID = GetPlayerID(player); var text; if (!deathmatchEnabled) SBRD_ScoreCol = 1; if (deathmatchEnabled) SBRD_ScoreCol = 0; // Ignore invalid player numbers if (!playerID) return; // Deathmatch only: Team rows if (deathmatchEnabled) { var team = GetPlayerTeam(player); if (team) { if (teamScore[team] == deathmatchWinScore) text = "$Win$"; else text = Format("%d", GetTeamColor(team), teamScore[team]); SetScoreboardData(TeamRow(team), SBRD_ScoreCol, text, teamScore[team]); } } // Normal mode only: Show remaining relaunches if (!deathmatchEnabled) { var relaunchesLeft = numRelaunches - playerDeaths[playerID]; if (relaunchesLeft < 0) text = "$Death$"; else if (leaver) text = "{{SLVR}}"; else text = Format("%d", relaunchesLeft); SetScoreboardData(PlayerRow(player), SBRD_RelaunchesCol, text, relaunchesLeft); } // Normal and deathmatch mode: Show score if (leaver) text = "{{SLVR}}"; else text = Format("%d", playerScore[playerID]); SetScoreboardData(PlayerRow(player), SBRD_ScoreCol, text, playerScore[playerID]); // Sort rows if (!deathmatchEnabled) { SortScoreboard(SBRD_RelaunchesCol, true); SortScoreboard(SBRD_ScoreCol, true); } else SortScoreboard(SBRD_ScoreCol, true); // SortScoreboard(SBRD_Caption); } func ShowLobby() { LoadScenarioSection("Lobby"); } func StartGame(object menu, string message) { // Copy settings from menu var sectionID = menu->LocalN("section"); mode = menu->LocalN("mode"); var extinguisherEnabled = menu->LocalN("extinguisherEnabled"); var suddendeathEnabled = menu->LocalN("suddendeathEnabled"); var rotateInJumpEnabled = menu->LocalN("rotateInJumpEnabled"); ambienceEnabled = menu->LocalN("ambienceEnabled"); numRelaunches = menu->LocalN("numRelaunches"); deathmatchEnabled = menu->LocalN("deathmatchEnabled"); deathmatchWinScore = menu->LocalN("deathmatchWinScore"); menu->RemoveObject(); // Change scenario section loadingSection = true; Log("Before LoadScenarioSection()"); LoadScenarioSection(DefinitionCall(sectionID, "SectionName")); Log("After LoadScenarioSection()"); loadingSection = false; section = CreateObject(sectionID, 0, 0, NO_OWNER); // Create spawnpoints if (mode != MODE_Apocalyptic) { Log("Creating spawnpoints"); var spawnPointSpawner = CreateObject(SPSR, 0, 0, NO_OWNER); spawnPointSpawner->SetLocations(section->SpawnpointLocations()); if (mode == MODE_Classic) { spawnPointSpawner->SetDefinitions([[ROCK, 7], [SCRL, 5], [FLNT, 7], [SFLN, 9], [STFN, 3], [EFLN, 5], [FBMP, 4], [ARWP, 4]]); spawnPointSpawner->SetSpawnInterval(900); } if (mode == MODE_Festive) { spawnPointSpawner->SetDefinitions([[TSWB, 10], [SCRL, 5]]); spawnPointSpawner->SetSpawnInterval(600); } if (mode == MODE_Knightly) { spawnPointSpawner->SetDefinitions([[SFLN, 3], [EFLN,3], [SWOR, 5], [AXE1, 5], [SPER, 2], [ARWP, 5], [SCRL, 5], [FARP, 3]]); spawnPointSpawner->SetSpawnInterval(1000); } if (mode == MODE_Magic) { spawnPointSpawner->SetDefinitions([[SCRL, 14], [GBLT, 1]]); spawnPointSpawner->SetSpawnInterval(750); spawnPointSpawner->SetSpawnpointGamma(RGB(5, 5, 10), RGB(80, 80, 150), RGB(200, 200, 255)); } spawnPointSpawner->CreateSpawnPoints(); } // Effects for apocalypse mode if (mode == MODE_Apocalyptic) { AddEffect("CreateTeraFlints", 0, 20, 90); AddEffect("ShakeScreen", 0, 20, 50); AddEffect("Bottom", 0, 20, 2); AddEffect("SkyAdjust", 0, 20, 1); } // Effects for festive mode if (mode == MODE_Festive) { if (ambienceEnabled) CreateObject(SNOR, 0, 0, NO_OWNER); SetSkyAdjust(RGB(189, 189, 255)); SetGamma(RGB(0, 0, 50), RGB(100, 100, 128), RGB(200, 200, 255)); } // Create melee goal Log("Creating melee goal"); CreateObject(MELE, 0, 0, NO_OWNER); // Create rules Log("Creating rules"); if (extinguisherEnabled) CreateObject(_ETG, 0, 0, NO_OWNER); if (suddendeathEnabled) CreateObject(SDDT, 0, 0, NO_OWNER); if (rotateInJumpEnabled) CreateObject(RIJP, 0, 0, NO_OWNER); CreateObject(OFDR, 0, 0, NO_OWNER); // Create mode objects if (mode == MODE_Magic) { CreateObject(MLPG, 0, 0, NO_OWNER); } if (mode == MODE_Festive) { CreateObject(FSTV, 0, 0, NO_OWNER); } if (mode == MODE_Knightly) { CreateObject(MKNI, 0, 0, NO_OWNER); } if (mode == MODE_Apocalyptic) { CreateObject(APCE, 0, 0, NO_OWNER); } // Deathmatch? if (deathmatchEnabled) { Log("Creating deathmatch rule"); CreateObject(DTHM, 0, 0, NO_OWNER); SetScoreboardData(SBRD_Caption, 0, "{{SKIL}}"); SetScoreboardData(0, SBRD_Caption, "$WinScore$"); SetScoreboardData(0, 0, Format("%d $Kills$", deathmatchWinScore)); } else { SetScoreboardData(SBRD_Caption, 0, "{{SREL}}"); SetScoreboardData(SBRD_Caption, 1, "{{SKIL}}"); } // Initialize players Log("Initializing players"); for (var i = 0; i < GetPlayerCount(); ++i) { InitializePlayer(GetPlayerByIndex(i)); } Log("Starting countdown"); gameStartMessage = message; ShowCountdown(); Log("StartGame() done"); } func ShowCountdown() { if (countdown == 0) { gameStartMessage = 0; Message(""); Schedule("SetMaxPlayer(0)", 60 * 38); // Reenable crew for (var i = 0; i < GetPlayerCount(); ++i) { var player = GetPlayerByIndex(i); var clonk = GetCrew(player); if (!clonk) continue; // Happens if player still in team choice menu // Relaunch if clonk does not stand. if (clonk->GetAction() != "Walk") { clonk->Kill(); clonk = GetCrew(player); } clonk->SetCrewEnabled(true); SelectCrew(player, clonk, true); // Make sure all clonks start with equal health clonk->Extinguish(); ResetHealth(clonk); } gameStarted = true; } else { Message("@%d|%s", 0, countdown, gameStartMessage); Schedule("GameCall(\"ShowCountdown\")", 38); } --countdown; } func LobbyActive() { return !section; } func HandleKill(int killed, int killer) { // Assume suicide if killer cannot be determined if (killer == NO_OWNER) killer = killed; var killedEliminated = false; var killedID = GetPlayerID(killed), killerID = GetPlayerID(killer); var killedTeam = GetPlayerTeam(killed), killerTeam = GetPlayerTeam(killer); var teamKill = killedTeam != 0 && killedTeam == killerTeam; ++playerDeaths[killedID]; // Update kill score if (!teamKill) { ++playerScore[killerID]; if (killerTeam) ++teamScore[killerTeam]; } else if (teamKill && killed != killer) // Decrement score if it's a team kill but not a suicide { if (playerScore[killerID] > 0) // Prevent negative scores { --playerScore[killerID]; if (killerTeam) --teamScore[killerTeam]; } } // Show relaunch message or eliminate player if no relaunches left var relaunchesLeft; if (!deathmatchEnabled) { relaunchesLeft = numRelaunches - playerDeaths[killedID]; if (relaunchesLeft < 0) { EliminatePlayer(killed); killedEliminated = true; } else if (relaunchesLeft == 0) { PlayerMessage(killed, "$MsgLastRelaunch$"); } else if (relaunchesLeft == 1) { PlayerMessage(killed, "$MsgOneRelaunch$"); } else // More than one relaunch left { PlayerMessage(killed, "$MsgRelaunch$", 0, relaunchesLeft); } } // Check for deathmatch winner if (deathmatchEnabled) { // Teams enabled? if (killerTeam) { // Winner? if (teamScore[killerTeam] >= deathmatchWinScore) { // Eliminate all players of losing teams for (var player in GetPlayers()) { if (GetPlayerTeam(player) != killerTeam) EliminatePlayer(player); } Message(Format("$MsgDeathmatchWin$", GetTeamColor(killerTeam), GetTeamName(killerTeam))); gameOver = true; killedEliminated = true; } } else // No teams? { // Winner? if (playerScore[killerID] >= deathmatchWinScore) { // Eliminate all losing players for (var player in GetPlayers()) { if (player != killer) EliminatePlayer(player); } gameOver = true; killedEliminated = true; } } } UpdateScoreboard(killed); UpdateScoreboard(killer); return killedEliminated; } func GetPlayers() { var players = CreateArray(GetPlayerCount()); for (var i = 0; i < GetPlayerCount(); ++i) { players[i] = GetPlayerByIndex(i); } return players; } func OnClonkDeath(object clonk, int killedBy) { if (loadingSection) return; var player = clonk->GetOwner(); // Do nothing if Clonk does not belong to a player if (GetPlayerID(player) == 0) return; // Handle kill and return if player got eliminated if (gameStarted && HandleKill(player, killedBy)) return; // Relaunch if (GetPlayerID(player)) LaunchClonk(player, clonk, true); } func LaunchClonk(int player, object clonk, bool relaunch) { if (!clonk || relaunch) { var newClonk = CreateObject(CLNK, 0, 0, player); if (clonk) { newClonk->GrabObjectInfo(clonk); } else { MakeCrewMember(newClonk, player); } clonk = newClonk; } ResetHealth(clonk); SelectCrew(player, clonk, true); // Move clonk to random position var NumCheckPos = 10; var positions = CreateArray(NumCheckPos); for (var i = 0; i < NumCheckPos; ++i) { var wipf = PlaceAnimal(WIPF); positions[i] = IIf(wipf, [wipf->GetX(), wipf->GetY()], [LandscapeWidth() / 2, LandscapeHeight() / 2]); if (wipf) wipf->RemoveObject(); } // Prevent spawning too close to an enemy var bestPosition = positions[0], bestDistance = 0; for (var pos in positions) { var closestHostileClonk = FindObject2(Find_OCF(OCF_CrewMember | OCF_Alive), Find_Hostile(player), Sort_Distance(pos[0], pos[1])); if (!closestHostileClonk) break; var distance = Distance(pos[0], pos[1], closestHostileClonk->GetX(), closestHostileClonk->GetY()); if (distance > bestDistance) { bestPosition = pos; bestDistance = distance; } } clonk->SetPosition(bestPosition[0], bestPosition[1]); // Still in countdown? if (!LobbyActive() && !gameStarted) { clonk->SetCrewEnabled(false); } // No corpses in apocalopyse mode if (mode == MODE_Apocalyptic) clonk->LocalN("removeOnDeath") = true; // Enable rotation in jump for the clonk if allowed by rule if (FindObject(RIJP)) clonk->LocalN("rotateInJump") = true; // Do not immediately pass player control to the new clonk to prevent accidental jumps if (relaunch) { clonk->SetCrewEnabled(false); clonk->SetCrewEnabled(true); clonk->Schedule("SelectCrew(GetOwner(), this, true)", 20); } PlayerMessage(player, "{{SREL}}", clonk); DrawParticleLine("PSpark", GetX(clonk), 0, GetX(clonk), GetY(clonk), 10, 150, RGBa(Random(255), Random(255), Random(255),50), RGBa(Random(255), Random(255), Random(255),50), 0); DrawParticleLine("PSpark", GetX(clonk) + RandomX(5, 60), 0, GetX(clonk), GetY(clonk), 10, 150, RGBa(Random(255), Random(255), Random(255),50), RGBa(Random(255), Random(255), Random(255),50), 0); DrawParticleLine("PSpark", GetX(clonk) - RandomX(5, 60), 0, GetX(clonk), GetY(clonk), 10, 150, RGBa(Random(255), Random(255), Random(255),50), RGBa(Random(255), Random(255), Random(255),50), 0); Sound("PlayerJoin", 0, clonk, 100); // CastParticles("MSpark", 50, 20, GetX(clonk), GetY(clonk), 50, 75, RGBa(128,128,255,0), RGBa(255,255,255,127)); SetPlrView(player, clonk); ResetHealth(clonk); } func ResetHealth(object clonk) { if (suddendeathEnabled) { clonk->DoEnergy(1 - clonk->GetEnergy()); } else { clonk->DoEnergy(100); } } func CheckDeathmatchKillCount() { if (GetTeamCount() == 0) { var winner = NO_OWNER; // Check if a player has reached the kill limit for (var i = 0; i < GetPlayerCount(); ++i) { var plr = GetPlayerByIndex(i); if (playerScore[plr] >= deathmatchWinScore) { winner = plr; break; } } // Eliminate other players if there is a winner for (var i = 0; i < GetPlayerCount(); ++i) { var plr = GetPlayerByIndex(i); if (plr != winner) EliminatePlayer(plr); } } else { var winnerTeam = 0; // Clear team score array teamScore = CreateArray(GetTeamCount()); // Calculate team score and check if kill limit is reached for (var i = 0; i < GetPlayerCount(); ++i) { var plr = GetPlayerByIndex(i), team = GetPlayerTeam(plr); teamScore[team] += playerScore[plr]; if (teamScore >= deathmatchWinScore) { winnerTeam = team; break; } } // Do we have a winner? if (winnerTeam != 0) { for (var i = 0; i < GetPlayerCount(); ++i) { var plr = GetPlayerByIndex(i); if (GetPlayerTeam(plr) != winnerTeam) EliminatePlayer(plr); } } } }