add defense goal

This is the goal in defense scenarios and controls the waves. In the future a league interface can also be build. The goal is to adapt the existing defense scenarios to this format (mostly internal changes and infinite waves). Also this should allow for easier creation of defense scenarios.
alut-include-path
Maikel de Vries 2017-01-08 19:07:58 +01:00
parent 5e6cf4e7b8
commit 1063610369
6 changed files with 371 additions and 0 deletions

View File

@ -0,0 +1,7 @@
[DefCore]
id=Goal_Defense
Version=8,0
Category=C4D_StaticBack|C4D_Goal
Width=64
Height=64
Offset=-32,-32

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -0,0 +1,325 @@
/**
Defense Goal
The players cooperate to defend a base against an enemy for a many attack waves as possible.
@author Maikel
*/
#include Library_Goal
local score;
local completed_waves;
local is_fulfilled;
local fx_wave_control;
local shared_wealth_remainder;
local observer_container;
local plrs_active;
local plrs_bonus;
local plrs_score;
public func Construction()
{
// Set initial score to zero.
SetScore(0);
completed_waves = 0;
is_fulfilled = false;
// Wealth is shared and remainder is stored.
shared_wealth_remainder = 0;
// Create a container for observers while the round still runs.
observer_container = CreateObject(RelaunchContainer, AbsX(LandscapeWidth() / 2), AbsY(LandscapeHeight() / 2));
// Init player tracking.
plrs_active = [];
plrs_bonus = [];
plrs_score = [];
// Add an effect to control the waves.
fx_wave_control = CreateEffect(FxWaveControl, 100, nil);
// Initialize scoreboard.
Scoreboard->SetTitle(Format("$MsgScoreboard$", GetScore()));
Scoreboard->Init([
{key = "bonus", title = Icon_Wealth, sorted = true, desc = true, default = "0", priority = 100}
]);
return _inherited(...);
}
/*-- Player Control --*/
public func InitializePlayer(int plr)
{
var plrid = GetPlayerID(plr);
if (GetPlayerType(plr) == C4PT_User)
{
// Store active players.
PushBack(plrs_active, GetPlayerID(plr));
// Initialize scoreboard.
Scoreboard->NewPlayerEntry(plr);
plrs_bonus[plrid] = 0;
Scoreboard->SetPlayerData(plr, "bonus", plrs_bonus[plrid]);
}
return;
}
public func RelaunchPlayer(int plr)
{
if (GetPlayerType(plr) == C4PT_User)
{
var crew = CreateObject(Clonk);
crew->MakeCrewMember(plr);
SetCursor(plr, crew);
crew->Enter(observer_container);
RemoveArrayValue(plrs_active, GetPlayerID(plr));
if (GetLength(plrs_active) == 0)
EndRound();
}
return;
}
public func EndRound()
{
for (var plr in GetPlayers(C4PT_Script))
EliminatePlayer(plr);
// Set evaluation data.
SetRoundEvaluationData();
// Set league score.
SetLeaguePerformance(GetScore());
// Remove wave control effect.
if (fx_wave_control)
fx_wave_control->Remove();
is_fulfilled = true;
return;
}
public func IsFulfilled() { return is_fulfilled; }
/*-- Score --*/
public func DoScore(int value)
{
return SetScore(score + value);
}
public func GetScore() { return score; }
public func SetScore(int value)
{
score = value;
// Update scoreboard title.
Scoreboard->SetTitle(Format("$MsgScoreboard$", GetScore()));
return;
}
/*-- Wave Control --*/
local FxWaveControl = new Effect
{
Construction = func()
{
// Do a timer call every second.
this.Interval = 36;
// Init wave number, time passed and the current wave.
this.wave_nr = 0;
this.time_passed = 0;
this.wave = nil;
// Launch first wave.
this->Timer(0);
},
Timer = func(int time)
{
// Start new wave.
if (!this.wave || this.time_passed >= this.wave.Duration)
{
this.wave = GameCall("GetAttackWave", this.wave_nr);
// Check if this was the last wave.
if (!this.wave)
{
Log("WARNING: wave not specified for wave number %d. Using default wave of defense goal instead.", this.wave_nr);
this.wave = Target->GetDefaultWave(this.wave_nr);
}
// Launch the wave.
DefenseWave->LaunchWave(this.wave, this.wave_nr, g_enemy_plr);
// Track the wave.
Target->CreateEffect(Target.FxTrackWave, 100, nil, this.wave_nr, this.wave);
// Reset passed time and increase wave number for next wave.
this.time_passed = 0;
this.wave_nr++;
}
// Increase the time passed in this wave.
this.time_passed++;
return FX_OK;
},
GetCurrentWave = func()
{
return this.wave;
}
};
public func GetDefaultWave(int wave_nr)
{
// Just launch some rockets as the default wave and increase the amount and their speed with every wave.
var wave = new DefaultWave
{
Name = "$WaveFallBack$",
Duration = 2 * 60,
Bounty = 25,
Score = 25,
Enemies = [new DefenseEnemy.BoomAttack {Amount = Max(10, 2 * wave_nr), Speed = 100 + wave_nr * 10}]
};
return wave;
}
/*-- Wave Tracking --*/
local FxTrackWave = new Effect
{
Construction = func(int wave_nr, proplist wave)
{
this.wave_nr = wave_nr;
this.wave = wave;
this.enemies = [];
},
OnEnemyInit = func(object enemy)
{
PushBack(this.enemies, enemy);
},
OnEnemyEliminated = func(object enemy)
{
RemoveArrayValue(this.enemies, enemy);
if (GetLength(this.enemies) == 0)
{
// Completed this wave: log, give bounty and remove effect.
//Log("[%d]Bounty %d for wave %d", FrameCounter(), this.wave.Bounty, this.wave_nr);
if (this.wave.Bounty != nil)
{
Target->DoWealthForAll(this.wave.Bounty);
CustomMessage(Format("$MsgWaveCleared$", this.wave_nr, this.wave.Bounty));
}
if (this.wave.Score != nil)
Target->DoScore(this.wave.Score);
Target->IncreaseCompletedWaves();
Sound("UI::NextWave");
this->Remove();
}
}
};
public func OnEnemyCreation(object enemy, int wave_nr)
{
var index = 0, fx;
while (fx = GetEffect("FxTrackWave", this, index++))
if (fx.wave_nr == wave_nr)
fx->OnEnemyInit(enemy);
return;
}
public func OnEnemyElimination(object enemy)
{
var index = 0, fx;
while (fx = GetEffect("FxTrackWave", this, index++))
fx->OnEnemyEliminated(enemy);
return;
}
public func IncreaseCompletedWaves()
{
completed_waves++;
return;
}
/*-- Reward --*/
public func OnClonkDeath(object clonk, int killed_by)
{
var plrid = GetPlayerID(killed_by);
if (clonk.Bounty)
{
//Log("[%d]Bounty %d for plr %d", FrameCounter(), clonk.Bounty, killed_by);
if (killed_by != NO_OWNER)
{
DoWealth(killed_by, clonk.Bounty);
plrs_bonus[plrid] += clonk.Bounty;
Scoreboard->SetPlayerData(killed_by, "bonus", plrs_bonus[plrid]);
}
else
{
DoSharedWealth(clonk.Bounty);
}
}
if (clonk.Score)
DoScore(clonk.Score);
// Notify about elimination of enemy.
OnEnemyElimination(clonk);
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;
}
// Add wealth to all players.
public func DoWealthForAll(int amount)
{
for (var plr in GetPlayers(C4PT_User))
DoWealth(plr, amount);
return;
}
/*-- Evaluation Data --*/
public func SetRoundEvaluationData()
{
// Obviously get rid of settlement score.
HideSettlementScoreInEvaluation(true);
// Display the number of completed waves.
AddEvaluationData(Format("$MsgEvaluationData$", completed_waves, GetScore()), 0);
return;
}
/*-- Description --*/
public func GetDescription(int plr)
{
var wave_msg = "$MsgCurrentWave$";
// Add enemies of current wave.
if (fx_wave_control)
{
var enemies = fx_wave_control->GetCurrentWave().Enemies;
if (enemies)
for (var enemy in enemies)
wave_msg = Format("%s%dx %s\n", wave_msg, enemy.Amount, enemy.Name);
}
// Add score.
wave_msg = Format("%s\n%s", wave_msg, Format("$MsgCurrentScore$", GetScore(), 0/*replace with plr high score*/));
return Format("%s\n\n%s", "$Description$", wave_msg);
}
/*-- Properties --*/
local Name = "$Name$";
local Description = "$Description$";
local Visibility = VIS_Editor;
local EditorPlacementLimit = 1;

