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