2013-01-28 21:22:18 +00:00
|
|
|
/* Hot ice */
|
|
|
|
|
2016-03-08 23:07:47 +00:00
|
|
|
static g_remaining_rounds, g_winners, g_check_victory_effect;
|
2016-03-09 20:08:14 +00:00
|
|
|
static g_gameover;
|
2016-03-08 23:07:47 +00:00
|
|
|
|
2013-01-28 21:22:18 +00:00
|
|
|
func Initialize()
|
|
|
|
{
|
2016-03-08 23:07:47 +00:00
|
|
|
g_remaining_rounds = SCENPAR_Rounds;
|
|
|
|
g_winners = [];
|
|
|
|
InitializeRound();
|
2016-03-09 20:08:14 +00:00
|
|
|
|
|
|
|
Scoreboard->Init([
|
|
|
|
// Invisible team column for sorting players under their teams.
|
|
|
|
{key = "team", title = "", sorted = true, desc = false, default = "", priority = 90},
|
|
|
|
{key = "wins", title = "Wins", sorted = true, desc = true, default = 0, priority = 100},
|
|
|
|
{key = "death", title = "", sorted = false, default = "", priority = 0},
|
|
|
|
]);
|
2018-01-07 19:59:05 +00:00
|
|
|
|
2016-03-08 23:07:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Resets the scenario, redrawing the map.
|
|
|
|
func ResetRound()
|
|
|
|
{
|
|
|
|
// Retrieve all Clonks.
|
2016-04-06 19:18:13 +00:00
|
|
|
var clonks = [];
|
|
|
|
for (var clonk in FindObjects(Find_OCF(OCF_CrewMember)))
|
2016-03-08 23:07:47 +00:00
|
|
|
{
|
|
|
|
var container = clonk->Contained();
|
|
|
|
if (container)
|
|
|
|
{
|
|
|
|
clonk->Exit();
|
|
|
|
container->RemoveObject();
|
|
|
|
}
|
2016-04-06 19:18:13 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
// Players not waiting for a relaunch get a new Clonk to prevent
|
|
|
|
// status effects from carrying over to the next round.
|
|
|
|
var new_clonk = CreateObject(clonk->GetID(), 0, 0, clonk->GetOwner());
|
|
|
|
new_clonk->GrabObjectInfo(clonk);
|
|
|
|
clonk = new_clonk;
|
|
|
|
}
|
|
|
|
PushBack(clonks, clonk);
|
2016-03-08 23:07:47 +00:00
|
|
|
clonk->SetObjectStatus(C4OS_INACTIVE);
|
|
|
|
}
|
|
|
|
// Clear and redraw the map.
|
|
|
|
LoadScenarioSection("main");
|
|
|
|
InitializeRound();
|
2017-02-18 22:07:34 +00:00
|
|
|
AssignHandicaps();
|
2016-03-08 23:07:47 +00:00
|
|
|
// Re-enable the players.
|
|
|
|
for (var clonk in clonks)
|
|
|
|
{
|
|
|
|
clonk->SetObjectStatus(C4OS_NORMAL);
|
|
|
|
SetCursor(clonk->GetOwner(), clonk);
|
|
|
|
// Select the first item. This fixes item ordering.
|
|
|
|
clonk->SetHandItemPos(0, 0);
|
2016-03-09 20:08:14 +00:00
|
|
|
InitPlayerRound(clonk->GetOwner());
|
2016-03-08 23:07:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func InitializeRound()
|
|
|
|
{
|
|
|
|
// Checking for victory: Only active after a Clonk dies.
|
|
|
|
g_check_victory_effect = AddEffect("CheckVictory", nil, 1, 0);
|
|
|
|
g_player_spawn_index = 0;
|
2016-08-08 16:25:15 +00:00
|
|
|
if (GetType(g_player_spawn_positions) == C4V_Array)
|
|
|
|
ShuffleArray(g_player_spawn_positions);
|
2016-03-08 23:07:47 +00:00
|
|
|
|
2013-01-28 21:22:18 +00:00
|
|
|
// Materials: Chests
|
|
|
|
var i,pos;
|
|
|
|
var ls_wdt = LandscapeWidth(), ls_hgt = LandscapeHeight();
|
2016-01-17 17:22:08 +00:00
|
|
|
var chest_area_y = ls_hgt*[0,30][SCENPAR_MapType]/100;
|
|
|
|
var chest_area_hgt = ls_hgt/2;
|
2016-01-31 21:51:43 +00:00
|
|
|
// Chests in regular mode. Boom packs in grenade launcher mode.
|
|
|
|
var num_extras = [6,12][SCENPAR_Weapons];
|
|
|
|
for (i=0; i<num_extras; ++i)
|
2016-01-17 17:22:08 +00:00
|
|
|
if (pos=FindLocation(Loc_InRect(0,chest_area_y,ls_wdt,chest_area_hgt-100), Loc_Wall(CNAT_Bottom))) // Loc_Wall adds us 100 pixels...
|
2013-01-28 21:22:18 +00:00
|
|
|
{
|
2016-01-31 21:51:43 +00:00
|
|
|
if (SCENPAR_Weapons == 0)
|
2013-01-28 21:22:18 +00:00
|
|
|
{
|
2016-01-31 21:51:43 +00:00
|
|
|
var chest = CreateObjectAbove(Chest,pos.x,pos.y);
|
|
|
|
if (chest)
|
|
|
|
{
|
|
|
|
chest->CreateContents(Firestone,5);
|
|
|
|
chest->CreateContents(Bread,1);
|
|
|
|
chest->CreateContents(Bow,1);
|
|
|
|
chest->CreateContents(FireArrow,1)->SetStackCount(5);
|
|
|
|
chest->CreateContents(BombArrow,1)->SetStackCount(5);
|
|
|
|
chest->CreateContents(Shield,1);
|
|
|
|
chest->CreateContents(IronBomb,3);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
var boompack= CreateObjectAbove(Boompack,pos.x,pos.y);
|
2013-01-28 21:22:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// Materials: Firestones
|
|
|
|
for (i=0; i<30; ++i)
|
2016-01-17 17:22:08 +00:00
|
|
|
if (pos=FindLocation(Loc_InRect(0,chest_area_y,ls_wdt,chest_area_hgt), Loc_Solid()))
|
2015-04-17 14:57:32 +00:00
|
|
|
if (IsFirestoneSpot(pos.x,pos.y))
|
2015-01-10 09:14:02 +00:00
|
|
|
CreateObjectAbove(Firestone,pos.x,pos.y-1);
|
2016-01-17 17:22:08 +00:00
|
|
|
// Some firestones and bombs in lower half. For ap type 1, more firestones in lower than upper half.
|
2013-01-28 21:22:18 +00:00
|
|
|
for (i=0; i<30; ++i)
|
|
|
|
if (pos=FindLocation(Loc_InRect(0,ls_hgt/2,ls_wdt,ls_hgt/3), Loc_Solid()))
|
2015-04-17 14:57:32 +00:00
|
|
|
if (IsFirestoneSpot(pos.x,pos.y))
|
2016-01-17 17:22:08 +00:00
|
|
|
CreateObjectAbove([Firestone,IronBomb][Random(Random(3))],pos.x,pos.y-1);
|
2016-03-11 16:26:43 +00:00
|
|
|
|
|
|
|
// The game starts after a delay to ensure that everyone is ready.
|
|
|
|
GUI_Clock->CreateCountdown(3);
|
|
|
|
|
2018-01-07 19:59:05 +00:00
|
|
|
SetSky(g_theme.Sky);
|
|
|
|
g_theme->InitializeRound();
|
2018-01-07 21:21:44 +00:00
|
|
|
g_theme->InitializeMusic();
|
2018-01-07 19:59:05 +00:00
|
|
|
|
2013-01-28 21:22:18 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2016-01-17 17:22:08 +00:00
|
|
|
static g_player_spawn_positions, g_map_width, g_player_spawn_index;
|
|
|
|
|
2016-03-09 20:08:14 +00:00
|
|
|
global func ScoreboardTeam(int team) { return team * 100; }
|
|
|
|
|
2013-01-28 21:22:18 +00:00
|
|
|
func InitializePlayer(int plr)
|
|
|
|
{
|
2016-03-09 20:08:14 +00:00
|
|
|
// Add the player and their team to the scoreboard.
|
|
|
|
Scoreboard->NewPlayerEntry(plr);
|
|
|
|
Scoreboard->SetPlayerData(plr, "wins", "");
|
|
|
|
var team = GetPlayerTeam(plr);
|
|
|
|
Scoreboard->NewEntry(ScoreboardTeam(team), GetTeamName(team));
|
|
|
|
Scoreboard->SetData(ScoreboardTeam(team), "team", "", ScoreboardTeam(team));
|
|
|
|
Scoreboard->SetPlayerData(plr, "team", "", ScoreboardTeam(team) + 1);
|
|
|
|
|
2017-02-18 22:07:34 +00:00
|
|
|
// Players joining at runtime will participate in the following round.
|
2017-07-08 13:06:47 +00:00
|
|
|
// Should only happen if it's not game start, else Clonks would start stuck in a RelaunchContainer.
|
2017-07-09 17:26:10 +00:00
|
|
|
if (FrameCounter() > 1) PutInRelaunchContainer(GetCrew(plr));
|
2016-03-09 20:08:14 +00:00
|
|
|
}
|
|
|
|
|
2016-08-07 11:56:04 +00:00
|
|
|
func InitializePlayers()
|
|
|
|
{
|
|
|
|
AssignHandicaps();
|
2017-02-18 22:07:34 +00:00
|
|
|
for (var i = 0; i < GetPlayerCount(); i++)
|
|
|
|
{
|
|
|
|
var plr = GetPlayerByIndex(i);
|
|
|
|
InitPlayerRound(plr);
|
|
|
|
}
|
2016-08-07 11:56:04 +00:00
|
|
|
}
|
|
|
|
|
2016-03-09 20:08:14 +00:00
|
|
|
func InitPlayerRound(int plr)
|
|
|
|
{
|
|
|
|
// Unmark death on scoreboard.
|
|
|
|
Scoreboard->SetPlayerData(plr, "death", "");
|
2015-01-04 11:14:32 +00:00
|
|
|
// everything visible
|
|
|
|
SetFoW(false, plr);
|
2016-03-09 20:29:42 +00:00
|
|
|
SetPlayerViewLock(plr, true);
|
2016-01-17 17:22:08 +00:00
|
|
|
// Player positioning.
|
2013-01-28 21:22:18 +00:00
|
|
|
var ls_wdt = LandscapeWidth(), ls_hgt = LandscapeHeight();
|
2016-01-17 17:22:08 +00:00
|
|
|
var crew = GetCrew(plr), start_pos;
|
|
|
|
// Position by map type?
|
2016-08-08 16:25:15 +00:00
|
|
|
if (SCENPAR_SpawnType == 0)
|
2016-01-17 17:22:08 +00:00
|
|
|
{
|
2016-08-08 16:25:15 +00:00
|
|
|
if (g_player_spawn_positions && g_player_spawn_index < GetLength(g_player_spawn_positions))
|
|
|
|
{
|
|
|
|
start_pos = g_player_spawn_positions[g_player_spawn_index++];
|
|
|
|
var map_zoom = ls_wdt / g_map_width;
|
|
|
|
start_pos = {x=start_pos[0]*map_zoom+map_zoom/2, y=start_pos[1]*map_zoom};
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Start positions not defined or exhausted: Spawn in lower area for both maps becuase starting high is an an advantage.
|
|
|
|
start_pos = FindLocation(Loc_InRect(ls_wdt/5,ls_hgt/2,ls_wdt*3/5,ls_hgt/3), Loc_Wall(CNAT_Bottom), Loc_Func(Scenario.IsStartSpot));
|
|
|
|
if (!start_pos) start_pos = FindLocation(Loc_InRect(ls_wdt/10,0,ls_wdt*8/10,ls_hgt*4/5), Loc_Wall(CNAT_Bottom), Loc_Func(Scenario.IsStartSpot));
|
|
|
|
if (!start_pos) start_pos = {x=Random(ls_wdt*6/10)+ls_wdt*2/10, y=ls_hgt*58/100};
|
|
|
|
}
|
|
|
|
crew->SetPosition(start_pos.x, start_pos.y-10);
|
2016-01-17 17:22:08 +00:00
|
|
|
}
|
2016-08-08 16:25:15 +00:00
|
|
|
else // Balloon spawn
|
2016-01-17 17:22:08 +00:00
|
|
|
{
|
2016-08-08 16:25:15 +00:00
|
|
|
var spawn_x = ls_wdt/3, spawn_y = 10;
|
|
|
|
spawn_x += Random(spawn_x);
|
|
|
|
var balloon = CreateObject(BalloonDeployed, spawn_x, spawn_y - 16, plr);
|
|
|
|
crew->SetPosition(spawn_x, spawn_y);
|
|
|
|
balloon->SetRider(crew);
|
|
|
|
crew->SetAction("Ride", balloon);
|
|
|
|
balloon->SetSpeed(0,0);
|
|
|
|
crew->SetSpeed(0,0);
|
2016-01-17 17:22:08 +00:00
|
|
|
}
|
2013-01-28 21:22:18 +00:00
|
|
|
// initial material
|
2016-01-31 21:51:43 +00:00
|
|
|
if (SCENPAR_Weapons == 0)
|
|
|
|
{
|
|
|
|
crew->CreateContents(Shovel);
|
|
|
|
crew->CreateContents(Club);
|
|
|
|
crew->CreateContents(WindBag);
|
|
|
|
crew->CreateContents(Firestone,2);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Grenade launcher mode
|
|
|
|
crew.MaxContentsCount = 2;
|
|
|
|
crew->CreateContents(WindBag);
|
|
|
|
var launcher = crew->CreateContents(GrenadeLauncher);
|
|
|
|
if (launcher)
|
|
|
|
{
|
|
|
|
var ammo = launcher->CreateContents(IronBomb);
|
|
|
|
launcher->AddTimer(Scenario.ReplenishLauncherAmmo, 10);
|
2016-03-11 16:26:43 +00:00
|
|
|
// Start reloading the launcher during the countdown.
|
2016-08-07 11:56:04 +00:00
|
|
|
if (!IsHandicapped(plr))
|
|
|
|
{
|
|
|
|
crew->SetHandItemPos(0, crew->GetItemPos(launcher));
|
|
|
|
// This doesn't play the animation properly - simulate a click instead.
|
|
|
|
/* crew->StartLoad(launcher); */
|
|
|
|
crew->StartUseControl(CON_Use, 0, 0, launcher);
|
|
|
|
crew->StopUseControl(0, 0, launcher);
|
|
|
|
}
|
2016-01-31 21:51:43 +00:00
|
|
|
}
|
|
|
|
}
|
2016-01-09 19:09:12 +00:00
|
|
|
crew.MaxEnergy = 100000;
|
2013-01-28 21:22:18 +00:00
|
|
|
crew->DoEnergy(1000);
|
2016-03-11 16:26:43 +00:00
|
|
|
// Disable the Clonk during the countdown.
|
|
|
|
crew->SetCrewEnabled(false);
|
|
|
|
crew->SetComDir(COMD_Stop);
|
2016-08-08 16:25:15 +00:00
|
|
|
|
|
|
|
if (SCENPAR_SpawnType == 1 && balloon)
|
|
|
|
balloon->CreateEffect(IntNoGravity, 1, 1);
|
|
|
|
|
2013-01-28 21:22:18 +00:00
|
|
|
return true;
|
|
|
|
}
|
2015-04-17 14:57:32 +00:00
|
|
|
|
2016-08-08 16:25:15 +00:00
|
|
|
local IntNoGravity = new Effect {
|
|
|
|
Timer = func() {
|
|
|
|
Target->SetSpeed(0,0);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-03-11 16:26:43 +00:00
|
|
|
// Called by the round start countdown.
|
|
|
|
func OnCountdownFinished()
|
|
|
|
{
|
|
|
|
// Re-enable all Clonks.
|
|
|
|
for (var clonk in FindObjects(Find_OCF(OCF_CrewMember)))
|
|
|
|
{
|
|
|
|
clonk->SetCrewEnabled(true);
|
|
|
|
SetCursor(clonk->GetOwner(), clonk);
|
2016-08-08 16:25:15 +00:00
|
|
|
if (SCENPAR_SpawnType == 1 && clonk->GetActionTarget())
|
|
|
|
RemoveEffect("IntNoGravity", clonk->GetActionTarget());
|
2016-03-11 16:26:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-18 22:07:34 +00:00
|
|
|
func PutInRelaunchContainer(object clonk)
|
|
|
|
{
|
|
|
|
var plr = clonk->GetOwner();
|
|
|
|
var relaunch = CreateObject(RelaunchContainer, LandscapeWidth() / 2, LandscapeHeight() / 2, plr);
|
|
|
|
// We just use the relaunch object as a dumb container.
|
|
|
|
clonk->Enter(relaunch);
|
|
|
|
// Allow scrolling around the landscape.
|
|
|
|
SetPlayerViewLock(plr, false);
|
|
|
|
}
|
|
|
|
|
2016-03-08 23:07:47 +00:00
|
|
|
func OnClonkDeath(object clonk)
|
|
|
|
{
|
|
|
|
var plr = clonk->GetOwner();
|
2016-03-09 20:08:14 +00:00
|
|
|
// Mark death on scoreboard.
|
|
|
|
Scoreboard->SetPlayerData(plr, "death", "{{Scoreboard_Death}}");
|
2016-03-08 23:07:47 +00:00
|
|
|
// Skip eliminated players, NO_OWNER, etc.
|
2016-03-09 20:08:14 +00:00
|
|
|
if (GetPlayerName(plr))
|
|
|
|
{
|
|
|
|
var crew = CreateObject(Clonk, 0, 0, plr);
|
|
|
|
crew->MakeCrewMember(plr);
|
2017-02-18 22:07:34 +00:00
|
|
|
PutInRelaunchContainer(crew);
|
2016-03-09 20:08:14 +00:00
|
|
|
}
|
2016-03-08 23:07:47 +00:00
|
|
|
|
|
|
|
// Check for victory after three seconds to allow stalemates.
|
2016-03-09 20:08:14 +00:00
|
|
|
if (!g_gameover)
|
2016-04-06 19:30:51 +00:00
|
|
|
g_check_victory_effect.Interval = g_check_victory_effect.Time + 36 * 3;
|
2016-03-08 23:07:47 +00:00
|
|
|
}
|
|
|
|
|
2016-08-07 11:56:04 +00:00
|
|
|
// Returns an array of team -> number of players in team.
|
|
|
|
func GetTeamPlayers()
|
|
|
|
{
|
|
|
|
var result = CreateArray(GetTeamCount() + 1);
|
|
|
|
for (var i = 0; i < GetPlayerCount(); i++)
|
|
|
|
{
|
|
|
|
var plr = GetPlayerByIndex(i), team = GetPlayerTeam(plr);
|
|
|
|
SetLength(result, Max(team + 1, GetLength(result)));
|
|
|
|
result[team] = result[team] ?? [];
|
|
|
|
PushBack(result[team], plr);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
static g_handicapped_players;
|
|
|
|
|
|
|
|
func _MinSize(int a, array b) { if (b == nil) return a; else return Min(a, GetLength(b)); }
|
|
|
|
|
|
|
|
// Assigns handicaps so that the number of not-handicapped players is the same for all teams.
|
|
|
|
func AssignHandicaps()
|
|
|
|
{
|
|
|
|
g_handicapped_players = CreateArray(GetPlayerCount());
|
|
|
|
var teams = GetTeamPlayers();
|
|
|
|
var smallest_size = Reduce(teams, Scenario._MinSize, ~(1<<31));
|
|
|
|
for (var team in teams) if (team != nil)
|
|
|
|
{
|
|
|
|
var to_handicap = GetLength(team) - smallest_size;
|
|
|
|
while (GetLength(team) > to_handicap)
|
|
|
|
RemoveArrayIndexUnstable(team, Random(GetLength(team)));
|
|
|
|
for (var plr in team)
|
|
|
|
{
|
|
|
|
SetLength(g_handicapped_players, Max(plr + 1, GetLength(g_handicapped_players)));
|
|
|
|
g_handicapped_players[plr] = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func IsHandicapped(int plr)
|
|
|
|
{
|
|
|
|
return !!g_handicapped_players[plr];
|
|
|
|
}
|
|
|
|
|
2016-03-08 23:07:47 +00:00
|
|
|
// Returns a list of colored player names, for example "Sven2, Maikel, Luchs"
|
|
|
|
global func GetTeamPlayerNames(int team)
|
|
|
|
{
|
|
|
|
var str = "";
|
|
|
|
for (var i = 0; i < GetPlayerCount(); i++)
|
|
|
|
{
|
|
|
|
var plr = GetPlayerByIndex(i);
|
|
|
|
if (GetPlayerTeam(plr) == team)
|
|
|
|
{
|
|
|
|
var comma = "";
|
|
|
|
if (str != "") comma = ", ";
|
|
|
|
str = Format("%s%s<c %x>%s</c>", str, comma, GetPlayerColor(plr), GetPlayerName(plr));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
|
|
|
global func FxCheckVictoryTimer(_, proplist effect)
|
|
|
|
{
|
|
|
|
var find_living = Find_And(Find_OCF(OCF_CrewMember), Find_NoContainer());
|
|
|
|
var clonk = FindObject(find_living);
|
2016-03-09 20:08:14 +00:00
|
|
|
var msg;
|
2016-03-08 23:07:47 +00:00
|
|
|
if (!clonk)
|
|
|
|
{
|
|
|
|
// Stalemate!
|
2016-03-09 20:08:14 +00:00
|
|
|
msg = "$Stalemate$";
|
|
|
|
Log(msg);
|
2016-03-08 23:07:47 +00:00
|
|
|
GameCall("ResetRound");
|
|
|
|
}
|
|
|
|
else if (!FindObject(find_living, Find_Hostile(clonk->GetOwner())))
|
|
|
|
{
|
|
|
|
// We have a winner!
|
|
|
|
var team = GetPlayerTeam(clonk->GetOwner());
|
|
|
|
PushBack(g_winners, team);
|
|
|
|
// Announce the winning team.
|
2016-03-09 20:08:14 +00:00
|
|
|
msg = Format("$WinningTeam$", GetTeamPlayerNames(team));
|
|
|
|
Log(msg);
|
|
|
|
|
|
|
|
// Update the scoreboard.
|
|
|
|
UpdateScoreboardWins(team);
|
|
|
|
|
2016-04-06 19:43:47 +00:00
|
|
|
// The leading team has to win the last round.
|
|
|
|
if (--g_remaining_rounds > 0 || GetLeadingTeam() != team)
|
2016-03-08 23:07:47 +00:00
|
|
|
{
|
2016-03-09 20:08:14 +00:00
|
|
|
var msg2 = CurrentRoundStr();
|
|
|
|
Log(msg2);
|
|
|
|
msg = Format("%s|%s", msg, msg2);
|
2016-03-08 23:07:47 +00:00
|
|
|
GameCall("ResetRound");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
GameCall("EliminateLosers");
|
|
|
|
}
|
|
|
|
}
|
2016-03-09 20:08:14 +00:00
|
|
|
// Switching scenario sections makes the Log() messages hard to see, so announce them using a message as well.
|
|
|
|
CustomMessage(msg);
|
2016-03-08 23:07:47 +00:00
|
|
|
// Go to sleep again.
|
|
|
|
effect.Interval = 0;
|
|
|
|
return FX_OK;
|
|
|
|
}
|
|
|
|
|
2016-03-09 20:08:14 +00:00
|
|
|
global func CurrentRoundStr()
|
|
|
|
{
|
|
|
|
if (g_remaining_rounds == 1)
|
|
|
|
return "$LastRound$";
|
|
|
|
else if (g_remaining_rounds > 1)
|
|
|
|
return Format("$RemainingRounds$", g_remaining_rounds);
|
2016-04-06 19:43:47 +00:00
|
|
|
else if (GetLeadingTeam() == nil)
|
2016-03-09 20:08:14 +00:00
|
|
|
return "$Tiebreak$";
|
2016-04-06 19:43:47 +00:00
|
|
|
else
|
|
|
|
return "$BonusRound$";
|
2016-03-09 20:08:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
global func UpdateScoreboardWins(int team)
|
|
|
|
{
|
|
|
|
var wins = GetTeamWins(team);
|
|
|
|
Scoreboard->SetData(ScoreboardTeam(team), "wins", wins, wins);
|
|
|
|
// We have to update each player as well to make the sorting work.
|
|
|
|
for (var i = 0; i < GetPlayerCount(); i++)
|
|
|
|
{
|
|
|
|
var plr = GetPlayerByIndex(i);
|
|
|
|
if (GetPlayerTeam(plr) == team)
|
|
|
|
{
|
|
|
|
Scoreboard->SetPlayerData(plr, "wins", "", wins);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
global func GetTeamWins(int team)
|
|
|
|
{
|
|
|
|
var wins = 0;
|
|
|
|
for (var w in g_winners)
|
|
|
|
if (w == team)
|
|
|
|
wins++;
|
|
|
|
return wins;
|
|
|
|
}
|
|
|
|
|
2016-03-08 23:07:47 +00:00
|
|
|
// Returns the team which won the most rounds, or nil if there is a tie.
|
|
|
|
global func GetLeadingTeam()
|
|
|
|
{
|
|
|
|
var teams = [], winning_team = g_winners[0];
|
|
|
|
for (var w in g_winners)
|
|
|
|
{
|
|
|
|
teams[w] += 1;
|
|
|
|
if (teams[w] > teams[winning_team])
|
|
|
|
winning_team = w;
|
|
|
|
}
|
|
|
|
// Detect a tie.
|
|
|
|
for (var i = 0; i < GetLength(teams); i++)
|
|
|
|
{
|
|
|
|
if (i != winning_team && teams[i] == teams[winning_team])
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
return winning_team;
|
|
|
|
}
|
|
|
|
|
|
|
|
func EliminateLosers()
|
|
|
|
{
|
2016-03-09 20:08:14 +00:00
|
|
|
g_gameover = true;
|
2016-03-08 23:07:47 +00:00
|
|
|
// Determine the winning team.
|
|
|
|
var winning_team = GetLeadingTeam();
|
|
|
|
// Eliminate everybody who isn't on the winning team.
|
|
|
|
for (var i = 0; i < GetPlayerCount(); i++)
|
|
|
|
{
|
|
|
|
var plr = GetPlayerByIndex(i);
|
|
|
|
if (GetPlayerTeam(plr) != winning_team)
|
|
|
|
EliminatePlayer(plr);
|
|
|
|
}
|
|
|
|
// The scenario goal will end the scenario.
|
|
|
|
}
|
|
|
|
|
2016-01-31 21:51:43 +00:00
|
|
|
/* Called periodically in grenade launcher */
|
|
|
|
func ReplenishLauncherAmmo()
|
|
|
|
{
|
|
|
|
if (!ContentsCount()) CreateContents(IronBomb);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-04-17 14:57:32 +00:00
|
|
|
// Horizontal Loc_Space doesn't work with Loc_Wall because it checks inside the ground.
|
|
|
|
func IsStartSpot(int x, int y)
|
|
|
|
{
|
|
|
|
// Don't spawn just at the border of an island.
|
|
|
|
if (!GBackSolid(x-3,y+2)) return false;
|
|
|
|
if (!GBackSolid(x+3,y+2)) return false;
|
|
|
|
// Spawn with some space.
|
|
|
|
return PathFree(x-5, y, x+5, y) && PathFree(x, y-21, x, y-1);
|
|
|
|
}
|
|
|
|
|
|
|
|
func IsFirestoneSpot(int x, int y)
|
|
|
|
{
|
|
|
|
// Very thorough ice surrounding check so they don't explode right away or when the first layer of ice melts
|
|
|
|
return GBackSolid(x,y-1) && GBackSolid(x,y+4) && GBackSolid(x-2,y) && GBackSolid(x+2,y);
|
|
|
|
}
|
2018-01-07 19:59:05 +00:00
|
|
|
|
|
|
|
// ============= Themes =============
|
2018-02-05 15:54:02 +00:00
|
|
|
static const DefaultTheme = new Global
|
|
|
|
{
|
2018-01-07 19:59:05 +00:00
|
|
|
InitializeRound = func() { },
|
2018-01-14 20:27:16 +00:00
|
|
|
LavaMat = "^DuroLava",
|
|
|
|
IceMats = ["^Ice-ice", "^Ice-ice2"],
|
2018-01-07 19:59:05 +00:00
|
|
|
AltMatRatio = 50,
|
|
|
|
BackgroundMat = nil,
|
|
|
|
Sky = "Default",
|
2018-01-07 21:21:44 +00:00
|
|
|
PlayList = nil,
|
|
|
|
InitializeMusic = func()
|
|
|
|
{
|
|
|
|
// No special play list => music by Ambience
|
|
|
|
if (this.PlayList == nil)
|
|
|
|
InitializeAmbience();
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Remove Ambience to avoid interference.
|
|
|
|
RemoveAll(Find_ID(Ambience));
|
|
|
|
SetPlayList(this.PlayList, NO_OWNER, true);
|
|
|
|
SetGlobalSoundModifier(nil);
|
|
|
|
}
|
2018-02-05 15:54:02 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
static const HotIce = new DefaultTheme
|
|
|
|
{
|
|
|
|
InitializeRound = func()
|
|
|
|
{
|
|
|
|
Stalactite->Place(10 + Random(3));
|
|
|
|
}
|
2018-01-07 19:59:05 +00:00
|
|
|
};
|
|
|
|
|
2018-02-05 15:54:02 +00:00
|
|
|
static const EciToh = new DefaultTheme
|
|
|
|
{
|
2018-01-14 20:27:16 +00:00
|
|
|
LavaMat = "DuroLava",
|
|
|
|
IceMats = ["Coal", "Rock-rock"],
|
2018-01-07 19:59:05 +00:00
|
|
|
AltMatRatio = 8,
|
|
|
|
BackgroundMat = "Tunnel",
|
2018-02-05 15:54:02 +00:00
|
|
|
InitializeRound = func()
|
|
|
|
{
|
|
|
|
Stalactite->Place(10 + Random(3));
|
|
|
|
}
|
2018-01-07 19:59:05 +00:00
|
|
|
};
|
|
|
|
|
2018-02-05 15:54:02 +00:00
|
|
|
static const MiamiIce = new DefaultTheme
|
|
|
|
{
|
2018-01-14 20:27:16 +00:00
|
|
|
IceMats = ["^BlackIce-black", "^BlackIce-black"],
|
2018-01-07 19:59:05 +00:00
|
|
|
Sky = "SkyMiami",
|
2018-01-07 21:21:44 +00:00
|
|
|
PlayList =
|
|
|
|
{
|
|
|
|
PlayList = "beach",
|
|
|
|
MusicBreakChance = 0,
|
|
|
|
},
|
2018-01-07 19:59:05 +00:00
|
|
|
|
|
|
|
InitializeRound = func()
|
|
|
|
{
|
|
|
|
// Colors
|
|
|
|
Scenario->CreateEffect(MiamiObjects, 1, 1);
|
|
|
|
|
|
|
|
Tree_Coconut->Place(RandomX(7, 13));
|
|
|
|
},
|
|
|
|
|
|
|
|
MiamiObjects = new Effect {
|
|
|
|
Timer = func(int time)
|
|
|
|
{
|
|
|
|
for (var o in FindObjects(Find_NoContainer()))
|
|
|
|
{
|
|
|
|
if (o->GetID() == Tree_Coconut)
|
|
|
|
continue;
|
|
|
|
o->SetClrModulation(HSL(time, 255, 100));
|
|
|
|
}
|
|
|
|
},
|
2018-02-05 15:54:02 +00:00
|
|
|
}
|
2018-01-07 19:59:05 +00:00
|
|
|
};
|