1 /**
2 	Insect Library
3 	Some standard behaviour to flying insects. Action with DFA_FLOAT required.
4 	If at any time you want to block insect behaviour, set a "Wait" command (but beware of in liquid check still be made).
5 
6 	@author Clonkonaut
7 --*/
8 
IsAnimal()9 public func IsAnimal() { return true; }
IsInsect()10 public func IsInsect() { return true; }
11 
Place(int amount,proplist area)12 public func Place(int amount, proplist area)
13 {
14 	// No calls to objects, only definitions
15 	if (GetType(this) == C4V_C4Object) return;
16 
17 	if (!area) area = Shape->LandscapeRectangle();
18 
19 	var insects = CreateArray(), insect, position;
20 	for (var i = 0 ; i < amount ; i++)
21 	{
22 		position = FindLocation(Loc_InArea(area), Loc_Wall(CNAT_Bottom), Loc_Sky());
23 		if (!position)
24 			position = FindLocation(Loc_InArea(area), Loc_Wall(CNAT_Bottom), Loc_Tunnel());
25 		if (position)
26 		{
27 			insect = CreateObjectAbove(this, position.x, position.y - 2, NO_OWNER);
28 			if (insect->Stuck()) insect->RemoveObject();
29 			if (insect) insects[GetLength(insects)] = insect;
30 		}
31 		insect = nil;
32 		position = nil;
33 	}
34 	return insects;
35 }
36 
37 /** Maximum travelling distance for one 'mission' (random target point).
38 	0 = a random point of the whole landscape
39 */
40 local lib_insect_max_dist = 0;
41 
42 /** Set true if the insect does its business at night.
43 	If there's no day/night cycle (Time object), all insects never sleep.
44 */
45 local lib_insect_nocturnal = false;
46 
47 /** If true, the insect will flee from clonks.
48 */
49 local lib_insect_shy = false;
50 
51 /** Overload to define any kind of 'sitting' state for the insect (e.g. a certain action).
52 	Must resolve itself otherwise the insect will cease to do anything.
53 */
IsSitting()54 private func IsSitting() {}
55 
56 /** Return true when the insect is asleep.
57 */
IsSleeping()58 public func IsSleeping()
59 {
60 	// Already returns true when the insect is still searching for a resting place.
61 	return lib_insect_going2sleep || lib_insect_sleeping;
62 }
63 
64 /** What to do when arrived at a random target point.
65 	Overload as necessary.
66 */
MissionComplete()67 private func MissionComplete()
68 {
69 	// Set a dummy command. After it finishes, regular Activity() behaviour will kick in.
70 	SetCommand("Wait", nil, nil, nil, nil, 20 + Random(80));
71 }
72 
73 /** Overload to define any kind of interesting spot for the insect. Return true if spot was found.
74 	coordinates.x and coordinates.y will be coordinates to fly to.
75 */
GetAttraction(proplist coordinates)76 private func GetAttraction(proplist coordinates) { return false; }
77 
78 /** Default sleeping behaviour: Move to a sleeping place and stay there.
79 	Overload as necessary.
80 */
Sleep()81 private func Sleep()
82 {
83 	if (CheckDanger()) return;
84 	// Sleeping phase just begun
85 	if (!lib_insect_going2sleep)
86 	{
87 		// Cancel current mission.
88 		SetCommand("None");
89 		lib_insect_going2sleep = true;
90 	}
91 	// As place to sleep is needed
92 	if (!GetCommand() && !lib_insect_sleeping)
93 	{
94 		var coordinates = {x = 0, y = 0};
95 		// Priority is always to move to a resting place
96 		if (!GetRestingPlace(coordinates))
97 		{
98 			// Default place is just at ground level
99 			coordinates.x = BoundBy(GetX() + Random(10) - 5, 10, LandscapeWidth()-10);
100 			coordinates.y = GetHorizonHeight(coordinates.x);
101 		}
102 		SetCommand("MoveTo", nil, coordinates.x, coordinates.y, nil, true);
103 		AppendCommand("Call", this, nil,nil,nil,nil, "SleepComplete");
104 	}
105 }
106 
107 /** What to do when arrived at a resting place.
108 	Overload as necessary but be sure to set lib_insect_sleeping true.
109 */
SleepComplete()110 private func SleepComplete()
111 {
112 	SetCommand("None");
113 	SetComDir(COMD_None);
114 	SetXDir(0);
115 	SetYDir(0);
116 	lib_insect_sleeping = true;
117 }
118 
119 /** Overload to define any kind of resting place for the insect. Return true if place was found.
120 	coordinates.x and coordinates.y will be coordinates to fly to.
121 */
GetRestingPlace(proplist coordinates)122 private func GetRestingPlace(proplist coordinates) { return false; }
123 
124 /** Called once when the insect just wakes up.
125 */
WakeUp()126 private func WakeUp()
127 {
128 	SetCommand("None");
129 	lib_insect_going2sleep = false;
130 	lib_insect_sleeping = false;
131 }
132 
133 // Moves towards a resting place
134 local lib_insect_going2sleep = false;
135 // Arrived at resting place and sleeps
136 local lib_insect_sleeping = false;
137 // Is fleeing from a danger (interrupts sleep)
138 local lib_insect_escape = false;
139 
140 /* Contact calls */
141 
142 /** Overloaded as necessary. As well as other directions.
143 	Beware of the special characteristic of ContactBottom.
144 */
ContactBottom()145 private func ContactBottom()
146 {
147 	if (lib_insect_going2sleep) return SleepComplete();
148 
149 	SetCommand("None");
150 	SetComDir(COMD_Up);
151 }
ContactTop()152 private func ContactTop()
153 {
154 	if (lib_insect_sleeping) return;
155 
156 	SetCommand("None");
157 	SetComDir(COMD_Down);
158 }
ContactLeft()159 private func ContactLeft()
160 {
161 	if (lib_insect_sleeping) return;
162 
163 	SetCommand("None");
164 	SetComDir(COMD_Right);
165 }
ContactRight()166 private func ContactRight()
167 {
168 	if (lib_insect_sleeping) return;
169 
170 	SetCommand("None");
171 	SetComDir(COMD_Left);
172 }
173 
174 /* Internal stuff */
175 
Initialize()176 private func Initialize()
177 {
178 	AddTimer("Activity");
179 	SetComDir(COMD_None);
180 	MoveToTarget();
181 }
182 
Death()183 private func Death()
184 {
185 	RemoveTimer("Activity");
186 	SetCommand("None");
187 	SetComDir(COMD_None);
188 }
189 
190 // Insects are to tiny to be thrown at!
QueryCatchBlow()191 private func QueryCatchBlow()
192 {
193 	return true;
194 }
195 
196 // They are also too tiny to be hit by arrows etc.
IsProjectileTarget()197 public func IsProjectileTarget()
198 {
199 	return false;
200 }
201 
Activity()202 private func Activity()
203 {
204 	if (Contained()) return;
205 
206 	// It's assumed being underwater is bad for insects
207 	if (InLiquid())
208 	{
209 		if (GetCommand()) SetCommand("None");
210 		return SetComDir(COMD_Up);
211 	}
212 	// Sitting? Wait.
213 	if (IsSitting()) return;
214 	// Go somewhere if no mission
215 	if (!GetCommand())
216 	{
217 		// Escape ended
218 		if (lib_insect_escape) lib_insect_escape = false;
219 		// Go to sleep?
220 		if (Time->HasDayNightCycle())
221 		{
222 			if (!lib_insect_nocturnal && Time->IsNight())
223 				return Sleep();
224 			if (lib_insect_nocturnal && Time->IsDay())
225 				return Sleep();
226 			if (IsSleeping())
227 				WakeUp();
228 		}
229 		if (!CheckDanger()) MoveToTarget();
230 	}
231 }
232 
233 // Set a new mission
MoveToTarget()234 private func MoveToTarget()
235 {
236 	var coordinates = {x = 0, y = 0};
237 	// Priority is always to move to an interesting spot
238 	if (!GetAttraction(coordinates))
239 	{
240 		// Insect may have died
241 		if (!this || !GetAlive()) return;
242 
243 		if (!lib_insect_max_dist)
244 			coordinates.x = Random(LandscapeWidth());
245 		else
246 		{
247 			coordinates.x = GetX();
248 			// Prevent insect from flying around at borders too much
249 			if (GetX() < lib_insect_max_dist / 2) coordinates.x += lib_insect_max_dist / 2;
250 			if (GetX() > LandscapeWidth() - lib_insect_max_dist / 2) coordinates.x -= lib_insect_max_dist / 2;
251 
252 			coordinates.x = BoundBy(coordinates.x + Random(lib_insect_max_dist) - lib_insect_max_dist / 2, 10, LandscapeWidth() - 10);
253 		}
254 		// Move to a place slightly above the surface.
255 		coordinates.y = GetHorizonHeight(coordinates.x, GetY()) - 30 - Random(60);
256 	}
257 	// Insect may have died
258 	if (!this || !GetAlive()) return;
259 
260 	SetCommand("MoveTo", nil, coordinates.x, coordinates.y, nil, true);
261 	AppendCommand("Call", this, nil, nil, nil, nil, "MissionComplete");
262 }
263 
GetHorizonHeight(int x,int y_current)264 private func GetHorizonHeight(int x, int y_current)
265 {
266 	var height = y_current;
267 	while (height < LandscapeHeight() && !GBackSemiSolid(AbsX(x), AbsY(height)))
268 		height += 5;
269 	return height;
270 }
271 
CheckDanger()272 private func CheckDanger()
273 {
274 	if (!lib_insect_shy) return false;
275 	// Just flee from the first found clonk
276 	var danger_zone = 40;
277 	if (IsSleeping()) danger_zone /= 2;
278 	var clonk = FindObject(Find_Distance(danger_zone), Find_OCF(OCF_CrewMember), Find_OCF(OCF_Alive), Find_NoContainer());
279 	if (clonk)
280 	{
281 		Flee(clonk);
282 		return true;
283 	}
284 	return false;
285 }
286 
Flee(object from)287 private func Flee(object from)
288 {
289 	if (!from) return;
290 	if (IsSleeping()) WakeUp();
291 	// Find a point that's somewhere up and away from the object
292 	var x,y;
293 	y = GetY() - 50 + Random(20);
294 	if (GetX() < from->GetX())
295 		x = GetX() + 50 - Random(20);
296 	else
297 		x = GetX() - 50 + Random(20);
298 	x = BoundBy(x, 10, LandscapeWidth()-10);
299 	y = BoundBy(y, 10, LandscapeHeight()-10);
300 	SetCommand("MoveTo", nil, x, y, nil, true);
301 	lib_insect_escape = true;
302 }