Add enemy spawn object

This object can be placed in the editor to spawn enemy AI clonks.
alut-include-path
Sven Eberhardt 2017-02-19 20:36:18 -05:00
parent 6579798907
commit 99a65008cd
7 changed files with 619 additions and 4 deletions

View File

@ -100,7 +100,7 @@ private func InitAttackModes()
if (!this.FxAI.EditorProps.attack_mode) this.FxAI.EditorProps.attack_mode = AI.FxAI.EditorProps.attack_mode;
}
public func RegisterAttackMode(string identifier, proplist am)
public func RegisterAttackMode(string identifier, proplist am, proplist am_default_values)
{
// Definition call during Definition()-initialization:
// Register a new attack mode selectable for the AI clonk
@ -108,11 +108,12 @@ public func RegisterAttackMode(string identifier, proplist am)
if (!AttackModes) this->InitAttackModes();
AttackModes[identifier] = am;
am.Identifier = identifier;
if (!am_default_values) am_default_values = { Identifier=identifier };
// Add to editor option for AI effect
var am_option = {
Name = am.Name ?? am->GetName(),
EditorHelp = am.EditorHelp,
Value = am
Value = am_default_values
};
if (!am_option.EditorHelp && am.GetEditorHelp) am_option.EditorHelp = am->GetEditorHelp();
var editor_opts = this.FxAI.EditorProps.attack_mode.Options;
@ -121,10 +122,10 @@ public func RegisterAttackMode(string identifier, proplist am)
private func DefinitionAttackModes(proplist def)
{
// Register presets for all the default weapons usable by the AI
this->InitAttackModes();
// Registration only once for base AI
if (this != AI) return;
// Register presets for all the default weapons usable by the AI
this->InitAttackModes();
def->RegisterAttackMode("Default", { Name = "$Default$", EditorHelp = "$DefaultHelp$", FindWeapon = AI.FindInventoryWeapon });
def->RegisterAttackMode("Sword", new SingleWeaponAttackMode { Weapon = Sword, Strategy = this.ExecuteMelee });
def->RegisterAttackMode("Club", new SingleWeaponAttackMode { Weapon = Club, Strategy = this.ExecuteMelee });

View File

@ -21,6 +21,8 @@
#include AI_Vehicles
#include AI_AttackModes
// Enemy spawn definition depends on this
local DefinitionPriority = 50;
// AI Settings.
local MaxAggroDistance = 200; // Lose sight to target if it is this far away (unless we're ranged - then always guard the range rect).
@ -285,6 +287,8 @@ local FxAI = new Effect
},
SetAttackMode = func(proplist attack_mode)
{
// Called by editor delegate when attack mdoe is changed.
// For now, attack mode parameter delegates are not supported. Just set by name.
return this.control->SetAttackMode(this.Target, attack_mode.Identifier);
},
EditorProps = {

View File

@ -0,0 +1,7 @@
[DefCore]
id=EnemySpawn
Version=8,0
Category=C4D_StaticBack|C4D_Environment
Width=16
Height=16
Offset=-8,-8

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,507 @@
/**
EnemySpawn
When activated, spawns one or more AI-controlled enemies that attack the players.
@author Sven2
*/
local Name="$Name$";
local Description="$Description$";
local Visibility=VIS_Editor;
local SPAWNCOUNT_INFINITE = 0x7fffffff; // magic value for spawn_count: Infinite enemy count. (must be a big value)
local active; // Triggered or currently spawning
local waiting_for_script_player; // Set to true if rule was activated but still waiting for
local enemy = nil; // No enemy defined
local spawn_position = nil; // Where / in which range to spawn
local spawn_count = 1; // Number of enemies to spawn. SPAWNCOUNT_INFINITE for infinite.
local spawn_delay = 0; // Delay after trigger before first spawn
local spawn_interval = 30; // Delay between spawned enemies
local attack_path = nil; // Optional: Array of points along which the spawned enemy moves/attacks
local auto_activate = false; // If true, the object is activated on the first player join
local spawned_count; // Number of enemies already spawned in current wave
local spawned_enemies; // Array of spawned enemies. Automatically cleared when clonks die.
local num_enemies_defeated = 0; // Increased for each defeated clonk of this spawner
local num_waves_defeated = 0; // Increased after each defeated activation
local EnemyDefs; // Proplist of possible enemy spawn definitions; indexed by Type
static g_enemyspawn_player; // player number of attacking player
public func IsEnemySpawn() { return true; }
/* Parameter interface */
public func SetEnemy(proplist new_enemy) { enemy = new_enemy; UpdateEnemyDisplay(); }
public func SetSpawnPosition(proplist new_spawn_position) { spawn_position = new_spawn_position; }
public func SetSpawnCount(int new_spawn_count) { spawn_count = new_spawn_count; }
public func SetSpawnDelay(int new_spawn_delay) { spawn_delay = new_spawn_delay; }
public func SetSpawnInterval(int new_spawn_interval) { spawn_interval = new_spawn_interval; }
public func SetAttackPath(array new_attack_path) { attack_path = new_attack_path; }
public func SetAutoActivate(bool new_auto_activate) { auto_activate = new_auto_activate; }
/* Initialization */
public func Initialize()
{
spawned_enemies = [];
// Make sure there's an enemy script player
if (!GetType(g_enemyspawn_player))
{
// Look for existing enemy player
for (var iplr = 0; iplr < GetPlayerCount(C4PT_Script); ++iplr)
{
var plr = GetPlayerByIndex(iplr, C4PT_Script);
if (GetScriptPlayerExtraID(plr) == GetID())
{
g_enemyspawn_player = plr;
break;
}
}
// Otherwise join it
if (!GetType(g_enemyspawn_player))
{
g_enemyspawn_player = NO_OWNER; // Sentinel value: Script player join scheduled but not joined yet
CreateScriptPlayer("$PlayerAttackers$", nil, 0, CSPF_NoEliminationCheck | CSPF_Invisible | CSPF_FixedAttributes | CSPF_NoScenarioInit | CSPF_NoScenarioSave, GetID());
}
}
}
private func IsEnemySpawnPlayerJoined()
{
return GetType(g_enemyspawn_player) && (g_enemyspawn_player != NO_OWNER);
}
public func InitializeScriptPlayer(int plr, int team)
{
// Init the enemy script player: Hostile to all players
if (g_enemyspawn_player == NO_OWNER)
{
// Handle hostility if not done through teams
for (var iplr = 0; iplr < GetPlayerCount(C4PT_User); ++iplr)
{
SetHostility(GetPlayerByIndex(iplr, C4PT_User), plr, true, true, true); // triple true hostility!
}
g_enemyspawn_player = plr;
}
// Perform delayed activation
if (waiting_for_script_player)
{
ActivateSpawn();
}
}
public func InitializePlayer(int plr, int x, int y, object base, int team, extra_id)
{
if (GetPlayerType(plr) == C4PT_User && IsEnemySpawnPlayerJoined())
{
// Make sure new enemy players are hostile
SetHostility(g_enemyspawn_player, plr, true, true, true);
}
}
/* Enemy spawning */
public func StartSpawn()
{
// Timed spawn or immediate spawn?
if (spawn_interval > 0 && spawn_count > 1)
{
// First enemy comes immediately. Then scheduled.
Spawn();
ScheduleCall(this, this.Spawn, spawn_interval, spawn_count - 1);
}
else
{
// Immediate spawn
// Can't do inifinite and immediate
if (spawn_count == SPAWNCOUNT_INFINITE)
{
SpawnFinished();
FatalError("EnemySpawn: Spawning of infinite enmies without timer delay disabled, because it would destroy the universe in a big boom (Hawking, Stephen W. \"Black hole explosions.\" Nature 248.5443 (1974): 30-31.)");
}
for (var i = 0; i < spawn_count; ++i)
{
Spawn();
}
}
}
private func SpawnFinished()
{
// All enemies have been spawned. Mark inactive.
active = waiting_for_script_player = false;
SetClrModulation();
spawned_count = 0;
// Count finished waves
if (!GetLength(spawned_enemies)) WaveDefeated();
}
private func GetAttackPath()
{
// Get attack path in global coordinates
if (!attack_path) return [ { X = GetX(), Y = GetY() } ];
var global_attack_path = CreateArray(GetLength(attack_path)), i;
for (var pt in attack_path)
{
global_attack_path[i++] = { X = GetX() + pt.X, Y = GetY() + pt.Y };
}
return global_attack_path;
}
private func Spawn()
{
// Find a spawn location
var spawn_pos = GetSpawnPosition();
// Spawn the actual enemy
if (enemy)
{
var enemy_def = EnemyDefs[enemy.Type];
var spawn_function = enemy_def.SpawnFunction;
var enemies = (enemy_def.SpawnCallTarget ?? GetID())->Call(spawn_function, spawn_pos, enemy, enemy_def, GetAttackPath(), this);
// Keep track of enemies. Could be returned as a single enemy or as an array
if (GetType(enemies) == C4V_Array)
{
for (var obj in enemies)
{
TrackSpawnedEnemy(obj);
}
}
else
{
TrackSpawnedEnemy(enemies);
}
}
// Keep track of count
if (++spawned_count == spawn_count)
{
SpawnFinished();
}
}
public func TrackSpawnedEnemy(object enemy)
{
// Remember any spawned enemies
if (enemy)
{
spawned_enemies[GetLength(spawned_enemies)] = enemy;
}
}
private func WaveDefeated()
{
// Just keep track of the count
++num_waves_defeated;
}
public func CancelSpawn()
{
// Stop any spawning timers
ClearScheduleCall(this, this.StartSpawn);
ClearScheduleCall(this, this.Spawn);
// Mark inactive
SpawnFinished();
}
public func RemoveSpawnedEnemies()
{
// Remove all spawned enemies
for (var enemy in spawned_enemies)
{
if (enemy)
{
enemy->RemoveObject();
}
}
}
public func ActivateSpawn()
{
// Already triggered or still running?
if (active) return;
// Needs to wait for script player join?
if (!IsEnemySpawnPlayerJoined())
{
waiting_for_script_player = true;
return;
}
// Activate
waiting_for_script_player = false;
active = true;
SetClrModulation(0xffff2020);
if (spawn_delay)
{
ScheduleCall(this, this.StartSpawn, spawn_delay, 1);
}
else
{
StartSpawn();
}
}
public func GetSpawnPosition()
{
// Evaluate spawn position setting
if (spawn_position)
{
var spawn_position_mode = spawn_position.Mode;
if (spawn_position_mode == "range")
{
// Random position in a circle around this position
var r = Sqrt(Random(spawn_position.Radius * spawn_position.Radius));
var ang = Random(360);
return [GetX() + Sin(ang, r), GetY() + Cos(ang, r)];
}
else if (spawn_position_mode == "rectangle")
{
var rect = spawn_position.Area;
return [GetX() + rect[0] + Random(rect[2]), GetY() + rect[1] + Random(rect[3])];
}
}
// Default: Spawn here
return [GetX(), GetY()];
}
public func InitializePlayers()
{
// Activate auto-triggered spawns
if (auto_activate) ActivateSpawn();
}
/* Clonk spawning */
public func SpawnClonk(array pos, proplist clonk_data, proplist enemy_def, array clonk_attack_path, object spawner)
{
// Spawn it
var clonk = CreateObject(Clonk, pos[0], pos[1], g_enemyspawn_player);
if (!clonk) return [];
clonk->SetController(g_enemyspawn_player);
clonk->MakeCrewMember(g_enemyspawn_player);
// Enemy visuals
if (clonk_data.Skin)
{
clonk->SetSkin(clonk_data.Skin);
}
if (!clonk_data.Backpack)
{
clonk->~RemoveBackpack();
}
if (clonk_data.ScaleX != 100 || clonk_data.ScaleY != 100)
{
var scale_z = (clonk_data.ScaleX + clonk_data.ScaleY) / 2;
clonk->SetMeshTransformation(Trans_Scale(clonk_data.ScaleX, clonk_data.ScaleY, scale_z), 6);
}
if (clonk_data.Name && GetLength(clonk_data.Name))
{
clonk->SetName(clonk_data.Name);
}
clonk->SetColor(clonk_data.Color);
// Physical properties
clonk.MaxEnergy = clonk_data.Energy * 1000;
clonk->DoEnergy(10000);
if (clonk_data.Speed != 100)
{
// Speed: Modify Speed in all ActMap entries
if (clonk.ActMap == clonk.Prototype.ActMap) clonk.ActMap = new clonk.ActMap {};
for (var action in GetProperties(Clonk.ActMap))
{
if (action == "Prototype") continue;
if (clonk.ActMap[action] == clonk.Prototype.ActMap[action]) clonk.ActMap[action] = new clonk.ActMap[action] {};
clonk.ActMap[action].Speed = clonk.ActMap[action].Speed * clonk_data.Speed / 100;
}
clonk.JumpSpeed = clonk.JumpSpeed * clonk_data.Speed / 100;
clonk.FlySpeed = clonk.FlySpeed * clonk_data.Speed / 100;
}
clonk.MaxContentsCount = 1;
// Reward for killing enemy
clonk.Bounty = clonk_data.Bounty;
// AI
AI->AddAI(clonk);
AI->SetMaxAggroDistance(clonk, Max(LandscapeWidth(), LandscapeHeight()));
var last_pos = clonk_attack_path[-1];
AI->SetHome(clonk, last_pos.X, last_pos.Y, Random(2));
var guard_range = clonk_data.GuardRange ?? { x=last_pos.X-300, y=last_pos.Y-150, wdt=600, hgt=300 };
AI->SetGuardRange(clonk, guard_range.x,guard_range.y,guard_range.wdt,guard_range.hgt);
if (clonk_data.AttackMode)
{
AI->SetAttackMode(clonk, clonk_data.AttackMode.Identifier);
}
// Return clonk to be added to spawned enemy list
return clonk;
}
/* Display */
private func UpdateEnemyDisplay()
{
// Show enemy type as text above this object
var msg;
if (enemy)
{
var enemy_def = EnemyDefs[enemy.Type];
if (enemy_def.GetInfoString)
{
// Custom dynamic info string
msg = enemy_def->GetInfoString(enemy);
}
else if (enemy_def.InfoString)
{
// Custom fixed info string
msg = enemy_def.InfoString;
}
else if (enemy_def.SpawnType)
{
// No info string. Fall back to object ID.
msg = Format("{{%i}}", enemy_def.SpawnType);
}
else
{
// Can't happen.
msg = enemy_def.Name ?? "??";
}
}
if (msg) Message("@%s", msg); else Message("");
}
/* Editor props and actions */
public func Definition(def)
{
// EditorActions
if (!def.EditorActions) def.EditorActions = {};
def.EditorActions.Activate = { Name="$Activate$", EditorHelp = "$ActivateHelp$", Command="ActivateSpawn()" };
def.EditorActions.Stop = { Name="$Stop$", EditorHelp = "$StopHelp$", Command="CancelSpawn()" };
def.EditorActions.RemoveSpawnedEnemies = { Name="$RemoveSpawnedEnemies$", EditorHelp = "$RemoveSpawnedEnemiesHelp$", Command="RemoveSpawnedEnemies()" };
// UserActions
UserAction->AddEvaluator("Action", "$Name$", "$ActActivate$", "$ActivateHelp$", "enemy_spawn_set_active", [def, def.EvalAct_Activate], { Target = { Function="action_object" } }, { Type="proplist", EditorProps = {
Target = UserAction->GetObjectEvaluator("IsEnemySpawn", "$Name$")
} } );
UserAction->AddEvaluator("Action", "$Name$", "$ActStop$", "$StopHelp$", "enemy_spawn_stop", [def, def.EvalAct_Stop], { Target = { Function="action_object" } }, { Type="proplist", EditorProps = {
Target = UserAction->GetObjectEvaluator("IsEnemySpawn", "$Name$")
} } );
// EditorProps
if (!def.EditorProps) def.EditorProps = {};
def.EditorProps.spawn_position = { Name="$SpawnPosition$", Type="enum", OptionKey="Mode", Set="SetSpawnPosition", Save="SpawnPosition", Options = [
{ Name="$Here$" },
{ Name="$InRange$", EditorHelp="$SpawnInRangeHelp$", Value={ Mode="range", Radius=25 }, ValueKey="Radius", Delegate={ Type="circle", Color=0xff8000, Relative=true } },
{ Name="$InRect$", EditorHelp="$SpawnInRectHelp$", Value={ Mode="rectangle", Rect=[-20, -20, 40, 40] }, ValueKey="Rect", Delegate={ Type="rect", Color=0xff8000, Relative=true } }
] };
def.EditorProps.spawn_count = { Name="$SpawnCount$", EditorHelp="$SpawnCountHelp$", Type="enum", Set="SetSpawnCount", Save="SpawnCount", Options = [
{ Name="$Infinite$", Value=SPAWNCOUNT_INFINITE },
{ Name="$FixedNumber$", Value=1, Type=C4V_Int, Delegate={ Type="int", Min=1, Set="SetSpawnCount", SetRoot=true } }
] };
def.EditorProps.spawn_delay = { Name="$SpawnDelay$", EditorHelp="$SpawnDelayHelp$", Type="int", Min=0, Set="SetSpawnDelay", Save="SpawnDelay" };
def.EditorProps.spawn_interval = { Name="$SpawnInterval$", EditorHelp="$SpawnIntervalHelp$", Type="int", Min=0, Set="SetSpawnInterval", Save="SpawnInterval" };
//def.EditorProps.attack_path
def.EditorProps.auto_activate = { Name="$AutoActivate$", EditorHelp="$AutoActivateHelp$", Type="bool", Set="SetAutoActivate", Save="AutoActivate" };
AddEnemyDef("Clonk", { SpawnType=Clonk, SpawnFunction=def.SpawnClonk }, def->GetAIClonkDefaultPropValues(), def->GetAIClonkEditorProps() );
}
public func GetAIClonkEditorProps()
{
// Return an editor props delegate for AI clonks
if (!this.AIClonkEditorProps)
{
var props = {};
props.AttackMode = new AI.FxAI.EditorProps.attack_mode { Set=nil, Priority=100 };
props.GuardRange = { Name="$AttackRange$", EditorHelp="$AttackRangeHelp$", Type="enum", Options = [
{ Name="$Automatic$", EditorHelp="$AutomaticGuardRangeHelp$"},
{ Name="$Custom$", Type=C4V_PropList, Value={}, DefaultValueFunction=this.GetDefaultAIRect, Delegate=AI.FxAI.EditorProps.guard_range }
] };
props.Color = { Name="$Color$", Type="color" };
props.Bounty = { Name="$Bounty$", EditorHelp="$BountyHelp$", Type="int", Min=0, Max=100000 };
props.ScaleX = { Name="$ScaleX$", EditorHelp="$ScaleXHelp$", Type="int", Min=50, Max=1000 };
props.ScaleY = { Name="$ScaleY$", EditorHelp="$ScaleYHelp$", Type="int", Min=50, Max=1000 };
props.Speed = { Name="$Speed$", EditorHelp="$SpeedHelp$", Type="int", Min=5 };
props.Energy = { Name="$Energy$", EditorHelp="$EnergyHelp$", Type="int", Min=1, Max=100000 };
props.Backpack = { Name="$Backpack$", EditorHelp="$BackpackHelp$", Type="bool" };
this.AIClonkEditorProps = { Type="proplist", Name=Clonk->GetName(), EditorProps=props };
}
return this.AIClonkEditorProps;
}
public func GetAIClonkDefaultPropValues()
{
// Default settings for AI enemy clonks
return {
AttackMode = { Identifier="Sword" },
Color = 0xff0000,
Bounty = 0,
ScaleX = 100,
ScaleY = 100,
Speed = 100,
Energy = 50,
Backpack = true,
};
}
private func SetAIClonkAttackMode(proplist attack_mode)
{
this.AttackMode.Identifier = attack_mode.Identifier;
}
private func GetDefaultAIRect(object target_object, proplist props)
{
// Default attack rectangle around spawner
var r = {};
if (target_object)
{
r.x = target_object->GetX()-300;
r.y = target_object->GetY()-150;
r.wdt = 600;
r.hgt = 300;
}
return r;
}
public func AddEnemyDef(identifier, enemy_def, default_value, parameter_delegate)
{
// First-time setup of enemy selection in editor
if (!this.EditorProps) this.EditorProps = {};
if (!this.EditorProps.enemy) this.EditorProps.enemy = { Name="$Enemy$", EditorHelp="$EnemyHelp$", Type="enum", OptionKey="Type", Set="SetEnemy", Save="Enemy", Sorted=true, Options = [ { Name="$None$", Priority=100 } ] };
if (!this.EnemyDefs) this.EnemyDefs = {};
// Remember definition
this.EnemyDefs[identifier] = enemy_def;
// Add editor selection
if (!default_value) default_value = {};
default_value.Type = identifier;
var enemy_opt = {
Name=enemy_def.Name ?? enemy_def.SpawnType->GetName(),
EditorHelp=enemy_def.EditorHelp,
Delegate=parameter_delegate,
Value=default_value
};
var opts = this.EditorProps.enemy.Options;
opts[GetLength(opts)] = enemy_opt;
}
private func EvalAct_Activate(proplist props, proplist context)
{
// User action: Activate spawner.
var spawner = UserAction->EvaluateValue("Object", props.Target, context);
if (!spawner || !spawner->IsEnemySpawn())
{
return;
}
spawner->ActivateSpawn();
}
private func EvalAct_Stop(proplist props, proplist context)
{
// User action: Cancel spawner activation.
var spawner = UserAction->EvaluateValue("Object", props.Target, context);
if (!spawner || !spawner->IsEnemySpawn())
{
return;
}
spawner->CancelSpawn();
}

View File

@ -0,0 +1,48 @@
Name=Gegnerspawn
Description=Erzeugt, wenn aktiviert, einen order mehrere KI-gesteuerte Gegner, die die Spieler angreifen.
Activate=Aktivieren
ActivateHelp=Startet das Erzeugen von Gegnern.
Stop=Deaktivieren
StopHelp=Stoppt das Erzeugen von Gegnern.
RemoveSpawnedEnemies=Gegner entfernen
RemoveSpawnedEnemiesHelp=Entfernt alle Gegner, die von diesem Spawner erzeugt wurden.
ActActivate=Gegnerspawn aktivieren
ActStop=Gegnerspawn deaktivieren
SpawnPosition=Position
Here=Hier
InRange=Im Radius
SpawnInRangeHelp=Zufaellige Position innerhalb eines Radius.
InRect=Im Rechteck
SpawnInRectHelp=Zufaellige Position innerhalb eines Rechtecks
SpawnCount=Anzahl
SpawnCountHelp=Zahl erzeugter Gegner.
Infinite=Unendlich
FixedNumber=Feste Anzahl
SpawnDelay=Verzoegerung
SpawnDelayHelp=Zeit in Frames zwischen dem Aktiviern dieses Spawns und dem Erzeugen des ersten Gegners.
SpawnInterval=Intervall
SpawnIntervalHelp=Zeit in Frames zwischen dem Erzeugen jedes einzelnen Gegners.
AutoActivate=Automatisch aktiviert
AutoActivateHelp=Wenn wahr, wird der Spawn beim ersten Spielerbeitritt automatisch aktiviert.
AttackRange=Angriffsbereich
AttackRangeHelp=In welchem Bereich die erzeugten Clonks nach Gegnern suchen.
Automatic=Automatisch
AutomaticGuardRangeHelp=Gegner suchen automatisch in Sichtweite ihres Zielbereiches.
Custom=Benutzerdefiniert
Color=Farbe
Bounty=Kopfgeld
BountyHelp=Clunker, die der Spieler pro getoeteten Gegner dieser Welle bekommt.
Enemy=Gegnertyp
EnemyHelp=Objekttyp des Angreifers.
None=Keiner
PlayerAttackers=Angreifer
ScaleX=Skalierung X
ScaleXHelp=Horizontale Groesse der Clonks in Prozent der Standardgroesse.
ScaleY=Skalierung Y
ScaleYHelp=Vertikale Groesse der Clonks in Prozent der Standardgroesse.
Backpack=Rucksack
BackpackHelp=Ob der Clonk einen Rucksack traegt.
Speed=Geschwindigkeit
SpeedHelp=Geschwindigkeit der Clonks in Prozent der Standardgeschwindigkeit
Energy=Energie
EnergyHelp=Lebenspunkte

View File

@ -0,0 +1,48 @@
Name=Enemy Spawn
Description=When activated, spawns one or more AI-controlled enemies that attack the players.
Activate=Activate
ActivateHelp=Starts spawning of enemies.
Stop=Deactivate
StopHelp=Stops spawning of enemies.
RemoveSpawnedEnemies=Remove enemies
RemoveSpawnedEnemiesHelp=Removes all enemies created by this spawner.
ActActivate=Activate enemy spawn
ActStop=Stop enemy spawn
SpawnPosition=Position
Here=Here
InRange=In radius
SpawnInRangeHelp=Random position within a radius.
InRect=In rectangle
SpawnInRectHelp=Random position within a rectangle.
SpawnCount=Enemy count
SpawnCountHelp=Number of spawned enemies.
Infinite=Infinite
FixedNumber=Fixed number
SpawnDelay=Delay
SpawnDelayHelp=Time in frames between activation of the spawn and creation of the first enemy.
SpawnInterval=Interval
SpawnIntervalHelp=Time in frames between each enemy creation. 0 for immediate creation of all enemies.
AutoActivate=Auto-activate
AutoActivateHelp=If true, the spawner activates automatically on first player join.
AttackRange=Attack range
AttackRangeHelp=In which range created clonks should look for enemies.
Automatic=Automatic
AutomaticGuardRangeHelp=Enemies automatically search within view of their target location.
Custom=Custom
Color=Color
Bounty=Bounty
BountyHelp=Clunker awarded to the player for killing a spawned enemy of this spawner.
Enemy=Enemy type
EnemyHelp=Object type of the attacker.
None=None
PlayerAttackers=Attacker
ScaleX=Scale X
ScaleXHelp=Horizontal scaling of the spawned clonk in percent of the default size.
ScaleY=Scale Y
ScaleYHelp=Vertical scaling of the spawned clonk in percent of the default size.
Backpack=Backpack
BackpackHelp=Whether the clonk carries a backpack.
Speed=Speed
SpeedHelp=Movement speed of the clonk in percent of the default.
Energy=Energy
EnergyHelp=Life points of the spwned clonk.