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