forked from Mirrors/openclonk
Port from AI library project
It seems that the vehicle AI is broken.install-platforms
parent
8b149a5433
commit
f204b959e7
|
@ -0,0 +1,5 @@
|
|||
[DefCore]
|
||||
id=AI_AttackEnemy
|
||||
Version=8,0
|
||||
Category=C4D_StaticBack
|
||||
HideInCreator=true
|
|
@ -0,0 +1,162 @@
|
|||
/**
|
||||
Basic AI component for attacking enemies.
|
||||
|
||||
@author Sven2, Maikel
|
||||
*/
|
||||
|
||||
// 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).
|
||||
local GuardRangeX = 300; // Search targets this far away in either direction (searching in rectangle).
|
||||
local GuardRangeY = 150; // Search targets this far away in either direction (searching in rectangle).
|
||||
|
||||
/*-- Public interface --*/
|
||||
|
||||
|
||||
public func SetAllyAlertRange(object clonk, int new_range)
|
||||
{
|
||||
if (GetType(this) != C4V_Def)
|
||||
Log("WARNING: SetAllyAlertRange(%v, %d) not called from definition context but from %v", clonk, new_range, this);
|
||||
var fx_ai = this->GetAI(clonk);
|
||||
if (!fx_ai)
|
||||
return false;
|
||||
fx_ai.ally_alert_range = new_range;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Set callback function name to be called in game script when this AI is first encountered
|
||||
// Callback function first parameter is (this) AI clonk, second parameter is player clonk.
|
||||
// The callback should return true to be cleared and not called again. Otherwise, it will be called every time a new target is found.
|
||||
public func SetEncounterCB(object clonk, string cb_fn)
|
||||
{
|
||||
if (GetType(this) != C4V_Def)
|
||||
Log("WARNING: SetEncounterCB(%v, %s) not called from definition context but from %v", clonk, cb_fn, this);
|
||||
var fx_ai = this->GetAI(clonk);
|
||||
if (!fx_ai)
|
||||
return false;
|
||||
fx_ai.encounter_cb = cb_fn;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Enable/disable auto-searching of targets.
|
||||
public func SetAutoSearchTarget(object clonk, bool new_auto_search_target)
|
||||
{
|
||||
if (GetType(this) != C4V_Def)
|
||||
Log("WARNING: SetAutoSearchTarget(%v, %v) not called from definition context but from %v", clonk, new_auto_search_target, this);
|
||||
var fx_ai = this->GetAI(clonk);
|
||||
if (!fx_ai)
|
||||
return false;
|
||||
fx_ai.auto_search_target = new_auto_search_target;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Set the guard range to the provided rectangle.
|
||||
public func SetGuardRange(object clonk, int x, int y, int wdt, int hgt)
|
||||
{
|
||||
if (GetType(this) != C4V_Def)
|
||||
Log("WARNING: SetGuardRange(%v, %d, %d, %d, %d) not called from definition context but from %v", clonk, x, y, wdt, hgt, this);
|
||||
var fx_ai = this->GetAI(clonk);
|
||||
if (!fx_ai)
|
||||
return false;
|
||||
// Clip to landscape size.
|
||||
if (x < 0)
|
||||
{
|
||||
wdt += x;
|
||||
x = 0;
|
||||
}
|
||||
if (y < 0)
|
||||
{
|
||||
hgt += y;
|
||||
y = 0;
|
||||
}
|
||||
wdt = Min(wdt, LandscapeWidth() - x);
|
||||
hgt = Min(hgt, LandscapeHeight() - y);
|
||||
fx_ai.guard_range = {x = x, y = y, wdt = wdt, hgt = hgt};
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
// Set the maximum distance the enemy will follow an attacking clonk.
|
||||
public func SetMaxAggroDistance(object clonk, int max_dist)
|
||||
{
|
||||
if (GetType(this) != C4V_Def)
|
||||
Log("WARNING: SetMaxAggroDistance(%v, %d) not called from definition context but from %v", clonk, max_dist, this);
|
||||
var fx_ai = this->GetAI(clonk);
|
||||
if (!fx_ai)
|
||||
return false;
|
||||
fx_ai.max_aggro_distance = max_dist;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*-- Callbacks --*/
|
||||
|
||||
// Callback from the effect Construction()-call
|
||||
public func OnAddAI(proplist fx_ai)
|
||||
{
|
||||
_inherited(fx_ai);
|
||||
|
||||
// Add AI default settings.
|
||||
SetGuardRange(fx_ai.Target, fx_ai.home_x - fx_ai.GuardRangeX, fx_ai.home_y - fx_ai.GuardRangeY, fx_ai.GuardRangeX * 2, fx_ai.GuardRangeY * 2);
|
||||
SetMaxAggroDistance(fx_ai.Target, fx_ai.MaxAggroDistance);
|
||||
SetAutoSearchTarget(fx_ai.Target, true);
|
||||
|
||||
// Store whether the enemy is controlled by a commander.
|
||||
fx_ai.commander = fx_ai.Target.commander;
|
||||
}
|
||||
|
||||
// Callback from the Definition()-call
|
||||
public func OnDefineAI(proplist def)
|
||||
{
|
||||
_inherited(def);
|
||||
|
||||
// Set the additional editor properties
|
||||
var additional_props =
|
||||
{
|
||||
ignore_allies = { Name = "$IgnoreAllies$", Type = "bool" },
|
||||
guard_range = { Name = "$GuardRange$", Type = "rect", Storage = "proplist", Color = 0xff00, Relative = false },
|
||||
max_aggro_distance = { Name = "$MaxAggroDistance$", Type = "circle", Color = 0x808080 },
|
||||
auto_search_target = { Name = "$AutoSearchTarget$", EditorHelp = "$AutoSearchTargetHelp$", Type = "bool" },
|
||||
};
|
||||
|
||||
AddProperties(def.FxAI.EditorProps, additional_props);
|
||||
}
|
||||
|
||||
|
||||
// Callback from the effect SaveScen()-call
|
||||
public func OnSaveScenarioAI(proplist fx_ai, proplist props)
|
||||
{
|
||||
_inherited(fx_ai, props);
|
||||
|
||||
if (fx_ai.ally_alert_range)
|
||||
props->AddCall(SAVESCEN_ID_AI, fx_ai.control, "SetAllyAlertRange", fx_ai.Target, fx_ai.ally_alert_range);
|
||||
props->AddCall(SAVESCEN_ID_AI, fx_ai.control, "SetGuardRange", fx_ai.Target, fx_ai.guard_range.x, fx_ai.guard_range.y, fx_ai.guard_range.wdt, fx_ai.guard_range.hgt);
|
||||
if (fx_ai.max_aggro_distance != fx_ai.control.MaxAggroDistance)
|
||||
props->AddCall(SAVESCEN_ID_AI, fx_ai.control, "SetMaxAggroDistance", fx_ai.Target, fx_ai.max_aggro_distance);
|
||||
if (!fx_ai.auto_search_target)
|
||||
props->AddCall(SAVESCEN_ID_AI, fx_ai.control, "SetAutoSearchTarget", fx_ai.Target, false);
|
||||
if (fx_ai.encounter_cb)
|
||||
props->AddCall(SAVESCEN_ID_AI, fx_ai.control, "SetEncounterCB", fx_ai.Target, Format("%v", fx_ai.encounter_cb));
|
||||
}
|
||||
|
||||
/*-- Editor Properties --*/
|
||||
|
||||
|
||||
// Gets an evaluator for the editor: Clonks
|
||||
public func UserAction_EnemyEvaluator()
|
||||
{
|
||||
var enemy_evaluator = UserAction->GetObjectEvaluator("IsClonk", "$Enemy$", "$EnemyHelp$");
|
||||
enemy_evaluator.Priority = 100;
|
||||
return enemy_evaluator;
|
||||
}
|
||||
|
||||
|
||||
// Gets an evaluator for the editor: Attack target
|
||||
public func UserAction_AttackTargetEvaluator()
|
||||
{
|
||||
return UserAction->GetObjectEvaluator("IsClonk", "$AttackTarget$", "$AttackTargetHelp$");
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
IgnoreAllies=Mitspieler ignorieren
|
||||
GuardRange=Bewachter Bereich
|
||||
MaxAggroDistance=Angriffsradius
|
||||
AutoSearchTarget=Ziel automatisch suchen
|
||||
AutoSearchTargetHelp=Wenn wahr, sucht die KI automatisch Gegner. Ansonsten muss der Gegner per Sequenzbefehl gegeben werden.
|
||||
AttackTarget=Angriffsziel
|
||||
AttackTargetHelp=Wenn angegeben, greift der KI-Clonk direkt diesen Gegner an.
|
||||
Enemy=Gegner
|
||||
EnemyHelp=Welcher Gegner angegriffen werden soll. Wenn nicht angegeben, greift der Gegner den nächsten Clonk an.
|
|
@ -0,0 +1,9 @@
|
|||
IgnoreAllies=Ignore allies
|
||||
GuardRange=Guarded area
|
||||
MaxAggroDistance=Attack radius
|
||||
AutoSearchTarget=Auto-search target
|
||||
AutoSearchTargetHelp=Looks for enemies automatically if true. If false, enemies must be provided via a script sequence.
|
||||
AttackTarget=Attack target
|
||||
AttackTargetHelp=If specified, this enemy is attacked by the AI clonk. If not specified, the nearest enemy will be attacked.
|
||||
Enemy=Enemy
|
||||
EnemyHelp=Which enemy to attack. If unspecified, attacks the nearest clonk.
|
|
@ -5,6 +5,53 @@
|
|||
@author Sven2
|
||||
*/
|
||||
|
||||
|
||||
/*-- Callbacks --*/
|
||||
|
||||
// Callback from the effect Construction()-call
|
||||
public func OnAddAI(proplist fx_ai)
|
||||
{
|
||||
_inherited(fx_ai);
|
||||
|
||||
// Add AI default settings.
|
||||
SetAttackMode(fx_ai.Target, "Default"); // also binds inventory
|
||||
}
|
||||
|
||||
|
||||
// Callback from the effect SaveScen()-call
|
||||
public func OnSaveScenarioAI(proplist fx_ai, proplist props)
|
||||
{
|
||||
_inherited(fx_ai, props);
|
||||
|
||||
if (fx_ai.attack_mode.Identifier != "Default")
|
||||
props->AddCall(SAVESCEN_ID_AI, fx_ai.control, "SetAttackMode", fx_ai.Target, Format("%v", fx_ai.attack_mode.Identifier));
|
||||
}
|
||||
|
||||
|
||||
/*-- Editor Properties --*/
|
||||
|
||||
// Callback from the Definition()-call
|
||||
public func OnDefineAI(proplist def)
|
||||
{
|
||||
_inherited(def);
|
||||
|
||||
def.FxAI.SetAttackMode = this.EditorDelegate_SetAttackMode;
|
||||
|
||||
// Set the other options
|
||||
def->DefinitionAttackModes(def);
|
||||
}
|
||||
|
||||
|
||||
public func EditorDelegate_SetAttackMode(proplist attack_mode)
|
||||
{
|
||||
// Called by editor delegate when attack mode is changed.
|
||||
// For now, attack mode parameter delegates are not supported. Just set by name.
|
||||
return this.control->SetAttackMode(this.Target, attack_mode.Identifier);
|
||||
}
|
||||
|
||||
|
||||
/*-- Internals --*/
|
||||
|
||||
// Set attack mode / spell to control attack behaviour
|
||||
public func SetAttackMode(object clonk, string attack_mode_identifier)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
[DefCore]
|
||||
id=AI_HelperClonk
|
||||
Version=8,0
|
||||
Category=C4D_StaticBack
|
||||
HideInCreator=true
|
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
AI helper functions: Clonk
|
||||
|
||||
Contains functions that are related to the clonk.
|
||||
|
||||
@author Sven2, Maikel
|
||||
*/
|
||||
|
||||
// Selects an item the clonk is about to use.
|
||||
public func SelectItem(effect fx, object item)
|
||||
{
|
||||
if (!item)
|
||||
return;
|
||||
if (item->Contained() != fx.Target)
|
||||
return;
|
||||
fx.Target->SetHandItemPos(0, fx.Target->GetItemPos(item));
|
||||
}
|
||||
|
||||
public func CancelAiming(effect fx)
|
||||
{
|
||||
if (fx.aim_weapon)
|
||||
{
|
||||
fx.aim_weapon->~ControlUseCancel(fx.Target);
|
||||
fx.aim_weapon = nil;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Also cancel aiming done outside AI control.
|
||||
fx.Target->~CancelAiming();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public func CheckHandsAction(effect fx)
|
||||
{
|
||||
// Can use hands?
|
||||
if (fx.Target->~HasHandAction())
|
||||
return true;
|
||||
// Can't throw: Is it because e.g. we're scaling?
|
||||
if (!fx.Target->HasActionProcedure())
|
||||
{
|
||||
this->ExecuteStand(fx);
|
||||
return false;
|
||||
}
|
||||
// Probably hands busy. Just wait.
|
||||
return false;
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
[DefCore]
|
||||
id=AI_HomePosition
|
||||
Version=8,0
|
||||
Category=C4D_StaticBack
|
||||
HideInCreator=true
|
|
@ -0,0 +1,118 @@
|
|||
/**
|
||||
Set the home position the Clonk returns to if he has no target.
|
||||
|
||||
@author Sven2, Maikel
|
||||
*/
|
||||
|
||||
/*-- Public interface --*/
|
||||
|
||||
// Set the home position the Clonk returns to if he has no target.
|
||||
public func SetHome(object clonk, int x, int y, int dir)
|
||||
{
|
||||
if (GetType(this) != C4V_Def)
|
||||
Log("WARNING: SetHome(%v, %d, %d, %d) not called from definition context but from %v", clonk, x, y, dir, this);
|
||||
var fx_ai = this->GetAI(clonk);
|
||||
if (!fx_ai)
|
||||
return false;
|
||||
// nil/nil defaults to current position.
|
||||
if (!GetType(x))
|
||||
x = clonk->GetX();
|
||||
if (!GetType(y))
|
||||
y = clonk->GetY();
|
||||
if (!GetType(dir))
|
||||
dir = clonk->GetDir();
|
||||
fx_ai.home_x = x;
|
||||
fx_ai.home_y = y;
|
||||
fx_ai.home_dir = dir;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*-- Callbacks --*/
|
||||
|
||||
// Callback from the effect Construction()-call
|
||||
public func OnAddAI(proplist fx_ai)
|
||||
{
|
||||
_inherited(fx_ai);
|
||||
|
||||
// Add AI default settings.
|
||||
SetHome(fx_ai.Target);
|
||||
}
|
||||
|
||||
|
||||
// Callback from the effect SaveScen()-call
|
||||
public func OnSaveScenarioAI(proplist fx_ai, proplist props)
|
||||
{
|
||||
_inherited(fx_ai, props);
|
||||
|
||||
if (fx_ai.home_x != fx_ai.Target->GetX() || fx_ai.home_y != fx_ai.Target->GetY() || fx_ai.home_dir != fx_ai.Target->GetDir())
|
||||
props->AddCall(SAVESCEN_ID_AI, fx_ai.control, "SetHome", fx_ai.Target, fx_ai.home_x, fx_ai.home_y, GetConstantNameByValueSafe(fx_ai.home_dir, "DIR_"));
|
||||
}
|
||||
|
||||
|
||||
/*-- Editor Properties --*/
|
||||
|
||||
// Callback from the Definition()-call
|
||||
public func OnDefineAI(proplist def)
|
||||
{
|
||||
_inherited(def);
|
||||
|
||||
// Add AI user actions.
|
||||
|
||||
UserAction->AddEvaluator("Action", "Clonk", "$SetAINewHome$", "$SetAINewHomeHelp$", "ai_set_new_home", [def, def.EvalAct_SetNewHome],
|
||||
{
|
||||
Enemy = nil,
|
||||
HomePosition = nil,
|
||||
Status = {
|
||||
Function = "bool_constant",
|
||||
Value = true
|
||||
}
|
||||
},
|
||||
{
|
||||
Type = "proplist",
|
||||
Display = "{{Enemy}} -> {{NewHome}}",
|
||||
EditorProps = {
|
||||
Enemy = this->~UserAction_EnemyEvaluator(),
|
||||
NewHome = new UserAction.Evaluator.Position {
|
||||
Name = "$NewHome$",
|
||||
EditorHelp = "$NewHomeHelp$"
|
||||
},
|
||||
NewHomeDir = {
|
||||
Type = "enum",
|
||||
Name = "$NewHomeDir$",
|
||||
EditorHelp = "$NewHomeDirHelp$",
|
||||
Options = [
|
||||
{ Name = "$Unchanged$" },
|
||||
{ Name = "$Left$", Value = DIR_Left },
|
||||
{ Name = "$Right$", Value = DIR_Right }
|
||||
]
|
||||
},
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public func EvalAct_SetNewHome(proplist props, proplist context)
|
||||
{
|
||||
// User action: Set new home.
|
||||
var enemy = UserAction->EvaluateValue("Object", props.Enemy, context);
|
||||
var new_home = UserAction->EvaluatePosition(props.NewHome, context);
|
||||
var new_home_dir = props.NewHomeDir;
|
||||
if (!enemy)
|
||||
return;
|
||||
// Ensure enemy AI exists.
|
||||
var fx = this->GetAI(enemy); // TODO: Streamline this
|
||||
if (!fx)
|
||||
{
|
||||
fx = this->AddAI(enemy, this);
|
||||
if (!fx || !enemy)
|
||||
return;
|
||||
// Create without attack command.
|
||||
this->~SetAutoSearchTarget(enemy, false);
|
||||
}
|
||||
fx.command = this.ExecuteIdle;
|
||||
fx.home_x = new_home[0];
|
||||
fx.home_y = new_home[1];
|
||||
if (GetType(new_home_dir))
|
||||
fx.home_dir = new_home_dir;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
SetAINewHome=KI Position setzen
|
||||
SetAINewHomeHelp=Lässt einen KI-Gegner an eine neue Position laufen.
|
||||
NewHome=Neue Position
|
||||
NewHomeHelp=Wohin der Clonk laufen soll.
|
||||
NewHomeDir=Richtung
|
||||
NewHomeDirHelp=In welche Richtung der KI-Clonk schauen soll, wenn kein Gegner in der Nähe ist.
|
||||
Unchanged=Ungeändert
|
||||
Left=Links
|
||||
Right=Rechts
|
|
@ -0,0 +1,9 @@
|
|||
SetAINewHome=AI set position
|
||||
SetAINewHomeHelp=Lets an AI clonk walk to another position.
|
||||
NewHome=New position
|
||||
NewHomeHelp=Where to send the AI clonk.
|
||||
NewHomeDir=Direction
|
||||
NewHomeDirHelp=In which direction the AI clonk will look if no enemy is nearby.
|
||||
Unchanged=Unchanged
|
||||
Left=Left
|
||||
Right=Right
|
|
@ -0,0 +1,5 @@
|
|||
[DefCore]
|
||||
id=AI_Inventory
|
||||
Version=8,0
|
||||
Category=C4D_StaticBack
|
||||
HideInCreator=true
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
AI helper functions: Inventory
|
||||
|
||||
Contains functions that are related AI inventory.
|
||||
|
||||
@author Sven2, Maikel
|
||||
*/
|
||||
|
||||
/*-- Public interface --*/
|
||||
|
||||
// Set the current inventory to be removed when the clonk dies. Only works if clonk has an AI.
|
||||
public func BindInventory(object clonk)
|
||||
{
|
||||
if (GetType(this) != C4V_Def)
|
||||
Log("WARNING: BindInventory(%v) not called from definition context but from %v", clonk, this);
|
||||
var fx_ai = this->GetAI(clonk);
|
||||
if (!fx_ai)
|
||||
return false;
|
||||
var cnt = clonk->ContentsCount();
|
||||
fx_ai.bound_weapons = CreateArray(cnt);
|
||||
for (var i = 0; i < cnt; ++i)
|
||||
fx_ai.bound_weapons[i] = clonk->Contents(i);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*-- Callbacks --*/
|
||||
|
||||
// Callback from the effect Destruction()-call
|
||||
public func OnRemoveAI(proplist fx_ai, int reason)
|
||||
{
|
||||
_inherited(fx_ai, reason);
|
||||
|
||||
// Remove weapons on death.
|
||||
if (reason == FX_Call_RemoveDeath)
|
||||
{
|
||||
if (fx_ai.bound_weapons)
|
||||
for (var obj in fx_ai.bound_weapons)
|
||||
if (obj && obj->Contained() == fx_ai.Target)
|
||||
obj->RemoveObject();
|
||||
}
|
||||
}
|
|
@ -6,6 +6,68 @@
|
|||
*/
|
||||
|
||||
|
||||
/*-- Public interface --*/
|
||||
|
||||
// Set attack path
|
||||
public func SetAttackPath(object clonk, array new_attack_path)
|
||||
{
|
||||
if (GetType(this) != C4V_Def)
|
||||
Log("WARNING: SetAttackPath(%v, %v) not called from definition context but from %v", clonk, new_attack_path, this);
|
||||
var fx_ai = this->GetAI(clonk);
|
||||
if (!fx_ai)
|
||||
return false;
|
||||
fx_ai.attack_path = new_attack_path;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*-- Callbacks --*/
|
||||
|
||||
// Callback from the effect SaveScen()-call
|
||||
public func OnSaveScenarioAI(proplist fx_ai, proplist props)
|
||||
{
|
||||
_inherited(fx_ai, props);
|
||||
|
||||
if (fx_ai.attack_path)
|
||||
props->AddCall(SAVESCEN_ID_AI, fx_ai.control, "SetAttackPath", fx_ai.Target, fx_ai.attack_path);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/*-- Editor Properties --*/
|
||||
|
||||
// Callback from the Definition()-call
|
||||
public func OnDefineAI(proplist def)
|
||||
{
|
||||
_inherited(def);
|
||||
|
||||
def.FxAI.SetAttackPath = this.EditorDelegate_SetAttackPath;
|
||||
|
||||
// Set the additional editor properties
|
||||
var additional_props =
|
||||
{
|
||||
attack_path = { Name = "$AttackPath$", EditorHelp = "$AttackPathHelp$", Type = "enum", Set = "SetAttackPath", Options = [
|
||||
{ Name="$None$" },
|
||||
{ Name="$AttackPath$", Type=C4V_Array, Value = [{X = 0, Y = 0}], Delegate =
|
||||
{ Name="$AttackPath$", EditorHelp="$AttackPathHelp$", Type="polyline", StartFromObject=true, DrawArrows=true, Color=0xdf0000, Relative=false }
|
||||
}
|
||||
] },
|
||||
};
|
||||
|
||||
AddProperties(def.FxAI.EditorProps, additional_props);
|
||||
}
|
||||
|
||||
|
||||
func EditorDelegate_SetAttackPath(array attack_path)
|
||||
{
|
||||
// Called by editor delegate when attack mode is changed.
|
||||
// For now, attack mode parameter delegates are not supported. Just set by name.
|
||||
return this.control->SetAttackPath(this.Target, attack_path);
|
||||
}
|
||||
|
||||
|
||||
/*-- Internals --*/
|
||||
|
||||
// Tries to make sure the clonk stands: i.e. scales down or let's go when hangling.
|
||||
public func ExecuteStand(effect fx)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
AttackPath=Angriffspfad
|
||||
AttackPathHelp=Pfad, entlang dessen sich der KI-Gegner berwegt. Befindet sich ein Gebaeude auf einem Eckpunkt des Pfades, greift der Clonk das Gebaeude an.
|
||||
None=Keiner
|
|
@ -0,0 +1,3 @@
|
|||
AttackPath=Attack path
|
||||
AttackPathHelp=Path along which the AI clonk moves. If a vertex of the attack path is placed on a structure (e.g. a gate), the clonk will attack the structure.
|
||||
None=None
|
|
@ -12,6 +12,35 @@ local AirshipBoardDistance = 100; // How near must an airship be to the target t
|
|||
local AirshipLostDistance = 50; // How far the pilot must be away from an airship for it to find a new pilot.
|
||||
local AirshipOccludedTargetMaxDistance = 250; // IF a target is further than this and occluded, search for a new target
|
||||
|
||||
|
||||
/*-- Public interface --*/
|
||||
|
||||
// Set controlled vehicle
|
||||
public func SetVehicle(object clonk, object new_vehicle)
|
||||
{
|
||||
if (GetType(this) != C4V_Def)
|
||||
Log("WARNING: SetVehicle(%v, %v) not called from definition context but from %v", clonk, new_vehicle, this);
|
||||
var fx_ai = this->GetAI(clonk);
|
||||
if (!fx_ai)
|
||||
return false;
|
||||
fx_ai.vehicle = new_vehicle;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*-- Callbacks --*/
|
||||
|
||||
// Callback from the effect Construction()-call
|
||||
public func OnAddAI(proplist fx_ai)
|
||||
{
|
||||
_inherited(fx_ai);
|
||||
|
||||
// Store the vehicle the AI is using.
|
||||
if (fx_ai.Target->GetProcedure() == "PUSH")
|
||||
fx_ai.vehicle = fx_ai.Target->GetActionTarget();
|
||||
}
|
||||
|
||||
|
||||
/*-- General Vehicle --*/
|
||||
|
||||
private func ExecuteVehicle(effect fx)
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
[DefCore]
|
||||
id=AI_Controller
|
||||
Version=8,0
|
||||
Category=C4D_None | C4D_MouseIgnore
|
||||
HideInCreator=true
|
|
@ -0,0 +1,404 @@
|
|||
/**
|
||||
AI
|
||||
Controls bots.
|
||||
|
||||
@author Marky
|
||||
@credits Original AI structure/design by Sven2, Maikel
|
||||
*/
|
||||
|
||||
static const SAVESCEN_ID_AI = "AI";
|
||||
|
||||
/*-- Engine callbacks --*/
|
||||
|
||||
public func Definition(proplist type)
|
||||
{
|
||||
this->OnDefineAI(type);
|
||||
}
|
||||
|
||||
/*-- Public interface --*/
|
||||
|
||||
// Change whether target Clonk has an AI (used by editor).
|
||||
public func SetAI(object clonk, id type)
|
||||
{
|
||||
if (type)
|
||||
{
|
||||
return type->AddAI(clonk, type); // call from the definition there
|
||||
}
|
||||
else
|
||||
{
|
||||
return RemoveAI(clonk);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Add AI execution timer to target Clonk.
|
||||
public func AddAI(object clonk, id type)
|
||||
{
|
||||
AssertDefinitionContext(Format("AddAI(%v, %v)", clonk, type));
|
||||
AssertNotNil(clonk);
|
||||
|
||||
var fx_ai = GetAI(clonk) ?? clonk->CreateEffect(FxAI, 1, 1, type ?? this);
|
||||
|
||||
return fx_ai;
|
||||
}
|
||||
|
||||
|
||||
// Remove the AI execution timer
|
||||
public func RemoveAI(object clonk)
|
||||
{
|
||||
AssertDefinitionContext(Format("RemoveAI(%v)", clonk));
|
||||
|
||||
var fx_ai = GetAI(clonk);
|
||||
if (fx_ai)
|
||||
{
|
||||
fx_ai->Remove();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func GetAI(object clonk)
|
||||
{
|
||||
AssertDefinitionContext(Format("GetAI(%v)", clonk));
|
||||
|
||||
if (clonk)
|
||||
{
|
||||
return clonk->~GetAI();
|
||||
}
|
||||
else
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public func GetControlEffect()
|
||||
{
|
||||
return this.FxAI;
|
||||
}
|
||||
|
||||
|
||||
// Set active state: Enables/Disables timer
|
||||
public func SetActive(object clonk, bool active)
|
||||
{
|
||||
AssertDefinitionContext(Format("SetActive(%v, %v)", clonk, active));
|
||||
|
||||
var fx_ai = GetAI(clonk);
|
||||
if (fx_ai)
|
||||
{
|
||||
if (!active)
|
||||
{
|
||||
// Inactive: Stop any activity.
|
||||
clonk->SetCommand("None");
|
||||
clonk->SetComDir(COMD_Stop);
|
||||
}
|
||||
return fx_ai->SetActive(active);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*-- AI Effect --*/
|
||||
|
||||
// The AI effect stores a lot of information about the AI clonk. This includes its state, enemy target, alertness etc.
|
||||
// Each of the functions which are called in the AI definition pass the effect and can then access these variables.
|
||||
// The most important variables are:
|
||||
// fx.Target - The AI clonk.
|
||||
// fx.target - The current target the AI clonk will attack.
|
||||
// fx.alert - Whether the AI clonk is alert and aware of enemies around it.
|
||||
// fx.weapon - Currently selected weapon by the AI clonk.
|
||||
// fx.ammo_check - Function that is called to check ammunition for fx.weapon.
|
||||
// fx.commander - Is commanded by another AI clonk.
|
||||
// fx.control - Definition controlling this AI, all alternative AIs should include the basic AI.
|
||||
|
||||
local FxAI = new Effect
|
||||
{
|
||||
Construction = func(id control_def)
|
||||
{
|
||||
// Execute AI every 3 frames.
|
||||
this.Interval = control_def->~GetTimerInterval() ?? 1;
|
||||
// Store the definition that controls this AI.
|
||||
this.control = control_def;
|
||||
// Give the AI a helper function to get the AI control effect.
|
||||
this.Target.ai = this;
|
||||
this.Target.GetAI = this.GetAI;
|
||||
// Callback to the controller
|
||||
this.control->~OnAddAI(this);
|
||||
return FX_OK;
|
||||
},
|
||||
|
||||
GetAI = func()
|
||||
{
|
||||
return this.ai;
|
||||
},
|
||||
|
||||
GetControl = func()
|
||||
{
|
||||
return this.control;
|
||||
},
|
||||
|
||||
Timer = func(int time)
|
||||
{
|
||||
// Execute the AI in the clonk.
|
||||
this.control->Execute(this, time);
|
||||
return FX_OK;
|
||||
},
|
||||
|
||||
Destruction = func(int reason)
|
||||
{
|
||||
// Callback to the controller
|
||||
this.control->~OnRemoveAI(this, reason);
|
||||
// Remove AI reference.
|
||||
if (Target && Target.ai == this)
|
||||
Target.ai = nil;
|
||||
return FX_OK;
|
||||
},
|
||||
|
||||
Damage = func(int dmg, int cause)
|
||||
{
|
||||
// AI takes damage: Make sure we're alert so evasion and healing scripts are executed!
|
||||
// It might also be feasible to execute encounter callbacks here (in case an AI is shot from a position it cannot see).
|
||||
// However, the attacking clonk is not known and the callback might be triggered e.g. by an unfortunate meteorite or lightning blast.
|
||||
// So let's just keep it at alert state for now.
|
||||
if (dmg < 0)
|
||||
this.alert = this.time;
|
||||
|
||||
this.control->~OnDamageAI(this, dmg, cause);
|
||||
|
||||
return dmg;
|
||||
},
|
||||
SetActive = func(bool active)
|
||||
{
|
||||
this.Interval = (this.control->~GetTimerInterval() ?? 1) * active;
|
||||
|
||||
if (active)
|
||||
{
|
||||
this.control->~OnActivateAI(this);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.control->~OnDeactivateAI(this);
|
||||
}
|
||||
},
|
||||
GetActive = func()
|
||||
{
|
||||
return this.Interval != 0;
|
||||
},
|
||||
EditorProps = {
|
||||
active = { Name = "$Active$", EditorHelp = "$ActiveHelp$", Type = "bool", Priority = 50, AsyncGet = "GetActive", Set = "SetActive" },
|
||||
},
|
||||
// Save this effect and the AI for scenarios.
|
||||
SaveScen = func(proplist props)
|
||||
{
|
||||
if (this.Target)
|
||||
{
|
||||
props->AddCall(SAVESCEN_ID_AI, this.control, "AddAI", this.Target);
|
||||
if (!this.Interval)
|
||||
{
|
||||
props->AddCall(SAVESCEN_ID_AI, this.control, "SetActive", this.Target, false);
|
||||
}
|
||||
this.control->~OnSaveScenarioAI(this, props);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*-- AI Execution --*/
|
||||
|
||||
public func Execute(effect fx, int time) // TODO: Adjust
|
||||
{
|
||||
return this->Call(fx.strategy, fx);
|
||||
}
|
||||
|
||||
|
||||
/*-- Editor Properties --*/
|
||||
|
||||
// Adds an AI to the selection
|
||||
public func AddEditorProp_AISelection(proplist type, id ai_type)
|
||||
{
|
||||
InitEditorProp_AISelection(type);
|
||||
PushBack(type.EditorProps.AI_Controller.Options, EditorProp_AIType(ai_type));
|
||||
}
|
||||
|
||||
|
||||
// Initializes the selection property
|
||||
private func InitEditorProp_AISelection(proplist type)
|
||||
{
|
||||
// ensure that the poperties exist
|
||||
if (!type.EditorProps)
|
||||
{
|
||||
type.EditorProps = {};
|
||||
}
|
||||
|
||||
// ensure that the list exists
|
||||
if (!type.EditorProps.AI_Controller)
|
||||
{
|
||||
type.EditorProps.AI_Controller =
|
||||
{
|
||||
Type = "enum",
|
||||
Name = "$ChooseAI$",
|
||||
Options = [],
|
||||
Set = Format("%i->SetAI", AI_Controller), // this is the AI_Controller on purpose
|
||||
SetGlobal = true
|
||||
};
|
||||
}
|
||||
|
||||
// add default options
|
||||
if (GetLength(type.EditorProps.AI_Controller.Options) == 0)
|
||||
{
|
||||
PushBack(type.EditorProps.AI_Controller.Options, EditorProp_AIType(nil));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Gets an AI type entry for the selection list
|
||||
private func EditorProp_AIType(id type)
|
||||
{
|
||||
if (type)
|
||||
{
|
||||
var option_ai = {
|
||||
Name = type.Name ?? type->GetName(),
|
||||
EditorHelp = type.EditorHelp,
|
||||
Value = type
|
||||
};
|
||||
|
||||
if (!option_ai.EditorHelp && type.GetEditorHelp) option_ai.EditorHelp = type->GetEditorHelp();
|
||||
|
||||
return option_ai;
|
||||
}
|
||||
else
|
||||
{
|
||||
return {
|
||||
Name = "$NoAI$",
|
||||
EditorHelp = "$NoAIEditorHelp$",
|
||||
Value = nil,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*-- Properties --*/
|
||||
|
||||
local Plane = 300;
|
||||
|
||||
|
||||
local DebugLoggingEnabled = false; // Whether or not debug logging is turned on.
|
||||
|
||||
|
||||
public func DebugLogAI(string message)
|
||||
{
|
||||
if (AI_Controller.DebugLoggingEnabled)
|
||||
DebugLog(message);
|
||||
}
|
||||
|
||||
|
||||
/*-- Callbacks --*/
|
||||
|
||||
// Callback from the effect Construction()-call
|
||||
public func OnAddAI(proplist fx_ai)
|
||||
{
|
||||
// called by the effect Construction()
|
||||
_inherited(fx_ai);
|
||||
}
|
||||
|
||||
|
||||
// Callback from the effect Destruction()-call
|
||||
public func OnRemoveAI(proplist fx_ai, int reason)
|
||||
{
|
||||
// called by the effect Destruction()
|
||||
_inherited(fx_ai, reason);
|
||||
}
|
||||
|
||||
|
||||
// Callback when the AI is activated by a trigger
|
||||
public func OnActivateAI(proplist fx_ai)
|
||||
{
|
||||
_inherited(fx_ai);
|
||||
}
|
||||
|
||||
// Callback when the AI is deactivated by a trigger
|
||||
public func OnDectivateAI(proplist fx_ai)
|
||||
{
|
||||
_inherited(fx_ai);
|
||||
}
|
||||
|
||||
|
||||
// Callback when the AI is damaged
|
||||
public func OnDamageAI(proplist fx_ai, int damage, int cause)
|
||||
{
|
||||
_inherited(fx_ai, damage, cause);
|
||||
}
|
||||
|
||||
|
||||
// Callback from the effect SaveScen()-call
|
||||
public func OnSaveScenarioAI(proplist fx_ai, proplist props)
|
||||
{
|
||||
// called by the effect SaveScen()
|
||||
_inherited(fx_ai, props);
|
||||
}
|
||||
|
||||
|
||||
// Callback from the Definition()-call
|
||||
public func OnDefineAI(proplist def)
|
||||
{
|
||||
_inherited(def);
|
||||
|
||||
// Add AI user actions.
|
||||
UserAction->AddEvaluator("Action", "Clonk", "$SetAIActivated$", "$SetAIActivatedHelp$", "ai_set_activated", [def, def.EvalAct_SetActive],
|
||||
{
|
||||
Enemy = nil,
|
||||
AttackTarget = {
|
||||
Function= "triggering_clonk"
|
||||
},
|
||||
Status = {
|
||||
Function = "bool_constant",
|
||||
Value = true
|
||||
}
|
||||
},
|
||||
{
|
||||
Type = "proplist",
|
||||
Display = "{{Enemy}}: {{Status}} ({{AttackTarget}})",
|
||||
EditorProps = {
|
||||
Enemy = this->~UserAction_EnemyEvaluator(),
|
||||
AttackTarget = this->~UserAction_AttackTargetEvaluator(),
|
||||
Status = new UserAction.Evaluator.Boolean { Name = "$Status$" }
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/*-- Editor Properties --*/
|
||||
|
||||
public func EvalAct_SetActive(proplist props, proplist context)
|
||||
{
|
||||
// User action: Activate enemy AI.
|
||||
var enemy = UserAction->EvaluateValue("Object", props.Enemy, context);
|
||||
var attack_target = UserAction->EvaluateValue("Object", props.AttackTarget, context);
|
||||
var status = UserAction->EvaluateValue("Boolean", props.Status, context);
|
||||
if (!enemy)
|
||||
return;
|
||||
// Ensure enemy AI exists
|
||||
var fx = GetAI(enemy);
|
||||
if (!fx)
|
||||
{
|
||||
// Deactivated? Then we don't need an AI effect.
|
||||
if (!status)
|
||||
return;
|
||||
fx = AddAI(enemy);
|
||||
if (!fx || !enemy)
|
||||
return;
|
||||
}
|
||||
// Set activation.
|
||||
fx->SetActive(status);
|
||||
// Set specific target if desired.
|
||||
if (attack_target)
|
||||
fx.target = attack_target;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
ChooseAI=KI-Auswahl
|
||||
NoAI=Ohne KI
|
||||
NoAIEditorHelp=Entfernt die KI von diesem Objekt.
|
||||
Active=Aktiv
|
||||
ActiveHelp=Ob die KI aktiv den Clonk momentan steuert. Eine inaktive KI kann über die Aktion 'KI aktivieren' zum Beispiel in einer Sequenz wieder aktiviert werden.
|
||||
SetAIActivated=KI aktivieren
|
||||
SetAIActivatedHelp=Aktiviert die Gegner-KI für ein Objekt. Wenn keine KI aktiviert ist, wird sie für das Objekt erstellt.
|
||||
Status=Aktiviert
|
||||
StatusHelp=Ob die KI aktiviert oder deaktiviert wird.
|
|
@ -0,0 +1,9 @@
|
|||
ChooseAI=AI selection
|
||||
NoAI=No AI
|
||||
NoAIEditorHelp=Removes the AI from this object.
|
||||
Active=Active
|
||||
ActiveHelp=Whether the AI controls the clonk currently. An inactive KI can be reactivated via the 'AI activate', for example in a sequence.
|
||||
SetAIActivated=AI activate
|
||||
SetAIActivatedHelp=Activates an enemy AI for a clonk. AI will be created if necessary.
|
||||
Status=Active
|
||||
StatusHelp=Whether to activate or deactivate the AI.
|
|
@ -4,11 +4,15 @@
|
|||
files below. This AI can be overloaded by making a new AI object and including this
|
||||
one and then add the needed changes. The relevant settings are all local constants
|
||||
which can be directly changed in the new AI object, or functions can be overloaded.
|
||||
|
||||
Optionally, a custom AI can also be implemented by including AI_Controller and
|
||||
all desired AI components.
|
||||
|
||||
@author Sven2, Maikel
|
||||
@author Sven2, Maikel, Marky
|
||||
*/
|
||||
|
||||
|
||||
// Include the basic functionality
|
||||
#include AI_Controller
|
||||
// Include the different parts of the AI.
|
||||
#include AI_Appearance
|
||||
#include AI_Debugging
|
||||
|
@ -20,345 +24,27 @@
|
|||
#include AI_TargetFinding
|
||||
#include AI_Vehicles
|
||||
#include AI_AttackModes
|
||||
#include AI_HelperClonk
|
||||
#include AI_HomePosition
|
||||
#include AI_AttackEnemy
|
||||
#include AI_Inventory
|
||||
|
||||
// Enemy spawn definition depends on this
|
||||
local DefinitionPriority = 50;
|
||||
/*-- Callbacks --*/
|
||||
|
||||
// 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).
|
||||
local GuardRangeX = 300; // Search targets this far away in either direction (searching in rectangle).
|
||||
local GuardRangeY = 150; // Search targets this far away in either direction (searching in rectangle).
|
||||
// Timer interval for the effect
|
||||
public func GetTimerInterval() { return 3; }
|
||||
|
||||
|
||||
/*-- Public interface --*/
|
||||
/*-- Editor Properties --*/
|
||||
|
||||
// Change whether target Clonk has an AI (used by editor).
|
||||
public func SetAI(object clonk, bool has_ai)
|
||||
// Callback from the Definition()-call
|
||||
public func OnDefineAI(proplist def)
|
||||
{
|
||||
var ai = GetAI(clonk);
|
||||
if (has_ai)
|
||||
{
|
||||
// Only add if it doesn't have the effect yet.
|
||||
if (!ai)
|
||||
ai = AddAI(clonk);
|
||||
return ai;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ai)
|
||||
ai->Remove();
|
||||
}
|
||||
}
|
||||
|
||||
// Add AI execution timer to target Clonk.
|
||||
public func AddAI(object clonk)
|
||||
{
|
||||
if (GetType(this) != C4V_Def)
|
||||
Log("WARNING: AddAI(%v) not called from definition context but from %v", clonk, this);
|
||||
var fx_ai = GetAI(clonk);
|
||||
if (!fx_ai)
|
||||
fx_ai = clonk->CreateEffect(FxAI, 1, 3, this);
|
||||
if (!fx_ai)
|
||||
return;
|
||||
// Add AI default settings.
|
||||
SetAttackMode(clonk, "Default"); // also binds inventory
|
||||
SetHome(clonk);
|
||||
SetGuardRange(clonk, fx_ai.home_x - this.GuardRangeX, fx_ai.home_y - this.GuardRangeY, this.GuardRangeX * 2, this.GuardRangeY * 2);
|
||||
SetMaxAggroDistance(clonk, this.MaxAggroDistance);
|
||||
SetAutoSearchTarget(clonk, true);
|
||||
return fx_ai;
|
||||
}
|
||||
|
||||
public func GetAI(object clonk)
|
||||
{
|
||||
if (GetType(this) != C4V_Def)
|
||||
Log("WARNING: GetAI(%v) not called from definition context but from %v", clonk, this);
|
||||
if (!clonk)
|
||||
return nil;
|
||||
return clonk->~GetAI();
|
||||
}
|
||||
|
||||
// Set the current inventory to be removed when the clonk dies. Only works if clonk has an AI.
|
||||
public func BindInventory(object clonk)
|
||||
{
|
||||
if (GetType(this) != C4V_Def)
|
||||
Log("WARNING: BindInventory(%v) not called from definition context but from %v", clonk, this);
|
||||
var fx_ai = GetAI(clonk);
|
||||
if (!fx_ai)
|
||||
return false;
|
||||
var cnt = clonk->ContentsCount();
|
||||
fx_ai.bound_weapons = CreateArray(cnt);
|
||||
for (var i = 0; i < cnt; ++i)
|
||||
fx_ai.bound_weapons[i] = clonk->Contents(i);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Set the home position the Clonk returns to if he has no target.
|
||||
public func SetHome(object clonk, int x, int y, int dir)
|
||||
{
|
||||
if (GetType(this) != C4V_Def)
|
||||
Log("WARNING: SetHome(%v, %d, %d, %d) not called from definition context but from %v", clonk, x, y, dir, this);
|
||||
var fx_ai = GetAI(clonk);
|
||||
if (!fx_ai)
|
||||
return false;
|
||||
// nil/nil defaults to current position.
|
||||
if (!GetType(x))
|
||||
x = clonk->GetX();
|
||||
if (!GetType(y))
|
||||
y = clonk->GetY();
|
||||
if (!GetType(dir))
|
||||
dir = clonk->GetDir();
|
||||
fx_ai.home_x = x;
|
||||
fx_ai.home_y = y;
|
||||
fx_ai.home_dir = dir;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Set active state: Enables/Disables timer
|
||||
public func SetActive(object clonk, bool active)
|
||||
{
|
||||
if (GetType(this) != C4V_Def)
|
||||
Log("WARNING: SetActive(%v, %v) not called from definition context but from %v", clonk, active, this);
|
||||
var fx_ai = GetAI(clonk);
|
||||
if (!fx_ai)
|
||||
return false;
|
||||
if (!active)
|
||||
{
|
||||
// Inactive: Stop any activity.
|
||||
clonk->SetCommand("None");
|
||||
clonk->SetComDir(COMD_Stop);
|
||||
}
|
||||
return fx_ai->SetActive(active);
|
||||
}
|
||||
|
||||
// Enable/disable auto-searching of targets.
|
||||
public func SetAutoSearchTarget(object clonk, bool new_auto_search_target)
|
||||
{
|
||||
if (GetType(this) != C4V_Def)
|
||||
Log("WARNING: SetAutoSearchTarget(%v, %v) not called from definition context but from %v", clonk, new_auto_search_target, this);
|
||||
var fx_ai = GetAI(clonk);
|
||||
if (!fx_ai)
|
||||
return false;
|
||||
fx_ai.auto_search_target = new_auto_search_target;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Set the guard range to the provided rectangle.
|
||||
public func SetGuardRange(object clonk, int x, int y, int wdt, int hgt)
|
||||
{
|
||||
if (GetType(this) != C4V_Def)
|
||||
Log("WARNING: SetGuardRange(%v, %d, %d, %d, %d) not called from definition context but from %v", clonk, x, y, wdt, hgt, this);
|
||||
var fx_ai = GetAI(clonk);
|
||||
if (!fx_ai)
|
||||
return false;
|
||||
// Clip to landscape size.
|
||||
if (x < 0)
|
||||
{
|
||||
wdt += x;
|
||||
x = 0;
|
||||
}
|
||||
if (y < 0)
|
||||
{
|
||||
hgt += y;
|
||||
y = 0;
|
||||
}
|
||||
wdt = Min(wdt, LandscapeWidth() - x);
|
||||
hgt = Min(hgt, LandscapeHeight() - y);
|
||||
fx_ai.guard_range = {x = x, y = y, wdt = wdt, hgt = hgt};
|
||||
return true;
|
||||
}
|
||||
|
||||
// Set the maximum distance the enemy will follow an attacking clonk.
|
||||
public func SetMaxAggroDistance(object clonk, int max_dist)
|
||||
{
|
||||
if (GetType(this) != C4V_Def)
|
||||
Log("WARNING: SetMaxAggroDistance(%v, %d) not called from definition context but from %v", clonk, max_dist, this);
|
||||
var fx_ai = GetAI(clonk);
|
||||
if (!fx_ai)
|
||||
return false;
|
||||
fx_ai.max_aggro_distance = max_dist;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Set range in which, on first encounter, allied AI clonks get the same aggro target set.
|
||||
public func SetAllyAlertRange(object clonk, int new_range)
|
||||
{
|
||||
if (GetType(this) != C4V_Def)
|
||||
Log("WARNING: SetAllyAlertRange(%v, %d) not called from definition context but from %v", clonk, new_range, this);
|
||||
var fx_ai = GetAI(clonk);
|
||||
if (!fx_ai)
|
||||
return false;
|
||||
fx_ai.ally_alert_range = new_range;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Set callback function name to be called in game script when this AI is first encountered
|
||||
// Callback function first parameter is (this) AI clonk, second parameter is player clonk.
|
||||
// The callback should return true to be cleared and not called again. Otherwise, it will be called every time a new target is found.
|
||||
public func SetEncounterCB(object clonk, string cb_fn)
|
||||
{
|
||||
if (GetType(this) != C4V_Def)
|
||||
Log("WARNING: SetEncounterCB(%v, %s) not called from definition context but from %v", clonk, cb_fn, this);
|
||||
var fx_ai = GetAI(clonk);
|
||||
if (!fx_ai)
|
||||
return false;
|
||||
fx_ai.encounter_cb = cb_fn;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Set attack path
|
||||
public func SetAttackPath(object clonk, array new_attack_path)
|
||||
{
|
||||
if (GetType(this) != C4V_Def)
|
||||
Log("WARNING: SetAttackPath(%v, %v) not called from definition context but from %v", clonk, new_attack_path, this);
|
||||
var fx_ai = GetAI(clonk);
|
||||
if (!fx_ai)
|
||||
return false;
|
||||
fx_ai.attack_path = new_attack_path;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Set controlled vehicle
|
||||
public func SetVehicle(object clonk, object new_vehicle)
|
||||
{
|
||||
if (GetType(this) != C4V_Def)
|
||||
Log("WARNING: SetVehicle(%v, %v) not called from definition context but from %v", clonk, new_vehicle, this);
|
||||
var fx_ai = GetAI(clonk);
|
||||
if (!fx_ai)
|
||||
return false;
|
||||
fx_ai.vehicle = new_vehicle;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*-- AI Effect --*/
|
||||
|
||||
// The AI effect stores a lot of information about the AI clonk. This includes its state, enemy target, alertness etc.
|
||||
// Each of the functions which are called in the AI definition pass the effect and can then access these variables.
|
||||
// The most important variables are:
|
||||
// fx.Target - The AI clonk.
|
||||
// fx.target - The current target the AI clonk will attack.
|
||||
// fx.alert - Whether the AI clonk is alert and aware of enemies around it.
|
||||
// fx.weapon - Currently selected weapon by the AI clonk.
|
||||
// fx.ammo_check - Function that is called to check ammunition for fx.weapon.
|
||||
// fx.commander - Is commanded by another AI clonk.
|
||||
// fx.control - Definition controlling this AI, all alternative AIs should include the basic AI.
|
||||
|
||||
local FxAI = new Effect
|
||||
{
|
||||
Construction = func(id control_def)
|
||||
{
|
||||
// Execute AI every 3 frames.
|
||||
this.Interval = 3;
|
||||
// Store the definition that controls this AI.
|
||||
this.control = control_def;
|
||||
// Store the vehicle the AI is using.
|
||||
if (this.Target->GetProcedure() == "PUSH")
|
||||
this.vehicle = this.Target->GetActionTarget();
|
||||
// Store whether the enemy is controlled by a commander.
|
||||
this.commander = this.Target.commander;
|
||||
// Give the AI a helper function to get the AI control effect.
|
||||
this.Target.ai = this;
|
||||
this.Target.GetAI = this.GetAI;
|
||||
return FX_OK;
|
||||
},
|
||||
_inherited(def);
|
||||
|
||||
GetAI = func()
|
||||
{
|
||||
return this.ai;
|
||||
},
|
||||
|
||||
Timer = func(int time)
|
||||
{
|
||||
// Execute the AI in the clonk.
|
||||
this.control->Execute(this, time);
|
||||
return FX_OK;
|
||||
},
|
||||
|
||||
Destruction = func(int reason)
|
||||
{
|
||||
// Remove weapons on death.
|
||||
if (reason == FX_Call_RemoveDeath)
|
||||
{
|
||||
if (this.bound_weapons)
|
||||
for (var obj in this.bound_weapons)
|
||||
if (obj && obj->Contained() == Target)
|
||||
obj->RemoveObject();
|
||||
}
|
||||
// Remove AI reference.
|
||||
if (Target && Target.ai == this)
|
||||
Target.ai = nil;
|
||||
return FX_OK;
|
||||
},
|
||||
|
||||
Damage = func(int dmg, int cause)
|
||||
{
|
||||
// AI takes damage: Make sure we're alert so evasion and healing scripts are executed!
|
||||
// It might also be feasible to execute encounter callbacks here (in case an AI is shot from a position it cannot see).
|
||||
// However, the attacking clonk is not known and the callback might be triggered e.g. by an unfortunate meteorite or lightning blast.
|
||||
// So let's just keep it at alert state for now.
|
||||
if (dmg < 0)
|
||||
this.alert = this.time;
|
||||
return dmg;
|
||||
},
|
||||
SetActive = func(bool active)
|
||||
{
|
||||
this.Interval = 3 * active;
|
||||
},
|
||||
GetActive = func()
|
||||
{
|
||||
return this.Interval != 0;
|
||||
},
|
||||
SetAttackMode = func(proplist attack_mode)
|
||||
{
|
||||
// Called by editor delegate when attack mode is changed.
|
||||
// For now, attack mode parameter delegates are not supported. Just set by name.
|
||||
return this.control->SetAttackMode(this.Target, attack_mode.Identifier);
|
||||
},
|
||||
SetAttackPath = func(array attack_path)
|
||||
{
|
||||
// Called by editor delegate when attack path is changed.
|
||||
return this.control->SetAttackPath(this.Target, attack_path);
|
||||
},
|
||||
EditorProps = {
|
||||
guard_range = { Name = "$GuardRange$", Type = "rect", Storage = "proplist", Color = 0xff00, Relative = false },
|
||||
ignore_allies = { Name = "$IgnoreAllies$", Type = "bool" },
|
||||
max_aggro_distance = { Name = "$MaxAggroDistance$", Type = "circle", Color = 0x808080 },
|
||||
active = { Name = "$Active$", EditorHelp = "$ActiveHelp$", Type = "bool", Priority = 50, AsyncGet = "GetActive", Set = "SetActive" },
|
||||
auto_search_target = { Name = "$AutoSearchTarget$", EditorHelp = "$AutoSearchTargetHelp$", Type = "bool" },
|
||||
attack_path = { Name = "$AttackPath$", EditorHelp = "$AttackPathHelp$", Type = "enum", Set = "SetAttackPath", Options = [
|
||||
{ Name="$None$" },
|
||||
{ Name="$AttackPath$", Type=C4V_Array, Value = [{X = 0, Y = 0}], Delegate =
|
||||
{ Name="$AttackPath$", EditorHelp="$AttackPathHelp$", Type="polyline", StartFromObject=true, DrawArrows=true, Color=0xdf0000, Relative=false }
|
||||
}
|
||||
] }
|
||||
},
|
||||
// Save this effect and the AI for scenarios.
|
||||
SaveScen = func(proplist props)
|
||||
{
|
||||
if (!this.Target)
|
||||
return false;
|
||||
props->AddCall("AI", this.control, "AddAI", this.Target);
|
||||
if (!this.Interval)
|
||||
props->AddCall("AI", this.control, "SetActive", this.Target, false);
|
||||
if (this.attack_mode.Identifier != "Default")
|
||||
props->AddCall("AI", this.control, "SetAttackMode", this.Target, Format("%v", this.attack_mode.Identifier));
|
||||
if (this.attack_path)
|
||||
props->AddCall("AI", this.control, "SetAttackPath", this.Target, this.attack_path);
|
||||
if (this.home_x != this.Target->GetX() || this.home_y != this.Target->GetY() || this.home_dir != this.Target->GetDir())
|
||||
props->AddCall("AI", this.control, "SetHome", this.Target, this.home_x, this.home_y, GetConstantNameByValueSafe(this.home_dir, "DIR_"));
|
||||
props->AddCall("AI", this.control, "SetGuardRange", this.Target, this.guard_range.x, this.guard_range.y, this.guard_range.wdt, this.guard_range.hgt);
|
||||
if (this.max_aggro_distance != this.control.MaxAggroDistance)
|
||||
props->AddCall("AI", this.control, "SetMaxAggroDistance", this.Target, this.max_aggro_distance);
|
||||
if (this.ally_alert_range)
|
||||
props->AddCall("AI", this.control, "SetAllyAlertRange", this.Target, this.ally_alert_range);
|
||||
if (!this.auto_search_target)
|
||||
props->AddCall("AI", this.control, "SetAutoSearchTarget", this.Target, false);
|
||||
if (this.encounter_cb)
|
||||
props->AddCall("AI", this.control, "SetEncounterCB", this.Target, Format("%v", this.encounter_cb));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
// Can be added to Clonk
|
||||
AddEditorProp_AISelection(Clonk, AI);
|
||||
}
|
||||
|
||||
|
||||
/*-- AI Execution --*/
|
||||
|
@ -436,30 +122,6 @@ public func Execute(effect fx, int time)
|
|||
return this->Call(fx.strategy, fx);
|
||||
}
|
||||
|
||||
// Selects an item the clonk is about to use.
|
||||
public func SelectItem(effect fx, object item)
|
||||
{
|
||||
if (!item)
|
||||
return;
|
||||
if (item->Contained() != fx.Target)
|
||||
return;
|
||||
fx.Target->SetHandItemPos(0, fx.Target->GetItemPos(item));
|
||||
}
|
||||
|
||||
public func CancelAiming(effect fx)
|
||||
{
|
||||
if (fx.aim_weapon)
|
||||
{
|
||||
fx.aim_weapon->~ControlUseCancel(fx.Target);
|
||||
fx.aim_weapon = nil;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Also cancel aiming done outside AI control.
|
||||
fx.Target->~CancelAiming();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public func ExecuteThrow(effect fx)
|
||||
{
|
||||
|
@ -501,20 +163,6 @@ public func ExecuteThrow(effect fx)
|
|||
return true;
|
||||
}
|
||||
|
||||
public func CheckHandsAction(effect fx)
|
||||
{
|
||||
// Can use hands?
|
||||
if (fx.Target->~HasHandAction())
|
||||
return true;
|
||||
// Can't throw: Is it because e.g. we're scaling?
|
||||
if (!fx.Target->HasActionProcedure())
|
||||
{
|
||||
this->ExecuteStand(fx);
|
||||
return false;
|
||||
}
|
||||
// Probably hands busy. Just wait.
|
||||
return false;
|
||||
}
|
||||
|
||||
public func ExecuteArm(effect fx)
|
||||
{
|
||||
|
@ -545,6 +193,7 @@ public func ExecuteArm(effect fx)
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
public func FindInventoryWeapon(effect fx)
|
||||
{
|
||||
// Find weapon in inventory, mark it as equipped and set according strategy, etc.
|
||||
|
@ -581,6 +230,7 @@ public func FindInventoryWeapon(effect fx)
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
private func FindInventoryWeaponGrenadeLauncher(effect fx)
|
||||
{
|
||||
if (fx.weapon = fx.Target->FindContents(GrenadeLauncher))
|
||||
|
@ -599,6 +249,7 @@ private func FindInventoryWeaponGrenadeLauncher(effect fx)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private func FindInventoryWeaponBlunderbuss(effect fx)
|
||||
{
|
||||
if (fx.weapon = fx.Target->FindContents(Blunderbuss))
|
||||
|
@ -617,6 +268,7 @@ private func FindInventoryWeaponBlunderbuss(effect fx)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private func FindInventoryWeaponBow(effect fx)
|
||||
{
|
||||
if (fx.weapon = fx.Target->FindContents(Bow))
|
||||
|
@ -636,6 +288,7 @@ private func FindInventoryWeaponBow(effect fx)
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private func FindInventoryWeaponJavelin(effect fx)
|
||||
{
|
||||
if (fx.weapon = fx.Target->FindContents(Javelin))
|
||||
|
@ -646,134 +299,3 @@ private func FindInventoryWeaponJavelin(effect fx)
|
|||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*-- Editor Properties --*/
|
||||
|
||||
public func Definition(proplist def)
|
||||
{
|
||||
if (!Clonk.EditorProps)
|
||||
Clonk.EditorProps = {};
|
||||
if (def == AI) // TODO: Make AI an enum so different AI types can be selected.
|
||||
{
|
||||
Clonk.EditorProps.AI =
|
||||
{
|
||||
Type = "has_effect",
|
||||
Effect = "FxAI",
|
||||
Set = Format("%i->SetAI", def),
|
||||
SetGlobal = true
|
||||
};
|
||||
}
|
||||
def->DefinitionAttackModes(def);
|
||||
// Add AI user actions.
|
||||
var enemy_evaluator = UserAction->GetObjectEvaluator("IsClonk", "$Enemy$", "$EnemyHelp$");
|
||||
enemy_evaluator.Priority = 100;
|
||||
UserAction->AddEvaluator("Action", "Clonk", "$SetAIActivated$", "$SetAIActivatedHelp$", "ai_set_activated", [def, def.EvalAct_SetActive],
|
||||
{
|
||||
Enemy = nil,
|
||||
AttackTarget = {
|
||||
Function= "triggering_clonk"
|
||||
},
|
||||
Status = {
|
||||
Function = "bool_constant",
|
||||
Value = true
|
||||
}
|
||||
},
|
||||
{
|
||||
Type = "proplist",
|
||||
Display = "{{Enemy}}: {{Status}} ({{AttackTarget}})",
|
||||
EditorProps = {
|
||||
Enemy = enemy_evaluator,
|
||||
AttackTarget = UserAction->GetObjectEvaluator("IsClonk", "$AttackTarget$", "$AttackTargetHelp$"),
|
||||
Status = new UserAction.Evaluator.Boolean { Name = "$Status$" }
|
||||
}
|
||||
}
|
||||
);
|
||||
UserAction->AddEvaluator("Action", "Clonk", "$SetAINewHome$", "$SetAINewHomeHelp$", "ai_set_new_home", [def, def.EvalAct_SetNewHome],
|
||||
{
|
||||
Enemy = nil,
|
||||
HomePosition = nil,
|
||||
Status = {
|
||||
Function = "bool_constant",
|
||||
Value = true
|
||||
}
|
||||
},
|
||||
{
|
||||
Type = "proplist",
|
||||
Display = "{{Enemy}} -> {{NewHome}}",
|
||||
EditorProps = {
|
||||
Enemy = enemy_evaluator,
|
||||
NewHome = new UserAction.Evaluator.Position {
|
||||
Name = "$NewHome$",
|
||||
EditorHelp = "$NewHomeHelp$"
|
||||
},
|
||||
NewHomeDir = {
|
||||
Type = "enum",
|
||||
Name = "$NewHomeDir$",
|
||||
EditorHelp = "$NewHomeDirHelp$",
|
||||
Options = [
|
||||
{ Name = "$Unchanged$" },
|
||||
{ Name = "$Left$", Value = DIR_Left },
|
||||
{ Name = "$Right$", Value = DIR_Right }
|
||||
]
|
||||
},
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public func EvalAct_SetActive(proplist props, proplist context)
|
||||
{
|
||||
// User action: Activate enemy AI.
|
||||
var enemy = UserAction->EvaluateValue("Object", props.Enemy, context);
|
||||
var attack_target = UserAction->EvaluateValue("Object", props.AttackTarget, context);
|
||||
var status = UserAction->EvaluateValue("Boolean", props.Status, context);
|
||||
if (!enemy)
|
||||
return;
|
||||
// Ensure enemy AI exists
|
||||
var fx = GetAI(enemy);
|
||||
if (!fx)
|
||||
{
|
||||
// Deactivated? Then we don't need an AI effect.
|
||||
if (!status)
|
||||
return;
|
||||
fx = AddAI(enemy);
|
||||
if (!fx || !enemy)
|
||||
return;
|
||||
}
|
||||
// Set activation.
|
||||
fx->SetActive(status);
|
||||
// Set specific target if desired.
|
||||
if (attack_target)
|
||||
fx.target = attack_target;
|
||||
}
|
||||
|
||||
public func EvalAct_SetNewHome(proplist props, proplist context)
|
||||
{
|
||||
// User action: Set new home.
|
||||
var enemy = UserAction->EvaluateValue("Object", props.Enemy, context);
|
||||
var new_home = UserAction->EvaluatePosition(props.NewHome, context);
|
||||
var new_home_dir = props.NewHomeDir;
|
||||
if (!enemy)
|
||||
return;
|
||||
// Ensure enemy AI exists.
|
||||
var fx = GetAI(enemy);
|
||||
if (!fx)
|
||||
{
|
||||
fx = AddAI(enemy);
|
||||
if (!fx || !enemy)
|
||||
return;
|
||||
// Create without attack command.
|
||||
SetAutoSearchTarget(enemy, false);
|
||||
}
|
||||
fx.command = this.ExecuteIdle;
|
||||
fx.home_x = new_home[0];
|
||||
fx.home_y = new_home[1];
|
||||
if (GetType(new_home_dir))
|
||||
fx.home_dir = new_home_dir;
|
||||
}
|
||||
|
||||
|
||||
/*-- Properties --*/
|
||||
|
||||
local Plane = 300;
|
||||
|
|
|
@ -43,7 +43,7 @@ protected func InitializePlayer(int plr)
|
|||
|
||||
// Add test control effect.
|
||||
var fx = AddEffect("IntTestControl", nil, 100, 2);
|
||||
fx.testnr = 11;
|
||||
fx.testnr = 1;
|
||||
fx.launched = false;
|
||||
fx.plr = plr;
|
||||
return;
|
||||
|
|
Loading…
Reference in New Issue