1 /**
2 AI Protection
3 Functionality that helps the AI to protect itself and evade danger. Also
4 handles healing.
5
6 @author Sven2, Maikel
7 */
8
9
10 // AI Settings.
11 local HealingHitPointsThreshold = 30; // Number of hitpoints below which the AI will try to heal itself even in a dangerous situation.
12 local AlertTime = 800; // Number of frames after alert after which AI no longer checks for projectiles.
13
14 // Called when the AI is added.
OnAddAI(effect fx_ai)15 public func OnAddAI(effect fx_ai)
16 {
17 _inherited(fx_ai);
18
19 // Put on any useful wearables in the inventory.
20 this->ExecuteWearable(fx_ai);
21 return;
22 }
23
ExecuteProtection(effect fx)24 public func ExecuteProtection(effect fx)
25 {
26 // Search for nearby projectiles. Ranged AI also searches for enemy clonks to evade.
27 var enemy_search;
28 if (fx.ranged)
29 enemy_search = Find_And(Find_OCF(OCF_CrewMember), Find_Not(Find_Owner(fx.Target->GetOwner())));
30 var projectiles = fx.Target->FindObjects(Find_InRect(-150, -50, 300, 80), Find_Or(Find_Category(C4D_Object), Find_Func("IsDangerous4AI"), Find_Func("IsArrow"), enemy_search), Find_OCF(OCF_HitSpeed2), Find_NoContainer(), Sort_Distance());
31 for (var obj in projectiles)
32 {
33 var dx = obj->GetX() - fx.Target->GetX(), dy = obj->GetY() - fx.Target->GetY();
34 var vx = obj->GetXDir(), vy = obj->GetYDir();
35 if (Abs(dx) > 40 && vx)
36 dy += (Abs(10 * dx / vx)**2) * GetGravity() / 200;
37 var v2 = Max(vx * vx + vy * vy, 1);
38 var d2 = dx * dx + dy * dy;
39 var time_to_impact = 10 * Sqrt(d2) / Sqrt(v2);
40 if (time_to_impact > 20)
41 {
42 // Won't hit within the next 20 frames.
43 continue;
44 }
45 // Distance at which projectile will pass clonk should be larger than clonk size (erroneously assumes clonk is a sphere).
46 var l = dx * vx + dy * vy;
47 if (l < 0 && Sqrt(d2 - l * l / v2) <= fx.Target->GetCon() / 8)
48 {
49 // Not if there's a wall between.
50 if (!PathFree(fx.Target->GetX(), fx.Target->GetY(), obj->GetX(), obj->GetY()))
51 continue;
52 // This might hit.
53 fx.alert = fx.time;
54 // Use a shield if the object is not explosive.
55 if (fx.shield && !obj->~HasExplosionOnImpact())
56 {
57 // Use it!
58 this->SelectItem(fx, fx.shield);
59 if (fx.aim_weapon == fx.shield)
60 {
61 // Continue to hold shield.
62 fx.shield->ControlUseHolding(fx.Target, dx, dy);
63 }
64 else
65 {
66 // Start holding shield.
67 if (fx.aim_weapon)
68 this->CancelAiming(fx);
69 if (!this->CheckHandsAction(fx))
70 return true;
71 if (!fx.shield->ControlUseStart(fx.Target, dx, dy))
72 return false; // Something's broken :(
73 fx.shield->ControlUseHolding(fx.Target, dx, dy);
74 fx.aim_weapon = fx.shield;
75 }
76 return true;
77 }
78 // Try to use club to bat away objects if available.
79 if (this->ExecuteClubProtection(fx, obj, time_to_impact))
80 return true;
81 // No shield. try to jump away.
82 if (dx < 0)
83 fx.Target->SetComDir(COMD_Right);
84 else
85 fx.Target->SetComDir(COMD_Left);
86 if (this->ExecuteJump(fx))
87 return true;
88 // Can only try to evade one projectile.
89 break;
90 }
91 }
92 // Stay alert if there's a target. Otherwise alert state may wear off.
93 if (!fx.target)
94 fx.target = nil; //this->FindEmergencyTarget(fx);
95 if (fx.target)
96 fx.alert = fx.time;
97 else if (fx.time - fx.alert > fx->GetControl().AlertTime)
98 fx.alert = nil;
99 // If not evading the AI may try to heal.
100 if (ExecuteHealing(fx))
101 return true;
102 // Nothing to do.
103 return false;
104 }
105
106 // Tries to hit away a projectile with a club that is about to hit the AI clonk.
ExecuteClubProtection(effect fx,object projectile,int time_to_impact)107 public func ExecuteClubProtection(effect fx, object projectile, int time_to_impact)
108 {
109 // Don't use club on explosives.
110 if (projectile->~HasExplosionOnImpact())
111 return false;
112 // Check for a club which can be used.
113 var club = fx.Target->FindObject(Find_Container(fx.Target), Find_ID(Club));
114 if (!club)
115 return false;
116 // Assume we are using it so just wait.
117 if (club->RejectUse(fx.Target) || time_to_impact > 8)
118 return true;
119 var dx = projectile->GetX() - fx.Target->GetX();
120 var dy = projectile->GetY() - fx.Target->GetY();
121 // Execute all control commands in a few frames.
122 this->SelectItem(fx, club);
123 if (club->~ControlUseStart(fx.Target, dx, dy))
124 {
125 ScheduleCall(club, "~ControlUseHolding", 1, 0, fx.Target, dx, dy);
126 ScheduleCall(club, "~ControlUseStop", 2, 0, fx.Target, dx, dy);
127 return true;
128 }
129 return false;
130 }
131
ExecuteHealing(effect fx)132 public func ExecuteHealing(effect fx)
133 {
134 var hp = fx.Target->GetEnergy();
135 var hp_needed = fx.Target->GetMaxEnergy() - hp;
136 // Only heal when alert if health drops below the healing threshold.
137 // If not alert also heal if more than 40 hitpoints of health are lost.
138 if (hp >= fx->GetControl().HealingHitPointsThreshold && (fx.alert || hp_needed <= 40))
139 return false;
140 // Don't heal if already healing. One can speed up healing by healing multiple times, but we don't.
141 if (GetEffect("HealingOverTime", fx.Target))
142 return false;
143 // Find food to heal with, find closest to but more than needed hp.
144 var food;
145 for (food in fx.Target->FindObjects(Find_Container(fx.Target), Find_Func("NutritionalValue"), Sort_Func("NutritionalValue")))
146 if (food->NutritionalValue() >= hp_needed)
147 break;
148 if (!food)
149 return false;
150 // Eat the food.
151 this->SelectItem(fx, food);
152 if (food->~ControlUse(fx.Target))
153 return true;
154 return false;
155 }
156
ExecuteWearable(effect fx)157 public func ExecuteWearable(effect fx)
158 {
159 // Put on wearable items in the current inventory.
160 for (var wearable in fx.Target->FindObjects(Find_Container(fx.Target), Find_Func("IsWearable"))) // TODO: sort by usefulness.
161 {
162 if (wearable->HasFreeWearPlace(fx.Target))
163 wearable->PutOn(fx.Target, true);
164 }
165 return;
166 }
167