/* Fight for Gidl Authors: Sven2 Defend the statue against waves of enemies */ static g_goal, g_object_fade, g_statue, g_doorleft, g_doorright; static g_wave; // index of current wave static g_spawned_enemies; static g_relaunchs; // array of relaunch counts static g_scores; // array of player scores static g_ai; // derived from AI; contains changes for this scenario static g_homebases; // item management / buy menus for each player static const ENEMY = 10; // player number of enemy static const ALLOW_DEBUG_COMMANDS = true; static const MAX_RELAUNCH = 10; static shared_wealth_remainder; //====================================================================== /* Initialization */ func Initialize() { // dev stuff (we will forget to turn this off for release) AddMsgBoardCmd("waveinfo", "GameCall(\"ShowWaveInfo\")"); AddMsgBoardCmd("next", "GameCall(\"SetNextWave\", \"%s\")"); AddMsgBoardCmd("nextwait", "GameCall(\"SetNextWave\", \"%s\", true)"); AddMsgBoardCmd("scrooge", "GameCall(\"DoWealthForAll\", 1000000000)"); // Init door dummies g_doorleft.dummy_target = g_doorleft->CreateObject(DoorDummy, -6, 6); g_doorright.dummy_target = g_doorright->CreateObject(DoorDummy, +6, 6); // Wealth shown at all time GUI_Controller->ShowWealth(); // static variable init g_homebases = []; InitWaveData(); } func InitializePlayer(int plr, int iX, int iY, object pBase, int iTeam) { if (GetPlayerType(plr) != C4PT_User) return; //DoWealth(plr, 10000); if (!g_statue) { EliminatePlayer(plr); return; } // no post-elimination join if (!g_relaunchs) { g_relaunchs = []; g_scores = []; Scoreboard->Init([{key = "relaunchs", title = Rule_Restart, sorted = true, desc = true, default = "", priority = 75}, {key = "score", title = Nugget, sorted = true, desc = true, default = "0", priority = 100}]); } for (var stonedoor in FindObjects(Find_ID(StoneDoor), Find_Owner(NO_OWNER))) stonedoor->SetOwner(plr); g_relaunchs[plr] = MAX_RELAUNCH; g_scores[plr] = 0; Scoreboard->NewPlayerEntry(plr); Scoreboard->SetPlayerData(plr, "relaunchs", g_relaunchs[plr]); Scoreboard->SetPlayerData(plr, "score", g_scores[plr]); //SetFoW(false,plr); - need FoW for lights CreateObject(Homebase, 0,0, plr); JoinPlayer(plr); if (!g_wave) StartGame(); return; } func RemovePlayer(int plr) { if (g_homebases[plr]) g_homebases[plr]->RemoveObject(); Scoreboard->SetPlayerData(plr, "relaunchs", Icon_Cancel); // Split player's wealth among the remaining players ScheduleCall(nil, Scenario.DoSharedWealth, 50, 1, GetWealth(plr)); return; } private func TransferInventory(object from, object to) { // Drop some items that cannot be transferred (such as connected pipes and dynamite igniters) var i = from->ContentsCount(), contents; while (i--) if (contents = from->Contents(i)) if (contents->~IsDroppedOnDeath(from)) contents->Exit(); return to->GrabContents(from); } func JoinPlayer(plr, prev_clonk) { var spawn_idx = Random(2); if (prev_clonk && g_statue) spawn_idx = (prev_clonk->GetX() > g_statue->GetX()); var x=[494,763][spawn_idx],y = 360; var clonk = GetCrew(plr); if (clonk) { clonk->SetPosition(x,y-10); } else { clonk = CreateObjectAbove(Clonk, x,y, plr); clonk->MakeCrewMember(plr); } SetCursor(plr, clonk); clonk->DoEnergy(1000); clonk->MakeInvincibleToFriendlyFire(); // contents clonk.MaxContentsCount = CustomAI.Clonk_MaxContentsCount; clonk.MaxContentsCountVal = 1; if (prev_clonk) TransferInventory(prev_clonk, clonk); if (!clonk->ContentsCount()) { clonk->CreateContents(Bow); var arrow = CreateObjectAbove(Arrow); clonk->Collect(arrow); arrow->SetInfiniteStackCount(); } //clonk->CreateContents(Musket); //clonk->Collect(CreateObjectAbove(LeadShot)); clonk->~CrewSelection(); // force update HUD return; } func StartGame() { // Init objects to defend var obj; for (obj in [g_statue, g_doorleft, g_doorright]) if (obj) { obj->SetCategory(C4D_Living); obj->SetAlive(true); obj.MaxEnergy = 800000; obj->DoEnergy(obj.MaxEnergy/1000); obj->AddEnergyBar(); obj.FxNoPlayerDamageDamage = Scenario.Object_NoPlayerDamage; AddEffect("NoPlayerDamage", obj, 500, 0, obj); } if (g_statue) { g_statue.Death = Scenario.Statue_Death; } // Launch first wave! g_wave = 1; ScheduleCall(nil, Scenario.LaunchWave, 50, 1, g_wave); return true; } func Object_NoPlayerDamage(object target, fx, dmg, cause, cause_player) { // players can't damage statue or doors if (GetPlayerType(cause_player) == C4PT_User) return 0; return dmg; } //====================================================================== /* Enemy waves */ func LaunchWave(int wave) { // * Schedules spawning of all enemies // * Schedules call to LaunchWaveDone() after last enemy has been spawned var wave_data = ENEMY_WAVE_DATA[g_wave]; g_spawned_enemies = []; if (wave_data) { var wave_spawn_time = 0; CustomMessage(Format("$MsgWave$: %s", wave, wave_data.Name)); Sound("Ding"); for (var enemy in ForceVal2Array(wave_data.Enemies)) if (enemy) { if (enemy.Delay) ScheduleCall(nil, Scenario.ScheduleLaunchEnemy, enemy.Delay, 1, enemy); else ScheduleLaunchEnemy(enemy); wave_spawn_time = Max(wave_spawn_time, enemy.Delay + enemy.Interval * enemy.Num); } ScheduleCall(nil, Scenario.LaunchWaveDone, wave_spawn_time+5, 1, wave); return true; } return false; } func ScheduleLaunchEnemy(proplist enemy) { // Schedules spawning of enemy definition // Spawn on ground or in air? var xmin, xmax, y; if (enemy.Type && enemy.Type->~IsFlyingEnemy()) { // Air spawn xmin = 0; xmax = 550; y = 5; } else { xmin = xmax = 0; y = 509; } // Spawn either only enemy or mirrored enemies on both sides var side = enemy.Side; if (!side) side = WAVE_SIDE_LEFT | WAVE_SIDE_RIGHT; if (side & WAVE_SIDE_LEFT) ScheduleCall(nil, CustomAI.LaunchEnemy, Max(enemy.Interval,1), Max(enemy.Num,1), enemy, 10 + xmin, xmax - xmin, y); if (side & WAVE_SIDE_RIGHT) ScheduleCall(nil, CustomAI.LaunchEnemy, Max(enemy.Interval,1), Max(enemy.Num,1), enemy, 1190 - xmax, xmax - xmin, y); return true; } func LaunchWaveDone(int wave) { // All enemies spawned! Now start timer to check whether they are all dead ScheduleCall(nil, Scenario.CheckWaveCleared, 20, 9999999, wave); return true; } func CheckWaveCleared(int wave) { // Check timer to determine if enemy wave has been cleared. // Enemies nil themselves when they're dead. So clear out nils and we're done when the list is empty var nil_idx; while ( (nil_idx=GetIndexOf(g_spawned_enemies))>=0 ) { var l = GetLength(g_spawned_enemies) - 1; if (nil_idx+%d{{Icon_Wealth}}", bounty); DoWealthForAll(bounty); } CustomMessage(Format("$MsgWaveCleared$%s| ", wave, bounty_msg)); Sound("Ding"); // Fade out corpses if (g_object_fade) for (var obj in FindObjects(Find_Or(Find_And(Find_ID(Clonk), Find_Not(Find_OCF(OCF_Alive))), Find_ID(Catapult)))) obj->AddEffect("IntFadeOut", obj, 100, 1, g_object_fade, Rule_ObjectFade); // Next wave! ++g_wave; if (ENEMY_WAVE_DATA[g_wave]) ScheduleCall(nil, Scenario.LaunchWave, 500, 1, g_wave); else { // There is no next wave? Game done D: ScheduleCall(nil, Scenario.OnAllWavesCleared, 50, 1); } } // Clonk death callback func OnClonkDeath(clonk, killed_by) { // Player died? if (!clonk) return; var plr = clonk->GetOwner(); if (GetPlayerType(plr) == C4PT_User) { // Relaunch count if (!g_relaunchs[plr]) { Log("$MsgOutOfRelaunchs$", GetTaggedPlayerName(plr)); Scoreboard->SetPlayerData(plr, "relaunchs", Icon_Cancel); EliminatePlayer(plr); return false; } // Relaunch count --g_relaunchs[plr]; Scoreboard->SetPlayerData(plr, "relaunchs", g_relaunchs[plr]); Log("$MsgRelaunch$", GetTaggedPlayerName(plr)); JoinPlayer(plr, clonk); //var gui_arrow = FindObject(Find_ID(GUI_GoalArrow), Find_Owner(plr)); //gui_arrow->SetAction("Show", GetCursor(plr)); } else { // Enemy clonk death // Remove inventory var i = clonk->ContentsCount(), obj; while (i--) if (obj=clonk->Contents(i)) if (!obj->~OnContainerDeath()) obj->RemoveObject(); // Clear enemies from list i = GetIndexOf(g_spawned_enemies, clonk); if (i>=0) { g_spawned_enemies[i] = nil; // Kill bounty if (killed_by>=0) { Scoreboard->SetPlayerData(killed_by, "score", ++g_scores[killed_by]); DoWealth(killed_by, clonk.Bounty); } else { // Killer could not be determined. Just give gold to everyone. DoSharedWealth(clonk.Bounty); } } } return; } public func DoSharedWealth(int amount) { // Split gold among all players. Keep track of remainder and use it next time shared_wealth_remainder += amount; var cnt = GetPlayerCount(C4PT_User); if (cnt) { var wealth_add = shared_wealth_remainder / cnt; if (wealth_add) { shared_wealth_remainder -= wealth_add * cnt; DoWealthForAll(wealth_add); } } return true; } public func DoWealthForAll(int amount) { // Add wealth to all players for (var iplr = 0; iplr < GetPlayerCount(C4PT_User); ++iplr) DoWealth(GetPlayerByIndex(iplr, C4PT_User), amount); return true; } //====================================================================== /* Game end */ func OnAllWavesCleared() { // Success! if (g_goal) g_goal.is_fulfilled = true; if (GetPlayerType(ENEMY) == C4PT_Script) EliminatePlayer(ENEMY); GainScenarioAchievement("Done"); GameOver(); return true; } func Statue_Death() { // Fail :( // Elminiate all players var i=GetPlayerCount(C4PT_User); while (i--) EliminatePlayer(GetPlayerByIndex(i, C4PT_User)); // Statue down :( CastObjects(Nugget, 5, 10); Explode(10); ScheduleCall(nil, Global.GameOver, 50, 1); return true; } /* Developer commands */ public func ShowWaveInfo() { // Debug summary to balance waves var total_reward = 0, total_bonus = 0; for (var i = 1; iRemoveObject(); // Give gold for skipped waves var total_reward = 0, total_bonus = 0; for (var i=g_wave; i