View File

@ -0,0 +1,12 @@
Name=Verteidigung
Description=Survive as many attack waves as possible, each wave and enemy eliminated earns you points.
MsgWaveCleared=Welle %d geschafft!|<c ffff00>+%d</c>{{Icon_Wealth}}| |
WaveFallBack=Viele Raketen
MsgEvaluationData=You completed %d waves and scored %d points.
MsgCurrentWave=<c ffff00>Jetztige Welle:</c>\n
MsgCurrentScore=<c ffff00>Current score:</c> %d (your high score is %d).
MsgScoreboard=Verteidigung: %d Punkte

View File

@ -0,0 +1,12 @@
Name=Defense
Description=Survive as many attack waves as possible, each wave and enemy eliminated earns you points.
MsgWaveCleared=Wave %d done!|<c ffff00>+%d</c>{{Icon_Wealth}}| |
WaveFallBack=Lots of rockets
MsgEvaluationData=You completed %d waves and scored %d points.
MsgCurrentWave=<c ffff00>Current wave:</c>\n
MsgCurrentScore=<c ffff00>Current score:</c> %d (your high score is %d).
MsgScoreboard=Defense: %d points

View File

@ -0,0 +1,15 @@
// Used by the AI to find targets to attack.
global func GetRandomAttackTarget(object attacker)
{
// First let the scenario tell what to do by a gamecall.
var target = GameCall("GiveRandomAttackTarget", attacker);
if (!target)
{
// Attack structures owned by the enemy of the attacker.
var controller = attacker->GetController();
var target = FindObject(Find_Category(C4D_Structure), Find_Hostile(controller), Sort_Random());
//Log("[%d]Found new attack target %v", FrameCounter(), target);
}
return target;
}