1 /**
2 Ladder Climbing
3 Gives the ability to clonks climb on ladders, to be included by the clonk.
4
5 @author Randrian
6 */
7
Definition(proplist def)8 public func Definition(proplist def)
9 {
10 // Only add action if included by clonk.
11 if (!def.ActMap)
12 return _inherited(def);
13 // Add actions for climbing and overload jumping actions to search for ladders.
14 def.ActMap = {
15 Prototype = def.ActMap,
16 Climb = {
17 Prototype = Action,
18 Name = "Climb",
19 Directions = 2,
20 Length = 0,
21 Delay = 0,
22 Wdt = 8,
23 Hgt = 20,
24 Procedure = DFA_FLOAT,
25 },
26 Jump = {
27 Prototype = def.ActMap.Jump,
28 StartCall = "StartSearchLadder",
29 // Save the old phasecall of the jump.
30 StartCallLadderOverloaded = def.ActMap.Jump.StartCall
31 },
32 WallJump = {
33 Prototype = def.ActMap.WallJump,
34 StartCall = "StartSearchLadder",
35 // Save the old phasecall of the wall jump.
36 StartCallLadderOverloaded = def.ActMap.WallJump.StartCall
37 }
38 };
39 return _inherited(def);
40 }
41
GetTurnPhase()42 public func GetTurnPhase() { return _inherited(...); }
SetTurnType()43 public func SetTurnType() { return _inherited(...); }
SetHandAction()44 public func SetHandAction() { return _inherited(...); }
45 // Should be overloaded, and add a climb animation here
StartScale()46 public func StartScale() { return _inherited(...); }
47
48
49 /*-- Ladder Searching --*/
50
StartSearchLadder()51 public func StartSearchLadder()
52 {
53 // Call the overwriten old phase call.
54 if (GetAction() == "Jump" && this.ActMap.Jump.StartCallLadderOverloaded)
55 Call(this.ActMap.Jump.StartCallLadderOverloaded);
56 if (GetAction() == "WallJump" && this.ActMap.WallJump.StartCallLadderOverloaded)
57 Call(this.ActMap.WallJump.StartCallLadderOverloaded);
58 // Add an effect to search for ladders.
59 ScheduleCall(this, this.AddSearchLadderEffect, 1, 1);
60 return;
61 }
62
AddSearchLadderEffect()63 public func AddSearchLadderEffect()
64 {
65 if (!GetEffect("IntSearchLadder", this))
66 AddEffect("IntSearchLadder", this, 1, 2, this);
67 FxIntSearchLadderTimer();
68 return;
69 }
70
GetLadderScaleAnimation()71 public func GetLadderScaleAnimation()
72 {
73 var animation = _inherited(...);
74 if (animation)
75 return animation;
76 return "Scale";
77 }
78
FxIntSearchLadderTimer(object target,proplist effect,int time)79 public func FxIntSearchLadderTimer(object target, proplist effect, int time)
80 {
81 // Only search for a ladder if jumping.
82 if (GetAction() != "Jump" && GetAction() != "WallJump")
83 return FX_Execute_Kill;
84
85 // Find a ladder which can be climbed.
86 var ladder;
87 for (ladder in FindObjects(Find_AtRect(-5, -10, 10, 8), Find_NoContainer(), Find_Property("IsLadder"), Find_Layer(GetObjectLayer())))
88 {
89 // Don't climb ladders that are blocked.
90 if (ladder->~CanNotBeClimbed(false, this) || IsBlockedLadder(ladder))
91 continue;
92
93 SetAction("Climb");
94 ladder->~OnLadderGrab(this);
95 PlayAnimation(GetLadderScaleAnimation(), CLONK_ANIM_SLOT_Movement, Anim_Y(0, GetAnimationLength(GetLadderScaleAnimation()), 0, 15), Anim_Linear(0, 0, 1000, 5, ANIM_Remove));
96 AddEffect("IntClimbControl", this, 1, 1, this, nil, ladder);
97 return FX_Execute_Kill;
98 }
99 return FX_OK;
100 }
101
FxIntSearchLadderStop(object target,proplist effect,reason,tmp)102 public func FxIntSearchLadderStop(object target, proplist effect, reason, tmp)
103 {
104 if (tmp)
105 return FX_OK;
106 return FX_OK;
107 }
108
109
110 /*-- Ladder Block --*/
111
AddLadderBlock(object ladder,int duration)112 private func AddLadderBlock(object ladder, int duration)
113 {
114 AddEffect("IntBlockLadder", this, 100, duration, this, nil, ladder);
115 return;
116 }
117
IsBlockedLadder(object ladder)118 private func IsBlockedLadder(object ladder)
119 {
120 var index = 0;
121 var fx;
122 while (fx = GetEffect("IntBlockLadder", this, index++))
123 if (fx.ladder && fx.ladder->IsSameLadder(ladder))
124 return true;
125 return false;
126 }
127
FxIntBlockLadderStart(object target,effect fx,int tmp,object to_ladder)128 public func FxIntBlockLadderStart(object target, effect fx, int tmp, object to_ladder)
129 {
130 if (tmp)
131 return FX_OK;
132 fx.ladder = to_ladder;
133 return FX_OK;
134 }
135
FxIntBlockLadderTimer(object target,effect fx,int time)136 public func FxIntBlockLadderTimer(object target, effect fx, int time)
137 {
138 return FX_Execute_Kill;
139 }
140
141
142 /*-- Ladder Control --*/
143
FxIntClimbControlStart(object target,effect fx,int tmp,object ladder)144 public func FxIntClimbControlStart(object target, effect fx, int tmp, object ladder)
145 {
146 if (tmp)
147 return FX_OK;
148 fx.ladder = ladder;
149 SetXDir(0);
150 SetYDir(0);
151 SetComDir(COMD_Stop);
152 // Start on an even segment.
153 fx.odd = 0;
154 // Correctly initalize the relative y-position of the clonk to the segment.
155 var data = fx.ladder->GetLadderData();
156 var sy = data[1], ey = data[3];
157 var cy = GetY(1000);
158 var posy = 0;
159 if (ey - sy != 0)
160 posy = 100 * (cy - sy) / (ey - sy);
161 fx.pos = BoundBy(posy, 0, 100);
162 // Set some stuff for the clonk.
163 SetHandAction(1);
164 SetTurnType(1);
165 return FX_OK;
166 }
167
LadderStep(object target,effect fx,int climb_direction)168 public func LadderStep(object target, effect fx, int climb_direction)
169 {
170 if (climb_direction != 1 && climb_direction != -1)
171 return fx.ladder != nil;
172 // Store old segment to forward to blocking.
173 var old_ladder_segment = fx.ladder;
174 // Increase position depending on direction and move to new segment if needed.
175 fx.pos += 10 * climb_direction;
176 if (fx.pos > 100)
177 {
178 fx.pos = 0;
179 fx.ladder = fx.ladder->GetNextLadder();
180 fx.odd = !fx.odd;
181 }
182 if (fx.pos < 0)
183 {
184 fx.pos = 100;
185 fx.ladder = fx.ladder->GetPreviousLadder();
186 fx.odd = !fx.odd;
187 }
188 // If no ladder has been found scale or jump off.
189 if (fx.ladder == nil)
190 {
191 var contact = GetContact(-1);
192 if (contact & CNAT_Left || contact & CNAT_Right)
193 {
194 SetAction("Scale");
195 old_ladder_segment->~OnLadderReleased(this);
196 return false;
197 }
198 AddLadderBlock(old_ladder_segment, 10);
199 SetAction("Jump");
200 // Increase speed if moving up.
201 if (climb_direction == 1)
202 {
203 SetXDir(-5 + 10 * GetDir());
204 SetYDir(-5);
205 }
206 old_ladder_segment->~OnLadderReleased(this);
207 return false;
208 }
209 return true;
210 }
211
FxIntClimbControlTimer(object target,effect fx,int time)212 public func FxIntClimbControlTimer(object target, effect fx, int time)
213 {
214 if (GetAction() != "Climb" || Contained())
215 return FX_Execute_Kill;
216 if (!fx.ladder || fx.ladder->~CanNotBeClimbed(true, this))
217 {
218 AddLadderBlock(fx.ladder, 5);
219 SetAction("Jump");
220 SetXDir(-5 + 10 * GetDir());
221 SetYDir(-5);
222 return FX_Execute_Kill;
223 }
224
225 // Progress movement in the controlled direction.
226 var climb_direction = 0;
227 if (GetComDir() == COMD_Down)
228 climb_direction = -1;
229 if (GetComDir() == COMD_Up)
230 climb_direction = 1;
231 // LadderStep advances the ladder segment or the ladder position.
232 if (climb_direction && !LadderStep(target, fx, climb_direction))
233 return FX_Execute_Kill;
234
235 // Move the clonk along the ladder according to the new pos/segment.
236 var data = fx.ladder->GetLadderData();
237 var startx = data[0], starty = data[1], endx = data[2], endy = data[3], angle = data[4];
238 var x = startx + (endx - startx) * fx.pos / 100 + 5000 - 100 * GetTurnPhase();
239 var y = starty + (endy - starty) * fx.pos / 100;
240 var lx = LadderToLandscapeCoordinates(x);
241 var ly = LadderToLandscapeCoordinates(y);
242 var old_x = GetX(), old_y = GetY();
243 if (Abs(old_x - lx) + Abs(old_y - ly) > 10 && time > 1)
244 return FX_Execute_Kill;
245 SetPosition(lx, ly);
246 SetXDir(0);
247 SetYDir(0);
248 SetLadderRotation(-angle, x - GetX() * 1000, y - GetY() * 1000);
249
250 // Handle if the clonk gets stuck.
251 if (Stuck())
252 {
253 var dir = -1;
254 if (GetDir() == DIR_Left)
255 dir = 1;
256 for (var i = 1; i <= 5; i++)
257 {
258 SetPosition(LadderToLandscapeCoordinates(x) + i * dir, LadderToLandscapeCoordinates(y));
259 if (!Stuck())
260 break;
261 }
262 if (Stuck())
263 SetPosition(LadderToLandscapeCoordinates(x) + 5 * dir, LadderToLandscapeCoordinates(y));
264 }
265 if (Stuck())
266 {
267 // Revert Position and step.
268 SetPosition(old_x, old_y);
269 if (climb_direction)
270 LadderStep(target, fx, -climb_direction);
271 // If we are too far left or right try to turn.
272 if (GetDir() == DIR_Left && LadderToLandscapeCoordinates(x) - 2 > GetX())
273 {
274 SetComDir(COMD_Right);
275 SetDir(DIR_Right);
276 }
277 else if (GetDir() == DIR_Right && LadderToLandscapeCoordinates(x) + 2 < GetX())
278 {
279 SetComDir(COMD_Left);
280 SetDir(DIR_Left);
281 }
282 }
283 else
284 {
285 fx.ladder->~OnLadderClimb(this);
286 }
287 // Make the animation synchron with movement.
288 // TODO: this only makes the feet synchronous for the arms the animation has to be adapted.
289 var animation = GetRootAnimation(5);
290 if (animation != nil)
291 {
292 if (GetAnimationName(animation) != nil)
293 {
294 var length = GetAnimationLength(GetAnimationName(animation));
295 var pos = fx.pos * length / 200 + length / 2 * fx.odd;
296 SetAnimationPosition(animation, Anim_Const(pos));
297 }
298 }
299 // Start walking or hangling if the clonk makes contact with the floor or ceiling.
300 var contact = GetContact(-1);
301 if (contact)
302 {
303 if (contact & CNAT_Top && GetComDir() == COMD_Up)
304 {
305 SetAction("Hangle");
306 if (GetDir() == 0)
307 {
308 SetComDir(COMD_Right);
309 SetDir(1);
310 }
311 else
312 {
313 SetComDir(COMD_Left);
314 SetDir(0);
315 }
316 return FX_Execute_Kill;
317 }
318 if (contact & CNAT_Bottom && GetComDir() == COMD_Down)
319 {
320 SetAction("Walk");
321 return FX_Execute_Kill;
322 }
323 }
324 return FX_OK;
325 }
326
LadderToLandscapeCoordinates(int x)327 private func LadderToLandscapeCoordinates(int x)
328 {
329 // Round to the next thousand.
330 return (x + 500) / 1000;
331 }
332
FxIntClimbControlStop(object target,effect fx,int reason,bool tmp)333 public func FxIntClimbControlStop(object target, effect fx, int reason, bool tmp)
334 {
335 if (tmp)
336 return FX_OK;
337 if (GetAction() == "Climb")
338 SetAction("Walk");
339 if (fx.ladder)
340 fx.ladder->~OnLadderReleased(this);
341 SetLadderRotation(0);
342 SetHandAction(0);
343 return FX_OK;
344 }
345
FxIntClimbControlControl(object target,effect fx,int ctrl,int x,int y,int strength,bool repeat,int status)346 public func FxIntClimbControlControl(object target, effect fx, int ctrl, int x, int y, int strength, bool repeat, int status)
347 {
348 // Only handle movement controls.
349 if (ctrl != CON_Up && ctrl != CON_Down && ctrl != CON_Right && ctrl != CON_Left)
350 return false;
351 // Perform actions on key down and not on release.
352 if (status != CONS_Down)
353 return false;
354 // Move up and down by setting com dir.
355 if (ctrl == CON_Up)
356 {
357 SetComDir(COMD_Up);
358 return true;
359 }
360 if (ctrl == CON_Down)
361 {
362 SetComDir(COMD_Down);
363 return true;
364 }
365
366 // Handle left and right controls.
367 if (ctrl == CON_Left)
368 {
369 if (GetDir() == DIR_Left)
370 {
371 // Switch to the other side of the ladder.
372 if (GetComDir() == COMD_Stop)
373 {
374 SetPosition(GetX() - 10, GetY());
375 if (!Stuck())
376 {
377 SetComDir(COMD_Right);
378 SetDir(DIR_Right);
379 }
380 SetPosition(GetX() + 10, GetY());
381 }
382 SetComDir(COMD_Stop);
383 }
384 else
385 {
386 // Let go of the ladder and remove the effect.
387 AddLadderBlock(fx.ladder, 5);
388 if (GetComDir() == COMD_Up)
389 this->ObjectCommand("Jump");
390 else
391 this->ObjectComLetGo(-10);
392 RemoveEffect(nil, target, fx);
393 }
394 return true;
395 }
396
397 if (ctrl == CON_Right)
398 {
399 if (GetDir() == DIR_Right)
400 {
401 // Switch to the other side of the ladder.
402 if (GetComDir() == COMD_Stop)
403 {
404 SetPosition(GetX() + 10, GetY());
405 if (!Stuck())
406 {
407 SetComDir(COMD_Left);
408 SetDir(DIR_Left);
409 }
410 SetPosition(GetX() - 10, GetY());
411 }
412 SetComDir(COMD_Stop);
413 }
414 else
415 {
416 // Let go of the ladder and remove the effect.
417 AddLadderBlock(fx.ladder, 5);
418 if (GetComDir() == COMD_Up)
419 this->ObjectCommand("Jump");
420 else
421 this->ObjectComLetGo(10);
422 RemoveEffect(nil, target, fx);
423 }
424 return true;
425 }
426 return true;
427 }
428
SetLadderRotation(int r,int xoff,int yoff)429 public func SetLadderRotation(int r, int xoff, int yoff)
430 {
431 SetMeshTransformation(Trans_Mul(Trans_Translate(0, -10000), Trans_Rotate(-r, 0, 0, 1), Trans_Translate(xoff, 10000 + yoff)), CLONK_MESH_TRANSFORM_SLOT_Rotation_Ladder);
432 return;
433 }
434
435 // Defined to prevent an error, because SetMeshTransformation is overloaded by the clonk.
SetMeshTransformation()436 func SetMeshTransformation() { return _inherited(...); }
437