1 /*
2 	Standard clonk controls
3 	Author: Newton
4 
5 	This object provides handling of the clonk controls including item
6 	management, backpack controls and standard throwing behaviour. It
7 	should be included into any clonk/crew definition.
8 	The controls in System.ocg/PlayerControl.c only provide basic movement
9 	handling, namely the movement left, right, up and down. The rest is
10 	handled here:
11 	Grabbing, ungrabbing, shifting and pushing vehicles into buildings;
12 	entering and exiting buildings; throwing, dropping; backpack control,
13 	(object) menu control, hotkey controls, usage and it's callbacks and
14 	forwards to script.
15 
16 	Objects that inherit this object need to return _inherited(...) in the
17 	following callbacks (if defined):
18 		Construction, Collection2, Ejection, RejectCollect, Departure,
19 		Entrance, AttachTargetLost, CrewSelection, Death,
20 		Destruction, OnActionChanged
21 
22 	The following callbacks are made to other objects:
23 		*Stop
24 		*Left, *Right, *Up, *Down
25 		*Use, *UseStop, *UseStart, *UseHolding, *UseCancel
26 	wheras * is 'Contained' if the clonk is contained and otherwise (riding,
27 	pushing, to self) it is 'Control'. The item in the inventory only gets
28 	the Use*-calls. If the callback is handled, you should return true.
29 	Currently, this is explained more in detail here:
30 	http://forum.openclonk.org/topic_show.pl?tid=337
31 */
32 
33 // make use of other sub-libraries
34 #include Library_Inventory
35 #include Library_ClonkInventoryControl
36 #include Library_ClonkInteractionControl
37 #include Library_ClonkMenuControl
38 #include Library_ClonkUseControl
39 #include Library_ClonkGamepadControl
40 
41 // used for interaction with objects
42 static const ACTIONTYPE_INVENTORY = 0;
43 static const ACTIONTYPE_VEHICLE = 1;
44 static const ACTIONTYPE_STRUCTURE = 2;
45 static const ACTIONTYPE_SCRIPT = 3;
46 static const ACTIONTYPE_EXTRA = 4;
47 
48 // elevators within this range (x) can be called
49 static const ELEVATOR_CALL_DISTANCE = 30;
50 
51 // default throwing angle used while the Clonk isn't aiming
52 static const DEFAULT_THROWING_ANGLE = 500;
53 
54 /* ++++++++++++++++++++++++ Clonk Inventory Control ++++++++++++++++++++++++ */
55 
56 /*
57 	used properties
58 	this.control.hotkeypressed: used to determine if an interaction has already been handled by a hotkey (space + 1-9)
59 
60 	this.control.alt: alternate usage by right mouse button
61 	this.control.mlastx: last x position of the cursor
62 	this.control.mlasty: last y position of the cursor
63 */
64 
65 
66 /* Item limit */
67 local MaxContentsCount = 5; // Size of the clonks inventory
68 local HandObjects = 1; // Amount of hands to select items
NoStackedContentMenu()69 public func NoStackedContentMenu() { return true; }	// Contents-Menu shall display each object in a seperate slot
70 
71 
72 /* ################################################# */
73 
Construction()74 protected func Construction()
75 {
76 	if(this.control == nil)
77 		this.control = {};
78 	this.control.hotkeypressed = false;
79 
80 	this.control.alt = false;
81 	return _inherited(...);
82 }
83 
OnActionChanged(string oldaction)84 protected func OnActionChanged(string oldaction)
85 {
86 	var old_act = this["ActMap"][oldaction];
87 	var act = this["ActMap"][GetAction()];
88 	var old_proc = 0;
89 	if(old_act) old_proc = old_act["Procedure"];
90 	var proc = 0;
91 	if(act) proc = act["Procedure"];
92 	// if the object's procedure has changed from a non Push/Attach
93 	// to a Push/Attach action or the other way round, the usage needs
94 	// to be cancelled
95 	if (proc != old_proc)
96 	{
97 		if (proc == DFA_PUSH || proc == DFA_ATTACH
98 		 || old_proc == DFA_PUSH || old_proc == DFA_ATTACH)
99 		{
100 			CancelUse();
101 		}
102 	}
103 	return _inherited(oldaction,...);
104 }
105 
106 /** Returns additional interactions the clonk possesses as an array of function pointers.
107 	Returned Proplist contains:
108 		Fn			= Name of the function to call
109 		Object		= Object to call the function in. Will also be displayed on the interaction-button
110 		Description	= A description of what the interaction does
111 		IconID		= ID of the definition that contains the icon (like GetInteractionMetaInfo)
112 		IconName	= Name of the graphic for the icon (like GetInteractionMetaInfo)
113 		Priority	= Where to sort in in the interaction-list. 0=front, 10=after script, 20=after vehicles, 30=after structures, nil means no preference
114 */
GetExtraInteractions()115 public func GetExtraInteractions()
116 {
117 	var functions = _inherited(...) ?? [];
118 
119 	// flipping construction-preview
120 	var effect;
121 	if(effect = GetEffect("ControlConstructionPreview", this))
122 	{
123 		if(effect.flipable)
124 			PushBack(functions, {Fn = "Flip", Description=ConstructionPreviewer->GetFlipDescription(), Object=effect.preview, IconID=ConstructionPreviewer_IconFlip, Priority=0});
125 	}
126 	// call elevator cases
127 	var elevators = FindObjects(Find_ID(ElevatorCase), Find_InRect(-ELEVATOR_CALL_DISTANCE, AbsY(0), ELEVATOR_CALL_DISTANCE * 2, GetY() + AbsY(LandscapeHeight())), Find_Func("Ready", this));
128 	for (var elevator in elevators)
129 		PushBack(functions, { Fn = "CallCase", Object=elevator, Description=elevator->GetCallDescription(), Priority=0 });
130 	return functions;
131 }
132 
133 /* +++++++++++++++++++++++++++ Clonk Control +++++++++++++++++++++++++++ */
134 
135 /* Main control function */
ObjectControl(int plr,int ctrl,int x,int y,int strength,bool repeat,int status)136 public func ObjectControl(int plr, int ctrl, int x, int y, int strength, bool repeat, int status)
137 {
138 	if (!this)
139 		return false;
140 
141 	// Contents menu
142 	if (ctrl == CON_Contents && status == CONS_Down)
143 	{
144 		// Close any menu if open.
145 		if (GetMenu())
146 		{
147 			var is_content = GetMenu()->~IsContentMenu();
148 			// unclosable menu? bad luck
149 			if (!this->~TryCancelMenu()) return true;
150 			// If contents menu, don't open new one and return.
151 			if (is_content)
152 				return true;
153 		}
154 		// Open contents menu.
155 		CancelUse();
156 		GUI_ObjectInteractionMenu->CreateFor(this);
157 		// the interaction menu calls SetMenu(this) in the clonk
158 		// so after this call menu = the created menu
159 		if(GetMenu())
160 			GetMenu()->~Show();
161 		return true;
162 	}
163 
164 	/* aiming with mouse:
165 	   The CON_Aim control is transformed into a use command. Con_Use if
166 	   repeated does not bear the updated x,y coordinates, that's why this
167 	   other control needs to be issued and transformed. CON_Aim is a
168 	   control which is issued on mouse move but disabled when not aiming
169 	   or when HoldingEnabled() of the used item does not return true.
170 	   For the rest of the control code, it looks like the x,y coordinates
171 	   came from CON_Use.
172 	  */
173 	if (GetUsedObject() && ctrl == CON_Aim)
174 	{
175 		if (this.control.alt) ctrl = CON_UseAlt;
176 		else     ctrl = CON_Use;
177 
178 		repeat = true;
179 		status = CONS_Down;
180 	}
181 	// controls except a few reset a previously given command
182 	else if (status != CONS_Moved)
183 		SetCommand("None");
184 
185 	/* aiming with analog pad or keys:
186 	   This works completely different. There are CON_AimAxis* and CON_Aim*,
187 	   both work about the same. A virtual cursor is created which moves in a
188 	   circle around the clonk and is controlled via these CON_Aim* functions.
189 	   CON_Aim* is normally on the same buttons as the movement and has a
190 	   higher priority, thus is called first. The aim is always done, even if
191 	   the clonk is not aiming. However this returns only true (=handled) if
192 	   the clonk is really aiming. So if the clonk is not aiming, the virtual
193 	   cursor aims into the direction in which the clonk is running and e.g.
194 	   CON_Left is still called afterwards. So if the clonk finally starts to
195 	   aim, the virtual cursor already aims into the direction in which he ran
196 	*/
197 	if (ctrl == CON_AimAxisUp || ctrl == CON_AimAxisDown || ctrl == CON_AimAxisLeft || ctrl == CON_AimAxisRight)
198 	{
199 		var success = VirtualCursor()->Aim(ctrl,this,strength,repeat,status);
200 		// in any case, CON_Aim* is called but it is only successful if the virtual cursor is aiming
201 		return success && VirtualCursor()->IsAiming();
202 	}
203 
204 	// Simulate a mouse cursor for gamepads.
205 	if (HasVirtualCursor())
206 	{
207 		x = this.control.mlastx;
208 		y = this.control.mlasty;
209 	}
210 
211 	// save last mouse position:
212 	// if the using has to be canceled, no information about the current x,y
213 	// is available. Thus, the last x,y position needs to be saved
214 	else if (ctrl == CON_Use || ctrl == CON_UseAlt)
215 	{
216 		this.control.mlastx = x;
217 		this.control.mlasty = y;
218 	}
219 
220 	var proc = GetProcedure();
221 
222 	// building, vehicle, mount, contents, menu control
223 	var house = Contained();
224 	var vehicle = GetActionTarget();
225 	// the clonk can have an action target even though he lost his action.
226 	// That's why the clonk may only interact with a vehicle if in an
227 	// appropiate procedure:
228 	if (proc != "ATTACH" && proc != "PUSH")
229 		vehicle = nil;
230 
231 	// menu
232 	if (this.control.menu)
233 	{
234 		return Control2Menu(ctrl, x,y,strength, repeat, status);
235 	}
236 
237 	var contents = this->GetHandItem(0);
238 
239 	// usage
240 	var use = (ctrl == CON_Use || ctrl == CON_UseAlt);
241 	if (use)
242 	{
243 		if (house)
244 		{
245 			return ControlUse2Script(ctrl, x, y, strength, repeat, status, house);
246 		}
247 		// control to grabbed vehicle
248 		else if (vehicle && proc == "PUSH")
249 		{
250 			return ControlUse2Script(ctrl, x, y, strength, repeat, status, vehicle);
251 		}
252 		else if (vehicle && proc == "ATTACH")
253 		{
254 			/* objects to which clonks are attached (like horses, mechs,...) have
255 			   a special handling:
256 			   Use controls are, forwarded to the
257 			   horse but if the control is considered unhandled (return false) on
258 			   the start of the usage, the control is forwarded further to the
259 			   item. If the item then returns true on the call, that item is
260 			   regarded as the used item for the subsequent ControlUse* calls.
261 			   BUT the horse always gets the ControlUse*-calls that'd go to the used
262 			   item, too and before it so it can decide at any time to cancel its
263 			   usage via CancelUse().
264 			  */
265 
266 			if (ControlUse2Script(ctrl, x, y, strength, repeat, status, vehicle))
267 				return true;
268 			else
269 			{
270 				// handled if the horse is the used object
271 				// ("using" is set to the object in StartUseControl - when the
272 				// object returns true on that callback. Exactly what we want)
273 				if (GetUsedObject() == vehicle) return true;
274 				// has been cancelled (it is not the start of the usage but no object is used)
275 //				if (vehicle && !GetUsedObject() && (repeat || status == CONS_Up)) return true;
276 			}
277 		}
278 		// releasing the use-key always cancels shelved commands (in that case no GetUsedObject() exists)
279 		if(status == CONS_Up) StopShelvedCommand();
280 		// Release commands are always forwarded even if contents is 0, in case we
281 		// need to cancel use of an object that left inventory
282 		if (contents || (status == CONS_Up && GetUsedObject()))
283 		{
284 			if (ControlUse2Script(ctrl, x, y, strength, repeat, status, contents))
285 				return true;
286 		}
287 	}
288 
289 	// A click on throw can also just abort usage without having any other effects.
290 	// todo: figure out if wise.
291 	var currently_in_use = GetUsedObject() != nil;
292 	if (ctrl == CON_Throw && currently_in_use && status == CONS_Down)
293 	{
294 		CancelUse();
295 		return true;
296 	}
297 
298 	// Throwing and dropping
299 	// only if not in house, not grabbing a vehicle and an item selected
300 	// only act on press, not release
301 	if (ctrl == CON_Throw && !house && (!vehicle || proc == "ATTACH" || proc == "PUSH") && status == CONS_Down)
302 	{
303 		if (contents)
304 		{
305 			// Object overloaded throw control?
306 			// Call this before QueryRejectDeparture to allow alternate use of non-droppable objects
307 			if (contents->~ControlThrow(this, x, y))
308 				return true;
309 
310 			// The object does not want to be dropped? Still handle command.
311 			if (contents->~QueryRejectDeparture(this))
312 				return true;
313 
314 			// Quick-stash into grabbed vehicle?
315 			if (vehicle && proc == "PUSH" && vehicle->~IsContainer())
316 			{
317 				CancelUse();
318 				vehicle->Collect(contents);
319 				if (!contents || contents->Contained() != this)
320 					Sound("Hits::SoftTouch*", false, nil, GetOwner());
321 				return true;
322 			}
323 
324 			// just drop in certain situations
325 			var only_drop = proc == "SCALE" || proc == "HANGLE" || proc == "SWIM";
326 			// also drop if no throw would be possible anyway
327 			if (only_drop || Distance(0, 0, x, y) < 10 || (Abs(x) < 10 && y > 10))
328 				only_drop = true;
329 			// throw
330 			CancelUse();
331 
332 			if (only_drop)
333 				return ObjectCommand("Drop", contents);
334 			else
335 			{
336 				if (HasVirtualCursor() && !VirtualCursor()->IsActive())
337 				{
338 					var angle = DEFAULT_THROWING_ANGLE * (GetDir()*2 - 1);
339 					x = +Sin(angle, CURSOR_Radius, 10);
340 					y = -Cos(angle, CURSOR_Radius, 10);
341 				}
342 				return ObjectCommand("Throw", contents, x, y);
343 			}
344 		}
345 	}
346 
347 	// Movement controls (defined in PlayerControl.c, partly overloaded here)
348 	if (ctrl == CON_Left || ctrl == CON_Right || ctrl == CON_Up || ctrl == CON_Down || ctrl == CON_Jump)
349 	{
350 		// forward to script...
351 		if (house)
352 		{
353 			return ControlMovement2Script(ctrl, x, y, strength, repeat, status, house);
354 		}
355 		else if (vehicle)
356 		{
357 			if (ControlMovement2Script(ctrl, x, y, strength, repeat, status, vehicle)) return true;
358 		}
359 
360 		return ObjectControlMovement(plr, ctrl, strength, status);
361 	}
362 
363 	// Do a roll on landing or when standing. This means that the CON_Down was not handled previously.
364 	if (ctrl == CON_Roll && ComDir2XY(GetComDir())[0] != 0)
365 	{
366 		if (this->IsWalking())
367 		{
368 			if (this->Stuck())
369 			{
370 				// Still show some visual feedback for the player.
371 				this->DoKneel();
372 			}
373 			else
374 			{
375 				this->DoRoll();
376 			}
377 			return true;
378 		}
379 	}
380 
381 	// Fall through half-solid mask
382 	if (ctrl == CON_FallThrough)
383 	{
384 		if(status == CONS_Down)
385 		{
386 			if (this->IsWalking())
387 			{
388 				HalfVehicleFadeJumpStart();
389 			}
390 		}
391 		else
392 		{
393 			HalfVehicleFadeJumpStop();
394 		}
395 		return true;
396 	}
397 
398 	// hotkeys action bar hotkeys
399 	var hot = 0;
400 	if (ctrl == CON_InteractionHotkey0) hot = 10;
401 	if (ctrl == CON_InteractionHotkey1) hot = 1;
402 	if (ctrl == CON_InteractionHotkey2) hot = 2;
403 	if (ctrl == CON_InteractionHotkey3) hot = 3;
404 	if (ctrl == CON_InteractionHotkey4) hot = 4;
405 	if (ctrl == CON_InteractionHotkey5) hot = 5;
406 	if (ctrl == CON_InteractionHotkey6) hot = 6;
407 	if (ctrl == CON_InteractionHotkey7) hot = 7;
408 	if (ctrl == CON_InteractionHotkey8) hot = 8;
409 	if (ctrl == CON_InteractionHotkey9) hot = 9;
410 
411 	if (hot > 0)
412 	{
413 		this.control.hotkeypressed = true;
414 		this->~ControlHotkey(hot-1);
415 		this->~StopInteractionCheck(); // for GUI_Controller_ActionBar
416 		return true;
417 	}
418 
419 	// Unhandled control
420 	return _inherited(plr, ctrl, x, y, strength, repeat, status, ...);
421 }
422 
423 // A wrapper to SetCommand to catch special behaviour for some actions.
ObjectCommand(string command,object target,int tx,int ty,object target2,data)424 public func ObjectCommand(string command, object target, int tx, int ty, object target2, /*any*/ data)
425 {
426 	// special control for throw and jump
427 	// but only with controls, not with general commands
428 	if (command == "Throw")
429 		return this->~ControlThrow(target, tx, ty);
430 	else if (command == "Jump")
431 		return this->~ControlJump();
432 	// else standard command
433 	else
434 	{
435 		// Make sure to not recollect the item immediately on drops.
436 		if (command == "Drop")
437 		{
438 			// Disable collection for a moment.
439 			if (target)
440 				this->OnDropped(target);
441 		}
442 		return SetCommand(command, target, tx, ty, target2, data);
443 	}
444 	// this function might be obsolete: a normal SetCommand does make a callback to
445 	// script before it is executed: ControlCommand(szCommand, pTarget, iTx, iTy)
446 }
447 
448 /*
449 	Called by the engine before a command is executed.
450 	Beware that this is NOT called when SetCommand was called by a script.
451 	At this point I am not sure whether we need this callback at all.
452 */
ControlCommand(string command,object target,int tx,int ty)453 public func ControlCommand(string command, object target, int tx, int ty)
454 {
455 	if (command == "Drop")
456 	{
457 		// Disable collection for a moment.
458 		if (target) this->OnDropped(target);
459 	}
460 	return _inherited(command, target, tx, ty, ...);
461 }
462 
463 /* ++++++++++++++++++++++++ Movement Controls ++++++++++++++++++++++++ */
464 
465 // Control use redirected to script
ControlMovement2Script(int ctrl,int x,int y,int strength,bool repeat,int status,object obj)466 func ControlMovement2Script(int ctrl, int x, int y, int strength, bool repeat, int status, object obj)
467 {
468 	// overloads of movement commandos
469 	if (ctrl == CON_Left || ctrl == CON_Right || ctrl == CON_Down || ctrl == CON_Up || ctrl == CON_Jump)
470 	{
471 		var control_string = "Control";
472 		if (Contained() == obj)
473 			control_string = "Contained";
474 
475 		if (status == CONS_Up)
476 		{
477 			// if any movement key has been released, ControlStop is called
478 			if (obj->Call(Format("~%sStop", control_string), this, ctrl))
479 				return true;
480 		}
481 		else
482 		{
483 			// what about gamepad-deadzone?
484 			if (strength != nil && strength < CON_Gamepad_Deadzone)
485 				return true;
486 
487 			// Control*
488 			if (ctrl == CON_Left)  if (obj->Call(Format("~%sLeft",control_string),this))  return true;
489 			if (ctrl == CON_Right) if (obj->Call(Format("~%sRight",control_string),this)) return true;
490 			if (ctrl == CON_Up)    if (obj->Call(Format("~%sUp",control_string),this))    return true;
491 			if (ctrl == CON_Down)  if (obj->Call(Format("~%sDown",control_string),this))  return true;
492 
493 			// for attached (e.g. horse: also Jump command
494 			if (GetProcedure() == "ATTACH")
495 				if (ctrl == CON_Jump)  if (obj->Call("ControlJump",this)) return true;
496 		}
497 	}
498 
499 }
500 
501 // Effect to free/unfree hands by disabling/enabling scale and hangle procedures
FxIntControlFreeHandsStart(object target,proplist fx,int temp)502 public func FxIntControlFreeHandsStart(object target, proplist fx, int temp)
503 {
504 	// Process on non-temp as well in case scale/handle effects need to stack
505 	// Stop current action
506 	var proc = GetProcedure();
507 	if (proc == "SCALE" || proc == "HANGLE") SetAction("Walk");
508 	// Make sure ActMap is writable
509 	if (this.ActMap == this.Prototype.ActMap) this.ActMap = new this.ActMap{};
510 	// Kill scale/hangle effects
511 	fx.act_scale = this.ActMap.Scale;
512 	this.ActMap.Scale = nil;
513 	fx.act_hangle = this.ActMap.Hangle;
514 	this.ActMap.Hangle = nil;
515 	return FX_OK;
516 }
517 
FxIntControlFreeHandsStop(object target,proplist fx,int reason,bool temp)518 public func FxIntControlFreeHandsStop(object target, proplist fx, int reason, bool temp)
519 {
520 	// Restore scale/hangle effects (engine will handle re-grabbing walls if needed)
521 	if (fx.act_scale) this.ActMap.Scale = fx.act_scale;
522 	if (fx.act_hangle) this.ActMap.Hangle = fx.act_hangle;
523 	return FX_OK;
524 }
525 
526 // returns true if the clonk is able to enter a building (procedurewise)
CanEnter()527 public func CanEnter()
528 {
529 	var proc = GetProcedure();
530 	if (proc != "WALK" && proc != "SWIM" && proc != "SCALE" &&
531 		proc != "HANGLE" && proc != "FLOAT" && proc != "FLIGHT" &&
532 		proc != "PUSH") return false;
533 	return true;
534 }
535 
IsMounted()536 public func IsMounted() { return GetProcedure() == "ATTACH"; }
537 
538 /*-- Throwing --*/
539 
540 // Throwing
DoThrow(object obj,int angle)541 func DoThrow(object obj, int angle)
542 {
543 	// parameters...
544 	var iX, iY, iR, iXDir, iYDir, iRDir;
545 	iX = 4; if (!GetDir()) iX = -iX;
546 	iY = Cos(angle,-4);
547 	iR = Random(360);
548 	iRDir = RandomX(-10,10);
549 
550 	iXDir = Sin(angle,this.ThrowSpeed);
551 	iYDir = Cos(angle,-this.ThrowSpeed);
552 	// throw boost (throws stronger upwards than downwards)
553 	if (iYDir < 0) iYDir = iYDir * 13/10;
554 	if (iYDir > 0) iYDir = iYDir * 8/10;
555 
556 	// add own velocity
557 	iXDir += GetXDir(100)/2;
558 	iYDir += GetYDir(100)/2;
559 
560 	// throw
561 	obj->Exit(iX, iY, iR, 0, 0, iRDir);
562 	obj->SetXDir(iXDir,100);
563 	obj->SetYDir(iYDir,100);
564 
565 	// Prevent hitting the thrower.
566 	var block_blow = AddEffect("BlockBlowControl", this, 100, 3, this);
567 	block_blow.obj = obj;
568 	return true;
569 }
570 
571 // custom throw
572 // implemented in Clonk.ocd/Animations.ocd
ControlThrow()573 public func ControlThrow() { return _inherited(...); }
574 
575 // Effect for blocking a blow by an object.
FxBlockBlowControlTimer()576 public func FxBlockBlowControlTimer()
577 {
578 	return FX_Execute_Kill;
579 }
580 
FxBlockBlowControlQueryCatchBlow(object target,effect fx,object obj)581 public func FxBlockBlowControlQueryCatchBlow(object target, effect fx, object obj)
582 {
583 	if (obj == fx.obj)
584 		return true;
585 	return false;
586 }
587 
588 /*-- Jumping --*/
589 
590 
591 /*
592  Triggers a regular jump, that means that the speed in y direction
593  is automatically decided, depending on the action of the clonk.
594 
595  If you want to execute a jump with a certain speed, use ControlJumpExecute().
596  */
ControlJump()597 public func ControlJump()
598 {
599 	var ydir = 0;
600 
601 	if (GetProcedure() == "WALK")
602 	{
603 		ydir = this.JumpSpeed;
604 	}
605 
606 	if (InLiquid() && !GBackSemiSolid(0, -5))
607 	{
608 		ydir = BoundBy(this.JumpSpeed * 3 / 5, 240, 380);
609 	}
610 
611 	// Jump speed of the wall kick is halved.
612 	if (GetProcedure() == "SCALE" || GetAction() == "Climb")
613 	{
614 		ydir = this.JumpSpeed / 2;
615 	}
616 
617 	return ControlJumpExecute(ydir);
618 }
619 
620 
621 /*
622  Additional function for actually triggering a jump directly.
623 
624  The parameter ydir can be decided directly by the user,
625  or you can use the clonk's jump speed by passing this.JumpSpeed
626 
627  Returns false if the jump was not successful.
628  */
ControlJumpExecute(int ydir)629 public func ControlJumpExecute(int ydir)
630 {
631 	if (ydir && !Stuck())
632 	{
633 		SetPosition(GetX(), GetY() - 1);
634 
635 		// Wall kick if scaling or climbing.
636 		if (GetProcedure() == "SCALE" || GetAction() == "Climb")
637 		{
638 			AddEffect("WallKick", this, 1);
639 			var xdir;
640 			if(GetDir() == DIR_Right)
641 			{
642 				xdir = -1;
643 				SetDir(DIR_Left);
644 			}
645 			else if(GetDir() == DIR_Left)
646 			{
647 				xdir = 1;
648 				SetDir(DIR_Right);
649 			}
650 
651 			SetYDir(-ydir * GetCon(), 100 * 100);
652 			SetXDir(xdir * 17);
653 			// Set speed first to have proper animations when jump starts.
654 			SetAction("Jump");
655 			return true;
656 		}
657 		//Normal jump
658 		else
659 		{
660 			SetYDir(-ydir * GetCon(), 100 * 100);
661 			// Set speed first to have proper animations when jump starts.
662 			SetAction("Jump");
663 			return true;
664 		}
665 	}
666 	return false;
667 }
668 
669 
670 // Interaction with clonks is special:
671 // * The clonk opening the menu should always have higher priority so the clonk is predictably selected on the left side even if standing behind e.g. a crate
672 // * Other clonks should be behind because interaction with them is rare but having your fellow players stand in front of a building is very common
673 //   (Allies also tend to run in front just when you opened that menu...)
GetInteractionPriority(object target)674 func GetInteractionPriority(object target)
675 {
676 	// Self with high priority
677 	if (target == this) return 100;
678 	// Dead Clonks are shown (for a death message e.g.) but sorted to the bottom.
679 	if (!GetAlive()) return -190;
680 	var owner = NO_OWNER;
681 	if (target) owner = target->GetOwner();
682 	// Prefer own clonks for item transfer
683 	if (owner == GetOwner()) return -100;
684 	// If no own clonk, prefer friendly
685 	if (!Hostile(owner, GetOwner())) return -120;
686 	// Hostile clonks? Lowest priority.
687 	return -200;
688 }
689