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