1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
|
/**
AI Movement
Functionality that helps the AI move.
@author Sven2, Maikel
*/
/*-- Public interface --*/
// Set attack path
public func SetAttackPath(object clonk, array new_attack_path)
{
AssertDefinitionContext(Format("SetAttackPath(%v, %v)", clonk, new_attack_path));
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->GetControl(), "SetAttackPath", fx_ai.Target, fx_ai.attack_path);
}
/*-- Editor Properties --*/
// Callback from the Definition()-call
public func OnDefineAI(proplist def)
{
_inherited(def);
def->GetControlEffect().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->GetControlEffect().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->GetControl()->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)
{
fx.Target->SetCommand("None");
if (fx.Target->GetProcedure() == "SCALE")
{
var tx;
if (fx.target)
tx = fx.target->GetX() - fx.Target->GetX();
// Scale: Either scale up if target is beyond this obstacle or let go if it's not.
if (fx.Target->GetDir() == DIR_Left)
{
if (tx < -20)
fx.Target->SetComDir(COMD_Left);
else
fx.Target->ObjectControlMovement(fx.Target->GetOwner(), CON_Right, 100); // let go
}
else
{
if (tx > -20)
fx.Target->SetComDir(COMD_Right);
else
fx.Target->ObjectControlMovement(fx.Target->GetOwner(), CON_Left, 100); // let go
}
}
else if (fx.Target->GetAction() == "Climb")
{
var climb_fx = GetEffect("IntClimbControl", fx.Target);
if (climb_fx)
{
// For now just climb down the ladder.
var ctrl = CON_Down;
EffectCall(fx.Target, climb_fx, "Control", ctrl, 0, 0, 100, false, CONS_Down);
}
}
else if (fx.Target->GetProcedure() == "HANGLE")
{
fx.Target->ObjectControlMovement(fx.Target->GetOwner(), CON_Down, 100);
}
else if (fx.Target->GetProcedure() == "FLIGHT" || fx.Target->GetAction() == "Roll")
{
// Don't do anything for these procedures and actions as they will end automatically.
}
else
{
this->~LogAI_Warning(fx, Format("ExecuteStand has no idea what to do for action %v and procedure %v.", fx.Target->GetAction(), fx.Target->GetProcedure()));
// Hm. What could it be? Let's just hope it resolves itself somehow...
fx.Target->SetComDir(COMD_Stop);
}
return true;
}
// Evade a threat from the given coordinates.
public func ExecuteEvade(effect fx, int threat_dx, int threat_dy)
{
// Don't try to evade if the AI has a commander, if an AI is being commanded
// it has more important tasks, like staying on an airship.
if (fx.commander)
return false;
// Evade from threat at position delta threat_dx, threat_dy.
if (threat_dx < 0)
fx.Target->SetComDir(COMD_Left);
else
fx.Target->SetComDir(COMD_Right);
if (threat_dy >= -5 && !Random(2))
if (this->ExecuteJump(fx))
return true;
// Shield? Todo.
return true;
}
public func ExecuteJump(effect fx)
{
// Jump if standing on floor.
if (fx.Target->GetProcedure() == "WALK")
{
if (fx.Target->~ControlJump())
return true; // For clonks.
return fx.Target->Jump(); // For others.
}
return false;
}
// Follow attack path or return to the AI's home if not yet there.
public func ExecuteIdle(effect fx)
{
// Persist commands because constant command resets may hinder execution
if (fx.Target->GetCommand() && Random(4)) return true;
// Follow attack path
if (this->ExecuteAttackPath(fx)) return true;
// Movement done (for now)
fx.Target->SetCommand("None");
fx.Target->SetComDir(COMD_Stop);
fx.Target->SetDir(fx.home_dir);
if (fx.vehicle) fx.vehicle->SetCommand();
// Nothing to do.
return false;
}
public func ExecuteAttackPath(effect fx)
{
// Attack path is to follow the commander.
if (fx.commander) return true;
if (fx.attack_path)
{
// Follow attack path
var next_pt = fx.attack_path[0];
// Check for structure to kill on path. Only if the structure is alive or of the clonk can attack structures with the current weapon.
var alive_check;
if (!fx.can_attack_structures && !fx.can_attack_structures_after_weapon_respawn)
{
alive_check = Find_OCF(OCF_Alive);
}
if ((fx.target = FindObject(Find_AtPoint(next_pt.X, next_pt.Y), Find_Func("IsStructure"), alive_check)))
{
// Do not advance on path unless target(s) destroyed.
return true;
}
// Follow path
fx.home_x = next_pt.X;
fx.home_y = next_pt.Y;
fx.home_dir = Random(2);
}
// Check if we need to move/push to a target
if (fx.vehicle)
{
if (!Inside(fx.vehicle->GetX() - fx.home_x, -15, 15) || !Inside(fx.vehicle->GetY() - fx.home_y, -20, 20))
{
if (fx.vehicle->~IsAirship())
{
if (!fx.vehicle->GetCommand())
{
fx.vehicle->SetCommand("MoveTo", nil, fx.home_x, fx.home_y);
}
}
else
{
// Default vehicle movement
return fx.Target->SetCommand("PushTo", fx.vehicle, fx.home_x, fx.home_y);
}
return true;
}
}
else
{
if (!Inside(fx.Target->GetX() - fx.home_x, -5, 5) || !Inside(fx.Target->GetY() - fx.home_y, -15, 15))
{
return fx.Target->SetCommand("MoveTo", nil, fx.home_x, fx.home_y);
}
}
// Next section on path or done?
this->AdvanceAttackPath(fx);
return false;
}
public func AdvanceAttackPath(effect fx)
{
// Pick next element in attack path if an attack path is set. Return whether a path remains.
if (fx.attack_path)
{
if (GetLength(fx.attack_path) > 1)
{
fx.attack_path = fx.attack_path[1:];
return true;
}
else
{
fx.attack_path = nil;
}
}
return false;
}
// Turns around the AI such that it looks at its target.
public func ExecuteLookAtTarget(effect fx)
{
// Set direction to look at target, we can assume this is instantaneous.
if (fx.target->GetX() > fx.Target->GetX())
fx.Target->SetDir(DIR_Right);
else
fx.Target->SetDir(DIR_Left);
return true;
}
|