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