forked from Mirrors/openclonk
ai: attack messages, logging and better target finding
parent
8159d73be6
commit
7d3062277a
|
@ -8,12 +8,19 @@
|
|||
#include AI
|
||||
|
||||
|
||||
// AI Settings.
|
||||
local AltTargetDistance = 400; // Use the scenario given target if normal AI target is further away than this distance.
|
||||
|
||||
|
||||
private func FindTarget(effect fx)
|
||||
{
|
||||
var target = _inherited(fx, ...);
|
||||
// Focus on defense target if normal target is too far away.
|
||||
if (!target || ObjectDistance(target, fx.Target) > DefenseAI.AI_AltTargetDistance)
|
||||
if (!target || ObjectDistance(target, fx.Target) > fx.control.AltTargetDistance)
|
||||
target = GetRandomAttackTarget(fx.Target);
|
||||
// If target can't be attacked just take normal target again.
|
||||
if (!this->HasWeaponForTarget(fx, target))
|
||||
target = _inherited(fx, ...);
|
||||
return target;
|
||||
}
|
||||
|
||||
|
@ -32,8 +39,3 @@ private func FindInventoryWeapon(effect fx)
|
|||
}
|
||||
return _inherited(fx, ...);
|
||||
}
|
||||
|
||||
|
||||
/*-- Properties --*/
|
||||
|
||||
local AI_AltTargetDistance = 400;
|
|
@ -0,0 +1,5 @@
|
|||
[DefCore]
|
||||
id=AI_Appearance
|
||||
Version=8,0
|
||||
Category=C4D_StaticBack
|
||||
HideInCreator=true
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
AI Appearance
|
||||
Functionality that helps the AI to display messages, make sounds, etc.
|
||||
|
||||
@author Maikel
|
||||
*/
|
||||
|
||||
|
||||
// AI Settings.
|
||||
local AttackMessageWaitTime = 36 * 30; // Number of frames between displaying an attack message.
|
||||
local AttackMessageRate = 80; // Likelihood of displaying an attack message in percent (0 - 100).
|
||||
|
||||
|
||||
public func ExecuteAppearance(effect fx)
|
||||
{
|
||||
// Do a random attack message.
|
||||
if (!Random(5) && Random(100) >= 100 - fx.control.AttackMessageRate)
|
||||
this->ExecuteAttackMessage(fx);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Shows an attack message if in a group of AI and is attacking.
|
||||
public func ExecuteAttackMessage(effect fx)
|
||||
{
|
||||
// Only if close to the target.
|
||||
if (fx.Target->ObjectDistance(fx.target) > 150)
|
||||
return true;
|
||||
// Find other AI enemies that attack together.
|
||||
var group_cnt = 0;
|
||||
for (var clonk in fx.Target->FindObjects(Find_OCF(OCF_CrewMember), Find_Distance(40), Find_Owner(fx.Target->GetOwner())))
|
||||
{
|
||||
var ai = clonk->~GetAI();
|
||||
if (!ai)
|
||||
continue;
|
||||
if (ai.last_message != nil && FrameCounter() - ai.last_message < fx.control.AttackMessageWaitTime)
|
||||
continue;
|
||||
group_cnt++;
|
||||
}
|
||||
// Display a message if group is big enough.
|
||||
if (group_cnt >= RandomX(3, 6))
|
||||
{
|
||||
fx.Target->Sound("Clonk::Action::GroupAttack");
|
||||
fx.Target->Message(Translate(Format("MsgAttack%d", Random(4))));
|
||||
fx.last_message = FrameCounter();
|
||||
}
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
MsgAttack0=Angriff!
|
||||
MsgAttack1=Zum Angriff!
|
||||
MsgAttack2=Attacke!
|
||||
MsgAttack3=Vorwärts!
|
||||
MsgAttack4=Hau drauf!
|
|
@ -0,0 +1,5 @@
|
|||
MsgAttack0=Attack!
|
||||
MsgAttack1=Charge!
|
||||
MsgAttack2=Get them!
|
||||
MsgAttack3=For glory!
|
||||
MsgAttack4=Beat 'em up!
|
|
@ -0,0 +1,5 @@
|
|||
[DefCore]
|
||||
id=AI_Debugging
|
||||
Version=8,0
|
||||
Category=C4D_StaticBack
|
||||
HideInCreator=true
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
AI Debugging
|
||||
Functionality that helps to debug AI control.
|
||||
|
||||
@author Maikel
|
||||
*/
|
||||
|
||||
|
||||
// AI Settings.
|
||||
local DebugLoggingOn = false; // Whether or not debug logging is turned on.
|
||||
|
||||
|
||||
public func LogAI(effect fx, string message)
|
||||
{
|
||||
if (fx.control.DebugLoggingOn)
|
||||
Log("[%d]AI WARNING (%v): %s", FrameCounter(), fx.Target, message);
|
||||
return;
|
||||
}
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
|
||||
// Include the different parts of the AI.
|
||||
#include AI_Appearance
|
||||
#include AI_Debugging
|
||||
#include AI_HelperFunctions
|
||||
#include AI_MeleeWeapons
|
||||
#include AI_Protection
|
||||
|
@ -201,6 +203,17 @@ public func SetEncounterCB(object clonk, string cb_fn)
|
|||
|
||||
/*-- 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)
|
||||
|
@ -306,7 +319,7 @@ local FxAI = new Effect
|
|||
|
||||
/*-- AI Execution --*/
|
||||
|
||||
private func Execute(effect fx, int time)
|
||||
public func Execute(effect fx, int time)
|
||||
{
|
||||
fx.time = time;
|
||||
// Evasion, healing etc. if alert.
|
||||
|
@ -334,6 +347,7 @@ private func Execute(effect fx, int time)
|
|||
if (fx.ammo_check && !this->Call(fx.ammo_check, fx, fx.weapon))
|
||||
{
|
||||
fx.weapon = nil;
|
||||
this->LogAI(fx, Format("weapon %v is out of ammo, AI won't do anything.", fx.weapon));
|
||||
return false;
|
||||
}
|
||||
// Find an enemy.
|
||||
|
@ -367,12 +381,16 @@ private func Execute(effect fx, int time)
|
|||
fx.ally_alert_range = nil;
|
||||
}
|
||||
}
|
||||
// Do stuff on the appearance of the enemy like displaying a message.
|
||||
this->ExecuteAppearance(fx);
|
||||
// Attack it!
|
||||
if (!this->IsWeaponForTarget(fx))
|
||||
this->LogAI(fx, Format("weapon of type %i is not fit to attack %v.", fx.weapon->GetID(), fx.target));
|
||||
return this->Call(fx.strategy, fx);
|
||||
}
|
||||
|
||||
// Selects an item the clonk is about to use.
|
||||
private func SelectItem(effect fx, object item)
|
||||
public func SelectItem(effect fx, object item)
|
||||
{
|
||||
if (!item)
|
||||
return;
|
||||
|
@ -381,7 +399,7 @@ private func SelectItem(effect fx, object item)
|
|||
fx.Target->SetHandItemPos(0, fx.Target->GetItemPos(item));
|
||||
}
|
||||
|
||||
private func CancelAiming(effect fx)
|
||||
public func CancelAiming(effect fx)
|
||||
{
|
||||
if (fx.aim_weapon)
|
||||
{
|
||||
|
@ -396,7 +414,7 @@ private func CancelAiming(effect fx)
|
|||
return true;
|
||||
}
|
||||
|
||||
private func ExecuteLookAtTarget(effect fx)
|
||||
public func ExecuteLookAtTarget(effect fx)
|
||||
{
|
||||
// Set direction to look at target, we can assume this is instantanuous.
|
||||
if (fx.target->GetX() > fx.Target->GetX())
|
||||
|
@ -406,7 +424,7 @@ private func ExecuteLookAtTarget(effect fx)
|
|||
return true;
|
||||
}
|
||||
|
||||
private func ExecuteThrow(effect fx)
|
||||
public func ExecuteThrow(effect fx)
|
||||
{
|
||||
// Still carrying the weapon to throw?
|
||||
if (fx.weapon->Contained() != fx.Target)
|
||||
|
@ -446,7 +464,7 @@ private func ExecuteThrow(effect fx)
|
|||
return true;
|
||||
}
|
||||
|
||||
private func CheckHandsAction(effect fx)
|
||||
public func CheckHandsAction(effect fx)
|
||||
{
|
||||
// Can use hands?
|
||||
if (fx.Target->~HasHandAction())
|
||||
|
@ -461,7 +479,7 @@ private func CheckHandsAction(effect fx)
|
|||
return false;
|
||||
}
|
||||
|
||||
private func ExecuteStand(effect fx)
|
||||
public func ExecuteStand(effect fx)
|
||||
{
|
||||
fx.Target->SetCommand("None");
|
||||
if (fx.Target->GetProcedure() == "SCALE" || fx.Target->GetAction() == "Climb")
|
||||
|
@ -497,7 +515,7 @@ private func ExecuteStand(effect fx)
|
|||
return true;
|
||||
}
|
||||
|
||||
private func ExecuteEvade(effect fx, int threat_dx, int threat_dy)
|
||||
public func ExecuteEvade(effect fx, int threat_dx, int threat_dy)
|
||||
{
|
||||
// Evade from threat at position delta threat_dx, threat_dy.
|
||||
if (threat_dx < 0)
|
||||
|
@ -511,7 +529,7 @@ private func ExecuteEvade(effect fx, int threat_dx, int threat_dy)
|
|||
return true;
|
||||
}
|
||||
|
||||
private func ExecuteJump(effect fx)
|
||||
public func ExecuteJump(effect fx)
|
||||
{
|
||||
// Jump if standing on floor.
|
||||
if (fx.Target->GetProcedure() == "WALK")
|
||||
|
@ -523,7 +541,7 @@ private func ExecuteJump(effect fx)
|
|||
return false;
|
||||
}
|
||||
|
||||
private func ExecuteArm(effect fx)
|
||||
public func ExecuteArm(effect fx)
|
||||
{
|
||||
// Find shield.
|
||||
fx.shield = fx.Target->FindContents(Shield);
|
||||
|
@ -537,7 +555,7 @@ private func ExecuteArm(effect fx)
|
|||
return false;
|
||||
}
|
||||
|
||||
private func FindInventoryWeapon(effect fx)
|
||||
public func FindInventoryWeapon(effect fx)
|
||||
{
|
||||
fx.ammo_check = nil;
|
||||
fx.ranged = false;
|
||||
|
@ -622,7 +640,7 @@ private func FindInventoryWeapon(effect fx)
|
|||
return false;
|
||||
}
|
||||
|
||||
private func ExecuteIdle(effect fx)
|
||||
public func ExecuteIdle(effect fx)
|
||||
{
|
||||
if (!Inside(fx.Target->GetX() - fx.home_x, -5, 5) || !Inside(fx.Target->GetY() - fx.home_y, -15, 15))
|
||||
{
|
||||
|
@ -709,7 +727,7 @@ public func Definition(proplist def)
|
|||
);
|
||||
}
|
||||
|
||||
private func EvalAct_SetActive(proplist props, proplist context)
|
||||
public func EvalAct_SetActive(proplist props, proplist context)
|
||||
{
|
||||
// User action: Activate enemy AI.
|
||||
var enemy = UserAction->EvaluateValue("Object", props.Enemy, context);
|
||||
|
@ -735,7 +753,7 @@ private func EvalAct_SetActive(proplist props, proplist context)
|
|||
fx.target = attack_target;
|
||||
}
|
||||
|
||||
private func EvalAct_SetNewHome(proplist props, proplist context)
|
||||
public func EvalAct_SetNewHome(proplist props, proplist context)
|
||||
{
|
||||
// User action: Set new home.
|
||||
var enemy = UserAction->EvaluateValue("Object", props.Enemy, context);
|
||||
|
|
|
@ -14,7 +14,8 @@ public func FindTarget(effect fx)
|
|||
hostile_criteria = Find_Not(Find_Owner(fx.Target->GetOwner()));
|
||||
for (var target in fx.Target->FindObjects(Find_InRect(fx.guard_range.x - fx.Target->GetX(), fx.guard_range.y - fx.Target->GetY(), fx.guard_range.wdt, fx.guard_range.hgt), Find_OCF(OCF_CrewMember), hostile_criteria, Find_NoContainer(), Sort_Random()))
|
||||
if (fx.ranged || PathFree(fx.Target->GetX(), fx.Target->GetY(), target->GetX(), target->GetY()))
|
||||
return target;
|
||||
if (this->HasWeaponForTarget(fx, target))
|
||||
return target;
|
||||
// Nothing found.
|
||||
return;
|
||||
}
|
||||
|
@ -28,7 +29,8 @@ public func FindEmergencyTarget(effect fx)
|
|||
// Search nearest enemy clonk in area even if not in guard range, used e.g. when outside guard range (AI fell down) and being attacked.
|
||||
for (var target in fx.Target->FindObjects(Find_Distance(200), Find_OCF(OCF_CrewMember), hostile_criteria, Find_NoContainer(), Sort_Distance()))
|
||||
if (PathFree(fx.Target->GetX(), fx.Target->GetY(), target->GetX(), target->GetY()))
|
||||
return target;
|
||||
if (this->HasWeaponForTarget(fx, target))
|
||||
return target;
|
||||
// Nothing found.
|
||||
return;
|
||||
}
|
||||
|
@ -47,9 +49,51 @@ public func CheckTargetInGuardRange(effect fx)
|
|||
return true;
|
||||
}
|
||||
|
||||
// Checks whether the AI has a weapon for the target.
|
||||
public func HasWeaponForTarget(effect fx, object target)
|
||||
{
|
||||
target = target ?? fx.target;
|
||||
for (var weapon in FindObjects(Find_Container(fx.Target)))
|
||||
if (this->IsWeaponForTarget(fx, weapon, target))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns whether a weapon can be used for a certain target.
|
||||
public func IsWeaponForTarget(effect fx, object weapon, object target)
|
||||
{
|
||||
// TODO: Implement shooting at different targets, e.g. alive vs. structure.
|
||||
weapon = weapon ?? fx.weapon;
|
||||
target = target ?? fx.target;
|
||||
// If on a vehicle forward behavior.
|
||||
if (fx.vehicle)
|
||||
return this->IsVehicleForTarget(fx, fx.vehicle, target);
|
||||
|
||||
// Assume that all weapons can attack living targets.
|
||||
if (target->GetAlive())
|
||||
return true;
|
||||
|
||||
// Structures and vehicles can only be attacked by explosives.
|
||||
var cat_target = target->GetCategory();
|
||||
if ((cat_target & C4D_Structure) || (cat_target & C4D_Vehicle))
|
||||
{
|
||||
if (weapon->~IsExplosive())
|
||||
return true;
|
||||
}
|
||||
|
||||
// Weapon is unable to attack current target.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Returns whether a weapon can be used for a certain target.
|
||||
public func IsVehicleForTarget(effect fx, object vehicle, object target)
|
||||
{
|
||||
vehicle = vehicle ?? fx.vehicle;
|
||||
target = target ?? fx.target;
|
||||
// Airships may board everywhere.
|
||||
if (vehicle->GetID() == Airship)
|
||||
return true;
|
||||
// Catapult may fire at everything.
|
||||
if (vehicle->GetID() == Catapult)
|
||||
return true;
|
||||
return false;
|
||||
}
|
Loading…
Reference in New Issue