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 }