1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 1998-2000, Matthes Bender
5  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
6  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
7  *
8  * Distributed under the terms of the ISC license; see accompanying file
9  * "COPYING" for details.
10  *
11  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12  * See accompanying file "TRADEMARK" for details.
13  *
14  * To redistribute this file separately, substitute the full license texts
15  * for the above references.
16  */
17 
18 /* Lots of handler functions for object action */
19 
20 #include "C4Include.h"
21 #include "object/C4ObjectCom.h"
22 
23 #include "game/C4Physics.h"
24 #include "graphics/C4GraphicsResource.h"
25 #include "gui/C4GameMessage.h"
26 #include "landscape/C4Material.h"
27 #include "lib/C4Random.h"
28 #include "object/C4Command.h"
29 #include "object/C4Def.h"
30 #include "object/C4GameObjects.h"
31 #include "object/C4Object.h"
32 #include "object/C4ObjectMenu.h"
33 #include "player/C4Player.h"
34 #include "player/C4PlayerList.h"
35 #include "script/C4Effect.h"
36 
37 bool SimFlightHitsLiquid(C4Real fcx, C4Real fcy, C4Real xdir, C4Real ydir);
38 
ObjectActionWalk(C4Object * cObj)39 bool ObjectActionWalk(C4Object *cObj)
40 {
41 	if (!cObj->SetActionByName("Walk")) return false;
42 	return true;
43 }
44 
ObjectActionStand(C4Object * cObj)45 bool ObjectActionStand(C4Object *cObj)
46 {
47 	cObj->Action.ComDir=COMD_Stop;
48 	if (!ObjectActionWalk(cObj)) return false;
49 	return true;
50 }
51 
ObjectActionJump(C4Object * cObj,C4Real xdir,C4Real ydir,bool fByCom)52 bool ObjectActionJump(C4Object *cObj, C4Real xdir, C4Real ydir, bool fByCom)
53 {
54 	// scripted jump?
55 	assert(cObj);
56 	C4AulParSet pars(fixtoi(xdir, 100), fixtoi(ydir, 100), fByCom);
57 	if (!!cObj->Call(PSF_OnActionJump, &pars)) return true;
58 	// hardcoded jump by action
59 	if (!cObj->SetActionByName("Jump")) return false;
60 	cObj->xdir=xdir; cObj->ydir=ydir;
61 	cObj->Mobile=true;
62 	// unstick from ground, because jump command may be issued in an Action-callback,
63 	// where attach-values have already been determined for that frame
64 	cObj->Action.t_attach&=~CNAT_Bottom;
65 	return true;
66 }
67 
ObjectActionDive(C4Object * cObj,C4Real xdir,C4Real ydir)68 bool ObjectActionDive(C4Object *cObj, C4Real xdir, C4Real ydir)
69 {
70 	if (!cObj->SetActionByName("Dive")) return false;
71 	cObj->xdir=xdir; cObj->ydir=ydir;
72 	cObj->Mobile=true;
73 	// unstick from ground, because jump command may be issued in an Action-callback,
74 	// where attach-values have already been determined for that frame
75 	cObj->Action.t_attach&=~CNAT_Bottom;
76 	return true;
77 }
78 
ObjectActionTumble(C4Object * cObj,int32_t dir,C4Real xdir,C4Real ydir)79 bool ObjectActionTumble(C4Object *cObj, int32_t dir, C4Real xdir, C4Real ydir)
80 {
81 	if (!cObj->SetActionByName("Tumble")) return false;
82 	cObj->SetDir(dir);
83 	cObj->xdir=xdir; cObj->ydir=ydir;
84 	return true;
85 }
86 
ObjectActionGetPunched(C4Object * cObj,C4Real xdir,C4Real ydir)87 bool ObjectActionGetPunched(C4Object *cObj, C4Real xdir, C4Real ydir)
88 {
89 	if (!cObj->SetActionByName("GetPunched")) return false;
90 	cObj->xdir=xdir; cObj->ydir=ydir;
91 	return true;
92 }
93 
ObjectActionKneel(C4Object * cObj)94 bool ObjectActionKneel(C4Object *cObj)
95 {
96 	if (!cObj->SetActionByName("KneelDown")) return false;
97 	return true;
98 }
99 
ObjectActionFlat(C4Object * cObj,int32_t dir)100 bool ObjectActionFlat(C4Object *cObj, int32_t dir)
101 {
102 	if (!cObj->SetActionByName("FlatUp")) return false;
103 	cObj->SetDir(dir);
104 	return true;
105 }
106 
ObjectActionScale(C4Object * cObj,int32_t dir)107 bool ObjectActionScale(C4Object *cObj, int32_t dir)
108 {
109 	if (!cObj->SetActionByName("Scale")) return false;
110 	cObj->SetDir(dir);
111 	return true;
112 }
113 
ObjectActionHangle(C4Object * cObj)114 bool ObjectActionHangle(C4Object *cObj)
115 {
116 	if (!cObj->SetActionByName("Hangle")) return false;
117 	return true;
118 }
119 
ObjectActionThrow(C4Object * cObj,C4Object * pThing)120 bool ObjectActionThrow(C4Object *cObj, C4Object *pThing)
121 {
122 	// No object specified, first from contents
123 	if (!pThing) pThing = cObj->Contents.GetObject();
124 	// Nothing to throw
125 	if (!pThing) return false;
126 	// Force and direction
127 	C4Real pthrow=C4REAL100(cObj->GetPropertyInt(P_ThrowSpeed));
128 	int32_t iDir=1; if (cObj->Action.Dir==DIR_Left) iDir=-1;
129 	// Set action
130 	if (!cObj->SetActionByName("Throw")) return false;
131 	// Exit object
132 	pThing->Exit(cObj->GetX(),
133 	             cObj->GetY()+cObj->Shape.y-1,
134 	             Random(360),
135 	             pthrow*iDir+cObj->xdir,-pthrow+cObj->ydir,pthrow*iDir);
136 	// Success
137 	return true;
138 }
139 
ObjectActionDig(C4Object * cObj)140 bool ObjectActionDig(C4Object *cObj)
141 {
142 	if (!cObj->SetActionByName("Dig")) return false;
143 	cObj->Action.Data=0; // Material Dig2Object request
144 	return true;
145 }
146 
ObjectActionPush(C4Object * cObj,C4Object * target)147 bool ObjectActionPush(C4Object *cObj, C4Object *target)
148 {
149 	return cObj->SetActionByName("Push",target);
150 }
151 
CornerScaleOkay(C4Object * cObj,int32_t iRangeX,int32_t iRangeY)152 static bool CornerScaleOkay(C4Object *cObj, int32_t iRangeX, int32_t iRangeY)
153 {
154 	int32_t ctx,cty;
155 	cty=cObj->GetY()-iRangeY;
156 	if (cObj->Action.Dir==DIR_Left) ctx=cObj->GetX()-iRangeX;
157 	else ctx=cObj->GetX()+iRangeX;
158 	cObj->ContactCheck(ctx,cty); // (resets VtxContact & t_contact)
159 	if (!(cObj->t_contact & CNAT_Top))
160 		if (!(cObj->t_contact & CNAT_Left))
161 			if (!(cObj->t_contact & CNAT_Right))
162 				if (!(cObj->t_contact & CNAT_Bottom))
163 					return true;
164 	return false;
165 }
166 
ObjectActionCornerScale(C4Object * cObj)167 bool ObjectActionCornerScale(C4Object *cObj)
168 {
169 	int32_t iRangeX = 1, iRangeY = 1;
170 	if (!CornerScaleOkay(cObj,iRangeX,iRangeY)) return false;
171 	// Do corner scale
172 	if (!cObj->SetActionByName("KneelUp"))
173 		cObj->SetActionByName("Walk");
174 	cObj->xdir=cObj->ydir=0;
175 	if (cObj->Action.Dir==DIR_Left) cObj->fix_x-=itofix(iRangeX);
176 	else cObj->fix_x+=itofix(iRangeX);
177 	cObj->fix_y-=itofix(iRangeY);
178 	return true;
179 }
180 
ObjectComStop(C4Object * cObj)181 bool ObjectComStop(C4Object *cObj)
182 {
183 	// Cease current action
184 	cObj->SetActionByName("Idle");
185 	// Action walk if possible
186 	return ObjectActionStand(cObj);
187 }
188 
ObjectComGrab(C4Object * cObj,C4Object * pTarget)189 bool ObjectComGrab(C4Object *cObj, C4Object *pTarget)
190 {
191 	if (!pTarget) return false;
192 	if (cObj->GetProcedure()!=DFA_WALK) return false;
193 	if (!ObjectActionPush(cObj,pTarget)) return false;
194 	cObj->Call(PSF_Grab, &C4AulParSet(pTarget, true));
195 	if (pTarget->Status && cObj->Status)
196 		pTarget->Call(PSF_Grabbed, &C4AulParSet(cObj, true));
197 	return true;
198 }
199 
ObjectComUnGrab(C4Object * cObj)200 bool ObjectComUnGrab(C4Object *cObj)
201 {
202 	// Only if pushing, -> stand
203 	if (cObj->GetProcedure() == DFA_PUSH)
204 	{
205 		C4Object *pTarget = cObj->Action.Target;
206 		if (ObjectActionStand(cObj))
207 		{
208 			if (!cObj->CloseMenu(false)) return false;
209 			cObj->Call(PSF_Grab, &C4AulParSet(pTarget, false));
210 			// clear action target
211 			cObj->Action.Target = nullptr;
212 			if (pTarget && pTarget->Status && cObj->Status)
213 			{
214 				pTarget->Call(PSF_Grabbed, &C4AulParSet(cObj, false));
215 			}
216 			return true;
217 		}
218 	}
219 
220 	return false;
221 }
222 
ObjectComJump(C4Object * cObj)223 bool ObjectComJump(C4Object *cObj) // by ObjectComUp, ExecCMDFMoveTo, FnJump
224 {
225 	// Only if walking
226 	if (cObj->GetProcedure()!=DFA_WALK) return false;
227 	// Calculate direction & forces
228 	C4Real TXDir=Fix0;
229 	C4PropList *pActionWalk = cObj->GetAction();
230 	C4Real iPhysicalWalk = C4REAL100(pActionWalk->GetPropertyInt(P_Speed)) * itofix(cObj->GetCon(), FullCon);
231 	C4Real iPhysicalJump = C4REAL100(cObj->GetPropertyInt(P_JumpSpeed)) * itofix(cObj->GetCon(), FullCon);
232 
233 	if (cObj->Action.ComDir==COMD_Left || cObj->Action.ComDir==COMD_UpLeft)  TXDir=-iPhysicalWalk;
234 	else if (cObj->Action.ComDir==COMD_Right || cObj->Action.ComDir==COMD_UpRight) TXDir=+iPhysicalWalk;
235 	C4Real x = cObj->fix_x, y = cObj->fix_y;
236 	// find bottom-most vertex, correct starting position for simulation
237 	int32_t iBtmVtx = cObj->Shape.GetBottomVertex();
238 	if (iBtmVtx != -1) { x += cObj->Shape.GetVertexX(iBtmVtx); y += cObj->Shape.GetVertexY(iBtmVtx); }
239 	// Try dive
240 	if (cObj->Shape.ContactDensity > C4M_Liquid)
241 		if (SimFlightHitsLiquid(x,y,TXDir,-iPhysicalJump))
242 			if (ObjectActionDive(cObj,TXDir,-iPhysicalJump))
243 				return true;
244 	// Regular jump
245 	return ObjectActionJump(cObj,TXDir,-iPhysicalJump,true);
246 }
247 
ObjectComLetGo(C4Object * cObj,int32_t xdirf)248 bool ObjectComLetGo(C4Object *cObj, int32_t xdirf)
249 { // by ACTSCALE, ACTHANGLE or ExecCMDFMoveTo
250 	return ObjectActionJump(cObj,itofix(xdirf),Fix0,true);
251 }
252 
ObjectComDig(C4Object * cObj)253 bool ObjectComDig(C4Object *cObj) // by DFA_WALK
254 {
255 	if (!ObjectActionDig(cObj))
256 	{
257 		GameMsgObjectError(FormatString(LoadResStr("IDS_OBJ_NODIG"),cObj->GetName()).getData(),cObj);
258 		return false;
259 	}
260 	return true;
261 }
262 
ObjectComPut(C4Object * cObj,C4Object * pTarget,C4Object * pThing)263 bool ObjectComPut(C4Object *cObj, C4Object *pTarget, C4Object *pThing)
264 {
265 	// No object specified, first from contents
266 	if (!pThing) pThing = cObj->Contents.GetObject();
267 	// Nothing to put
268 	if (!pThing) return false;
269 	// No target
270 	if (!pTarget) return false;
271 	// Grabbing: check C4D_Grab_Put
272 	if (pTarget!=cObj->Contained)
273 		if (!(pTarget->Def->GrabPutGet & C4D_Grab_Put))
274 		{
275 			// No grab put: fail
276 			return false;
277 		}
278 	// Target no fullcon
279 	if (!(pTarget->OCF & OCF_FullCon)) return false;
280 	// Transfer thing
281 	bool fRejectCollect;
282 	if (!pThing->Enter(pTarget, true, true, &fRejectCollect)) return false;
283 	// Put call to object script
284 	cObj->Call(PSF_Put);
285 	// Target collection call
286 	pTarget->Call(PSF_Collection,&C4AulParSet(pThing, true));
287 	// Success
288 	return true;
289 }
290 
ObjectComThrow(C4Object * cObj,C4Object * pThing)291 bool ObjectComThrow(C4Object *cObj, C4Object *pThing)
292 {
293 	// No object specified, first from contents
294 	if (!pThing) pThing = cObj->Contents.GetObject();
295 	// Nothing to throw
296 	if (!pThing) return false;
297 	// Throw com
298 	switch (cObj->GetProcedure())
299 	{
300 	case DFA_WALK: return ObjectActionThrow(cObj,pThing);
301 	}
302 	// Failure
303 	return false;
304 }
305 
ObjectComDrop(C4Object * cObj,C4Object * pThing)306 bool ObjectComDrop(C4Object *cObj, C4Object *pThing)
307 {
308 	// No object specified, first from contents
309 	if (!pThing) pThing = cObj->Contents.GetObject();
310 	// Nothing to throw
311 	if (!pThing) return false;
312 	// Force and direction
313 	// When dropping diagonally, drop from edge of shape
314 	// When doing a diagonal forward drop during flight, exit a bit closer to the Clonk to allow planned tumbling
315 	// Except when hangling, so you can mine effectively form the ceiling, and when swimming because you cannot tumble then
316 	C4Real pthrow=C4REAL100(cObj->GetPropertyInt(P_ThrowSpeed));
317 	int32_t tdir=0; int right=0;
318 	bool isHanglingOrSwimming = false;
319 	int32_t iProc = -1;
320 	C4PropList* pActionDef = cObj->GetAction();
321 	if (pActionDef)
322 	{
323 		iProc = pActionDef->GetPropertyP(P_Procedure);
324 		if (iProc == DFA_HANGLE || iProc == DFA_SWIM) isHanglingOrSwimming = true;
325 	}
326 	int32_t iOutposReduction = 1; // don't exit object too far forward during jump
327 	if (iProc != DFA_SCALE) // never diagonal during scaling (can have com into wall during scaling!)
328 	{
329 		if (ComDirLike(cObj->Action.ComDir, COMD_Left)) { tdir=-1; right = 0; if (cObj->xdir < C4REAL10(15) && !isHanglingOrSwimming) --iOutposReduction; }
330 		if (ComDirLike(cObj->Action.ComDir, COMD_Right)) { tdir=+1; right = 1;  if (cObj->xdir > C4REAL10(-15) && !isHanglingOrSwimming) --iOutposReduction; }
331 	}
332 	// Exit object
333 	pThing->Exit(cObj->GetX() + (cObj->Shape.x + cObj->Shape.Wdt * right) * !!tdir * iOutposReduction,
334 	             cObj->GetY()+cObj->Shape.y+cObj->Shape.Hgt-(pThing->Shape.y+pThing->Shape.Hgt),0,pthrow*tdir,Fix0,Fix0);
335 	// Update OCF
336 	cObj->SetOCF();
337 	// Ungrab
338 	ObjectComUnGrab(cObj);
339 	// Done
340 	return true;
341 }
342 
ObjectComPutTake(C4Object * cObj,C4Object * pTarget,C4Object * pThing)343 bool ObjectComPutTake(C4Object *cObj, C4Object *pTarget, C4Object *pThing) // by C4CMD_Throw
344 {                                                                        // by C4CMD_Drop
345 	// Valid checks
346 	if (!pTarget) return false;
347 	// No object specified, first from contents
348 	if (!pThing) pThing = cObj->Contents.GetObject();
349 	// Has thing, put to target
350 	if (pThing)
351 		return ObjectComPut(cObj,pTarget,pThing);
352 	// If target is own container, activate activation menu
353 	if (pTarget==cObj->Contained)
354 		return ObjectComTake(cObj); // carlo
355 	// Assuming target is grabbed, check for grab get
356 	if (pTarget->Def->GrabPutGet & C4D_Grab_Get)
357 	{
358 		// Activate get menu
359 		return cObj->ActivateMenu(C4MN_Get,0,0,0,pTarget);
360 	}
361 	// Failure
362 	return false;
363 }
364 
365 // carlo
ObjectComTake(C4Object * cObj)366 bool ObjectComTake(C4Object *cObj) // by C4CMD_Take
367 {
368 	return cObj->ActivateMenu(C4MN_Activate);
369 }
370 
371 // carlo
ObjectComTake2(C4Object * cObj)372 bool ObjectComTake2(C4Object *cObj) // by C4CMD_Take2
373 {
374 	return cObj->ActivateMenu(C4MN_Get,0,0,0,cObj->Contained);
375 }
376 
ObjectComPunch(C4Object * cObj,C4Object * pTarget,int32_t punch)377 bool ObjectComPunch(C4Object *cObj, C4Object *pTarget, int32_t punch)
378 {
379 	if (!cObj || !pTarget) return false;
380 	if (!punch) return true;
381 	bool fBlowStopped = !!pTarget->Call(PSF_QueryCatchBlow,&C4AulParSet(cObj));
382 	if (fBlowStopped && punch>1) punch=punch/2; // half damage for caught blow, so shield+armor help in fistfight and vs monsters
383 	pTarget->DoEnergy(-punch, false, C4FxCall_EngGetPunched, cObj->Controller);
384 	int32_t tdir=+1; if (cObj->Action.Dir==DIR_Left) tdir=-1;
385 	pTarget->Action.ComDir=COMD_Stop;
386 	// No tumbles when blow was caught
387 	if (fBlowStopped) return false;
388 	// Hard punch
389 	if (punch>=10)
390 		if (ObjectActionTumble(pTarget,pTarget->Action.Dir,C4REAL100(150)*tdir,itofix(-2)))
391 		{
392 			pTarget->Call(PSF_CatchBlow,&C4AulParSet(punch, cObj));
393 			return true;
394 		}
395 
396 	// Regular punch
397 	if (ObjectActionGetPunched(pTarget,C4REAL100(250)*tdir,Fix0))
398 	{
399 		pTarget->Call(PSF_CatchBlow,&C4AulParSet(punch, cObj));
400 		return true;
401 	}
402 
403 	return false;
404 }
405 
ObjectComCancelAttach(C4Object * cObj)406 bool ObjectComCancelAttach(C4Object *cObj)
407 {
408 	if (cObj->GetProcedure()==DFA_ATTACH)
409 		return cObj->SetAction(nullptr);
410 	return false;
411 }
412 
ObjectComStopDig(C4Object * cObj)413 void ObjectComStopDig(C4Object *cObj)
414 {
415 	// Stand - but keep momentum to allow more dyanamic digging
416 	C4Real o_xdir = cObj->xdir, o_ydir = cObj->ydir;
417 	ObjectActionStand(cObj);
418 	cObj->xdir = o_xdir; cObj->ydir = o_ydir;
419 	// Clear digging command
420 	if (cObj->Command)
421 		if (cObj->Command->Command == C4CMD_Dig)
422 			cObj->ClearCommand(cObj->Command);
423 }
424 
ComDirLike(int32_t iComDir,int32_t iSample)425 bool ComDirLike(int32_t iComDir, int32_t iSample)
426 {
427 	if (iComDir == iSample) return true;
428 	if (iComDir % 8 + 1 == iSample) return true;
429 	if (iComDir == iSample % 8 + 1 ) return true;
430 	return false;
431 }
432 
433