ai: attack messages, logging and better target finding

alut-include-path
Maikel de Vries 2017-01-27 14:02:24 +01:00
parent 8159d73be6
commit 7d3062277a
9 changed files with 172 additions and 23 deletions

View File

@ -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;

View File

@ -0,0 +1,5 @@
[DefCore]
id=AI_Appearance
Version=8,0
Category=C4D_StaticBack
HideInCreator=true

View File

@ -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;
}

View File

@ -0,0 +1,5 @@
MsgAttack0=Angriff!
MsgAttack1=Zum Angriff!
MsgAttack2=Attacke!
MsgAttack3=Vorwärts!
MsgAttack4=Hau drauf!

View File

@ -0,0 +1,5 @@
MsgAttack0=Attack!
MsgAttack1=Charge!
MsgAttack2=Get them!
MsgAttack3=For glory!
MsgAttack4=Beat 'em up!

View File

@ -0,0 +1,5 @@
[DefCore]
id=AI_Debugging
Version=8,0
Category=C4D_StaticBack
HideInCreator=true

View File

@ -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;
}

View File

@ -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);

View File

@ -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;
}