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