OpenClonk
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros
C4Object.cpp
Go to the documentation of this file.
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 /* That which fills the world with life */
19 
20 #include "C4Include.h"
22 #include "object/C4Object.h"
23 
24 #include "script/C4AulExec.h"
25 #include "object/C4Def.h"
26 #include "object/C4DefList.h"
27 #include "script/C4Effect.h"
28 #include "object/C4ObjectInfo.h"
29 #include "game/C4Physics.h"
30 #include "landscape/C4PXS.h"
31 #include "object/C4ObjectCom.h"
32 #include "object/C4Command.h"
33 #include "game/C4Viewport.h"
35 #include "control/C4Record.h"
36 #include "landscape/C4SolidMask.h"
37 #include "platform/C4SoundSystem.h"
38 #include "lib/C4Random.h"
39 #include "lib/C4Log.h"
40 #include "player/C4Player.h"
41 #include "object/C4ObjectMenu.h"
42 #include "player/C4RankSystem.h"
43 #include "gui/C4GameMessage.h"
45 #include "graphics/C4Draw.h"
46 #include "game/C4GraphicsSystem.h"
47 #include "game/C4Game.h"
48 #include "game/C4Application.h"
49 #include "player/C4PlayerList.h"
50 #include "object/C4GameObjects.h"
51 #include "control/C4Record.h"
52 #include "object/C4MeshAnimation.h"
54 #include "landscape/fow/C4FoW.h"
55 #include "landscape/C4Particles.h"
56 
57 static void DrawVertex(C4Facet &cgo, float tx, float ty, int32_t col, int32_t contact)
58 {
59  if (Inside<int32_t>(tx,cgo.X,cgo.X+cgo.Wdt) && Inside<int32_t>(ty,cgo.Y,cgo.Y+cgo.Hgt))
60  {
61  pDraw->DrawLineDw(cgo.Surface, tx - 1, ty, tx + 1, ty, col, 0.5f);
62  pDraw->DrawLineDw(cgo.Surface, tx, ty - 1, tx, ty + 1, col, 0.5f);
63  if (contact) pDraw->DrawFrameDw(cgo.Surface,tx-1.5,ty-1.5,tx+1.5,ty+1.5,C4RGB(0xff, 0xff, 0xff));
64  }
65 }
66 
67 void C4Action::SetBridgeData(int32_t iBridgeTime, bool fMoveClonk, bool fWall, int32_t iBridgeMaterial)
68 {
69  // validity
70  iBridgeMaterial = std::min(iBridgeMaterial, ::MaterialMap.Num-1);
71  if (iBridgeMaterial < 0) iBridgeMaterial = 0xff;
72  iBridgeTime = Clamp<int32_t>(iBridgeTime, 0, 0xffff);
73  // mask in this->Data
74  Data = (uint32_t(iBridgeTime) << 16) + (uint32_t(fMoveClonk) << 8) + (uint32_t(fWall) << 9) + iBridgeMaterial;
75 }
76 
77 void C4Action::GetBridgeData(int32_t &riBridgeTime, bool &rfMoveClonk, bool &rfWall, int32_t &riBridgeMaterial)
78 {
79  // mask from this->Data
80  uint32_t uiData = Data;
81  riBridgeTime = (uint32_t(uiData) >> 16);
82  rfMoveClonk = !!(uiData & 0x100);
83  rfWall = !!(uiData & 0x200);
84  riBridgeMaterial = (uiData & 0xff);
85  if (riBridgeMaterial == 0xff) riBridgeMaterial = -1;
86 }
87 
89 {
91  Default();
92 }
93 
95 {
96  id=C4ID::None;
97  nInfo.Clear();
98  RemovalDelay=0;
102  Category=0;
103  Con=0;
104  Mass=OwnMass=0;
105  Damage=0;
106  Energy=0;
107  Alive=0;
108  Breath=0;
109  InMat=MNone;
110  Color=0;
111  lightRange=0;
113  lightColor=0xffffffff;
114  fix_x=fix_y=fix_r=0;
115  xdir=ydir=rdir=0;
116  Mobile=0;
117  Unsorted=false;
118  Initializing=false;
119  OnFire=0;
120  InLiquid=0;
121  EntranceStatus=0;
124  t_contact=0;
125  OCF=0;
126  Action.Default();
127  Shape.Default();
128  fOwnVertices=0;
129  Contents.Default();
130  SolidMask.Default();
132  Def=nullptr;
133  Info=nullptr;
134  Command=nullptr;
135  Contained=nullptr;
136  TopFace.Default();
137  Menu=nullptr;
138  MaterialContents=nullptr;
139  Marker=0;
140  ColorMod=0xffffffff;
141  BlitMode=0;
142  CrewDisabled=false;
143  Layer=nullptr;
144  pSolidMaskData=nullptr;
145  pGraphics=nullptr;
146  pMeshInstance=nullptr;
147  pDrawTransform=nullptr;
148  pEffects=nullptr;
149  pGfxOverlay=nullptr;
151 
153 }
154 
155 bool C4Object::Init(C4PropList *pDef, C4Object *pCreator,
156  int32_t iOwner, C4ObjectInfo *pInfo,
157  int32_t nx, int32_t ny, int32_t nr,
158  C4Real nxdir, C4Real nydir, C4Real nrdir, int32_t iController)
159 {
161  // currently initializing
162  Initializing=true;
163 
164  // Def & basics
165  Owner=iOwner;
166  if (iController > NO_OWNER) Controller = iController; else Controller=iOwner;
168  Info=pInfo;
169  Def=pDef->GetDef(); assert(Def);
171  id=Def->id;
172  if (Info) SetName(pInfo->Name);
174  Plane = Def->GetPlane(); assert(Plane);
175  Def->Count++;
176  if (pCreator) Layer=pCreator->Layer;
177 
178  // graphics
179  pGraphics = &Def->Graphics;
181  {
182  pMeshInstance = new StdMeshInstance(*pGraphics->Mesh, Def->GrowthType ? 1.0f : static_cast<float>(Con)/static_cast<float>(FullCon));
184  }
185  else
186  {
187  pMeshInstance = nullptr;
188  }
189  BlitMode = Def->BlitMode;
190 
191  // Position
192  if (!Def->Rotateable) { nr=0; nrdir=0; }
193  fix_x=itofix(nx);
194  fix_y=itofix(ny);
195  fix_r=itofix(nr);
196  xdir=nxdir; ydir=nydir; rdir=nrdir;
197 
198  // Initial mobility
199  if (!!xdir || !!ydir || !!rdir)
200  Mobile=1;
201 
202  // Mass
203  Mass=std::max<int32_t>(Def->Mass*Con/FullCon,1);
204 
205  // Life, energy, breath
206  if (Category & C4D_Living) Alive=1;
209 
210  // Color
211  if (Def->ColorByOwner)
212  {
213  if (ValidPlr(Owner))
215  else
216  Color=0xff; // no-owner color: blue
217  }
218 
219  // Shape & face
220  Shape=Def->Shape;
223  UpdateGraphics(false);
224  UpdateFace(true);
225 
226  // Initial audibility
228 
229  // Initial OCF
230  SetOCF();
231 
232  // finished initializing
233  Initializing=false;
234 
235  return true;
236 }
237 
239 {
240  Clear();
241 
242 #if defined(_DEBUG)
243  // debug: mustn't be listed in any list now
245 #endif
246 }
247 
249 {
250  if (FrontParticles != 0)
252  if (BackParticles != 0)
255 }
256 
257 void C4Object::AssignRemoval(bool fExitContents)
258 {
259  // check status
260  if (!Status) return;
261  if (Config.General.DebugRec)
262  {
263  C4RCCreateObj rc;
264  memset(&rc, '\0', sizeof(rc));
265  rc.oei=Number;
266  if (Def && Def->GetName()) strncpy(rc.id, Def->GetName(), 32+1);
267  rc.x=GetX(); rc.y=GetY(); rc.ownr=Owner;
268  AddDbgRec(RCT_DsObj, &rc, sizeof(rc));
269  }
270  // Destruction call in container
271  if (Contained)
272  {
273  C4AulParSet pars(this);
275  if (!Status) return;
276  }
277  // Destruction call
279  // Destruction-callback might have deleted the object already
280  if (!Status) return;
281  // remove all effects (extinguishes as well)
282  if (pEffects)
283  {
285  // Effect-callback might actually have deleted the object already
286  if (!Status) return;
287  }
288  // remove particles
290  // Action idle
291  SetAction(0);
292  // Object system operation
293  if (Status == C4OS_INACTIVE)
294  {
295  // object was inactive: activate first, then delete
298  ::Objects.Add(this);
299  }
300  Status=0;
301  // count decrease
302  Def->Count--;
303 
304  // get container for next actions
305  C4Object *pCont = Contained;
306  // remove or exit contents
307  for (C4Object *cobj : Contents)
308  {
309  if (fExitContents)
310  {
311  // move objects to parent container or exit them completely
312  if (!pCont || !cobj->Enter(pCont, false))
313  cobj->Exit(GetX(), GetY());
314  }
315  else
316  {
317  Contents.Remove(cobj);
318  cobj->Contained = nullptr;
319  cobj->AssignRemoval();
320  }
321  }
322  // remove this object from container *after* its contents have been removed!
323  if (pCont)
324  {
325  pCont->Contents.Remove(this);
326  pCont->UpdateMass();
327  pCont->SetOCF();
328  Contained=nullptr;
329  }
330  // Object info
331  if (Info) Info->Retire();
332  Info = nullptr;
333  // Object system operation
334  ClearRefs();
335  Game.ClearPointers(this);
336  ClearCommands();
337  if (pSolidMaskData)
338  {
339  delete pSolidMaskData;
340  pSolidMaskData = nullptr;
341  }
342  SolidMask.Wdt = 0;
343  RemovalDelay=2;
344 }
345 
346 void C4Object::UpdateShape(bool bUpdateVertices)
347 {
348 
349  // Line shape independent
350  if (Def->Line) return;
351 
352  // Copy shape from def
353  Shape.CopyFrom(Def->Shape, bUpdateVertices, !!fOwnVertices);
354 
355  // Construction zoom
356  if (Con!=FullCon)
357  {
358  if (Def->GrowthType)
359  Shape.Stretch(Con, bUpdateVertices);
360  else
361  Shape.Jolt(Con, bUpdateVertices);
362  }
363 
364  // Rotation
365  if (Def->Rotateable)
366  if (fix_r != Fix0)
367  Shape.Rotate(fix_r, bUpdateVertices);
368 
369  // covered area changed? to be on the save side, update pos
370  UpdatePos();
371 }
372 
374 {
375  // get new area covered
376  // do *NOT* do this while initializing, because object cannot be sorted by main list
377  if (!Initializing && Status == C4OS_NORMAL)
378  ::Objects.UpdatePos(this);
379 }
380 
381 void C4Object::UpdateFace(bool bUpdateShape, bool fTemp)
382 {
383 
384  // Update shape - NOT for temp call, because temnp calls are done in drawing routine
385  // must not change sync relevant data here (although the shape and pos *should* be updated at that time anyway,
386  // because a runtime join would desync otherwise)
387  if (!fTemp) { if (bUpdateShape) UpdateShape(); else UpdatePos(); }
388 
389  // SolidMask
390  if (!fTemp) UpdateSolidMask(false);
391 
392  // Null defaults
393  TopFace.Default();
394 
395  // newgfx: TopFace only
396  if (Con>=FullCon || Def->GrowthType)
397  if (!Def->Rotateable || (fix_r == Fix0))
398  if (Def->TopFace.Wdt>0) // Fullcon & no rotation
399  TopFace.Set(GetGraphics()->GetBitmap(Color),
400  Def->TopFace.x,Def->TopFace.y,
402 
403  // Active face
405 }
406 
407 void C4Object::UpdateGraphics(bool fGraphicsChanged, bool fTemp)
408 {
409  // check color
410  if (!fTemp) if (!pGraphics->IsColorByOwner()) Color=0;
411  // new grafics: update face
412  if (fGraphicsChanged)
413  {
414  // Keep mesh instance if it uses the same underlying mesh
416  &pMeshInstance->GetMesh() != pGraphics->Mesh)
417  {
418  // If this mesh is attached somewhere, detach it before deletion
419  if(pMeshInstance && pMeshInstance->GetAttachParent() != nullptr)
420  {
421  // TODO: If the new mesh has a bone with the same name, we could try updating...
423  attach_parent->Parent->DetachMesh(attach_parent->Number);
424  }
425 
426  delete pMeshInstance;
427 
429  {
430  pMeshInstance = new StdMeshInstance(*pGraphics->Mesh, Def->GrowthType ? 1.0f : static_cast<float>(Con)/static_cast<float>(FullCon));
432  }
433  else
434  {
435  pMeshInstance = nullptr;
436  }
437  }
438 
439  // update face - this also puts any SolidMask
440  UpdateFace(false);
441  }
442 }
443 
445 {
446  int32_t iFlipDir;
447  // We're active
448  C4PropList* pActionDef = GetAction();
449  if (pActionDef)
450  // Get flipdir value from action
451  if ((iFlipDir = pActionDef->GetPropertyInt(P_FlipDir)))
452  // Action dir is in flipdir range
453  if (Action.Dir >= iFlipDir)
454  {
455  // Calculate flipped drawing dir (from the flipdir direction going backwards)
456  Action.DrawDir = (iFlipDir - 1 - (Action.Dir - iFlipDir));
457  // Set draw transform, creating one if necessary
458  if (pDrawTransform)
460  else
462  // Done setting flipdir
463  return;
464  }
465  // No flipdir necessary
467  // Draw transform present?
468  if (pDrawTransform)
469  {
470  // reset flip dir
472  // if it's identity now, remove the matrix
473  if (pDrawTransform->IsIdentity())
474  {
475  delete pDrawTransform;
476  pDrawTransform=nullptr;
477  }
478  }
479 }
480 
481 void C4Object::DrawFaceImpl(C4TargetFacet &cgo, bool action, float fx, float fy, float fwdt, float fhgt, float tx, float ty, float twdt, float thgt, C4DrawTransform* transform) const
482 {
483  C4Surface* sfc;
484  switch (GetGraphics()->Type)
485  {
487  // no graphics.
488  break;
490  sfc = action ? Action.Facet.Surface : GetGraphics()->GetBitmap(Color);
491 
492  pDraw->Blit(sfc,
493  fx, fy, fwdt, fhgt,
494  cgo.Surface, tx, ty, twdt, thgt,
495  true, transform);
496  break;
498  C4Value value;
500  StdMeshMatrix matrix;
501  if (!C4ValueToMatrix(value, &matrix))
502  matrix = StdMeshMatrix::Identity();
503 
504  if (fix_r != Fix0)
505  {
506  // Rotation should happen around the mesh center after application of any mesh transformation
507  // So translate back by the transformed mesh center before rotation
508  auto mesh_center = pMeshInstance->GetMesh().GetBoundingBox().GetCenter();
509  mesh_center = matrix * mesh_center;
510  matrix = StdMeshMatrix::Translate(-mesh_center.x, -mesh_center.y, -mesh_center.z) * matrix;
511  matrix = StdMeshMatrix::Rotate(fixtof(fix_r) * (M_PI / 180.0f), 0.0f, 0.0f, 1.0f) * matrix;
512  matrix = StdMeshMatrix::Translate(mesh_center.x, mesh_center.y, mesh_center.z) * matrix;
513  }
514 
515  if(twdt != fwdt || thgt != fhgt)
516  {
517  // Also scale Z so that the mesh is not totally distorted and
518  // so that normals halfway keep pointing into sensible directions.
519  // We don't have a better guess so use the geometric mean for Z scale.
520  matrix = StdMeshMatrix::Scale(twdt/fwdt,thgt/fhgt,std::sqrt(twdt*thgt/(fwdt*fhgt))) * matrix;
521  }
522 
523  pDraw->SetMeshTransform(&matrix);
524 
525  pDraw->RenderMesh(*pMeshInstance, cgo.Surface, tx, ty, twdt, thgt, Color, transform);
526  pDraw->SetMeshTransform(nullptr);
527  break;
528  }
529 }
530 
531 void C4Object::DrawFace(C4TargetFacet &cgo, float offX, float offY, int32_t iPhaseX, int32_t iPhaseY) const
532 {
533  const float swdt = float(Def->Shape.Wdt);
534  const float shgt = float(Def->Shape.Hgt);
535  // Grow Type Display
536  float fx = float(swdt * iPhaseX);
537  float fy = float(shgt * iPhaseY);
538  float fwdt = float(swdt);
539  float fhgt = float(shgt);
540 
541  float stretch_factor = static_cast<float>(Con) / FullCon;
542  float tx = offX + Def->Shape.GetX() * stretch_factor;
543  float ty = offY + Def->Shape.GetY() * stretch_factor;
544  float twdt = swdt * stretch_factor;
545  float thgt = shgt * stretch_factor;
546 
547  // Construction Type Display
548  if (!Def->GrowthType)
549  {
550  tx = offX + Def->Shape.GetX();
551  twdt = swdt;
552 
553  fy += fhgt - thgt;
554  fhgt = thgt;
555  }
556 
557  C4DrawTransform transform;
558  bool transform_active = false;
559  if (pDrawTransform)
560  {
561  transform.SetTransformAt(*pDrawTransform, offX, offY);
562  transform_active = true;
563  }
564 
565  // Meshes aren't rotated via DrawTransform to ensure lighting is applied correctly.
566  if (GetGraphics()->Type != C4DefGraphics::TYPE_Mesh && Def->Rotateable && fix_r != Fix0)
567  {
568  if (pDrawTransform)
569  transform.Rotate(fixtof(fix_r), offX, offY);
570  else
571  transform.SetRotate(fixtof(fix_r), offX, offY);
572  transform_active = true;
573  }
574 
575  DrawFaceImpl(cgo, false, fx, fy, fwdt, fhgt, tx, ty, twdt, thgt, transform_active ? &transform : nullptr);
576 }
577 
578 void C4Object::DrawActionFace(C4TargetFacet &cgo, float offX, float offY) const
579 {
580  // This should not be called for meshes since Facet has no meaning
581  // for them. Only use DrawFace() with meshes!
582  assert(GetGraphics()->Type == C4DefGraphics::TYPE_Bitmap);
583  C4PropList* pActionDef = GetAction();
584 
585  // Regular action facet
586  const float swdt = float(Action.Facet.Wdt);
587  const float shgt = float(Action.Facet.Hgt);
588  int32_t iPhase = Action.Phase;
589  if (pActionDef->GetPropertyInt(P_Reverse)) iPhase = pActionDef->GetPropertyInt(P_Length) - 1 - Action.Phase;
590 
591  // Grow Type Display
592  float fx = float(Action.Facet.X + swdt * iPhase);
593  float fy = float(Action.Facet.Y + shgt * Action.DrawDir);
594  float fwdt = float(swdt);
595  float fhgt = float(shgt);
596 
597  // draw stretched towards shape center with transform
598  float stretch_factor = static_cast<float>(Con) / FullCon;
599  float tx = (Def->Shape.GetX() + Action.FacetX) * stretch_factor + offX;
600  float ty = (Def->Shape.GetY() + Action.FacetY) * stretch_factor + offY;
601  float twdt = swdt * stretch_factor;
602  float thgt = shgt * stretch_factor;
603 
604  // Construction Type Display
605  if (!Def->GrowthType)
606  {
607  // FIXME
608  if (Con != FullCon)
609  {
610  // incomplete constructions do not show actions
611  DrawFace(cgo, offX, offY);
612  return;
613  }
614  tx = Def->Shape.GetX() + Action.FacetX + offX;
615  twdt = swdt;
616  float offset_from_top = shgt * std::max(FullCon - Con, 0) / FullCon;
617  fy += offset_from_top;
618  fhgt -= offset_from_top;
619  }
620 
621  C4DrawTransform transform;
622  bool transform_active = false;
623  if (pDrawTransform)
624  {
625  transform.SetTransformAt(*pDrawTransform, offX, offY);
626  transform_active = true;
627  }
628 
629  // Meshes aren't rotated via DrawTransform to ensure lighting is applied correctly.
630  if (GetGraphics()->Type != C4DefGraphics::TYPE_Mesh && Def->Rotateable && fix_r != Fix0)
631  {
632  if (pDrawTransform)
633  transform.Rotate(fixtof(fix_r), offX, offY);
634  else
635  transform.SetRotate(fixtof(fix_r), offX, offY);
636  transform_active = true;
637  }
638 
639  DrawFaceImpl(cgo, true, fx, fy, fwdt, fhgt, tx, ty, twdt, thgt, transform_active ? &transform : nullptr);
640 }
641 
643 {
644  Mass=std::max<int32_t>((Def->Mass+OwnMass)*Con/FullCon,1);
646  if (Contained)
647  {
650  }
651 }
652 
653 void C4Object::UpdateInMat()
654 {
655  // get new mat
656  int32_t newmat;
657  if (Contained)
659  else
660  newmat = GBackMat(GetX(), GetY());
661 
662  // mat changed?
663  if (newmat != InMat)
664  {
666  InMat = newmat;
667  }
668 }
669 
671 {
672  C4PropList* pActionDef = GetAction();
673 #ifdef DEBUGREC_OCF
674  uint32_t dwOCFOld = OCF;
675 #endif
676  // Update the object character flag according to the object's current situation
677  C4Real cspeed=GetSpeed();
678 #ifdef _DEBUG
680  { LogF("Warning: contained in wild object %p!", static_cast<void*>(Contained)); }
681  else if (Contained && !Contained->Status)
682  { LogF("Warning: contained in deleted object (#%d) (%s)!", Contained->Number, Contained->GetName()); }
683 #endif
684  // OCF_Normal: The OCF is never zero
685  OCF=OCF_Normal;
686  // OCF_Construct: Can be built outside
687  if (Def->Constructable && (Con<FullCon)
688  && (fix_r==Fix0) && !OnFire)
690  // OCF_Grab: Can be pushed
692  OCF|=OCF_Grab;
693  // OCF_Carryable: Can be picked up
696  // OCF_OnFire: Is burning
697  if (OnFire)
698  OCF|=OCF_OnFire;
699  // OCF_Inflammable: Is not burning and is inflammable
702  // OCF_FullCon: Is fully completed/grown
703  if (Con>=FullCon)
704  OCF|=OCF_FullCon;
705  // OCF_Rotate: Can be rotated
706  if (Def->Rotateable)
707  // Don't rotate minimum (invisible) construction sites
708  if (Con>100)
709  OCF|=OCF_Rotate;
710  // OCF_Exclusive: No action through this, no construction in front of this
711  if (Def->Exclusive)
713  // OCF_Entrance: Can currently be entered/activated
714  if ((Def->Entrance.Wdt>0) && (Def->Entrance.Hgt>0))
715  if ((OCF & OCF_FullCon) && ((Def->RotatedEntrance == 1) || (GetR() <= Def->RotatedEntrance)))
716  OCF|=OCF_Entrance;
717  // HitSpeeds
718  if (cspeed>=HitSpeed1) OCF|=OCF_HitSpeed1;
719  if (cspeed>=HitSpeed2) OCF|=OCF_HitSpeed2;
720  if (cspeed>=HitSpeed3) OCF|=OCF_HitSpeed3;
721  if (cspeed>=HitSpeed4) OCF|=OCF_HitSpeed4;
722  // OCF_Collection
723  if ((OCF & OCF_FullCon) || Def->IncompleteActivity)
724  if ((Def->Collection.Wdt>0) && (Def->Collection.Hgt>0))
725  if (!pActionDef || (!pActionDef->GetPropertyInt(P_ObjectDisabled)))
727  // OCF_Alive
728  if (Alive) OCF|=OCF_Alive;
729  // OCF_CrewMember
730  if (Def->CrewMember)
731  if (Alive)
733  // OCF_AttractLightning
734  if (Def->AttractLightning)
735  if (OCF & OCF_FullCon)
737  // OCF_NotContained
738  if (!Contained)
740  // OCF_InLiquid
741  if (InLiquid)
742  if (!Contained)
743  OCF|=OCF_InLiquid;
744  // OCF_InSolid
745  if (!Contained)
746  if (GBackSolid(GetX(), GetY()))
747  OCF|=OCF_InSolid;
748  // OCF_InFree
749  if (!Contained)
750  if (!GBackSemiSolid(GetX(), GetY()-1))
751  OCF|=OCF_InFree;
752  // OCF_Available
754  if (!GBackSemiSolid(GetX(), GetY()-1) || (!GBackSolid(GetX(), GetY()-1) && !GBackSemiSolid(GetX(), GetY()-8)))
756  // OCF_Container
759 #ifdef DEBUGREC_OCF
760  if (Config.General.DebugRec)
761  {
762  C4RCOCF rc = { dwOCFOld, OCF, false };
763  AddDbgRec(RCT_OCF, &rc, sizeof(rc));
764  }
765 #endif
766 }
767 
768 
770 {
771  C4PropList* pActionDef = GetAction();
772 #ifdef DEBUGREC_OCF
773  uint32_t dwOCFOld = OCF;
774 #endif
775  // Update the object character flag according to the object's current situation
776  C4Real cspeed=GetSpeed();
777 #ifdef _DEBUG
779  { LogF("Warning: contained in wild object %p!", static_cast<void*>(Contained)); }
780  else if (Contained && !Contained->Status)
781  { LogF("Warning: contained in deleted object %p (%s)!", static_cast<void*>(Contained), Contained->GetName()); }
782 #endif
783  // Keep the bits that only have to be updated with SetOCF (def, category, con, alive, onfire)
786  // OCF_inflammable: can catch fire and is not currently burning.
788  OCF |= OCF_Inflammable;
789  // OCF_Carryable: Can be picked up
792  // OCF_Grab: Can be grabbed.
794  OCF |= OCF_Grab;
795  // OCF_Construct: Can be built outside
796  if (Def->Constructable && (Con<FullCon)
797  && (fix_r == Fix0) && !OnFire)
799  // OCF_Entrance: Can currently be entered/activated
800  if ((Def->Entrance.Wdt>0) && (Def->Entrance.Hgt>0))
801  if ((OCF & OCF_FullCon) && ((Def->RotatedEntrance == 1) || (GetR() <= Def->RotatedEntrance)))
802  OCF|=OCF_Entrance;
803  // HitSpeeds
804  if (cspeed>=HitSpeed1) OCF|=OCF_HitSpeed1;
805  if (cspeed>=HitSpeed2) OCF|=OCF_HitSpeed2;
806  if (cspeed>=HitSpeed3) OCF|=OCF_HitSpeed3;
807  if (cspeed>=HitSpeed4) OCF|=OCF_HitSpeed4;
808  // OCF_Collection
809  if ((OCF & OCF_FullCon) || Def->IncompleteActivity)
810  if ((Def->Collection.Wdt>0) && (Def->Collection.Hgt>0))
811  if (!pActionDef || (!pActionDef->GetPropertyInt(P_ObjectDisabled)))
813  // OCF_NotContained
814  if (!Contained)
816  // OCF_InLiquid
817  if (InLiquid)
818  if (!Contained)
819  OCF|=OCF_InLiquid;
820  // OCF_InSolid
821  if (!Contained)
822  if (GBackSolid(GetX(), GetY()))
823  OCF|=OCF_InSolid;
824  // OCF_InFree
825  if (!Contained)
826  if (!GBackSemiSolid(GetX(), GetY()-1))
827  OCF|=OCF_InFree;
828  // OCF_Available
830  if (!GBackSemiSolid(GetX(), GetY()-1) || (!GBackSolid(GetX(), GetY()-1) && !GBackSemiSolid(GetX(), GetY()-8)))
832  // OCF_Container
835 #ifdef DEBUGREC_OCF
836  if (Config.General.DebugRec)
837  {
838  C4RCOCF rc = { dwOCFOld, OCF, true };
839  AddDbgRec(RCT_OCF, &rc, sizeof(rc));
840  }
841 #endif
842 #ifdef _DEBUG
844  uint32_t updateOCF = OCF;
845  SetOCF();
846  assert (updateOCF == OCF);
848 #endif
849 }
850 
851 
853 {
854  // Breathing
855  if (!::Game.iTick5)
856  if (Alive && !Def->NoBreath)
857  {
858  // Supply check
859  bool Breathe=false;
860  // Forcefields are breathable.
861  if (GBackMat(GetX(), GetY()+Shape.GetY()/2)==MVehic)
862  { Breathe=true; }
863  else if (GetPropertyInt(P_BreatheWater))
864  { if (GBackMat(GetX(), GetY())==MWater) Breathe=true; }
865  else
866  { if (!GBackSemiSolid(GetX(), GetY()+Shape.GetY()/2)) Breathe=true; }
867  if (Contained) Breathe=true;
868  // No supply
869  if (!Breathe)
870  {
871  // Reduce breath, then energy, bubble
872  if (Breath > 0) DoBreath(-5);
874  }
875  // Supply
876  else
877  {
878  // Take breath
879  int32_t takebreath = GetPropertyInt(P_MaxBreath) - Breath;
880  if (takebreath > 0) DoBreath(takebreath);
881  }
882  }
883 
884  // Corrosion energy loss
885  if (!::Game.iTick10)
886  if (Alive)
887  if (InMat!=MNone)
891 
892  // InMat incineration
893  if (!::Game.iTick10)
894  if (InMat!=MNone)
897  {
899  }
900 
901  // birthday
902  if (!::Game.iTick255)
903  if (Alive)
904  if (Info)
905  {
906  int32_t iPlayingTime = Info->TotalPlayingTime + (Game.Time - Info->InActionTime);
907 
908  int32_t iNewAge = iPlayingTime / 3600 / 5;
909 
910  if (Info->Age != iNewAge && Info->Age)
911  {
912  // message
913  GameMsgObject(FormatString(LoadResStr("IDS_OBJ_BIRTHDAY"),GetName (), Info->TotalPlayingTime / 3600 / 5).getData(),this);
914  StartSoundEffect("UI::Trumpet",false,100,this);
915  }
916 
917  Info->Age = iNewAge;
918 
919 
920  }
921 
922  return true;
923 }
924 
926 {
927  if (Config.General.DebugRec)
928  {
929  // record debug
930  C4RCExecObj rc;
931  rc.Number=Number;
932  rc.fx=fix_x;
933  rc.fy=fix_y;
934  rc.fr=fix_r;
935  AddDbgRec(RCT_ExecObj, &rc, sizeof(rc));
936  }
937  // OCF
938  UpdateOCF();
939  // Command
940  ExecuteCommand();
941  // Action
942  // need not check status, because dead objects have lost their action
943  ExecAction();
944  // commands and actions are likely to have removed the object, and movement
945  // *must not* be executed for dead objects (SolidMask-errors)
946  if (!Status) return;
947  // Movement
948  ExecMovement();
949  if (!Status) return;
950  // effects
951  if (pEffects)
952  {
954  if (!Status) return;
955  }
956  // Life
957  ExecLife();
958  // Animation. If the mesh is attached, then don't execute animation here but let the parent object do it to make sure it is only executed once a frame.
960  pMeshInstance->ExecuteAnimation(1.0f/37.0f /* play smoothly at 37 FPS */);
961  // Menu
962  if (Menu) Menu->Execute();
963 }
964 
965 bool C4Object::At(int32_t ctx, int32_t cty) const
966 {
967  if (Status) if (!Contained) if (Def)
968  if (Inside<int32_t>(cty - (GetY() + Shape.GetY() - addtop()), 0, Shape.Hgt - 1 + addtop()))
969  if (Inside<int32_t>(ctx - (GetX() + Shape.GetX()), 0, Shape.Wdt - 1))
970  return true;
971  return false;
972 }
973 
974 bool C4Object::At(int32_t ctx, int32_t cty, DWORD &ocf) const
975 {
976  if (Status) if (!Contained) if (Def)
977  if (OCF & ocf)
978  if (Inside<int32_t>(cty - (GetY() + Shape.GetY() - addtop()), 0, Shape.Hgt - 1 + addtop()))
979  if (Inside<int32_t>(ctx - (GetX() + Shape.GetX()), 0, Shape.Wdt - 1))
980  {
981  // Set ocf return value
982  GetOCFForPos(ctx, cty, ocf);
983  return true;
984  }
985  return false;
986 }
987 
988 void C4Object::GetOCFForPos(int32_t ctx, int32_t cty, DWORD &ocf) const
989 {
990  DWORD rocf=OCF;
991  // Verify entrance area OCF return
992  if (rocf & OCF_Entrance)
993  if (!Inside<int32_t>(cty - (GetY() + Def->Entrance.y), 0, Def->Entrance.Hgt - 1)
994  || !Inside<int32_t>(ctx - (GetX() + Def->Entrance.x), 0, Def->Entrance.Wdt - 1))
995  rocf &= (~OCF_Entrance);
996  // Verify collection area OCF return
997  if (rocf & OCF_Collection)
998  if (!Inside<int32_t>(cty - (GetY() + Def->Collection.y), 0, Def->Collection.Hgt - 1)
999  || !Inside<int32_t>(ctx - (GetX() + Def->Collection.x), 0, Def->Collection.Wdt - 1))
1000  rocf &= (~OCF_Collection);
1001  ocf=rocf;
1002 }
1003 
1004 void C4Object::AssignDeath(bool fForced)
1005 {
1006  C4Object *thing;
1007  // Alive objects only
1008  if (!Alive) return;
1009  // clear all effects
1010  // do not delete effects afterwards, because they might have denied removal
1011  // set alive-flag before, so objects know what's up
1012  // and prevent recursive death-calls this way
1013  // get death causing player before doing effect calls, because those might meddle around with the flags
1014  int32_t iDeathCausingPlayer = LastEnergyLossCausePlayer;
1015  Alive=0;
1017  // if the object is alive again, abort here if the kill is not forced
1018  if (Alive && !fForced) return;
1019  // Action
1020  SetActionByName("Dead");
1021  // Values
1022  Alive=0;
1023  ClearCommands();
1024  C4ObjectInfo * pInfo = Info;
1025  if (Info)
1026  {
1027  Info->HasDied=true;
1028  ++Info->DeathCount;
1029  Info->Retire();
1030  }
1031  // Remove from crew/cursor/view
1032  C4Player *pPlr = ::Players.Get(Owner);
1033  if (pPlr) pPlr->ClearPointers(this, true);
1034  // Remove from light sources
1035  SetLightRange(0,0);
1036  // Engine script call
1037  C4AulParSet pars(iDeathCausingPlayer);
1038  Call(PSF_Death, &pars);
1039  // Lose contents
1040  while ((thing=Contents.GetObject())) thing->Exit(thing->GetX(),thing->GetY());
1041  // Update OCF. Done here because previously it would have been done in the next frame
1042  // Whats worse: Having the OCF change because of some unrelated script-call like
1043  // SetCategory, or slightly breaking compatibility?
1044  SetOCF();
1045  // Engine broadcast: relaunch player (in CR, this was called from clonk script.
1046  // Now, it is done for every crew member)
1047  if(pPlr)
1048  if(!pPlr->Crew.ObjectCount())
1050  &C4AulParSet(Owner, iDeathCausingPlayer, Status ? this : nullptr));
1051  if (pInfo)
1052  pInfo->HasDied = false;
1053 }
1054 
1056 {
1057  // Get new definition
1058  C4Def *pDef=C4Id2Def(idNew);
1059  if (!pDef) return false;
1060  // Containment storage
1061  C4Object *pContainer=Contained;
1062  // Exit container (no Ejection/Departure)
1063  if (Contained) Exit(0,0,0,Fix0,Fix0,Fix0,false);
1064  // Pre change resets
1065  SetAction(0);
1066  ResetProperty(&Strings.P[P_Action]); // Enforce ActIdle because SetAction may have failed due to NoOtherAction
1067  SetDir(0); // will drop any outdated flipdir
1068  if (pSolidMaskData) { delete pSolidMaskData; pSolidMaskData=nullptr; }
1069  Def->Count--;
1070  // Def change
1071  Def=pDef;
1073  id=pDef->id;
1074  Def->Count++;
1075  // new def: Needs to be resorted
1076  Unsorted=true;
1077  // graphics change
1078  pGraphics = &pDef->Graphics;
1079  // blit mode adjustment
1081  // an object may have newly become an ColorByOwner-object
1082  // if it had been ColorByOwner, but is not now, this will be caught in UpdateGraphics()
1083  if (!Color && ValidPlr(Owner))
1085  if (!Def->Rotateable) { fix_r=rdir=Fix0; }
1086  // Reset solid mask
1088  // Post change updates
1089  UpdateGraphics(true);
1090  UpdateMass();
1091  UpdateFace(true);
1092  SetOCF();
1093  // Any effect callbacks to this object might need to reinitialize their target functions
1094  // This is ugly, because every effect there is must be updated...
1099  for (C4Object *obj : Objects)
1100  if (obj->pEffects) obj->pEffects->OnObjectChangedDef(this);
1101  // Containment (no Entrance)
1102  if (pContainer) Enter(pContainer,false);
1103  // Done
1104  return true;
1105 }
1106 
1107 void C4Object::DoDamage(int32_t iChange, int32_t iCausedBy, int32_t iCause)
1108 {
1109  // non-living: ask effects first
1110  if (pEffects && !Alive)
1111  {
1112  pEffects->DoDamage(iChange, iCause, iCausedBy);
1113  if (!iChange) return;
1114  }
1115  // Change value
1116  Damage = std::max<int32_t>( Damage+iChange, 0 );
1117  // Engine script call
1118  Call(PSF_Damage,&C4AulParSet(iChange, iCause, iCausedBy));
1119 }
1120 
1121 void C4Object::DoEnergy(int32_t iChange, bool fExact, int32_t iCause, int32_t iCausedByPlr)
1122 {
1123  if (!fExact)
1124  {
1125  // Clamp range of change to prevent integer overflow errors
1126  // Do not clamp directly to (0...MaxEnergy)-current_energy, because
1127  // the change value calculated here may be reduced by effect callbacks
1128  int32_t scale = C4MaxPhysical / 100; // iChange 100% = Physical 100000
1129  iChange = Clamp<int32_t>(iChange, std::numeric_limits<int32_t>::min()/scale, std::numeric_limits<int32_t>::max()/scale)*scale;
1130  }
1131  // Was zero?
1132  bool fWasZero=(Energy==0);
1133  // Mark last damage causing player to trace kills
1134  if (iChange < 0) UpdatLastEnergyLossCause(iCausedByPlr);
1135  // Living things: ask effects for change first
1136  if (pEffects && Alive)
1137  pEffects->DoDamage(iChange, iCause, iCausedByPlr);
1138  // Do change
1139  iChange = Clamp<int32_t>(iChange, -Energy, GetPropertyInt(P_MaxEnergy) - Energy);
1140  Energy += iChange;
1141  // call to object
1142  Call(PSF_EnergyChange,&C4AulParSet(iChange, iCause, iCausedByPlr));
1143  // Alive and energy reduced to zero: death
1144  if (Alive) if (Energy==0) if (!fWasZero) AssignDeath(false);
1145 }
1146 
1147 void C4Object::UpdatLastEnergyLossCause(int32_t iNewCausePlr)
1148 {
1149  // Mark last damage causing player to trace kills
1150  // do not regard self-administered damage if there was a previous damage causing player, because that would steal kills
1151  // if people tumble themselves via stop-stop-(left/right)-throw while falling into teh abyss
1152  if (iNewCausePlr != Controller || LastEnergyLossCausePlayer < 0)
1153  {
1154  LastEnergyLossCausePlayer = iNewCausePlr;
1155  }
1156 }
1157 
1158 void C4Object::DoBreath(int32_t iChange)
1159 {
1160  // Do change
1161  iChange = Clamp<int32_t>(iChange, -Breath, GetPropertyInt(P_MaxBreath) - Breath);
1162  Breath += iChange;
1163  // call to object
1164  Call(PSF_BreathChange,&C4AulParSet(iChange));
1165 }
1166 
1167 void C4Object::DoCon(int32_t iChange, bool grow_from_center)
1168 {
1169  C4Real strgt_con_b = fix_y + Shape.GetBottom();
1170  bool fWasFull = (Con>=FullCon);
1171  int32_t old_con = Con;
1172 
1173  // Change con
1174  if (Def->Oversize)
1175  Con=std::max<int32_t>(Con+iChange,0);
1176  else
1177  Con=Clamp<int32_t>(Con+iChange,0,FullCon);
1178 
1179  // Update OCF
1180  SetOCF();
1181 
1182  // Mass
1183  UpdateMass();
1184 
1185  // shape and position
1186  UpdateShape();
1187  // make the bottom-most vertex stay in place
1188  if (!grow_from_center)
1189  {
1190  fix_y = strgt_con_b - Shape.GetBottom();
1191  }
1192  // Face (except for the shape)
1193  UpdateFace(false);
1194 
1195  // Do a callback on completion change.
1196  if (iChange != 0)
1197  Call(PSF_OnCompletionChange, &C4AulParSet(old_con, Con));
1198 
1199  // Unfullcon
1200  if (fWasFull && (Con<FullCon))
1201  {
1202  // Lose contents
1203  if (!Def->IncompleteActivity)
1204  {
1205  C4Object *cobj;
1206  while ((cobj=Contents.GetObject()))
1207  if (Contained) cobj->Enter(Contained);
1208  else cobj->Exit(cobj->GetX(),cobj->GetY());
1209  SetAction(0);
1210  }
1211  }
1212 
1213  // Completion
1214  if (!fWasFull && (Con>=FullCon))
1216 
1217  // Con Zero Removal
1218  if (Con<=0)
1219  AssignRemoval();
1220  // Mesh Graphics Update
1221  else if(pMeshInstance)
1222  pMeshInstance->SetCompletion(Def->GrowthType ? 1.0f : static_cast<float>(Con)/static_cast<float>(FullCon));
1223 }
1224 
1225 void C4Object::DoExperience(int32_t change)
1226 {
1227  const int32_t MaxExperience = 100000000;
1228 
1229  if (!Info) return;
1230 
1231  Info->Experience=Clamp<int32_t>(Info->Experience+change,0,MaxExperience);
1232 
1233  // Promotion check
1234  if (Info->Experience<MaxExperience)
1236  Promote(Info->Rank+1, false, false);
1237 }
1238 
1239 bool C4Object::Exit(int32_t iX, int32_t iY, int32_t iR, C4Real iXDir, C4Real iYDir, C4Real iRDir, bool fCalls)
1240 {
1241  // 1. Exit the current container.
1242  // 2. Update Contents of container object and set Contained to nullptr.
1243  // 3. Set offset position/motion if desired.
1244  // 4. Call Ejection for container and Departure for object.
1245 
1246  // Not contained
1247  C4Object *pContainer=Contained;
1248  if (!pContainer) return false;
1249  // Remove object from container
1250  pContainer->Contents.Remove(this);
1251  pContainer->UpdateMass();
1252  pContainer->SetOCF();
1253  // No container
1254  Contained=nullptr;
1255  // Position/motion
1256  fix_x=itofix(iX); fix_y=itofix(iY);
1257  fix_r=itofix(iR);
1259  xdir=iXDir; ydir=iYDir; rdir=iRDir;
1260  // Misc updates
1261  Mobile=1;
1262  InLiquid=0;
1263  CloseMenu(true);
1264  UpdateFace(true);
1265  SetOCF();
1266  // Object list callback (before script callbacks, because script callbacks may enter again)
1267  ObjectListChangeListener.OnObjectContainerChanged(this, pContainer, nullptr);
1268  // Engine calls
1269  if (fCalls) pContainer->Call(PSF_Ejection,&C4AulParSet(this));
1270  if (fCalls) Call(PSF_Departure,&C4AulParSet(pContainer));
1271  // Success (if the obj wasn't "re-entered" by script)
1272  return !Contained;
1273 }
1274 
1275 bool C4Object::Enter(C4Object *pTarget, bool fCalls, bool fCopyMotion, bool *pfRejectCollect)
1276 {
1277  // 0. Query entrance and collection
1278  // 1. Exit if contained.
1279  // 2. Set new container.
1280  // 3. Update Contents and mass of the new container.
1281  // 4. Call collection for container
1282  // 5. Call entrance for object.
1283 
1284  // No valid target or target is self
1285  if (!pTarget || (pTarget==this)) return false;
1286  // check if entrance is allowed
1287  if (!! Call(PSF_RejectEntrance, &C4AulParSet(pTarget))) return false;
1288  // check if we end up in an endless container-recursion
1289  for (C4Object *pCnt=pTarget->Contained; pCnt; pCnt=pCnt->Contained)
1290  if (pCnt==this) return false;
1291  // Check RejectCollect, if desired
1292  if (pfRejectCollect)
1293  {
1294  if (!!pTarget->Call(PSF_RejectCollection,&C4AulParSet(Def, this)))
1295  {
1296  *pfRejectCollect = true;
1297  return false;
1298  }
1299  *pfRejectCollect = false;
1300  }
1301  // Exit if contained
1302  if (Contained) if (!Exit(GetX(),GetY())) return false;
1303  if (Contained || !Status || !pTarget->Status) return false;
1304  // Failsafe updates
1305  if (Menu)
1306  {
1307  CloseMenu(true);
1308  // CloseMenu might do bad stuff
1309  if (Contained || !Status || !pTarget->Status) return false;
1310  }
1311  SetOCF();
1312  // Set container
1313  Contained=pTarget;
1314  // Enter
1316  {
1317  Contained=nullptr;
1318  return false;
1319  }
1320  // Assume that the new container controls this object, if it cannot control itself (i.e.: Alive)
1321  // So it can be traced back who caused the damage, if a projectile hits its target
1322  if (!Alive)
1323  Controller = pTarget->Controller;
1324  // Misc updates
1325  // motion must be copied immediately, so the position will be correct when OCF is set, and
1326  // OCF_Available will be set for newly bought items, even if 50/50 is solid in the landscape
1327  // however, the motion must be preserved sometimes to keep flags like OCF_HitSpeed upon collection
1328  if (fCopyMotion)
1329  {
1330  // remove any solidmask before copying the motion...
1331  UpdateSolidMask(false);
1333  }
1334  SetOCF();
1335  UpdateFace(true);
1336  // Update container
1337  Contained->UpdateMass();
1338  Contained->SetOCF();
1339  // Object list callback (before script callbacks, because script callbacks may exit again)
1341  // Collection call
1342  if (fCalls) pTarget->Call(PSF_Collection2,&C4AulParSet(this));
1343  if (!Contained || !Contained->Status || !pTarget->Status) return true;
1344  // Entrance call
1345  if (fCalls) Call(PSF_Entrance,&C4AulParSet(Contained));
1346  if (!Contained || !Contained->Status || !pTarget->Status) return true;
1347  // Success
1348  return true;
1349 }
1350 
1351 void C4Object::Fling(C4Real txdir, C4Real tydir, bool fAddSpeed)
1352 {
1353  if (fAddSpeed) { txdir+=xdir/2; tydir+=ydir/2; }
1354  if (!ObjectActionTumble(this,(txdir<0),txdir,tydir))
1355  if (!ObjectActionJump(this,txdir,tydir,false))
1356  {
1357  xdir=txdir; ydir=tydir;
1358  Mobile=1;
1360  }
1361 }
1362 
1363 bool C4Object::ActivateEntrance(int32_t by_plr, C4Object *by_obj)
1364 {
1365 
1366  // Try entrance activation
1367  if (OCF & OCF_Entrance)
1368  if (!! Call(PSF_ActivateEntrance,&C4AulParSet(by_obj)))
1369  return true;
1370  // Failure
1371  return false;
1372 }
1373 
1374 bool C4Object::Push(C4Real txdir, C4Real dforce, bool fStraighten)
1375 {
1376  // Valid check
1377  if (!Status || !Def || Contained || !(OCF & OCF_Grab)) return false;
1378  // Grabbing okay, no pushing
1379  if (GetPropertyInt(P_Touchable)==2) return true;
1380  // Mobilization check (pre-mobilization zero)
1381  if (!Mobile)
1382  { xdir=ydir=Fix0; }
1383  // General pushing force vs. object mass
1384  dforce=dforce*100/Mass;
1385  // Set dir
1386  if (xdir<0) SetDir(DIR_Left);
1387  if (xdir>0) SetDir(DIR_Right);
1388  // Work towards txdir
1389  if (Abs(xdir-txdir)<=dforce) // Close-enough-set
1390  { xdir=txdir; }
1391  else // Work towards
1392  {
1393  if (xdir<txdir) xdir+=dforce;
1394  if (xdir>txdir) xdir-=dforce;
1395  }
1396  // Straighten
1397  if (fStraighten)
1398  {
1399  if (Inside<int32_t>(GetR(),-StableRange,+StableRange))
1400  {
1401  rdir=0; // cheap way out
1402  }
1403  else
1404  {
1405  if (fix_r > Fix0) { if (rdir>-RotateAccel) rdir-=dforce; }
1406  else { if (rdir<+RotateAccel) rdir+=dforce; }
1407  }
1408  }
1409 
1410  // Mobilization check
1411  if (!!xdir || !!ydir || !!rdir) Mobile=1;
1412 
1413  // Stuck check
1414  if (!::Game.iTick35) if (txdir) if (!Def->NoHorizontalMove)
1415  if (ContactCheck(GetX(), GetY())) // Resets t_contact
1416  {
1417  GameMsgObjectError(FormatString(LoadResStr("IDS_OBJ_STUCK"),GetName()).getData(),this);
1418  Call(PSF_Stuck);
1419  }
1420 
1421  return true;
1422 }
1423 
1424 bool C4Object::Lift(C4Real tydir, C4Real dforce)
1425 {
1426  // Valid check
1427  if (!Status || !Def || Contained) return false;
1428  // Mobilization check
1429  if (!Mobile)
1430  { xdir=ydir=Fix0; Mobile=1; }
1431  // General pushing force vs. object mass
1432  dforce=dforce*100/Mass;
1433  // If close enough, set tydir
1434  if (Abs(tydir-ydir)<=Abs(dforce))
1435  ydir=tydir;
1436  else // Work towards tydir
1437  {
1438  if (ydir<tydir) ydir+=dforce;
1439  if (ydir>tydir) ydir-=dforce;
1440  }
1441  // Stuck check
1442  if (tydir != -GravAccel)
1443  if (ContactCheck(GetX(), GetY())) // Resets t_contact
1444  {
1445  GameMsgObjectError(FormatString(LoadResStr("IDS_OBJ_STUCK"),GetName()).getData(),this);
1446  Call(PSF_Stuck);
1447  }
1448  return true;
1449 }
1450 
1452 {
1453  C4Object *nobj;
1454  if (!(nobj=Game.CreateObject(PropList,this,Owner))) return nullptr;
1455  if (!nobj->Enter(this)) { nobj->AssignRemoval(); return nullptr; }
1456  return nobj;
1457 }
1458 
1460 {
1461  int32_t cnt,cnt2;
1462  for (cnt=0; idlist.GetID(cnt); cnt++)
1463  for (cnt2=0; cnt2<idlist.GetCount(cnt); cnt2++)
1464  if (!CreateContents(C4Id2Def(idlist.GetID(cnt))))
1465  return false;
1466  return true;
1467 }
1468 
1469 static void DrawMenuSymbol(int32_t iMenu, C4Facet &cgo, int32_t iOwner)
1470 {
1471  C4Facet ccgo;
1472 
1473  DWORD dwColor=0;
1474  if (ValidPlr(iOwner)) dwColor=::Players.Get(iOwner)->ColorDw;
1475 
1476  switch (iMenu)
1477  {
1478  case C4MN_Buy:
1479  ::GraphicsResource.fctFlagClr.DrawClr(ccgo = cgo.GetFraction(75, 75), true, dwColor);
1481  ::GraphicsResource.fctArrow.Draw(ccgo = cgo.GetFraction(70, 70, C4FCT_Right, C4FCT_Center), false, 0);
1482  break;
1483  case C4MN_Sell:
1484  ::GraphicsResource.fctFlagClr.DrawClr(ccgo = cgo.GetFraction(75, 75), true, dwColor);
1486  ::GraphicsResource.fctArrow.Draw(ccgo = cgo.GetFraction(70, 70, C4FCT_Right, C4FCT_Center), false, 1);
1487  break;
1488  }
1489 }
1490 
1491 bool C4Object::ActivateMenu(int32_t iMenu, int32_t iMenuSelect,
1492  int32_t iMenuData, int32_t iMenuPosition,
1493  C4Object *pTarget)
1494 {
1495  // Variables
1496  C4FacetSurface fctSymbol;
1497  C4IDList ListItems;
1498  // Close any other menu
1499  if (Menu && Menu->IsActive()) if (!Menu->TryClose(true, false)) return false;
1500  // Create menu
1501  if (!Menu) Menu = new C4ObjectMenu; else Menu->ClearItems();
1502  // Open menu
1503  switch (iMenu)
1504  {
1505  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1506  case C4MN_Activate:
1507  // No target specified: use own container as target
1508  if (!pTarget) if (!(pTarget=Contained)) break;
1509  // Opening contents menu blocked by RejectContents
1510  if (!!pTarget->Call(PSF_RejectContents)) return false;
1511  // Create symbol
1512  fctSymbol.Create(C4SymbolSize,C4SymbolSize);
1513  pTarget->Def->Draw(fctSymbol,false,pTarget->Color,pTarget);
1514  // Init
1515  Menu->Init(fctSymbol,FormatString(LoadResStr("IDS_OBJ_EMPTY"),pTarget->GetName()).getData(),this,C4MN_Extra_None,0,iMenu);
1516  Menu->SetPermanent(true);
1517  Menu->SetRefillObject(pTarget);
1518  // Success
1519  return true;
1520  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1521  case C4MN_Buy:
1522  // No target specified: container is base
1523  if (!pTarget) if (!(pTarget=Contained)) break;
1524  // Create symbol
1525  fctSymbol.Create(C4SymbolSize,C4SymbolSize);
1526  DrawMenuSymbol(C4MN_Buy, fctSymbol, pTarget->Owner);
1527  // Init menu
1528  Menu->Init(fctSymbol,LoadResStr("IDS_PLR_NOBUY"),this,C4MN_Extra_Value,0,iMenu);
1529  Menu->SetPermanent(true);
1530  Menu->SetRefillObject(pTarget);
1531  // Success
1532  return true;
1533  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1534  case C4MN_Sell:
1535  // No target specified: container is base
1536  if (!pTarget) if (!(pTarget=Contained)) break;
1537  // Create symbol & init
1538  fctSymbol.Create(C4SymbolSize,C4SymbolSize);
1539  DrawMenuSymbol(C4MN_Sell, fctSymbol, pTarget->Owner);
1540  Menu->Init(fctSymbol,FormatString(LoadResStr("IDS_OBJ_EMPTY"),pTarget->GetName()).getData(),this,C4MN_Extra_Value,0,iMenu);
1541  Menu->SetPermanent(true);
1542  Menu->SetRefillObject(pTarget);
1543  // Success
1544  return true;
1545  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1546  case C4MN_Get:
1547  case C4MN_Contents:
1548  // No target specified
1549  if (!pTarget) break;
1550  // Opening contents menu blocked by RejectContents
1551  if (!!pTarget->Call(PSF_RejectContents)) return false;
1552  // Create symbol & init
1553  fctSymbol.Create(C4SymbolSize,C4SymbolSize);
1554  pTarget->Def->Draw(fctSymbol,false,pTarget->Color,pTarget);
1555  Menu->Init(fctSymbol,FormatString(LoadResStr("IDS_OBJ_EMPTY"),pTarget->GetName()).getData(),this,C4MN_Extra_None,0,iMenu);
1556  Menu->SetPermanent(true);
1557  Menu->SetRefillObject(pTarget);
1558  // Success
1559  return true;
1560  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1561  case C4MN_Info:
1562  // Target by parameter
1563  if (!pTarget) break;
1564  // Create symbol & init menu
1565  fctSymbol.Create(C4SymbolSize, C4SymbolSize); GfxR->fctOKCancel.Draw(fctSymbol,true,0,1);
1566  Menu->Init(fctSymbol, pTarget->GetName(), this, C4MN_Extra_None, 0, iMenu, C4MN_Style_Info);
1567  Menu->SetPermanent(true);
1569  C4Viewport *pViewport = ::Viewports.GetViewport(Controller); // Hackhackhack!!!
1570  if (pViewport) Menu->SetLocation((pTarget->GetX() + pTarget->Shape.GetX() + pTarget->Shape.Wdt + 10 - pViewport->GetViewX()) * pViewport->GetZoom(),
1571  (pTarget->GetY() + pTarget->Shape.GetY() - pViewport->GetViewY()) * pViewport->GetZoom());
1572  // Add info item
1573  fctSymbol.Create(C4PictureSize, C4PictureSize); pTarget->Def->Draw(fctSymbol, false, pTarget->Color, pTarget);
1574  Menu->Add(pTarget->GetName(), fctSymbol, "", C4MN_Item_NoCount, nullptr, pTarget->GetInfoString().getData());
1575  fctSymbol.Default();
1576  // Success
1577  return true;
1578  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1579  }
1580  // Invalid menu identification
1581  CloseMenu(true);
1582  return false;
1583 }
1584 
1585 bool C4Object::CloseMenu(bool fForce)
1586 {
1587  if (Menu)
1588  {
1589  if (Menu->IsActive()) if (!Menu->TryClose(fForce, false)) return false;
1590  if (!Menu->IsCloseQuerying()) { delete Menu; Menu=nullptr; } // protect menu deletion from recursive menu operation calls
1591  }
1592  return true;
1593 }
1594 
1595 BYTE C4Object::GetArea(int32_t &aX, int32_t &aY, int32_t &aWdt, int32_t &aHgt) const
1596 {
1597  if (!Status || !Def) return 0;
1598  aX = GetX() + Shape.GetX(); aY = GetY() + Shape.GetY();
1599  aWdt=Shape.Wdt; aHgt=Shape.Hgt;
1600  return 1;
1601 }
1602 
1603 BYTE C4Object::GetEntranceArea(int32_t &aX, int32_t &aY, int32_t &aWdt, int32_t &aHgt) const
1604 {
1605  if (!Status || !Def) return 0;
1606  // Return actual entrance
1607  if (OCF & OCF_Entrance)
1608  {
1609  aX=GetX() + Def->Entrance.x;
1610  aY=GetY() + Def->Entrance.y;
1611  aWdt=Def->Entrance.Wdt;
1612  aHgt=Def->Entrance.Hgt;
1613  }
1614  // Return object center
1615  else
1616  {
1617  aX=GetX(); aY=GetY();
1618  aWdt=0; aHgt=0;
1619  }
1620  // Done
1621  return 1;
1622 }
1623 
1625 {
1626  rxdir=rydir=0;
1627  if (!Status || !Def) return 0;
1628  rxdir=xdir; rydir=ydir;
1629  return 1;
1630 }
1631 
1633 {
1634  C4Real cobjspd=Fix0;
1635  if (xdir<0) cobjspd-=xdir; else cobjspd+=xdir;
1636  if (ydir<0) cobjspd-=ydir; else cobjspd+=ydir;
1637  return cobjspd;
1638 }
1639 
1641 {
1642  StdStrBuf Output;
1643  // Type
1644  Output.AppendFormat(LoadResStr("IDS_CNS_TYPE"),GetName(),Def->id.ToString());
1645  // Owner
1646  if (ValidPlr(Owner))
1647  {
1648  Output.Append(LineFeed);
1649  Output.AppendFormat(LoadResStr("IDS_CNS_OWNER"),::Players.Get(Owner)->GetName());
1650  }
1651  // Contents
1652  if (Contents.ObjectCount())
1653  {
1654  Output.Append(LineFeed);
1655  Output.Append(LoadResStr("IDS_CNS_CONTENTS"));
1657  }
1658  // Action
1659  if (GetAction())
1660  {
1661  Output.Append(LineFeed);
1662  Output.Append(LoadResStr("IDS_CNS_ACTION"));
1663  Output.Append(GetAction()->GetName());
1664  }
1665  // Properties
1666  Output.Append(LineFeed);
1667  Output.Append(LoadResStr("IDS_CNS_PROPERTIES"));
1668  Output.Append(LineFeed " ");
1669  AppendDataString(&Output, LineFeed " ");
1670  // Effects
1671  if (pEffects)
1672  {
1673  Output.Append(LineFeed);
1674  Output.Append(LoadResStr("IDS_CNS_EFFECTS"));
1675  Output.Append(": ");
1676  }
1677  for (C4Effect *pEffect = pEffects; pEffect; pEffect = pEffect->pNext)
1678  {
1679  Output.Append(LineFeed);
1680  // Effect name
1681  Output.AppendFormat(" %s: Priority %d, Interval %d", pEffect->GetName(), pEffect->iPriority, pEffect->iInterval);
1682  }
1683 
1684  StdStrBuf Output2;
1685  C4ValueNumbers numbers;
1686  DecompileToBuf_Log<StdCompilerINIWrite>(mkNamingAdapt(mkInsertAdapt(mkParAdapt(*this, &numbers),
1687  mkNamingAdapt(numbers, "Values"), false),
1688  "Object"), &Output2, "C4Object::GetDataString");
1689  Output.Append(LineFeed);
1690  Output.Append(Output2);
1691  return Output;
1692 }
1693 
1694 void C4Object::SetName(const char * NewName)
1695 {
1696  if (!NewName && Info)
1698  else
1699  C4PropList::SetName(NewName);
1700 }
1701 
1702 int32_t C4Object::GetValue(C4Object *pInBase, int32_t iForPlayer)
1703 {
1704  C4Value r = Call(PSF_CalcValue, &C4AulParSet(pInBase, iForPlayer));
1705  int32_t iValue;
1706  if (r != C4VNull)
1707  iValue = r.getInt();
1708  else
1709  {
1710  // get value of def
1711  // Caution: Do not pass pInBase here, because the def base value is to be queried
1712  // - and not the value if you had to buy the object in this particular base
1713  iValue = Def->GetValue(nullptr, iForPlayer);
1714  }
1715  // Con percentage
1716  iValue = iValue * Con / FullCon;
1717  // do any adjustments based on where the item is bought
1718  if (pInBase)
1719  {
1720  r = pInBase->Call(PSF_CalcSellValue, &C4AulParSet(this, iValue));
1721  if (r != C4VNull)
1722  iValue = r.getInt();
1723  }
1724  return iValue;
1725 }
1726 
1727 bool C4Object::Promote(int32_t torank, bool exception, bool fForceRankName)
1728 {
1729  if (!Info) return false;
1730  // get rank system
1731  C4Def *pUseDef = C4Id2Def(Info->id);
1732  C4RankSystem *pRankSys;
1733  if (pUseDef && pUseDef->pRankNames)
1734  pRankSys = pUseDef->pRankNames;
1735  else
1736  pRankSys = &::DefaultRanks;
1737  // always promote info
1738  Info->Promote(torank,*pRankSys, fForceRankName);
1739  // silent update?
1740  if (!pRankSys->GetRankName(torank,false)) return false;
1741  GameMsgObject(FormatString(LoadResStr("IDS_OBJ_PROMOTION"),GetName (),Info->sRankName.getData()).getData(),this);
1742 
1743  // call to object
1745 
1746  StartSoundEffect("UI::Trumpet",0,100,this);
1747  return true;
1748 }
1749 
1751 {
1752  // mesh attachments and animation nodes
1754  // effects
1755  if (pEffects) pEffects->ClearPointers(pObj);
1756  // contents/contained: although normally not necessery because it's done in AssignRemoval and StatusDeactivate,
1757  // it is also required during game destruction (because ClearPointers might do script callbacks)
1758  // Perform silent exit to avoid additional callbacks
1759  if (Contained == pObj)
1760  {
1761  Contained->Contents.Remove(this);
1762  Contained = nullptr;
1763  }
1764  Contents.Remove(pObj);
1765  // Action targets
1766  if (Action.Target==pObj) Action.Target=nullptr;
1767  if (Action.Target2==pObj) Action.Target2=nullptr;
1768  // Commands
1769  C4Command *cCom;
1770  for (cCom=Command; cCom; cCom=cCom->Next)
1771  cCom->ClearPointers(pObj);
1772  // Menu
1773  if (Menu) Menu->ClearPointers(pObj);
1774  // Layer
1775  if (Layer==pObj) Layer=nullptr;
1776  // gfx overlays
1777  if (pGfxOverlay)
1778  {
1779  C4GraphicsOverlay *pNextGfxOvrl = pGfxOverlay, *pGfxOvrl;
1780  while ((pGfxOvrl = pNextGfxOvrl))
1781  {
1782  pNextGfxOvrl = pGfxOvrl->GetNext();
1783  if (pGfxOvrl->GetOverlayObject() == pObj)
1784  // overlay relying on deleted object: Delete!
1785  RemoveGraphicsOverlay(pGfxOvrl->GetID());
1786  }
1787  }
1788 }
1789 
1790 bool C4Object::SetPhase(int32_t iPhase)
1791 {
1792  C4PropList* pActionDef = GetAction();
1793  if (!pActionDef) return false;
1794  const int32_t length = pActionDef->GetPropertyInt(P_Length);
1795  Action.Phase=Clamp<int32_t>(iPhase,0,length);
1796  Action.PhaseDelay = 0;
1797  return true;
1798 }
1799 
1800 void C4Object::Draw(C4TargetFacet &cgo, int32_t iByPlayer, DrawMode eDrawMode, float offX, float offY)
1801 {
1802 #ifndef USE_CONSOLE
1803  C4Facet ccgo;
1804 
1805  // Status
1806  if (!Status || !Def) return;
1807 
1808  // visible?
1809  if (!IsVisible(iByPlayer, !!eDrawMode)) return;
1810 
1811  // Set up custom uniforms.
1812  auto uniform_popper = pDraw->scriptUniform.Push(this);
1813 
1814  // Line
1815  if (Def->Line) { DrawLine(cgo, iByPlayer); return; }
1816 
1817  // background particles (bounds not checked)
1818  if (BackParticles) BackParticles->Draw(cgo, this);
1819 
1820  // Object output position
1821  float newzoom = cgo.Zoom;
1822  if (eDrawMode!=ODM_Overlay)
1823  {
1824  if (!GetDrawPosition(cgo, offX, offY, newzoom)) return;
1825  }
1826  ZoomDataStackItem zdsi(newzoom);
1827 
1828  bool fYStretchObject=false;
1829  C4PropList* pActionDef = GetAction();
1830  if (pActionDef)
1831  if (pActionDef->GetPropertyInt(P_FacetTargetStretch))
1832  fYStretchObject=true;
1833 
1834  // Set audibility
1835  if (!eDrawMode) SetAudibilityAt(cgo, GetX(), GetY(), iByPlayer);
1836 
1837  // Output boundary
1838  if (!fYStretchObject && !eDrawMode && !(Category & C4D_Parallax))
1839  {
1840  // For actions with a custom facet set, check against that action facet. Otherwise (or with oversize objects), just check against shape.
1841  if (pActionDef && fix_r == Fix0 && !pActionDef->GetPropertyInt(P_FacetBase) && Con <= FullCon && Action.Facet.Wdt)
1842  {
1843  // active
1844  if ( !Inside<float>(offX+Shape.GetX()+Action.FacetX,cgo.X-Action.Facet.Wdt,cgo.X+cgo.Wdt)
1845  || (!Inside<float>(offY+Shape.GetY()+Action.FacetY,cgo.Y-Action.Facet.Hgt,cgo.Y+cgo.Hgt)) )
1846  {
1847  if (FrontParticles && !Contained) FrontParticles->Draw(cgo, this);
1848  return;
1849  }
1850  }
1851  else
1852  // idle
1853  if ( !Inside<float>(offX+Shape.GetX(),cgo.X-Shape.Wdt,cgo.X+cgo.Wdt)
1854  || (!Inside<float>(offY+Shape.GetY(),cgo.Y-Shape.Hgt,cgo.Y+cgo.Hgt)) )
1855  {
1856  if (FrontParticles && !Contained) FrontParticles->Draw(cgo, this);
1857  return;
1858  }
1859  }
1860 
1861  // ensure correct color is set
1862  if (GetGraphics()->Type == C4DefGraphics::TYPE_Bitmap)
1863  if (GetGraphics()->Bmp.BitmapClr) GetGraphics()->Bmp.BitmapClr->SetClr(Color);
1864 
1865  // Debug Display //////////////////////////////////////////////////////////////////////
1866  if (::GraphicsSystem.ShowCommand && !eDrawMode)
1867  {
1868  C4Command *pCom;
1869  int32_t ccx=GetX(),ccy=GetY();
1870  float offX1, offY1, offX2, offY2, newzoom;
1871  char szCommand[200];
1872  StdStrBuf Cmds;
1873  int32_t iMoveTos=0;
1874  for (pCom=Command; pCom; pCom=pCom->Next)
1875  {
1876  switch (pCom->Command)
1877  {
1878  case C4CMD_MoveTo:
1879  // Angle
1880  int32_t iAngle; iAngle=Angle(ccx,ccy,pCom->Tx._getInt(),pCom->Ty); while (iAngle>180) iAngle-=360;
1881  // Path
1882  if(GetDrawPosition(cgo, ccx, ccy, cgo.Zoom, offX1, offY1, newzoom) &&
1883  GetDrawPosition(cgo, pCom->Tx._getInt(), pCom->Ty, cgo.Zoom, offX2, offY2, newzoom))
1884  {
1885  ZoomDataStackItem zdsi(newzoom);
1886  pDraw->DrawLineDw(cgo.Surface,offX1,offY1,offX2,offY2,C4RGB(0xca,0,0));
1887  pDraw->DrawFrameDw(cgo.Surface,offX2-1,offY2-1,offX2+1,offY2+1,C4RGB(0xca,0,0));
1888  }
1889 
1890  ccx=pCom->Tx._getInt(); ccy=pCom->Ty;
1891  // Message
1892  iMoveTos++; szCommand[0]=0;
1893  break;
1894  case C4CMD_Put:
1895  sprintf(szCommand,"%s %s to %s",CommandName(pCom->Command),pCom->Target2 ? pCom->Target2->GetName() : pCom->Data ? pCom->Data.GetDataString().getData() : "Content",pCom->Target ? pCom->Target->GetName() : "");
1896  break;
1897  case C4CMD_Buy: case C4CMD_Sell:
1898  sprintf(szCommand,"%s %s at %s",CommandName(pCom->Command),pCom->Data.GetDataString().getData(),pCom->Target ? pCom->Target->GetName() : "closest base");
1899  break;
1900  case C4CMD_Acquire:
1901  sprintf(szCommand,"%s %s",CommandName(pCom->Command),pCom->Data.GetDataString().getData());
1902  break;
1903  case C4CMD_Call:
1904  sprintf(szCommand,"%s %s in %s",CommandName(pCom->Command),pCom->Text->GetCStr(),pCom->Target ? pCom->Target->GetName() : "(null)");
1905  break;
1906  case C4CMD_None:
1907  szCommand[0]=0;
1908  break;
1909  case C4CMD_Transfer:
1910  // Path
1911  if(GetDrawPosition(cgo, ccx, ccy, cgo.Zoom, offX1, offY1, newzoom) &&
1912  GetDrawPosition(cgo, pCom->Tx._getInt(), pCom->Ty, cgo.Zoom, offX2, offY2, newzoom))
1913  {
1914  ZoomDataStackItem zdsi(newzoom);
1915  pDraw->DrawLineDw(cgo.Surface,offX1,offY1,offX2,offY2,C4RGB(0,0xca,0));
1916  pDraw->DrawFrameDw(cgo.Surface,offX2-1,offY2-1,offX2+1,offY2+1,C4RGB(0,0xca,0));
1917  }
1918 
1919  ccx=pCom->Tx._getInt(); ccy=pCom->Ty;
1920  // Message
1921  sprintf(szCommand,"%s %s",CommandName(pCom->Command),pCom->Target ? pCom->Target->GetName() : "");
1922  break;
1923  default:
1924  sprintf(szCommand,"%s %s",CommandName(pCom->Command),pCom->Target ? pCom->Target->GetName() : "");
1925  break;
1926  }
1927  // Compose command stack message
1928  if (szCommand[0])
1929  {
1930  // End MoveTo stack first
1931  if (iMoveTos) { Cmds.AppendChar('|'); Cmds.AppendFormat("%dx MoveTo",iMoveTos); iMoveTos=0; }
1932  // Current message
1933  Cmds.AppendChar('|');
1934  if (pCom->Finished) Cmds.Append("<i>");
1935  Cmds.Append(szCommand);
1936  if (pCom->Finished) Cmds.Append("</i>");
1937  }
1938  }
1939  // Open MoveTo stack
1940  if (iMoveTos) { Cmds.AppendChar('|'); Cmds.AppendFormat("%dx MoveTo",iMoveTos); iMoveTos=0; }
1941  // Draw message
1942  int32_t cmwdt,cmhgt; ::GraphicsResource.FontRegular.GetTextExtent(Cmds.getData(),cmwdt,cmhgt,true);
1944  }
1945  // Debug Display ///////////////////////////////////////////////////////////////////////////////
1946 
1947  // Don't draw (show solidmask)
1948  if (::GraphicsSystem.Show8BitSurface != 0)
1949  if (SolidMask.Wdt)
1950  {
1951  // DrawSolidMask(cgo); - no need to draw it, because the 8bit-surface will be shown
1952  return;
1953  }
1954 
1955  // Contained check
1956  if (Contained && !eDrawMode) return;
1957 
1958  // Visibility inside FoW
1959  const C4FoWRegion* pOldFoW = pDraw->GetFoW();
1960  if(pOldFoW && (Category & C4D_IgnoreFoW))
1961  pDraw->SetFoW(nullptr);
1962 
1963  // color modulation (including construction sign...)
1964  if (ColorMod != 0xffffffff || BlitMode) if (!eDrawMode) PrepareDrawing();
1965 
1966  // Not active or rotated: BaseFace only
1967  if (!pActionDef)
1968  {
1969  DrawFace(cgo, offX, offY);
1970  }
1971 
1972  // Active
1973  else
1974  {
1975  // FacetBase
1977  DrawFace(cgo, offX, offY, 0, Action.DrawDir);
1978 
1979  // Special: stretched action facet
1981  {
1982  if (Action.Target)
1984  float(Action.Facet.X),float(Action.Facet.Y),float(Action.Facet.Wdt),float(Action.Facet.Hgt),
1985  cgo.Surface,
1986  offX + Shape.GetX() + Action.FacetX, offY + Shape.GetY() + Action.FacetY,Action.Facet.Wdt,
1988  true);
1989  }
1990  else if (Action.Facet.Surface)
1991  DrawActionFace(cgo, offX, offY);
1992  }
1993 
1994  // end of color modulation
1995  if (ColorMod != 0xffffffff || BlitMode) if (!eDrawMode) FinishedDrawing();
1996 
1997  // draw overlays - after blit mode changes, because overlay gfx set their own
1998  if (pGfxOverlay) if (eDrawMode!=ODM_BaseOnly)
1999  for (C4GraphicsOverlay *pGfxOvrl = pGfxOverlay; pGfxOvrl; pGfxOvrl = pGfxOvrl->GetNext())
2000  if (!pGfxOvrl->IsPicture())
2001  pGfxOvrl->Draw(cgo, this, iByPlayer);
2002 
2003  // local particles in front of the object
2004  if (eDrawMode!=ODM_BaseOnly)
2005  {
2006  if (FrontParticles)
2007  FrontParticles->Draw(cgo, this);
2008  }
2009 
2010  // Debug Display ////////////////////////////////////////////////////////////////////////
2011  if (::GraphicsSystem.ShowVertices) if (eDrawMode!=ODM_BaseOnly)
2012  {
2013  int32_t cnt;
2014  if (Shape.VtxNum>1)
2015  for (cnt=0; cnt<Shape.VtxNum; cnt++)
2016  {
2017  DrawVertex(cgo,
2018  offX+Shape.VtxX[cnt],
2019  offY+Shape.VtxY[cnt],
2020  (Shape.VtxCNAT[cnt] & CNAT_NoCollision) ? C4RGB(0, 0, 0xff) : (Mobile ? C4RGB(0xff, 0, 0) : C4RGB(0xef, 0xef, 0)),
2021  Shape.VtxContactCNAT[cnt]);
2022  }
2023  }
2024 
2025  if (::GraphicsSystem.ShowEntrance) if (eDrawMode!=ODM_BaseOnly)
2026  {
2027  if (OCF & OCF_Entrance)
2028  pDraw->DrawFrameDw(cgo.Surface,offX+Def->Entrance.x,
2029  offY+Def->Entrance.y,
2030  offX+Def->Entrance.x+Def->Entrance.Wdt-1,
2031  offY+Def->Entrance.y+Def->Entrance.Hgt-1,
2032  C4RGB(0, 0, 0xff));
2033  if (OCF & OCF_Collection)
2034  pDraw->DrawFrameDw(cgo.Surface,offX+Def->Collection.x,
2035  offY+Def->Collection.y,
2036  offX+Def->Collection.x+Def->Collection.Wdt-1,
2037  offY+Def->Collection.y+Def->Collection.Hgt-1,
2038  C4RGB(0xca, 0, 0));
2039  }
2040 
2041  if (::GraphicsSystem.ShowAction) if (eDrawMode!=ODM_BaseOnly)
2042  {
2043  if (pActionDef)
2044  {
2045  StdStrBuf str;
2046  str.Format("%s (%d)",pActionDef->GetName(),Action.Phase);
2047  int32_t cmwdt,cmhgt; ::GraphicsResource.FontRegular.GetTextExtent(str.getData(),cmwdt,cmhgt,true);
2049  1.0, cgo.Surface, offX, offY + Shape.GetY() - cmhgt,
2051  }
2052  }
2053  // Debug Display ///////////////////////////////////////////////////////////////////////
2054 
2055  // Restore visibility inside FoW
2056  if (pOldFoW) pDraw->SetFoW(pOldFoW);
2057 #endif
2058 }
2059 
2060 void C4Object::DrawTopFace(C4TargetFacet &cgo, int32_t iByPlayer, DrawMode eDrawMode, float offX, float offY)
2061 {
2062 #ifndef USE_CONSOLE
2063  // Status
2064  if (!Status || !Def) return;
2065  // visible?
2066  if (!IsVisible(iByPlayer, eDrawMode==ODM_Overlay)) return;
2067  // target pos (parallax)
2068  float newzoom = cgo.Zoom;
2069  if (eDrawMode!=ODM_Overlay) GetDrawPosition(cgo, offX, offY, newzoom);
2070  ZoomDataStackItem zdsi(newzoom);
2071  // TopFace
2072  if (!(TopFace.Surface || (OCF & OCF_Construct))) return;
2073  // Output bounds check
2074  if (!Inside<float>(offX, cgo.X - Shape.Wdt, cgo.X + cgo.Wdt)
2075  || !Inside<float>(offY, cgo.Y - Shape.Hgt, cgo.Y + cgo.Hgt))
2076  return;
2077  // Don't draw (show solidmask)
2078  if (::GraphicsSystem.Show8BitSurface != 0 && SolidMask.Wdt) return;
2079  // Contained
2080  if (Contained) if (eDrawMode!=ODM_Overlay) return;
2081  // Construction sign
2082  if (OCF & OCF_Construct && fix_r == Fix0)
2083  if (eDrawMode!=ODM_BaseOnly)
2084  {
2086  pDraw->Blit(fctConSign.Surface,
2087  fctConSign.X, fctConSign.Y,
2088  fctConSign.Wdt, fctConSign.Hgt,
2089  cgo.Surface,
2090  offX + Shape.GetX(), offY + Shape.GetY() + Shape.Hgt - fctConSign.Hgt,
2091  fctConSign.Wdt, fctConSign.Hgt, true);
2092  }
2093  if(TopFace.Surface)
2094  {
2095  // FacetTopFace: Override TopFace.GetX()/GetY()
2096  C4PropList* pActionDef = GetAction();
2097  if (pActionDef && pActionDef->GetPropertyInt(P_FacetTopFace))
2098  {
2099  int32_t iPhase = Action.Phase;
2100  if (pActionDef->GetPropertyInt(P_Reverse)) iPhase = pActionDef->GetPropertyInt(P_Length) - 1 - Action.Phase;
2101  TopFace.X = pActionDef->GetPropertyInt(P_X) + Def->TopFace.x + pActionDef->GetPropertyInt(P_Wdt) * iPhase;
2102  TopFace.Y = pActionDef->GetPropertyInt(P_Y) + Def->TopFace.y + pActionDef->GetPropertyInt(P_Hgt) * Action.DrawDir;
2103  }
2104  // ensure correct color is set
2105  if (GetGraphics()->Bmp.BitmapClr) GetGraphics()->Bmp.BitmapClr->SetClr(Color);
2106  // color modulation
2107  if (!eDrawMode) PrepareDrawing();
2108  // Draw top face bitmap
2109  if (Con!=FullCon && Def->GrowthType)
2110  // stretched
2113  cgo.Surface,
2114  offX + Shape.GetX() + float(Def->TopFace.tx * Con) / FullCon, offY + Shape.GetY() + float(Def->TopFace.ty * Con) / FullCon,
2115  float(TopFace.Wdt * Con) / FullCon, float(TopFace.Hgt * Con) / FullCon,
2116  true, pDrawTransform ? &C4DrawTransform(*pDrawTransform, offX, offY) : nullptr);
2117  else
2118  // normal
2120  TopFace.X,TopFace.Y,
2122  cgo.Surface,
2123  offX + Shape.GetX() + Def->TopFace.tx, offY + Shape.GetY() + Def->TopFace.ty,
2125  true, pDrawTransform ? &C4DrawTransform(*pDrawTransform, offX, offY) : nullptr);
2126  }
2127  // end of color modulation
2128  if (!eDrawMode) FinishedDrawing();
2129 #endif
2130 }
2131 
2132 void C4Object::DrawLine(C4TargetFacet &cgo, int32_t at_player)
2133 {
2134  // Nothing to draw if the object has less than two vertices
2135  if (Shape.VtxNum < 2)
2136  return;
2137 #ifndef USE_CONSOLE
2138  // Audibility
2139  SetAudibilityAt(cgo, Shape.VtxX[0], Shape.VtxY[0], at_player);
2140  SetAudibilityAt(cgo, Shape.VtxX[Shape.VtxNum - 1], Shape.VtxY[Shape.VtxNum - 1], at_player);
2141  // additive mode?
2142  PrepareDrawing();
2143  // Draw line segments
2144  C4Value colorsV; GetProperty(P_LineColors, &colorsV);
2145  C4ValueArray *colors = colorsV.getArray();
2146  // TODO: Edge color (color1) is currently ignored.
2147  int32_t color0 = 0xFFFF00FF;// , color1 = 0xFFFF00FF; // use bright colors so author notices
2148  if (colors)
2149  {
2150  color0 = colors->GetItem(0).getInt();
2151  }
2152 
2153  std::vector<C4BltVertex> vertices;
2154  vertices.resize( (Shape.VtxNum - 1) * 2);
2155  for (int32_t vtx=0; vtx+1<Shape.VtxNum; vtx++)
2156  {
2157  DwTo4UB(color0, vertices[2*vtx].color);
2158  DwTo4UB(color0, vertices[2*vtx+1].color);
2159 
2160  vertices[2*vtx].ftx = Shape.VtxX[vtx] + cgo.X - cgo.TargetX;
2161  vertices[2*vtx].fty = Shape.VtxY[vtx] + cgo.Y - cgo.TargetY;
2162  vertices[2*vtx+1].ftx = Shape.VtxX[vtx+1] + cgo.X - cgo.TargetX;
2163  vertices[2*vtx+1].fty = Shape.VtxY[vtx+1] + cgo.Y - cgo.TargetY;
2164  }
2165 
2166  pDraw->PerformMultiLines(cgo.Surface, &vertices[0], vertices.size(), 1.0f, nullptr);
2167 
2168  // reset blit mode
2169  FinishedDrawing();
2170 #endif
2171 }
2172 
2174 {
2175  bool deserializing = pComp->isDeserializer();
2176  if (deserializing)
2177  Clear();
2178 
2179  // Compile ID, search definition
2180  pComp->Value(mkNamingAdapt(id, "id", C4ID::None ));
2181  if (deserializing)
2182  {
2183  Def = ::Definitions.ID2Def(id);
2184  if (!Def)
2185  { pComp->excNotFound(LoadResStr("IDS_PRC_UNDEFINEDOBJECT"),id.ToString()); return; }
2186  }
2187 
2188  pComp->Value(mkNamingAdapt( mkParAdapt(static_cast<C4PropListNumbered&>(*this), numbers), "Properties"));
2189  pComp->Value(mkNamingAdapt( Status, "Status", 1 ));
2190  if (Info) nInfo = Info->Name; else nInfo.Clear();
2191  pComp->Value(mkNamingAdapt( toC4CStrBuf(nInfo), "Info", "" ));
2192  pComp->Value(mkNamingAdapt( Owner, "Owner", NO_OWNER ));
2193  pComp->Value(mkNamingAdapt( Controller, "Controller", NO_OWNER ));
2194  pComp->Value(mkNamingAdapt( LastEnergyLossCausePlayer, "LastEngLossPlr", NO_OWNER ));
2195  pComp->Value(mkNamingAdapt( Category, "Category", 0 ));
2196  pComp->Value(mkNamingAdapt( Plane, "Plane", 0 ));
2197 
2198  pComp->Value(mkNamingAdapt( iLastAttachMovementFrame, "LastSolidAtchFrame", -1 ));
2199  pComp->Value(mkNamingAdapt( Con, "Size", 0 ));
2200  pComp->Value(mkNamingAdapt( OwnMass, "OwnMass", 0 ));
2201  pComp->Value(mkNamingAdapt( Mass, "Mass", 0 ));
2202  pComp->Value(mkNamingAdapt( Damage, "Damage", 0 ));
2203  pComp->Value(mkNamingAdapt( Energy, "Energy", 0 ));
2204  pComp->Value(mkNamingAdapt( Alive, "Alive", false ));
2205  pComp->Value(mkNamingAdapt( Breath, "Breath", 0 ));
2206  pComp->Value(mkNamingAdapt( Color, "Color", 0u ));
2207  pComp->Value(mkNamingAdapt( fix_x, "X", Fix0 ));
2208  pComp->Value(mkNamingAdapt( fix_y, "Y", Fix0 ));
2209  pComp->Value(mkNamingAdapt( fix_r, "R", Fix0 ));
2210  pComp->Value(mkNamingAdapt( xdir, "XDir", 0 ));
2211  pComp->Value(mkNamingAdapt( ydir, "YDir", 0 ));
2212  pComp->Value(mkNamingAdapt( rdir, "RDir", 0 ));
2213  pComp->Value(mkParAdapt(Shape, &Def->Shape));
2214  pComp->Value(mkNamingAdapt( fOwnVertices, "OwnVertices", false ));
2215  pComp->Value(mkNamingAdapt( SolidMask, "SolidMask", Def->SolidMask ));
2216  pComp->Value(mkNamingAdapt( PictureRect, "Picture" ));
2217  pComp->Value(mkNamingAdapt( Mobile, "Mobile", false ));
2218  pComp->Value(mkNamingAdapt( OnFire, "OnFire", false ));
2219  pComp->Value(mkNamingAdapt( InLiquid, "InLiquid", false ));
2220  pComp->Value(mkNamingAdapt( EntranceStatus, "EntranceStatus", false ));
2221  pComp->Value(mkNamingAdapt( OCF, "OCF", 0u ));
2222  pComp->Value(Action);
2223  pComp->Value(mkNamingAdapt( Contained, "Contained", C4ObjectPtr::Null ));
2224  pComp->Value(mkNamingAdapt( Action.Target, "ActionTarget1", C4ObjectPtr::Null ));
2225  pComp->Value(mkNamingAdapt( Action.Target2, "ActionTarget2", C4ObjectPtr::Null ));
2226  pComp->Value(mkNamingAdapt( mkParAdapt(Contents, numbers), "Contents" ));
2227  pComp->Value(mkNamingAdapt( lightRange, "LightRange", 0 ));
2228  pComp->Value(mkNamingAdapt( lightFadeoutRange, "LightFadeoutRange", 0 ));
2229  pComp->Value(mkNamingAdapt( lightColor, "lightColor", 0xffffffffu ));
2230  pComp->Value(mkNamingAdapt( ColorMod, "ColorMod", 0xffffffffu ));
2231  pComp->Value(mkNamingAdapt( BlitMode, "BlitMode", 0u ));
2232  pComp->Value(mkNamingAdapt( CrewDisabled, "CrewDisabled", false ));
2233  pComp->Value(mkNamingAdapt( Layer, "Layer", C4ObjectPtr::Null ));
2234  pComp->Value(mkNamingAdapt( C4DefGraphicsAdapt(pGraphics), "Graphics", &Def->Graphics ));
2235  pComp->Value(mkNamingPtrAdapt( pDrawTransform, "DrawTransform" ));
2236  pComp->Value(mkParAdapt(mkNamingPtrAdapt( pEffects, "Effects" ), this, numbers));
2237  pComp->Value(mkNamingAdapt( C4GraphicsOverlayListAdapt(pGfxOverlay),"GfxOverlay", (C4GraphicsOverlay *)nullptr));
2238 
2239  // Serialize mesh instance if we have a mesh graphics
2241  {
2242  if(pComp->isDeserializer())
2243  {
2244  assert(!pMeshInstance);
2245  pMeshInstance = new StdMeshInstance(*pGraphics->Mesh, Def->GrowthType ? 1.0f : static_cast<float>(Con)/static_cast<float>(FullCon));
2246  }
2247 
2249 
2250  // Does not work because unanimated meshes without attached meshes
2251  // do not even write a [Mesh] header so this does not create a mesh instance in that case
2252 /* pComp->Value(mkNamingContextPtrAdapt( pMeshInstance, *pGraphics->Mesh, "Mesh"));
2253  if(!pMeshInstance)
2254  pComp->excCorrupt("Mesh graphics without mesh instance");*/
2255  }
2256 
2257  // TODO: Animations / attached meshes
2258 
2259  // Commands
2260  if (pComp->FollowName("Commands"))
2261  {
2262  if (deserializing)
2263  {
2264  C4Command *pCmd = nullptr;
2265  for (int i = 1; ; i++)
2266  {
2267  // Every command has its own naming environment
2268  StdStrBuf Naming = FormatString("Command%d", i);
2269  pComp->Value(mkParAdapt(mkNamingPtrAdapt(pCmd ? pCmd->Next : Command, Naming.getData()), numbers));
2270  // Last command?
2271  pCmd = (pCmd ? pCmd->Next : Command);
2272  if (!pCmd)
2273  break;
2274  pCmd->cObj = this;
2275  }
2276  }
2277  else
2278  {
2279  C4Command *pCmd = Command;
2280  for (int i = 1; pCmd; i++, pCmd = pCmd->Next)
2281  {
2282  StdStrBuf Naming = FormatString("Command%d", i);
2283  pComp->Value(mkNamingAdapt(mkParAdapt(*pCmd, numbers), Naming.getData()));
2284  }
2285  }
2286  }
2287 
2288  // Compiling? Do initialization.
2289  if (deserializing)
2290  {
2291  // add to def count
2292  Def->Count++;
2293 
2294 
2295  // Set action (override running data)
2296  /* FIXME
2297  int32_t iTime=Action.Time;
2298  int32_t iPhase=Action.Phase;
2299  int32_t iPhaseDelay=Action.PhaseDelay;
2300  if (SetActionByName(Action.pActionDef->GetName(),0,0,false))
2301  {
2302  Action.Time=iTime;
2303  Action.Phase=iPhase; // No checking for valid phase
2304  Action.PhaseDelay=iPhaseDelay;
2305  }*/
2306 
2307  if (pMeshInstance)
2308  {
2309  // Set Action animation by slot 0
2312  }
2313 
2314  // blit mode not assigned? use definition default then
2315  if (!BlitMode) BlitMode = Def->BlitMode;
2316 
2317  // object needs to be resorted? May happen if there's unsorted objects in savegame
2318  if (Unsorted) Game.fResortAnyObject = true;
2319  }
2320 
2321 }
2322 
2324 {
2325  C4PropList::Denumerate(numbers);
2326  // Standard enumerated pointers
2331 
2332  // Post-compile object list
2334 
2335  // Commands
2336  for (C4Command *pCom=Command; pCom; pCom=pCom->Next)
2337  pCom->Denumerate(numbers);
2338 
2339  // effects
2340  if (pEffects) pEffects->Denumerate(numbers);
2341 
2342  // gfx overlays
2343  if (pGfxOverlay)
2344  for (C4GraphicsOverlay *pGfxOvrl = pGfxOverlay; pGfxOvrl; pGfxOvrl = pGfxOvrl->GetNext())
2345  pGfxOvrl->DenumeratePointers();
2346 
2347  // mesh instance
2349 }
2350 
2351 void C4Object::DrawPicture(C4Facet &cgo, bool fSelected, C4DrawTransform* transform)
2352 {
2353  // Draw def picture with object color
2354  Def->Draw(cgo,fSelected,Color,this,0,0,transform);
2355 }
2356 
2358 {
2359  // set picture rect to facet
2360  C4Rect fctPicRect = PictureRect;
2361  if (!fctPicRect.Wdt) fctPicRect = Def->PictureRect;
2362  C4Facet fctPicture;
2363  fctPicture.Set(GetGraphics()->GetBitmap(Color),fctPicRect.x,fctPicRect.y,fctPicRect.Wdt,fctPicRect.Hgt);
2364 
2365  // use direct facet w/o own data if possible
2366  if (ColorMod == 0xffffffff && BlitMode == C4GFXBLIT_NORMAL && !pGfxOverlay)
2367  {
2368  cgo.Set(fctPicture);
2369  return;
2370  }
2371 
2372  // otherwise, draw to picture facet
2373  if (!cgo.Create(cgo.Wdt, cgo.Hgt)) return;
2374 
2375  // specific object color?
2376  PrepareDrawing();
2377 
2378  // draw picture itself
2379  fctPicture.Draw(cgo,true);
2380 
2381  // draw overlays
2382  if (pGfxOverlay)
2383  for (C4GraphicsOverlay *pGfxOvrl = pGfxOverlay; pGfxOvrl; pGfxOvrl = pGfxOvrl->GetNext())
2384  if (pGfxOvrl->IsPicture())
2385  pGfxOvrl->DrawPicture(cgo, this, nullptr);
2386 
2387  // done; reset drawing states
2388  FinishedDrawing();
2389 }
2390 
2392 {
2393  // Check owner and controller
2394  if (!ValidPlr(Owner)) Owner=NO_OWNER;
2396  // Color is not reset any more, because many scripts change colors to non-owner-colors these days
2397  // Additionally, player colors are now guarantueed to remain the same in savegame resumes
2398  return true;
2399 }
2400 
2402 {
2403  if (Info || !ValidPlr(Owner)) return false;
2404  // In crew list?
2405  C4Player *pPlr = ::Players.Get(Owner);
2406  if (pPlr->Crew.GetLink(this))
2407  {
2408  // Register with player
2409  if (!::Players.Get(Owner)->MakeCrewMember(this, true, false))
2410  pPlr->Crew.Remove(this);
2411  return true;
2412  }
2413  // Info set, but not in crew list, so
2414  // a) The savegame is old-style (without crew list)
2415  // or b) The clonk is dead
2416  // or c) The clonk belongs to a script player that's restored without Game.txt
2417  else if (nInfo.getLength())
2418  {
2419  if (!::Players.Get(Owner)->MakeCrewMember(this, true, false))
2420  return false;
2421  // Dead and gone (info flags, remove from crew/cursor)
2422  if (!Alive)
2423  {
2424  if (ValidPlr(Owner)) ::Players.Get(Owner)->ClearPointers(this, true);
2425  }
2426  return true;
2427  }
2428  return false;
2429 }
2430 
2432 {
2433  if (!lightRange && !lightFadeoutRange) return true;
2434 
2435  UpdateLight();
2436  return true;
2437 }
2438 
2440 {
2441  if (Info==pInfo)
2442  {
2443  Info=nullptr;
2444  }
2445 }
2446 
2448 {
2450 
2451  if (pEffects) { delete pEffects; pEffects=nullptr; }
2452  if (pSolidMaskData) { delete pSolidMaskData; pSolidMaskData=nullptr; }
2453  if (Menu) delete Menu; Menu=nullptr;
2454  if (MaterialContents) delete MaterialContents; MaterialContents=nullptr;
2455  // clear commands!
2456  C4Command *pCom, *pNext;
2457  for (pCom=Command; pCom; pCom=pNext)
2458  {
2459  pNext=pCom->Next; delete pCom; pCom=pNext;
2460  }
2461  if (pDrawTransform) { delete pDrawTransform; pDrawTransform=nullptr; }
2462  if (pGfxOverlay) { delete pGfxOverlay; pGfxOverlay=nullptr; }
2463  if (pMeshInstance) { delete pMeshInstance; pMeshInstance = nullptr; }
2464 }
2465 
2466 bool C4Object::MenuCommand(const char *szCommand)
2467 {
2468  // Native script execution
2469  if (!Def || !Status) return false;
2470  return !! ::AulExec.DirectExec(this, szCommand, "MenuCommand");
2471 }
2472 
2473 void C4Object::SetSolidMask(int32_t iX, int32_t iY, int32_t iWdt, int32_t iHgt, int32_t iTX, int32_t iTY)
2474 {
2475  // remove old
2476  if (pSolidMaskData) { delete pSolidMaskData; pSolidMaskData=nullptr; }
2477  // set new data
2478  SolidMask.Set(iX,iY,iWdt,iHgt,iTX,iTY);
2479  // re-put if valid
2480  if (CheckSolidMaskRect()) UpdateSolidMask(false);
2481 }
2482 
2484 {
2485  if (!pSolidMaskData) return;
2487 }
2488 
2490 {
2491  // Ensure SolidMask rect lies within bounds of SolidMask bitmap in definition
2492  CSurface8 *sfcGraphics = Def->pSolidMask;
2493  if (!sfcGraphics)
2494  {
2495  // no graphics to set solid in
2496  SolidMask.Set(0,0,0,0,0,0);
2497  return false;
2498  }
2499  SolidMask.Set(std::max<int32_t>(SolidMask.x,0), std::max<int32_t>(SolidMask.y,0),
2500  std::min<int32_t>(SolidMask.Wdt,sfcGraphics->Wdt-SolidMask.x), std::min<int32_t>(SolidMask.Hgt, sfcGraphics->Hgt-SolidMask.y),
2502  if (SolidMask.Hgt<=0) SolidMask.Wdt=0;
2503  return SolidMask.Wdt>0;
2504 }
2505 
2507 {
2508  // Misc. no-save safeties
2510  InMat = MNone;
2511  t_contact = 0;
2512  // Update OCF
2513  SetOCF();
2514  // Menu
2515  CloseMenu(true);
2516  // Material contents
2517  if (MaterialContents) delete MaterialContents; MaterialContents=nullptr;
2518  // reset speed of staticback-objects
2519  if (Category & C4D_StaticBack)
2520  {
2521  xdir = ydir = 0;
2522  }
2523 }
2524 
2526 {
2527  // Status
2528  if (!Status) return;
2529  // No select marks in film playback
2530  if (Game.C4S.Head.Film && Game.C4S.Head.Replay) return;
2531  // target pos (parallax)
2532  float offX, offY, newzoom;
2533  GetDrawPosition(cgo, offX, offY, newzoom);
2534  // Output boundary
2535  if (!Inside<float>(offX, cgo.X, cgo.X + cgo.Wdt)
2536  || !Inside<float>(offY, cgo.Y, cgo.Y + cgo.Hgt)) return;
2537  // Draw select marks
2538  float cox = offX + Shape.GetX() - cgo.X + cgo.X - 2;
2539  float coy = offY + Shape.GetY() - cgo.Y + cgo.Y - 2;
2540  GfxR->fctSelectMark.Draw(cgo.Surface,cox,coy,0);
2541  GfxR->fctSelectMark.Draw(cgo.Surface,cox+Shape.Wdt,coy,1);
2542  GfxR->fctSelectMark.Draw(cgo.Surface,cox,coy+Shape.Hgt,2);
2543  GfxR->fctSelectMark.Draw(cgo.Surface,cox+Shape.Wdt,coy+Shape.Hgt,3);
2544 }
2545 
2547 {
2548  C4Command *pNext;
2549  while (Command)
2550  {
2551  pNext=Command->Next;
2552  if (!Command->iExec)
2553  delete Command;
2554  else
2555  Command->iExec = 2;
2556  Command=pNext;
2557  }
2558 }
2559 
2561 {
2562  C4Command *pCom,*pNext;
2563  for (pCom=Command; pCom; pCom=pNext)
2564  {
2565  // Last one to clear
2566  if (pCom==pUntil) pNext=nullptr;
2567  // Next one to clear after this
2568  else pNext=pCom->Next;
2569  Command=pCom->Next;
2570  if (!pCom->iExec)
2571  delete pCom;
2572  else
2573  pCom->iExec = 2;
2574  }
2575 }
2576 
2577 bool C4Object::AddCommand(int32_t iCommand, C4Object *pTarget, C4Value iTx, int32_t iTy,
2578  int32_t iUpdateInterval, C4Object *pTarget2,
2579  bool fInitEvaluation, C4Value iData, bool fAppend,
2580  int32_t iRetries, C4String *szText, int32_t iBaseMode)
2581 {
2582  // Command stack size safety
2583  const int32_t MaxCommandStack = 35;
2584  C4Command *pCom,*pLast; int32_t iCommands;
2585  for (pCom=Command,iCommands=0; pCom; pCom=pCom->Next,iCommands++) {}
2586  if (iCommands>=MaxCommandStack) return false;
2587  // Valid command safety
2588  if (!Inside(iCommand,C4CMD_First,C4CMD_Last)) return false;
2589  // Allocate and set new command
2590  if (!(pCom=new C4Command)) return false;
2591  pCom->Set(iCommand,this,pTarget,iTx,iTy,pTarget2,iData,
2592  iUpdateInterval,!fInitEvaluation,iRetries,szText,iBaseMode);
2593  // Append to bottom of stack
2594  if (fAppend)
2595  {
2596  for (pLast=Command; pLast && pLast->Next; pLast=pLast->Next) {}
2597  if (pLast) pLast->Next=pCom;
2598  else Command=pCom;
2599  }
2600  // Add to top of command stack
2601  else
2602  {
2603  pCom->Next=Command;
2604  Command=pCom;
2605  }
2606  // Success
2607  return true;
2608 }
2609 
2610 void C4Object::SetCommand(int32_t iCommand, C4Object *pTarget, C4Value iTx, int32_t iTy,
2611  C4Object *pTarget2, bool fControl, C4Value iData,
2612  int32_t iRetries, C4String *szText)
2613 {
2614  // Clear stack
2615  ClearCommands();
2616  // Close menu
2617  if (fControl)
2618  if (!CloseMenu(false)) return;
2619  // Script overload
2620  if (fControl)
2622  pTarget,
2623  iTx,
2624  iTy,
2625  pTarget2,
2626  iData)))
2627  return;
2628  // Inside vehicle control overload
2629  if (Contained)
2631  {
2634  pTarget,
2635  iTx,
2636  iTy,
2637  pTarget2,
2638  iData,
2639  this)))
2640  return;
2641  }
2642  // Outside vehicle control overload
2643  if (GetProcedure()==DFA_PUSH)
2645  {
2648  pTarget,
2649  iTx,
2650  iTy,
2651  pTarget2,
2652  iData)))
2653  return;
2654  }
2655  // Add new command
2656  AddCommand(iCommand,pTarget,iTx,iTy,0,pTarget2,true,iData,false,iRetries,szText,C4CMD_Mode_Base);
2657 }
2658 
2659 C4Command *C4Object::FindCommand(int32_t iCommandType) const
2660 {
2661  // seek all commands
2662  for (C4Command *pCom = Command; pCom; pCom=pCom->Next)
2663  if (pCom->Command == iCommandType) return pCom;
2664  // nothing found
2665  return nullptr;
2666 }
2667 
2669 {
2670  // Execute first command
2671  if (Command) Command->Execute();
2672  // Command finished: engine call
2673  if (Command && Command->Finished)
2675  // Clear finished commands
2677  // Done
2678  return true;
2679 }
2680 
2682 {
2683  // Flag resort
2684  Unsorted=true;
2685  Game.fResortAnyObject = true;
2686  // Must not immediately resort - link change/removal would crash Game::ExecObjects
2687 }
2688 
2690 {
2691  C4Value value;
2692  GetProperty(P_Action, &value);
2693  return value.getPropList();
2694 }
2695 
2696 bool C4Object::SetAction(C4PropList * Act, C4Object *pTarget, C4Object *pTarget2, int32_t iCalls, bool fForce)
2697 {
2698  C4Value vLastAction;
2699  GetProperty(P_Action, &vLastAction);
2700  C4PropList * LastAction = vLastAction.getPropList();
2701  int32_t iLastPhase=Action.Phase;
2702  C4Object *pLastTarget = Action.Target;
2703  C4Object *pLastTarget2 = Action.Target2;
2704  // No other action
2705  if (LastAction)
2706  if (LastAction->GetPropertyInt(P_NoOtherAction) && !fForce)
2707  if (Act != LastAction)
2708  return false;
2709  // Set animation on instance. Abort if the mesh does not have
2710  // such an animation.
2711  if (pMeshInstance)
2712  {
2714  Action.Animation = nullptr;
2715 
2716  C4String* Animation = Act ? Act->GetPropertyStr(P_Animation) : nullptr;
2717  if (Animation)
2718  {
2719  // note that weight is ignored
2720  Action.Animation = pMeshInstance->PlayAnimation(Animation->GetData(), 0, nullptr, new C4ValueProviderAction(this), new C4ValueProviderConst(itofix(1)), true);
2721  }
2722  }
2723  // Stop previous act sound
2724  if (LastAction)
2725  if (Act != LastAction)
2726  if (LastAction->GetPropertyStr(P_Sound))
2727  StopSoundEffect(LastAction->GetPropertyStr(P_Sound)->GetCStr(),this);
2728  // Unfullcon objects no action
2729  if (Con<FullCon)
2730  if (!Def->IncompleteActivity)
2731  Act = 0;
2732  // Reset action time on change
2733  if (Act!=LastAction)
2734  {
2735  Action.Time=0;
2736  // reset action data and targets if procedure is changed
2737  if ((Act ? Act->GetPropertyP(P_Procedure) : -1)
2738  != (LastAction ? LastAction->GetPropertyP(P_Procedure) : -1))
2739  {
2740  Action.Data = 0;
2741  Action.Target = nullptr;
2742  Action.Target2 = nullptr;
2743  }
2744  }
2745  // Set new action
2748  // Set target if specified
2749  if (pTarget) Action.Target=pTarget;
2750  if (pTarget2) Action.Target2=pTarget2;
2751  // Set Action Facet
2752  UpdateActionFace();
2753  // update flipdir
2754  if ((LastAction ? LastAction->GetPropertyInt(P_FlipDir) : 0)
2755  != (Act ? Act->GetPropertyInt(P_FlipDir) : 0)) UpdateFlipDir();
2756  // Start act sound
2757  if (Act)
2758  if (Act != LastAction)
2759  if (Act->GetPropertyStr(P_Sound))
2760  StartSoundEffect(Act->GetPropertyStr(P_Sound)->GetCStr(),+1,100,this);
2761  // Reset OCF
2762  SetOCF();
2763  // issue calls
2764  // Execute EndCall for last action
2765  if (iCalls & SAC_EndCall && !fForce)
2766  if (LastAction)
2767  {
2768  if (LastAction->GetPropertyStr(P_EndCall))
2769  {
2770  C4Def *pOldDef = Def;
2771  Call(LastAction->GetPropertyStr(P_EndCall)->GetCStr());
2772  // abort exeution if def changed
2773  if (Def != pOldDef || !Status) return true;
2774  }
2775  }
2776  // Execute AbortCall for last action
2777  if (iCalls & SAC_AbortCall && !fForce)
2778  if (LastAction)
2779  {
2780  if (LastAction->GetPropertyStr(P_AbortCall))
2781  {
2782  C4Def *pOldDef = Def;
2783  if (pLastTarget && !pLastTarget->Status) pLastTarget = nullptr;
2784  if (pLastTarget2 && !pLastTarget2->Status) pLastTarget2 = nullptr;
2785  Call(LastAction->GetPropertyStr(P_AbortCall)->GetCStr(), &C4AulParSet(iLastPhase, pLastTarget, pLastTarget2));
2786  // abort exeution if def changed
2787  if (Def != pOldDef || !Status) return true;
2788  }
2789  }
2790  // Execute StartCall for new action
2791  if (iCalls & SAC_StartCall)
2792  if (Act)
2793  {
2794  if (Act->GetPropertyStr(P_StartCall))
2795  {
2796  C4Def *pOldDef = Def;
2798  // abort exeution if def changed
2799  if (Def != pOldDef || !Status) return true;
2800  }
2801  }
2802 
2803  C4Def *pOldDef = Def;
2804  Call(PSF_OnActionChanged, &C4AulParSet(LastAction ? LastAction->GetName() : "Idle"));
2805  if (Def != pOldDef || !Status) return true;
2806 
2807  return true;
2808 }
2809 
2811 {
2812  // Default: no action face
2813  Action.Facet.Default();
2814  // Active: get action facet from action definition
2815  C4PropList* pActionDef = GetAction();
2816  if (pActionDef)
2817  {
2818  if (pActionDef->GetPropertyInt(P_Wdt)>0)
2819  {
2820  Action.Facet.Set(GetGraphics()->GetBitmap(Color),
2821  pActionDef->GetPropertyInt(P_X),pActionDef->GetPropertyInt(P_Y),
2822  pActionDef->GetPropertyInt(P_Wdt),pActionDef->GetPropertyInt(P_Hgt));
2823  Action.FacetX=pActionDef->GetPropertyInt(P_OffX);
2824  Action.FacetY=pActionDef->GetPropertyInt(P_OffY);
2825  }
2826  }
2827 }
2828 
2830  C4Object *pTarget, C4Object *pTarget2,
2831  int32_t iCalls, bool fForce)
2832 {
2833  assert(ActName);
2834  // If we get the null string or ActIdle by name, set ActIdle
2835  if (!ActName || ActName == &Strings.P[P_Idle])
2836  return SetAction(0,0,0,iCalls,fForce);
2837  C4Value ActMap; GetProperty(P_ActMap, &ActMap);
2838  if (!ActMap.getPropList()) return false;
2839  C4Value Action; ActMap.getPropList()->GetPropertyByS(ActName, &Action);
2840  if (!Action.getPropList()) return false;
2841  return SetAction(Action.getPropList(),pTarget,pTarget2,iCalls,fForce);
2842 }
2843 
2844 bool C4Object::SetActionByName(const char * szActName,
2845  C4Object *pTarget, C4Object *pTarget2,
2846  int32_t iCalls, bool fForce)
2847 {
2848  C4String * ActName = Strings.RegString(szActName);
2849  ActName->IncRef();
2850  bool r = SetActionByName(ActName, pTarget, pTarget2, iCalls, fForce);
2851  ActName->DecRef();
2852  return r;
2853 }
2854 
2855 void C4Object::SetDir(int32_t iDir)
2856 {
2857  // Not active
2858  C4PropList* pActionDef = GetAction();
2859  if (!pActionDef) return;
2860  // Invalid direction
2861  if (!Inside<int32_t>(iDir,0,pActionDef->GetPropertyInt(P_Directions)-1)) return;
2862  // Execute turn action
2863  if (iDir != Action.Dir)
2864  if (pActionDef->GetPropertyStr(P_TurnAction))
2865  { SetActionByName(pActionDef->GetPropertyStr(P_TurnAction)); }
2866  // Set dir
2867  Action.Dir=iDir;
2868  // update by flipdir?
2869  if (pActionDef->GetPropertyInt(P_FlipDir))
2870  UpdateFlipDir();
2871  else
2872  Action.DrawDir=iDir;
2873 }
2874 
2875 int32_t C4Object::GetProcedure() const
2876 {
2877  C4PropList* pActionDef = GetAction();
2878  if (!pActionDef) return -1;
2879  return pActionDef->GetPropertyP(P_Procedure);
2880 }
2881 
2882 void GrabLost(C4Object *cObj, C4Object *prev_target)
2883 {
2884  // Grab lost script call on target (quite hacky stuff...)
2885  if (prev_target && prev_target->Status) prev_target->Call(PSF_GrabLost);
2886  // Clear commands down to first PushTo (if any) in command stack
2887  for (C4Command *pCom=cObj->Command; pCom; pCom=pCom->Next)
2888  if (pCom->Next && pCom->Next->Command==C4CMD_PushTo)
2889  {
2890  cObj->ClearCommand(pCom);
2891  break;
2892  }
2893 }
2894 
2895 static void DoGravity(C4Object *cobj);
2896 
2898 {
2899  // Active objects
2900  if (GetAction())
2901  {
2902  int32_t iProcedure = GetProcedure();
2903  C4Object *prev_target = Action.Target;
2904  // Scaling upwards: corner scale
2905  if (iProcedure == DFA_SCALE && Action.ComDir != COMD_Stop && ComDirLike(Action.ComDir, COMD_Up))
2906  if (ObjectActionCornerScale(this)) return;
2907  if (iProcedure == DFA_SCALE && Action.ComDir == COMD_Left && Action.Dir == DIR_Left)
2908  if (ObjectActionCornerScale(this)) return;
2909  if (iProcedure == DFA_SCALE && Action.ComDir == COMD_Right && Action.Dir == DIR_Right)
2910  if (ObjectActionCornerScale(this)) return;
2911  // Scaling and stopped: fall off to side (avoid zuppel)
2912  if ((iProcedure == DFA_SCALE) && (Action.ComDir == COMD_Stop))
2913  {
2914  if (Action.Dir == DIR_Left)
2915  { if (ObjectActionJump(this,itofix(1),Fix0,false)) return; }
2916  else
2917  { if (ObjectActionJump(this,itofix(-1),Fix0,false)) return; }
2918  }
2919  // Pushing: grab loss
2920  if (iProcedure==DFA_PUSH) GrabLost(this, prev_target);
2921  // Else jump
2922  ObjectActionJump(this,xdir,ydir,false);
2923  }
2924  // Inactive objects, simple mobile natural gravity
2925  else
2926  {
2927  DoGravity(this);
2928  Mobile=1;
2929  }
2930 }
2931 
2933 {
2934  // Take certain action on contact. Evaluate t_contact-CNAT and Procedure.
2935 
2936  // Determine Procedure
2937  C4PropList* pActionDef = GetAction();
2938  if (!pActionDef) return;
2939  int32_t iProcedure=pActionDef->GetPropertyP(P_Procedure);
2940  int32_t fDisabled=pActionDef->GetPropertyInt(P_ObjectDisabled);
2941 
2942  //------------------------------- Hit Bottom ---------------------------------------------
2943  if (t_contact & CNAT_Bottom)
2944  switch (iProcedure)
2945  {
2946  case DFA_FLIGHT:
2947  if (ydir < 0) return;
2948  // Jump: FlatHit / HardHit / Walk
2949  if ((OCF & OCF_HitSpeed4) || fDisabled)
2950  if (ObjectActionFlat(this,Action.Dir)) return;
2951  if (OCF & OCF_HitSpeed3)
2952  if (ObjectActionKneel(this)) return;
2953  ObjectActionWalk(this);
2954  ydir = 0;
2955  return;
2956  case DFA_SCALE:
2957  // Scale down: stand
2959  {
2960  ObjectActionStand(this);
2961  return;
2962  }
2963  break;
2964  case DFA_DIG:
2965  // no special action
2966  break;
2967  case DFA_SWIM:
2968  // Try corner scale out
2969  if (!GBackSemiSolid(GetX(),GetY()-1+Def->Float*Con/FullCon-1))
2970  if (ObjectActionCornerScale(this)) return;
2971  break;
2972  }
2973 
2974  //------------------------------- Hit Ceiling -----------------------------------------
2975  if (t_contact & CNAT_Top)
2976  switch (iProcedure)
2977  {
2978  case DFA_WALK:
2979  // Walk: Stop
2980  ObjectActionStand(this); return;
2981  case DFA_SCALE:
2982  // Scale: Try hangle, else stop if going upward
2984  {
2985  if (ObjectActionHangle(this))
2986  {
2988  return;
2989  }
2991  }
2992  break;
2993  case DFA_FLIGHT:
2994  // Jump: Try hangle, else bounce off
2995  // High Speed Flight: Tumble
2996  if ((OCF & OCF_HitSpeed3) || fDisabled)
2997  { ObjectActionTumble(this, Action.Dir, xdir, ydir); break; }
2998  if (ObjectActionHangle(this)) return;
2999  break;
3000  case DFA_DIG:
3001  // No action
3002  break;
3003  case DFA_HANGLE:
3005  break;
3006  }
3007 
3008  //---------------------------- Hit Left Wall ----------------------------------------
3009  if (t_contact & CNAT_Left)
3010  {
3011  switch (iProcedure)
3012  {
3013  case DFA_FLIGHT:
3014  // High Speed Flight: Tumble
3015  if ((OCF & OCF_HitSpeed3) || fDisabled)
3016  { ObjectActionTumble(this, DIR_Left, xdir, ydir); break; }
3017  // Else
3018  else if (!ComDirLike(Action.ComDir, COMD_Right) && ObjectActionScale(this,DIR_Left)) return;
3019  break;
3020  case DFA_WALK:
3021  // Walk: Try scale
3023  {
3024  if (ObjectActionScale(this,DIR_Left))
3025  {
3026  ydir = C4REAL100(-1);
3027  return;
3028  }
3029  }
3030  // Heading away from solid
3032  {
3033  // Slide off
3034  ObjectActionJump(this,xdir/2,ydir,false);
3035  }
3036  return;
3037  case DFA_SWIM:
3038  // Only scale if swimming at the surface
3039  if (!GBackSemiSolid(GetX(),GetY()-1+Def->Float*Con/FullCon-1))
3040  {
3041  // Try scale, only if swimming at the surface.
3043  if (ObjectActionScale(this,DIR_Left)) return;
3044  // Try corner scale out
3045  if (ObjectActionCornerScale(this)) return;
3046  }
3047  return;
3048  case DFA_HANGLE:
3049  // Hangle: Try scale
3050  if (ObjectActionScale(this,DIR_Left))
3051  {
3052  ydir = C4REAL100(1);
3053  return;
3054  }
3055  return;
3056  case DFA_DIG:
3057  // Dig: no action
3058  break;
3059  }
3060  }
3061 
3062  //------------------------------ Hit Right Wall --------------------------------------
3063  if (t_contact & CNAT_Right)
3064  {
3065  switch (iProcedure)
3066  {
3067  case DFA_FLIGHT:
3068  // High Speed Flight: Tumble
3069  if ((OCF & OCF_HitSpeed3) || fDisabled)
3070  { ObjectActionTumble(this, DIR_Right, xdir, ydir); break; }
3071  // Else Scale
3072  else if (!ComDirLike(Action.ComDir, COMD_Left) && ObjectActionScale(this,DIR_Right)) return;
3073  break;
3074  case DFA_WALK:
3075  // Walk: Try scale
3077  {
3078  if (ObjectActionScale(this,DIR_Right))
3079  {
3080  ydir = C4REAL100(-1);
3081  return;
3082  }
3083  }
3084  // Heading away from solid
3086  {
3087  // Slide off
3088  ObjectActionJump(this,xdir/2,ydir,false);
3089  }
3090  return;
3091  case DFA_SWIM:
3092  // Only scale if swimming at the surface
3093  if (!GBackSemiSolid(GetX(),GetY()-1+Def->Float*Con/FullCon-1))
3094  {
3095  // Try scale
3097  if (ObjectActionScale(this,DIR_Right)) return;
3098  // Try corner scale out
3099  if (ObjectActionCornerScale(this)) return;
3100  }
3101  return;
3102  case DFA_HANGLE:
3103  // Hangle: Try scale
3104  if (ObjectActionScale(this,DIR_Right))
3105  {
3106  ydir = C4REAL100(1);
3107  return;
3108  }
3109  return;
3110  case DFA_DIG:
3111  // Dig: no action
3112  break;
3113  }
3114  }
3115 
3116  //---------------------------- Unresolved Cases ---------------------------------------
3117 
3118  // Flight stuck
3119  if (iProcedure==DFA_FLIGHT)
3120  {
3121  // Enforce slide free (might slide through tiny holes this way)
3122  if (!ydir)
3123  {
3124  int fAllowDown = !(t_contact & CNAT_Bottom);
3125  if (t_contact & CNAT_Right)
3126  {
3127  ForcePosition(fix_x - 1, fix_y + fAllowDown);
3128  xdir=ydir=0;
3129  }
3130  if (t_contact & CNAT_Left)
3131  {
3132  ForcePosition(fix_x + 1, fix_y + fAllowDown);
3133  xdir=ydir=0;
3134  }
3135  }
3136  if (!xdir)
3137  {
3138  if (t_contact & CNAT_Top)
3139  {
3140  ForcePosition(fix_x, fix_y + 1);
3141  xdir=ydir=0;
3142  }
3143  }
3144  }
3145 }
3146 
3147 void Towards(C4Real &val, C4Real target, C4Real step)
3148 {
3149  if (val==target) return;
3150  if (Abs(val-target)<=step) { val=target; return; }
3151  if (val<target) val+=step; else val-=step;
3152 }
3153 
3154 bool DoBridge(C4Object *clk)
3155 {
3156  int32_t iBridgeTime; bool fMoveClonk, fWall; int32_t iBridgeMaterial;
3157  clk->Action.GetBridgeData(iBridgeTime, fMoveClonk, fWall, iBridgeMaterial);
3158  if (!iBridgeTime) iBridgeTime = 100; // default bridge time
3159  if (clk->Action.Time>=iBridgeTime) { ObjectActionStand(clk); return false; }
3160  // get bridge advancement
3161  int32_t dtp;
3162  if (fWall) switch (clk->Action.ComDir)
3163  {
3164  case COMD_Left: case COMD_Right: dtp = 4; fMoveClonk = false; break; // vertical wall: default 25 pixels
3165  case COMD_UpLeft: case COMD_UpRight: dtp = 5; fMoveClonk = false; break; // diagonal roof over Clonk: default 20 pixels up and 20 pixels side (28 pixels - optimized to close tunnels completely)
3166  case COMD_Up: dtp = 5; break; // horizontal roof over Clonk
3167  default: return true; // bridge procedure just for show
3168  }
3169  else switch (clk->Action.ComDir)
3170  {
3171  case COMD_Left: case COMD_Right: dtp = 5; break; // horizontal bridges: default 20 pixels
3172  case COMD_Up: dtp = 4; break; // vertical bridges: default 25 pixels (same as
3173  case COMD_UpLeft: case COMD_UpRight: dtp = 6; break; // diagonal bridges: default 16 pixels up and 16 pixels side (23 pixels)
3174  default: return true; // bridge procedure just for show
3175  }
3176  if (clk->Action.Time % dtp) return true; // no advancement in this frame
3177  // get target pos for Clonk and bridge
3178  int32_t cx=clk->GetX(), cy=clk->GetY(), cw=clk->Shape.Wdt, ch=clk->Shape.Hgt;
3179  int32_t tx=cx,ty=cy+ch/2;
3180  int32_t dt;
3181  if (fMoveClonk) dt = 0; else dt = clk->Action.Time / dtp;
3182  if (fWall) switch (clk->Action.ComDir)
3183  {
3184  case COMD_Left: tx-=cw/2; ty+=-dt; break;
3185  case COMD_Right: tx+=cw/2; ty+=-dt; break;
3186  case COMD_Up:
3187  {
3188  int32_t x0;
3189  if (fMoveClonk) x0=-3; else x0=(iBridgeTime/dtp)/-2;
3190  tx+=(x0+dt)*((clk->Action.Dir==DIR_Right)*2-1); cx+=((clk->Action.Dir==DIR_Right)*2-1); ty-=ch+3; break;
3191  }
3192  case COMD_UpLeft: tx-=-4+dt; ty+=-ch-7+dt; break;
3193  case COMD_UpRight: tx+=-4+dt; ty+=-ch-7+dt; break;
3194  }
3195  else switch (clk->Action.ComDir)
3196  {
3197  case COMD_Left: tx+=-3-dt; --cx; break;
3198  case COMD_Right: tx+=+2+dt; ++cx; break;
3199  case COMD_Up: tx+=(-cw/2+(cw-1)*(clk->Action.Dir==DIR_Right))*(!fMoveClonk); ty+=-dt-fMoveClonk; --cy; break;
3200  case COMD_UpLeft: tx+=-5-dt+fMoveClonk*3; ty+=2-dt-fMoveClonk*3; --cx; --cy; break;
3201  case COMD_UpRight: tx+=+5+dt-fMoveClonk*2; ty+=2-dt-fMoveClonk*3; ++cx; --cy; break;
3202  }
3203  // check if Clonk movement is posible
3204  if (fMoveClonk)
3205  {
3206  int32_t cx2=cx, cy2=cy;
3207  if ( clk->Shape.CheckContact(cx2, cy2-1))
3208  {
3209  // Clonk would collide here: Change to nonmoving Clonk mode and redo bridging
3210  iBridgeTime -= clk->Action.Time;
3211  clk->Action.Time = 0;
3212  if (fWall && clk->Action.ComDir==COMD_Up)
3213  {
3214  // special for roof above Clonk: The nonmoving roof is started at bridgelength before the Clonk
3215  // so, when interrupted, an action time halfway through the action must be set
3216  clk->Action.Time = iBridgeTime;
3217  iBridgeTime += iBridgeTime;
3218  }
3219  clk->Action.SetBridgeData(iBridgeTime, false, fWall, iBridgeMaterial);
3220  return DoBridge(clk);
3221  }
3222  }
3223  // draw bridge into landscape
3224  ::Landscape.DrawMaterialRect(iBridgeMaterial,tx-2,ty,4,3);
3225  // Move Clonk
3226  if (fMoveClonk) clk->MovePosition(cx-clk->GetX(), cy-clk->GetY());
3227  return true;
3228 }
3229 
3230 static void DoGravity(C4Object *cobj)
3231 {
3232  // Floatation in liquids
3233  if (cobj->InLiquid && cobj->Def->Float)
3234  {
3235  cobj->ydir-=GravAccel * C4REAL100(80);
3236  if (cobj->ydir<C4REAL100(-160)) cobj->ydir=C4REAL100(-160);
3237  if (cobj->xdir<-FloatFriction) cobj->xdir+=FloatFriction;
3238  if (cobj->xdir>+FloatFriction) cobj->xdir-=FloatFriction;
3239  if (cobj->rdir<-FloatFriction) cobj->rdir+=FloatFriction;
3240  if (cobj->rdir>+FloatFriction) cobj->rdir-=FloatFriction;
3241  // Reduce upwards speed when about to leave liquid to prevent eternal moving up and down.
3242  // Check both for no liquid one and two pixels above the surface, because the object could
3243  // skip one pixel as its speed can be more than 100.
3244  int32_t y_float = cobj->GetY() - 1 + cobj->Def->Float * cobj->GetCon() / FullCon;
3245  if (!GBackLiquid(cobj->GetX(), y_float - 1) || !GBackLiquid(cobj->GetX(), y_float - 2))
3246  if (cobj->ydir < 0)
3247  {
3248  // Reduce the upwards speed and set to zero for small values to prevent fluctuations.
3249  cobj->ydir /= 2;
3250  if (Abs(cobj->ydir) < C4REAL100(10))
3251  cobj->ydir = 0;
3252  }
3253  }
3254  // Free fall gravity
3255  else if (~cobj->Category & C4D_StaticBack)
3256  cobj->ydir+=GravAccel;
3257 }
3258 
3260 {
3261  ObjectComStop(cobj);
3262  cobj->AddCommand(C4CMD_Wait,nullptr,0,0,50);
3263 }
3264 
3265 bool ReduceLineSegments(C4Shape &rShape, bool fAlternate)
3266 {
3267  // try if line could go by a path directly when skipping on evertex. If fAlternate is true, try by skipping two vertices
3268  for (int32_t cnt=0; cnt+2+fAlternate<rShape.VtxNum; cnt++)
3269  if (PathFree(rShape.VtxX[cnt],rShape.VtxY[cnt],
3270  rShape.VtxX[cnt+2+fAlternate],rShape.VtxY[cnt+2+fAlternate]))
3271  {
3272  if (fAlternate) rShape.RemoveVertex(cnt+2);
3273  rShape.RemoveVertex(cnt+1);
3274  return true;
3275  }
3276  return false;
3277 }
3278 
3280 {
3281  C4Real iTXDir;
3282  C4Real lftspeed,tydir;
3283  int32_t iTargetX;
3284  int32_t iPushRange,iPushDistance;
3285 
3286  // Standard phase advance
3287  int32_t iPhaseAdvance=1;
3288 
3289  // Upright attachment check
3290  if (!Mobile)
3291  if (Def->UprightAttach)
3292  if (Inside<int32_t>(GetR(),-StableRange,+StableRange))
3293  {
3295  Mobile=1;
3296  }
3297 
3298  C4PropList* pActionDef = GetAction();
3299  // No IncompleteActivity? Reset action if there was one
3300  if (!(OCF & OCF_FullCon) && !Def->IncompleteActivity && pActionDef)
3301  {
3302  SetAction(0);
3303  pActionDef = 0;
3304  }
3305 
3306  // InLiquidAction check
3307  if (InLiquid)
3308  if (pActionDef && pActionDef->GetPropertyStr(P_InLiquidAction))
3309  {
3311  pActionDef = GetAction();
3312  }
3313 
3314  // Idle objects do natural gravity only
3315  if (!pActionDef)
3316  {
3318  if (Mobile) DoGravity(this);
3319  return;
3320  }
3321 
3322  C4Real fWalk,fMove;
3323 
3324  // Action time advance
3325  Action.Time++;
3326 
3327  C4Value Attach;
3328  pActionDef->GetProperty(P_Attach, &Attach);
3329  if (Attach.GetType() != C4V_Nil)
3330  {
3331  Action.t_attach = Attach.getInt();
3332  }
3333  else switch (pActionDef->GetPropertyP(P_Procedure))
3334  {
3335  case DFA_SCALE:
3338  break;
3339  case DFA_HANGLE:
3341  break;
3342  case DFA_WALK:
3343  case DFA_KNEEL:
3344  case DFA_THROW:
3345  case DFA_BRIDGE:
3346  case DFA_PUSH:
3347  case DFA_PULL:
3348  case DFA_DIG:
3350  break;
3351  default:
3353  }
3354 
3355  // if an object is in controllable state, so it can be assumed that if it dies later because of NO_OWNER's cause,
3356  // it has been its own fault and not the fault of the last one who threw a flint on it
3357  // do not reset for burning objects to make sure the killer is set correctly if they fall out of the map while burning
3358  if (!pActionDef->GetPropertyInt(P_ObjectDisabled) && pActionDef->GetPropertyP(P_Procedure) != DFA_FLIGHT && !OnFire)
3360 
3361  // Handle Default Action Procedure: evaluates Procedure and Action.ComDir
3362  // Update xdir,ydir,Action.Dir,attachment,iPhaseAdvance
3363  int32_t dir = Action.Dir;
3364  C4Real accel = C4REAL100(pActionDef->GetPropertyInt(P_Accel));
3365  C4Real decel = accel;
3366  {
3367  C4Value decel_val;
3368  pActionDef->GetProperty(P_Decel, &decel_val);
3369  if (decel_val.GetType() != C4V_Nil)
3370  decel = C4REAL100(decel_val.getInt());
3371  }
3372  C4Real limit = C4REAL100(pActionDef->GetPropertyInt(P_Speed));
3373 
3374  switch (pActionDef->GetPropertyP(P_Procedure))
3375  {
3376  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3377  case DFA_WALK:
3378  switch (Action.ComDir)
3379  {
3380  case COMD_Left: case COMD_UpLeft: case COMD_DownLeft:
3381  // breaaak!!!
3382  if (dir == DIR_Right)
3383  xdir-=decel;
3384  else
3385  xdir-=accel;
3386  if (xdir<-limit) xdir=-limit;
3387  break;
3388  case COMD_Right: case COMD_UpRight: case COMD_DownRight:
3389  if (dir == DIR_Left)
3390  xdir+=decel;
3391  else
3392  xdir+=accel;
3393  if (xdir>+limit) xdir=+limit;
3394  break;
3395  case COMD_Stop: case COMD_Up: case COMD_Down:
3396  if (xdir<0) xdir+=decel;
3397  if (xdir>0) xdir-=decel;
3398  if ((xdir>-decel) && (xdir<+decel)) xdir=0;
3399  break;
3400  }
3401  iPhaseAdvance=0;
3402  if (xdir<0)
3403  {
3404  if (dir != DIR_Left) { SetDir(DIR_Left); xdir = -1; }
3405  iPhaseAdvance=-fixtoi(xdir*10);
3406  }
3407  if (xdir>0)
3408  {
3409  if (dir != DIR_Right) { SetDir(DIR_Right); xdir = 1; }
3410  iPhaseAdvance=+fixtoi(xdir*10);
3411  }
3412 
3413  Mobile=1;
3414  // object is rotateable? adjust to ground, if in horizontal movement or not attached to the center vertex
3415  if (Def->Rotateable && Shape.AttachMat != MNone && (!!xdir || Def->Shape.VtxX[Shape.iAttachVtx]))
3416  AdjustWalkRotation(20, 20, 100);
3417  else
3418  rdir=0;
3419  break;
3420  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3421  case DFA_KNEEL:
3422  ydir=0;
3423  Mobile=1;
3424  break;
3425  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3426  case DFA_SCALE:
3427  {
3428  int ComDir = Action.ComDir;
3429  if (Shape.CheckScaleToWalk(GetX(), GetY()))
3430  {
3431  ObjectActionWalk(this);
3432  return;
3433  }
3434  if ((Action.Dir == DIR_Left && ComDir == COMD_Left) || (Action.Dir == DIR_Right && ComDir == COMD_Right))
3435  {
3436  ComDir = COMD_Up;
3437  }
3438  switch (ComDir)
3439  {
3440  case COMD_Up: case COMD_UpRight: case COMD_UpLeft:
3441  if (ydir > 0) ydir -= decel;
3442  else ydir -= accel;
3443  if (ydir < -limit) ydir = -limit; break;
3444  case COMD_Down: case COMD_DownRight: case COMD_DownLeft:
3445  if (ydir < 0) ydir += decel;
3446  else ydir += accel;
3447  if (ydir > +limit) ydir = +limit; break;
3448  case COMD_Left: case COMD_Right: case COMD_Stop:
3449  if (ydir < 0) ydir += decel;
3450  if (ydir > 0) ydir -= decel;
3451  if ((ydir > -decel) && (ydir < +decel)) ydir = 0;
3452  break;
3453  }
3454  iPhaseAdvance=0;
3455  if (ydir<0) iPhaseAdvance=-fixtoi(ydir*14);
3456  if (ydir>0) iPhaseAdvance=+fixtoi(ydir*14);
3457  xdir=0;
3458  Mobile=1;
3459  break;
3460  }
3461  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3462  case DFA_HANGLE:
3463  switch (Action.ComDir)
3464  {
3465  case COMD_Left: case COMD_UpLeft: case COMD_DownLeft:
3466  if (xdir > 0) xdir -= decel;
3467  else xdir -= accel;
3468  if (xdir < -limit) xdir = -limit;
3469  break;
3470  case COMD_Right: case COMD_UpRight: case COMD_DownRight:
3471  if (xdir < 0) xdir += decel;
3472  else xdir += accel;
3473  if (xdir > +limit) xdir = +limit;
3474  break;
3475  case COMD_Up:
3476  if (Action.Dir == DIR_Left)
3477  if (xdir > 0) xdir -= decel;
3478  else xdir -= accel;
3479  else
3480  if (xdir < 0) xdir += decel;
3481  else xdir += accel;
3482  if (xdir < -limit) xdir = -limit;
3483  if (xdir > +limit) xdir = +limit;
3484  break;
3485  case COMD_Stop: case COMD_Down:
3486  if (xdir < 0) xdir += decel;
3487  if (xdir > 0) xdir -= decel;
3488  if ((xdir > -decel) && (xdir < +decel)) xdir = 0;
3489  break;
3490  }
3491  iPhaseAdvance=0;
3492  if (xdir<0) { iPhaseAdvance=-fixtoi(xdir*10); SetDir(DIR_Left); }
3493  if (xdir>0) { iPhaseAdvance=+fixtoi(xdir*10); SetDir(DIR_Right); }
3494  ydir=0;
3495  Mobile=1;
3496  break;
3497  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3498  case DFA_FLIGHT:
3499  // Contained: fall out (one try only)
3500  if (!::Game.iTick10)
3501  if (Contained)
3502  {
3503  StopActionDelayCommand(this);
3505  }
3506 
3507  switch (Action.ComDir)
3508  {
3509  case COMD_Left: case COMD_UpLeft: case COMD_DownLeft:
3510  xdir -= std::max(std::min(limit + xdir, xdir > 0 ? decel : accel), itofix(0));
3511  break;
3512  case COMD_Right: case COMD_UpRight: case COMD_DownRight:
3513  xdir += std::max(std::min(limit - xdir, xdir < 0 ? decel : accel), itofix(0));
3514  break;
3515  }
3516 
3517  // Gravity/mobile
3518  DoGravity(this);
3519  Mobile=1;
3520  break;
3521  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3522  case DFA_DIG:
3523  {
3524  int32_t smpx = GetX(), smpy = GetY();
3525  bool fAttachOK = false;
3526  if (Action.t_attach & CNAT_Bottom && Shape.Attach(smpx,smpy,CNAT_Bottom)) fAttachOK = true;
3527  else if (Action.t_attach & CNAT_Left && Shape.Attach(smpx,smpy,CNAT_Left)) { fAttachOK = true; }
3528  else if (Action.t_attach & CNAT_Right && Shape.Attach(smpx,smpy,CNAT_Right)) { fAttachOK = true; }
3529  else if (Action.t_attach & CNAT_Top && Shape.Attach(smpx,smpy,CNAT_Top)) fAttachOK = true;
3530  if (!fAttachOK)
3531  { ObjectComStopDig(this); return; }
3532  iPhaseAdvance=40*limit;
3533 
3534  if (xdir < 0) SetDir(DIR_Left); else if (xdir > 0) SetDir(DIR_Right);
3536  Mobile=1;
3537  break;
3538  }
3539  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3540  case DFA_SWIM:
3541  // ComDir changes xdir/ydir
3542  switch (Action.ComDir)
3543  {
3544  case COMD_Up: ydir-=accel; break;
3545  case COMD_UpRight: ydir-=accel; xdir+=accel; break;
3546  case COMD_Right: xdir+=accel; break;
3547  case COMD_DownRight:ydir+=accel; xdir+=accel; break;
3548  case COMD_Down: ydir+=accel; break;
3549  case COMD_DownLeft: ydir+=accel; xdir-=accel; break;
3550  case COMD_Left: xdir-=accel; break;
3551  case COMD_UpLeft: ydir-=accel; xdir-=accel; break;
3552  case COMD_Stop:
3553  if (xdir<0) xdir+=decel;
3554  if (xdir>0) xdir-=decel;
3555  if ((xdir>-decel) && (xdir<+decel)) xdir=0;
3556  if (ydir<0) ydir+=decel;
3557  if (ydir>0) ydir-=decel;
3558  if ((ydir>-decel) && (ydir<+decel)) ydir=0;
3559  break;
3560  }
3561 
3562  // Out of liquid check
3563  if (!InLiquid)
3564  {
3565  // Just above liquid: move down
3566  if (GBackLiquid(GetX(),GetY()+1+Def->Float*Con/FullCon-1)) ydir=+accel;
3567  // Free fall: walk
3568  else { ObjectActionWalk(this); return; }
3569  }
3570 
3571  // xdir/ydir bounds, don't apply if COMD_None
3572  if (Action.ComDir != COMD_None)
3573  {
3574  if (ydir<-limit) ydir=-limit; if (ydir>+limit) ydir=+limit;
3575  if (xdir>+limit) xdir=+limit; if (xdir<-limit) xdir=-limit;
3576  }
3577  // Surface dir bound
3578  if (!GBackLiquid(GetX(),GetY()-1+Def->Float*Con/FullCon-1)) if (ydir<0) ydir=0;
3579  // Dir, Phase, Attach
3580  if (xdir<0) SetDir(DIR_Left);
3581  if (xdir>0) SetDir(DIR_Right);
3582  iPhaseAdvance=fixtoi(limit*10);
3583  Mobile=1;
3584 
3585  break;
3586  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3587  case DFA_THROW:
3588  Mobile=1;
3589  break;
3590  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3591  case DFA_BRIDGE:
3592  {
3593  if (!DoBridge(this)) return;
3594  switch (Action.ComDir)
3595  {
3596  case COMD_Left: case COMD_UpLeft: SetDir(DIR_Left); break;
3597  case COMD_Right: case COMD_UpRight: SetDir(DIR_Right); break;
3598  }
3599  ydir=0; xdir=0;
3600  Mobile=1;
3601  }
3602  break;
3603  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3604  case DFA_PUSH:
3605  // No target
3606  if (!Action.Target) { StopActionDelayCommand(this); return; }
3607  // Inside target
3608  if (Contained==Action.Target) { StopActionDelayCommand(this); return; }
3609  // Target pushing force
3610  bool fStraighten;
3611  iTXDir=0; fStraighten=false;
3612  switch (Action.ComDir)
3613  {
3614  case COMD_Left: case COMD_DownLeft: iTXDir=-limit; break;
3615  case COMD_UpLeft: fStraighten=1; iTXDir=-limit; break;
3616  case COMD_Right: case COMD_DownRight: iTXDir=+limit; break;
3617  case COMD_UpRight: fStraighten=1; iTXDir=+limit; break;
3618  case COMD_Up: fStraighten=1; break;
3619  case COMD_Stop: case COMD_Down: iTXDir=0; break;
3620  }
3621  // Push object
3622  if (!Action.Target->Push(iTXDir,accel,fStraighten))
3623  { StopActionDelayCommand(this); return; }
3624  // Set target controller
3626  // ObjectAction got hold check
3627  iPushDistance = std::max(Shape.Wdt/2-8,0);
3628  iPushRange = iPushDistance + 10;
3629  int32_t sax,say,sawdt,sahgt;
3630  Action.Target->GetArea(sax,say,sawdt,sahgt);
3631  // Object lost
3632  if (!Inside(GetX()-sax,-iPushRange,sawdt-1+iPushRange)
3633  || !Inside(GetY()-say,-iPushRange,sahgt-1+iPushRange))
3634  {
3635  C4Object *prev_target = Action.Target;
3636  // Wait command (why, anyway?)
3637  StopActionDelayCommand(this);
3638  // Grab lost action
3639  GrabLost(this, prev_target);
3640  // Done
3641  return;
3642  }
3643  // Follow object (full xdir reset)
3644  // Vertical follow: If object moves out at top, assume it's being pushed upwards and the Clonk must run after it
3645  if (GetY()-iPushDistance > say+sahgt && iTXDir) { if (iTXDir>0) sax+=sawdt/2; sawdt/=2; }
3646  // Horizontal follow
3647  iTargetX=Clamp(GetX(),sax-iPushDistance,sax+sawdt-1+iPushDistance);
3648  if (GetX()==iTargetX) xdir=0;
3649  else { if (GetX()<iTargetX) xdir=+limit; if (GetX()>iTargetX) xdir=-limit; }
3650  // Phase by XDir
3651  if (xdir<0) { iPhaseAdvance=-fixtoi(xdir*10); SetDir(DIR_Left); }
3652  if (xdir>0) { iPhaseAdvance=+fixtoi(xdir*10); SetDir(DIR_Right); }
3653  // No YDir
3654  ydir=0;
3655  Mobile=1;
3656  break;
3657  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3658  case DFA_PULL:
3659  // No target
3660  if (!Action.Target) { StopActionDelayCommand(this); return; }
3661  // Inside target
3662  if (Contained==Action.Target) { StopActionDelayCommand(this); return; }
3663  // Target contained
3664  if (Action.Target->Contained) { StopActionDelayCommand(this); return; }
3665 
3666  int32_t iPullDistance;
3667  int32_t iPullX;
3668 
3669  iPullDistance = Action.Target->Shape.Wdt/2 + Shape.Wdt/2;
3670 
3671  iTargetX=GetX();
3672  if (Action.ComDir==COMD_Right) iTargetX = Action.Target->GetX()+iPullDistance;
3673  if (Action.ComDir==COMD_Left) iTargetX = Action.Target->GetX()-iPullDistance;
3674 
3675  iPullX=Action.Target->GetX();
3676  if (Action.ComDir==COMD_Right) iPullX = GetX()-iPullDistance;
3677  if (Action.ComDir==COMD_Left) iPullX = GetX()+iPullDistance;
3678 
3679  fWalk = limit;
3680 
3681  fMove = 0;
3682  if (Action.ComDir==COMD_Right) fMove = +fWalk;
3683  if (Action.ComDir==COMD_Left) fMove = -fWalk;
3684 
3685  iTXDir = fMove + fWalk * Clamp<int32_t>(iPullX-Action.Target->GetX(),-10,+10) / 10;
3686 
3687  // Push object
3688  if (!Action.Target->Push(iTXDir,accel,false))
3689  { StopActionDelayCommand(this); return; }
3690  // Set target controller
3692 
3693  // Train pulling: com dir transfer
3694  if ( (Action.Target->GetProcedure()==DFA_WALK)
3695  || (Action.Target->GetProcedure()==DFA_PULL) )
3696  {
3698  if (iTXDir<0) Action.Target->Action.ComDir=COMD_Left;
3699  if (iTXDir>0) Action.Target->Action.ComDir=COMD_Right;
3700  }
3701 
3702  // Pulling range
3703  iPushDistance = std::max(Shape.Wdt/2-8,0);
3704  iPushRange = iPushDistance + 20;
3705  Action.Target->GetArea(sax,say,sawdt,sahgt);
3706  // Object lost
3707  if (!Inside(GetX()-sax,-iPushRange,sawdt-1+iPushRange)
3708  || !Inside(GetY()-say,-iPushRange,sahgt-1+iPushRange))
3709  {
3710  // Remember target. Will be lost on changing action.
3711  C4Object *prev_target = Action.Target;
3712  // Wait command (why, anyway?)
3713  StopActionDelayCommand(this);
3714  // Grab lost action
3715  GrabLost(this, prev_target);
3716  // Lose target
3717  Action.Target=nullptr;
3718  // Done
3719  return;
3720  }
3721 
3722  // Move to pulling position
3723  xdir = fMove + fWalk * Clamp<int32_t>(iTargetX-GetX(),-10,+10) / 10;
3724 
3725  // Phase by XDir
3726  iPhaseAdvance=0;
3727  if (xdir<0) { iPhaseAdvance=-fixtoi(xdir*10); SetDir(DIR_Left); }
3728  if (xdir>0) { iPhaseAdvance=+fixtoi(xdir*10); SetDir(DIR_Right); }
3729  // No YDir
3730  ydir=0;
3731  Mobile=1;
3732 
3733  break;
3734  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3735  case DFA_LIFT:
3736  // Valid check
3737  if (!Action.Target) { SetAction(0); return; }
3738  // Target lifting force
3739  lftspeed=itofix(2); tydir=0;
3740  switch (Action.ComDir)
3741  {
3742  case COMD_Up: tydir=-lftspeed; break;
3743  case COMD_Stop: tydir=-GravAccel; break;
3744  case COMD_Down: tydir=+lftspeed; break;
3745  }
3746  // Lift object
3747  if (!Action.Target->Lift(tydir,C4REAL100(50)))
3748  { SetAction(0); return; }
3749  // Check LiftTop
3750  if (Def->LiftTop)
3751  if (Action.Target->GetY()<=(GetY()+Def->LiftTop))
3752  if (Action.ComDir==COMD_Up)
3753  Call(PSF_LiftTop);
3754  // General
3755  DoGravity(this);
3756  break;
3757  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3758  case DFA_FLOAT:
3759  // ComDir changes xdir/ydir
3760  switch (Action.ComDir)
3761  {
3762  case COMD_Up:
3763  ydir-=accel;
3764  if (xdir<0) xdir+=decel;
3765  if (xdir>0) xdir-=decel;
3766  if ((xdir>-decel) && (xdir<+decel)) xdir=0;
3767  break;
3768  case COMD_UpRight:
3769  ydir-=accel; xdir+=accel; break;
3770  case COMD_Right:
3771  xdir+=accel;
3772  if (ydir<0) ydir+=decel;
3773  if (ydir>0) ydir-=decel;
3774  if ((ydir>-decel) && (ydir<+decel)) ydir=0;
3775  break;
3776  case COMD_DownRight:
3777  ydir+=accel; xdir+=accel; break;
3778  case COMD_Down:
3779  ydir+=accel;
3780  if (xdir<0) xdir+=decel;
3781  if (xdir>0) xdir-=decel;
3782  if ((xdir>-decel) && (xdir<+decel)) xdir=0;
3783  break;
3784  case COMD_DownLeft:
3785  ydir+=accel; xdir-=accel; break;
3786  case COMD_Left:
3787  xdir-=accel;
3788  if (ydir<0) ydir+=decel;
3789  if (ydir>0) ydir-=decel;
3790  if ((ydir>-decel) && (ydir<+decel)) ydir=0;
3791  break;
3792  case COMD_UpLeft:
3793  ydir-=accel; xdir-=accel; break;
3794  case COMD_Stop:
3795  if (xdir<0) xdir+=decel;
3796  if (xdir>0) xdir-=decel;
3797  if ((xdir>-decel) && (xdir<+decel)) xdir=0;
3798  if (ydir<0) ydir+=decel;
3799  if (ydir>0) ydir-=decel;
3800  if ((ydir>-decel) && (ydir<+decel)) ydir=0;
3801  break;
3802  }
3803  // xdir/ydir bounds, don't apply if COMD_None
3804  if (Action.ComDir != COMD_None)
3805  {
3806  if (ydir<-limit) ydir=-limit; if (ydir>+limit) ydir=+limit;
3807  if (xdir>+limit) xdir=+limit; if (xdir<-limit) xdir=-limit;
3808  }
3809 
3810  Mobile=1;
3811  break;
3812  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3813  // ATTACH: Force position to target object
3814  // own vertex index is determined by high-order byte of action data
3815  // target vertex index is determined by low-order byte of action data
3816  case DFA_ATTACH:
3817 
3818  // No target
3819  if (!Action.Target)
3820  {
3821  if (Status)
3822  {
3823  SetAction(0);
3825  }
3826  return;
3827  }
3828 
3829  // Target incomplete and no incomplete activity
3830  if (!(Action.Target->OCF & OCF_FullCon))
3832  { SetAction(0); return; }
3833 
3834  // Force containment
3836  {
3837  if (Action.Target->Contained)
3839  else
3840  Exit(GetX(),GetY(),GetR());
3841  }
3842 
3843  // Object might have detached in Enter/Exit call
3844  if (!Action.Target) break;
3845 
3846  // Move position (so objects on solidmask move)
3848  -Shape.VtxX[Action.Data>>8] - fix_x,
3850  -Shape.VtxY[Action.Data>>8] - fix_y);
3851  // must zero motion...
3852  xdir=ydir=0;
3853 
3854  break;
3855  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3856  case DFA_CONNECT:
3857  {
3858  bool fBroke=false;
3859  bool fLineChange = false;
3860 
3861  // Line destruction check: Target missing or incomplete
3862  if (!Action.Target || (Action.Target->Con<FullCon)) fBroke=true;
3863  if (!Action.Target2 || (Action.Target2->Con<FullCon)) fBroke=true;
3864  if (fBroke)
3865  {
3867  AssignRemoval();
3868  return;
3869  }
3870 
3871  // Movement by Target
3872  // Connect to attach vertex
3873  C4Value lineAttachV; C4ValueArray *lineAttach;
3874  Action.Target->GetProperty(P_LineAttach, &lineAttachV);
3875  lineAttach = lineAttachV.getArray();
3876  int32_t iConnectX1, iConnectY1;
3877  iConnectX1 = Action.Target->GetX();
3878  iConnectY1 = Action.Target->GetY();
3879  if (lineAttach)
3880  {
3881  iConnectX1 += lineAttach->GetItem(0).getInt();
3882  iConnectY1 += lineAttach->GetItem(1).getInt();
3883  }
3884  if ((iConnectX1!=Shape.VtxX[0]) || (iConnectY1!=Shape.VtxY[0]))
3885  {
3886  // Regular wrapping line
3887  if (Def->LineIntersect == 0)
3888  if (!Shape.LineConnect(iConnectX1,iConnectY1,0,+1,
3889  Shape.VtxX[0],Shape.VtxY[0])) fBroke=true;
3890  // No-intersection line
3891  if (Def->LineIntersect == 1)
3892  { Shape.VtxX[0]=iConnectX1; Shape.VtxY[0]=iConnectY1; }
3893 
3894  fLineChange = true;
3895  }
3896 
3897  // Movement by Target2
3898  // Connect to attach vertex
3899  Action.Target2->GetProperty(P_LineAttach, &lineAttachV);
3900  lineAttach = lineAttachV.getArray();
3901  int32_t iConnectX2, iConnectY2;
3902  iConnectX2 = Action.Target2->GetX();
3903  iConnectY2 = Action.Target2->GetY();
3904  if (lineAttach)
3905  {
3906  iConnectX2 += lineAttach->GetItem(0).getInt();
3907  iConnectY2 += lineAttach->GetItem(1).getInt();
3908  }
3909  if ((iConnectX2!=Shape.VtxX[Shape.VtxNum-1]) || (iConnectY2!=Shape.VtxY[Shape.VtxNum-1]))
3910  {
3911  // Regular wrapping line
3912  if (Def->LineIntersect == 0)
3913  if (!Shape.LineConnect(iConnectX2,iConnectY2,Shape.VtxNum-1,-1,
3914  Shape.VtxX[Shape.VtxNum-1],Shape.VtxY[Shape.VtxNum-1])) fBroke=true;
3915  // No-intersection line
3916  if (Def->LineIntersect == 1)
3917  { Shape.VtxX[Shape.VtxNum-1]=iConnectX2; Shape.VtxY[Shape.VtxNum-1]=iConnectY2; }
3918 
3919  fLineChange = true;
3920  }
3921 
3922  // Line fBroke
3923  if (fBroke)
3924  {
3925  Call(PSF_OnLineBreak,0);
3926  AssignRemoval();
3927  return;
3928  }
3929 
3930  // Reduce line segments
3931  if (!::Game.iTick35)
3933  fLineChange = true;
3934 
3935  // Line change callback
3936  if (fLineChange)
3938  }
3939  break;
3940  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3941  default:
3942  // Attach
3943  if (Action.t_attach)
3944  {
3945  xdir = ydir = 0;
3946  Mobile = 1;
3947  }
3948  // Free gravity
3949  else
3950  DoGravity(this);
3951  break;
3952  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3953  }
3954 
3955  // Phase Advance (zero delay means no phase advance)
3956  if (pActionDef->GetPropertyInt(P_Delay))
3957  {
3958  Action.PhaseDelay+=iPhaseAdvance;
3959  if (Action.PhaseDelay >= pActionDef->GetPropertyInt(P_Delay))
3960  {
3961  // Advance Phase
3962  Action.PhaseDelay=0;
3963  Action.Phase += pActionDef->GetPropertyInt(P_Step);
3964  // Phase call
3965  if (pActionDef->GetPropertyStr(P_PhaseCall))
3966  {
3967  Call(pActionDef->GetPropertyStr(P_PhaseCall)->GetCStr());
3968  }
3969  // Phase end
3970  if (Action.Phase>=pActionDef->GetPropertyInt(P_Length))
3971  {
3972  C4String *next_action = pActionDef->GetPropertyStr(P_NextAction);
3973  // Keep current action if there is no NextAction
3974  if (!next_action)
3975  Action.Phase = 0;
3976  // set new action if it's not Hold
3977  else if (next_action == &Strings.P[P_Hold])
3978  {
3979  Action.Phase = pActionDef->GetPropertyInt(P_Length)-1;
3980  Action.PhaseDelay = pActionDef->GetPropertyInt(P_Delay)-1;
3981  }
3982  else
3983  {
3984  // Set new action
3985  SetActionByName(next_action, nullptr, nullptr, SAC_StartCall | SAC_EndCall);
3986  }
3987  }
3988  }
3989  }
3990 
3991  return;
3992 }
3993 
3994 
3995 bool C4Object::SetOwner(int32_t iOwner)
3996 {
3997  // Check valid owner
3998  if (!(ValidPlr(iOwner) || iOwner == NO_OWNER)) return false;
3999  // always set color, even if no owner-change is done
4000  if (iOwner != NO_OWNER)
4001  if (GetGraphics()->IsColorByOwner())
4002  {
4003  Color=::Players.Get(iOwner)->ColorDw;
4004  UpdateFace(false);
4005  }
4006  // no change?
4007  if (Owner == iOwner) return true;
4008  // set new owner
4009  int32_t iOldOwner=Owner;
4010  Owner=iOwner;
4011  // this automatically updates controller
4012  Controller = Owner;
4013  // script callback
4014  Call(PSF_OnOwnerChanged, &C4AulParSet(Owner, iOldOwner));
4015  // done
4016  return true;
4017 }
4018 
4019 
4020 bool C4Object::SetLightRange(int32_t iToRange, int32_t iToFadeoutRange)
4021 {
4022  // set new range
4023  lightRange = iToRange;
4024  lightFadeoutRange = iToFadeoutRange;
4025  // resort into player's FoW-repeller-list
4026  UpdateLight();
4027  // success
4028  return true;
4029 }
4030 
4031 
4032 bool C4Object::SetLightColor(uint32_t iValue)
4033 {
4034  // set new color value
4035  lightColor = iValue;
4036  // resort into player's FoW-repeller-list
4037  UpdateLight();
4038  // success
4039  return true;
4040 }
4041 
4042 
4044 {
4045  if (Landscape.HasFoW()) Landscape.GetFoW()->Add(this);
4046 }
4047 
4048 void C4Object::SetAudibilityAt(C4TargetFacet &cgo, int32_t iX, int32_t iY, int32_t player)
4049 {
4050  // target pos (parallax)
4051  float offX, offY, newzoom;
4052  GetDrawPosition(cgo, iX, iY, cgo.Zoom, offX, offY, newzoom);
4053  int32_t audible_at_pos = Clamp(100 - 100 * Distance(cgo.X + cgo.Wdt / 2, cgo.Y + cgo.Hgt / 2, offX, offY) / 700, 0, 100);
4054  if (audible_at_pos > Audible)
4055  {
4056  Audible = audible_at_pos;
4057  AudiblePan = Clamp<int>(200 * (offX - cgo.X - (cgo.Wdt / 2)) / cgo.Wdt, -100, 100);
4059  }
4060 }
4061 
4062 bool C4Object::IsVisible(int32_t iForPlr, bool fAsOverlay) const
4063 {
4064  bool fDraw;
4065  C4Value vis;
4066  if (!GetProperty(P_Visibility, &vis))
4067  return true;
4068 
4069  int32_t Visibility;
4070  C4ValueArray *parameters = vis.getArray();
4071  if (parameters && parameters->GetSize())
4072  {
4073  Visibility = parameters->GetItem(0).getInt();
4074  }
4075  else
4076  {
4077  Visibility = vis.getInt();
4078  }
4079  // check layer
4080  if (Layer && Layer != this && !fAsOverlay)
4081  {
4082  fDraw = Layer->IsVisible(iForPlr, false);
4083  if (Layer->GetPropertyInt(P_Visibility) & VIS_LayerToggle) fDraw = !fDraw;
4084  if (!fDraw) return false;
4085  }
4086  // no flags set?
4087  if (!Visibility) return true;
4088  // check overlay
4089  if (Visibility & VIS_OverlayOnly)
4090  {
4091  if (!fAsOverlay) return false;
4092  if (Visibility == VIS_OverlayOnly) return true;
4093  }
4094  // editor visibility
4095  if (::Application.isEditor)
4096  {
4097  if (Visibility & VIS_Editor) return true;
4098  }
4099  // check visibility
4100  fDraw=false;
4101  if (Visibility & VIS_Owner) fDraw = fDraw || (iForPlr==Owner);
4102  if (iForPlr!=NO_OWNER)
4103  {
4104  // check all
4105  if (Visibility & VIS_Allies) fDraw = fDraw || (iForPlr!=Owner && !Hostile(iForPlr, Owner));
4106  if (Visibility & VIS_Enemies) fDraw = fDraw || (iForPlr!=Owner && Hostile(iForPlr, Owner));
4107  if (parameters)
4108  {
4109  if (Visibility & VIS_Select) fDraw = fDraw || parameters->GetItem(1+iForPlr).getBool();
4110  }
4111  }
4112  else fDraw = fDraw || (Visibility & VIS_God);
4113  return fDraw;
4114 }
4115 
4117 {
4118  return GBackLiquid(GetX(),GetY()+Def->Float*Con/FullCon-1);
4119 }
4120 
4121 void C4Object::SetRotation(int32_t nr)
4122 {
4123  while (nr<0) nr+=360; nr%=360;
4124  // remove solid mask
4125  if (pSolidMaskData) pSolidMaskData->Remove(false);
4126  // set rotation
4127  fix_r=itofix(nr);
4128  // Update face
4129  UpdateFace(true);
4130 }
4131 
4133 {
4134  // color modulation
4136  // other blit modes
4138 }
4139 
4141 {
4142  // color modulation
4144  // extra blitting flags
4145  pDraw->ResetBlitMode();
4146 }
4147 
4149 {
4150  // mask must exist
4151  if (!pSolidMaskData) return;
4152  // draw it
4153  pSolidMaskData->Draw(cgo);
4154 }
4155 
4156 void C4Object::UpdateSolidMask(bool fRestoreAttachedObjects)
4157 {
4158  // solidmask doesn't make sense with non-existant objects
4159  // (the solidmask has already been destroyed in AssignRemoval -
4160  // do not reset it!)
4161  if (!Status) return;
4162  // Determine necessity, update cSolidMask, put or remove mask
4163  // Mask if enabled, fullcon, not contained
4164  if (SolidMask.Wdt > 0 && Con >= FullCon && !Contained)
4165  {
4166  // Recheck and put mask
4167  if (!pSolidMaskData)
4168  {
4169  pSolidMaskData = new C4SolidMask(this);
4170  }
4171  else
4172  pSolidMaskData->Remove(false);
4173  pSolidMaskData->Put(true, nullptr, fRestoreAttachedObjects);
4174  }
4175  // Otherwise, remove and destroy mask
4176  else if (pSolidMaskData)
4177  {
4178  delete pSolidMaskData; pSolidMaskData = nullptr;
4179  }
4180 }
4181 
4183 {
4184  // Object enter container
4185  bool fRejectCollect;
4186  if (!pObj->Enter(this, true, false, &fRejectCollect))
4187  return false;
4188  // Cancel attach (hacky)
4189  ObjectComCancelAttach(pObj);
4190  // Container Collection call
4192  // Object Hit call
4193  if (pObj->Status && pObj->OCF & OCF_HitSpeed1) pObj->Call(PSF_Hit);
4194  if (pObj->Status && pObj->OCF & OCF_HitSpeed2) pObj->Call(PSF_Hit2);
4195  if (pObj->Status && pObj->OCF & OCF_HitSpeed3) pObj->Call(PSF_Hit3);
4196  // post-copy the motion of the new container
4197  if (pObj->Contained == this) pObj->CopyMotion(this);
4198  // done, success
4199  return true;
4200 }
4201 
4203 {
4204  // safety
4205  if (!pFrom) return false; if (!Status || !pFrom->Status) return false;
4206  // even more safety (own info: success)
4207  if (pFrom == this) return true;
4208  // only if other object has info
4209  if (!pFrom->Info) return false;
4210  // clear own info object
4211  if (Info)
4212  {
4213  Info->Retire();
4214  ClearInfo (Info);
4215  }
4216  // remove objects from any owning crews
4217  ::Players.ClearPointers(pFrom);
4218  ::Players.ClearPointers(this);
4219  // set info
4220  Info = pFrom->Info; pFrom->ClearInfo (pFrom->Info);
4221  // set name
4222  SetName(Info->Name);
4223  // retire from old crew
4224  Info->Retire();
4225  // if alive, recruit to new crew
4226  if (Alive) Info->Recruit();
4227  // make new crew member
4228  C4Player *pPlr = ::Players.Get(Owner);
4229  if (pPlr) pPlr->MakeCrewMember(this);
4230  // done, success
4231  return true;
4232 }
4233 
4234 bool C4Object::ShiftContents(bool fShiftBack, bool fDoCalls)
4235 {
4236  // get current object
4237  C4Object *c_obj = Contents.GetObject();
4238  if (!c_obj) return false;
4239  // get next/previous
4240  auto it = fShiftBack ? Contents.reverse().begin() : ++Contents.begin();
4241  while (!it.atEnd())
4242  {
4243  auto pObj = (*it);
4244  // check object
4245  if (pObj->Status)
4246  if (!c_obj->CanConcatPictureWith(pObj))
4247  {
4248  // object different: shift to this
4249  DirectComContents(pObj, !!fDoCalls);
4250  return true;
4251  }
4252  // next/prev item
4253  it++;
4254  }
4255  return false;
4256 }
4257 
4258 void C4Object::DirectComContents(C4Object *pTarget, bool fDoCalls)
4259 {
4260  // safety
4261  if (!pTarget || !pTarget->Status || pTarget->Contained != this) return;
4262  // Desired object already at front?
4263  if (Contents.GetObject() == pTarget) return;
4264  // select object via script?
4265  if (fDoCalls)
4266  if (Call("~ControlContents", &C4AulParSet(pTarget)))
4267  return;
4268  // default action
4269  if (!(Contents.ShiftContents(pTarget))) return;
4270  // Selection sound
4271  if (fDoCalls) if (!Contents.GetObject()->Call("~Selection", &C4AulParSet(this))) StartSoundEffect("Clonk::Action::Grab",false,100,this);
4272  // update menu with the new item in "put" entry
4273  if (Menu && Menu->IsActive() && Menu->IsContextMenu())
4274  {
4275  Menu->Refill();
4276  }
4277  // Done
4278  return;
4279 }
4280 
4281 void C4Object::GetParallaxity(int32_t *parX, int32_t *parY) const
4282 {
4283  assert(parX); assert(parY);
4284  *parX = 100; *parY = 100;
4285  if (Category & C4D_Foreground)
4286  {
4287  *parX = 0; *parY = 0;
4288  return;
4289  }
4290  if (!(Category & C4D_Parallax)) return;
4291  C4Value parV; GetProperty(P_Parallaxity, &parV);
4292  C4ValueArray *par = parV.getArray();
4293  if (!par) return;
4294  *parX = par->GetItem(0).getInt();
4295  *parY = par->GetItem(1).getInt();
4296 }
4297 
4298 bool C4Object::GetDragImage(C4Object **drag_object, C4Def **drag_def) const
4299 {
4300  // drag is possible if MouseDragImage is assigned
4301  C4Value parV; GetProperty(P_MouseDragImage, &parV);
4302  if (!parV) return false;
4303  // determine drag object/id
4304  C4Object *obj = parV.getObj();
4305  C4Def * def = nullptr;
4306  if (!obj) def = parV.getDef();
4307  if (drag_object) *drag_object = obj;
4308  if (drag_def) *drag_def = def;
4309  // drag possible, even w./o image
4310  return true;
4311 }
4312 
4313 int32_t C4Object::AddObjectAndContentsToArray(C4ValueArray *target_array, int32_t index)
4314 {
4315  // add self, contents and child contents count recursively to value array. Return index after last added item.
4316  target_array->SetItem(index++, C4VObj(thi