/*-- Temple Pushing --*/ #strict 2 static const MODE_Classic = 0, MODE_Magic = 1, MODE_Festive = 2, MODE_Apocalyptic = 3, MODE_Knightly = 4; static const SBRD_FirstRow = 0; static const SBRD_TeamSortCol = -3, SBRD_SortCol = -2, SBRD_NameCol = -1, SBRD_RelaunchesCol = 0, SBRD_ScoreCol = 1; static const SORT_SORTCOL_Team = 0, SORT_SORTCOL_Player = 1, SORT_SORTCOL_Leaver = 2; static const MaxTeamCount = 3; static const PlrData_EnableAmbienceSounds = "TemplePushing_EnableAmbienceSounds"; static const PlrData_EnableAmbienceSounds_Yes = 1; static const PlrData_EnableAmbienceSounds_No = 2; static section, mode; static numRelaunches; static deathmatchEnabled, deathmatchWinScore; static playerDeaths, playerScore, teamScore; static sectionID; static rotateInJumpEnabled; static ambienceEnabled; static suddendeathEnabled; static loadingSection; static eliminatedPlayers; static gameStartMessage, countdown, gameStarted, gameOver; func Initialize() { ShowLobby(); eliminatedPlayers = []; // 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, LandscapeWidth()/2, LandscapeHeight()-20, NO_OWNER); menu->LocalN("rotateInJumpEnabled") = true; menu->LocalN("numRelaunches") = 7; menu->LocalN("deathmatchWinScore") = 15; menu->LocalN("ambienceEnabled") = !GetLeague(); // Modulate endscreen SetNextMission("TemplePushing.c4s", "Rematch", "$Rematch$"); // Set possible spells SetScrollSpells([ABLA, MBOT, MFRB, MDBT, GVTY, MLGT, CFAL, MSSH, MINV, MARK, MFWV, MGFL, LAVS, MGPL, ICNL, MDFL, AFST, MGCY, MATT]); } /*func TeamRow(int team) { return 1 + team * 1000; } func PlayerRow(int player) { return TeamRow(GetPlayerTeam(player)) + GetPlayerID(player); }*/ func TeamRow(int team) { return SBRD_FirstRow + team; } func PlayerRow(int player) { return SBRD_FirstRow + MaxTeamCount + 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_NameCol, Format("Team %s", GetTeamColor(team), GetTeamName(team))); SetScoreboardData(TeamRow(team), SBRD_SortCol, " ", SORT_SORTCOL_Team); } } SetScoreboardData(PlayerRow(player), SBRD_NameCol, GetTaggedPlayerName(player)); SetScoreboardData(PlayerRow(player), SBRD_SortCol, " ", SORT_SORTCOL_Player); SetScoreboardData(PlayerRow(player), SBRD_TeamSortCol, " ", GetPlayerTeam(player)); UpdateScoreboard(player); LaunchClonk(player, GetCrew(player)); DoAmbienceSounds(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); eliminatedPlayers[GetPlayerID(player)] = true; CheckGameOver(); } } func UpdateScoreboard(int player, bool leaver) { var playerID = GetPlayerID(player); var text; // 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 player score if (leaver) { text = "{{SLVR}}"; // Move leaver to the bottom of the list SetScoreboardData(PlayerRow(player), SBRD_NameCol, GetTaggedPlayerName(player)); SetScoreboardData(PlayerRow(player), SBRD_SortCol, " ", SORT_SORTCOL_Leaver); } else text = Format("%d", playerScore[playerID]); SetScoreboardData(PlayerRow(player), SBRD_ScoreCol, text, playerScore[playerID]); // Sort rows SortScoreboard(SBRD_TeamSortCol); SortScoreboard(SBRD_ScoreCol, true); if (!deathmatchEnabled) SortScoreboard(SBRD_RelaunchesCol, true); SortScoreboard(SBRD_SortCol); UpdateComment(); } func ShowLobby() { LoadScenarioSection("Lobby"); UpdateComment(true); } func SetupDone(object menu, string message) { // Copy settings from menu sectionID = menu->LocalN("section"); mode = menu->LocalN("mode"); suddendeathEnabled = menu->LocalN("suddendeathEnabled"); rotateInJumpEnabled = menu->LocalN("rotateInJumpEnabled"); ambienceEnabled = menu->LocalN("ambienceEnabled"); numRelaunches = BoundBy(menu->LocalN("numRelaunches"), 3, 10); deathmatchEnabled = menu->LocalN("deathmatchEnabled"); deathmatchWinScore = menu->LocalN("deathmatchWinScore"); menu->RemoveObject(); gameStartMessage = message; // Make sure LoadScenarioSection is not called from an object by swapping it out to a script counter callback ScriptGo(true); } func Script0() { ScriptGo(false); // Change scenario section loadingSection = true; LoadScenarioSection(DefinitionCall(sectionID, "SectionName")); loadingSection = false; section = CreateObject(sectionID, 0, 0, NO_OWNER); // Create spawnpoints if (mode != MODE_Apocalyptic) { var spawnPointSpawner = CreateObject(SPSR, 0, 0, NO_OWNER); spawnPointSpawner->SetLocations(section->SpawnpointLocations()); if (mode == MODE_Classic) { spawnPointSpawner->SetDefinitions([[ROCK, 5], [SCRL, 5], [FLNT, 4], [SFLN, 9], [STFN, 3], [EFLN, 5], [FBMP, 4]]); spawnPointSpawner->SetSpawnInterval(800); } if (mode == MODE_Festive) { spawnPointSpawner->SetDefinitions([[TSWB, 7], [SCRL, 5], [ICE1, 3]]); 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(800); } if (mode == MODE_Magic) { if (suddendeathEnabled) spawnPointSpawner->SetDefinitions([[SCRL, 19]]); else spawnPointSpawner->SetDefinitions([[SCRL, 19], [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)); SetScrollSpells([MICS, ABLA, MFWV, MLGT, MGPL, ICNL, AFST, MDFL, MGCY, MATT]); } // Create melee goal CreateObject(MELE, 0, 0, NO_OWNER); // Create rules CreateObject(TBMC, 0, 0, NO_OWNER); // Rule for enabling/disabling music 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); } SetScoreboardData(SBRD_Caption, SBRD_NameCol, " "); // Make sure the name column gets created first // Deathmatch? if (deathmatchEnabled) { CreateObject(DTHM, 0, 0, NO_OWNER); SetScoreboardData(SBRD_FirstRow, SBRD_NameCol, "$WinScore$"); SetScoreboardData(SBRD_FirstRow, SBRD_SortCol, " "); SetScoreboardData(SBRD_FirstRow, SBRD_ScoreCol, Format("%d $Kills$", deathmatchWinScore)); } else { SetScoreboardData(SBRD_Caption, SBRD_RelaunchesCol, "{{SREL}}"); } SetScoreboardData(SBRD_Caption, SBRD_ScoreCol, "{{SKIL}}"); // Initialize players for (var i = 0; i < GetPlayerCount(); ++i) { InitializePlayer(GetPlayerByIndex(i)); } ShowCountdown(); } 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; } global func GetActiveTeamCount() { var teams = []; for(var i = 0; i < GetPlayerCount(); i++) { if(!eliminatedPlayers[GetPlayerID(GetPlayerByIndex(i))]) { ++teams[GetPlayerTeam(GetPlayerByIndex(i))]; } } teams[-1] = 0; var count = 0; for(var teamCount in teams) { if(teamCount > 0) { ++count; } } return count; } global func EliminatePlayer(int iPlr, bool fQuiet) { var ret = _inherited(iPlr, fQuiet); eliminatedPlayers[GetPlayerID(iPlr)] = true; // Check if the game is over CheckGameOver(); return ret; } global func CheckGameOver() { if (GetActiveTeamCount() <= 1) StartGameOverEffect(); } func HandleKill(int killed, int killer) { // Check if the game is over if (GetEffectCount("GameOver")) return 0; // 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 OnGoalsFulfilled() { // Safety only, GameOver-Effect should have already started CheckGameOver(); return 1; } global func StartGameOverEffect() { if (!GetEffect("GameOver")) AddEffect("GameOver", 0, 1, 1); } global func FxGameOverStart(object pTarget, int iEffectNumber) { EffectVar(0, pTarget, iEffectNumber) = section->SectionAmbienceSounds(); } global func FxGameOverTimer(object pTarget, int iEffectNumber, int iEffectTime) { var sounds = EffectVar(0, pTarget, iEffectNumber); if (iEffectTime <= 100) { for(var sound in sounds) { SoundLevel(sound[0], sound[1]*(100 - iEffectTime)/100); for(var i = 0; i < GetPlayerCount(); ++i) { var player = GetPlayerByIndex(i); if (!WantsAmbienceSounds(player)) Sound(sound[0], true, 0, sound[1], player + 1, -1); } } } else if((iEffectTime > 200) || (iEffectTime > 100 && !ambienceEnabled)) { return -1; } } global func FxGameOverStop(object pTarget, int iEffectNumber) { Sound("Trumpet", 1); GameOver(); RemoveAll(MELE); } 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. Do not relaunch if player got eliminated if (gameStarted && !gameOver && 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 = 25; 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 apocalypse 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); } // Respawn effects 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 CheckGameStatus() { 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); } } } } global func WantsAmbienceSounds(int player) { // Check if user already specified in the past if he wants to enable ambience sounds var preference = GetPlrExtraData(player, PlrData_EnableAmbienceSounds); if (preference == PlrData_EnableAmbienceSounds_Yes) return true; if (preference == PlrData_EnableAmbienceSounds_No) return false; // Preference not set yet/invalid? Enable for non-league games and disable otherwise. return !GetLeague(); } func DoAmbienceSounds(int player) { for (var sound in section->SectionAmbienceSounds()) { Sound(sound[0], true, 0, sound[1], player + 1, WantsAmbienceSounds(player) * 2 - 1); } } func ToggleAmbienceSounds(int player) { var preference; if (WantsAmbienceSounds(player)) { preference = PlrData_EnableAmbienceSounds_No; } else { preference = PlrData_EnableAmbienceSounds_Yes; } SetPlrExtraData(player, PlrData_EnableAmbienceSounds, preference); DoAmbienceSounds(player); } func UpdateComment(bool inLobby) { if (inLobby) SetGameComment(" $LobbyComment$"); else { var score = ""; var teams = []; var teamRelaunches = []; for (var i = 0; i < GetPlayerCount(); i++) { if (!eliminatedPlayers[GetPlayerID(GetPlayerByIndex(i))]) { ++teams[GetPlayerTeam(GetPlayerByIndex(i))]; if (!deathmatchEnabled) teamRelaunches[GetPlayerTeam(GetPlayerByIndex(i))] += numRelaunches - playerDeaths[GetPlayerID(GetPlayerByIndex(i))]; } } for (var i = 1; i <= GetTeamCount(); ++i) { if (teams[i]) { if (GetLength(score)) score = Format("%s : ", score); if (deathmatchEnabled) score = Format("%s%d", score, GetTeamColor(i), teamScore[i]); else score = Format("%s%d", score, GetTeamColor(i), teamRelaunches[i]); } } var modes = SPMU->GetModes(); var sudden = ""; if(suddendeathEnabled) sudden = " $SuddenDeath$"; var goalDesc; if(deathmatchEnabled) goalDesc = Format("$DM$", deathmatchWinScore); else goalDesc = Format("$LMS$", numRelaunches); SetGameComment(Format(" $GameComment$", GetName(0, sectionID), modes[mode][1], sudden, goalDesc, score)); } }