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