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"
21 #include "C4ForbidLibraryCompilation.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"
30 #include "graphics/C4GraphicsResource.h"
31 #include "gui/C4GameMessage.h"
32 #include "landscape/C4MaterialList.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"
43 #include "object/C4MeshDenumerator.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 
DrawVertex(C4Facet & cgo,float tx,float ty,int32_t col,int32_t contact)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 
SetBridgeData(int32_t iBridgeTime,bool fMoveClonk,bool fWall,int32_t iBridgeMaterial)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 
GetBridgeData(int32_t & riBridgeTime,bool & rfMoveClonk,bool & rfWall,int32_t & riBridgeMaterial)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 
C4Object()85 C4Object::C4Object()
86 {
87 	FrontParticles = BackParticles = nullptr;
88 	Default();
89 }
90 
Default()91 void C4Object::Default()
92 {
93 	id=C4ID::None;
94 	nInfo.Clear();
95 	RemovalDelay=0;
96 	Owner=NO_OWNER;
97 	Controller=NO_OWNER;
98 	LastEnergyLossCausePlayer=NO_OWNER;
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;
109 	lightFadeoutRange=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;
119 	Audible=AudiblePan=0;
120 	AudiblePlayer = NO_OWNER;
121 	t_contact=0;
122 	OCF=0;
123 	Action.Default();
124 	Shape.Default();
125 	fOwnVertices=false;
126 	Contents.Default();
127 	SolidMask.Default();
128 	HalfVehicleSolidMask=false;
129 	PictureRect.Default();
130 	Def=nullptr;
131 	Info=nullptr;
132 	Command=nullptr;
133 	Contained=nullptr;
134 	TopFace.Default();
135 	Menu=nullptr;
136 	MaterialContents=nullptr;
137 	Marker=0;
138 	ColorMod=0xffffffff;
139 	BlitMode=0;
140 	CrewDisabled=false;
141 	Layer=nullptr;
142 	pSolidMaskData=nullptr;
143 	pGraphics=nullptr;
144 	pMeshInstance=nullptr;
145 	pDrawTransform=nullptr;
146 	pEffects=nullptr;
147 	pGfxOverlay=nullptr;
148 	iLastAttachMovementFrame=-1;
149 
150 	ClearParticleLists();
151 }
152 
Init(C4PropList * pDef,C4Object * pCreator,int32_t iOwner,C4ObjectInfo * pInfo,int32_t nx,int32_t ny,int32_t nr,C4Real nxdir,C4Real nydir,C4Real nrdir,int32_t iController)153 bool C4Object::Init(C4PropList *pDef, C4Object *pCreator,
154                     int32_t iOwner, C4ObjectInfo *pInfo,
155                     int32_t nx, int32_t ny, int32_t nr,
156                     C4Real nxdir, C4Real nydir, C4Real nrdir, int32_t iController)
157 {
158 	C4PropListNumbered::AcquireNumber();
159 	// currently initializing
160 	Initializing=true;
161 
162 	// Def & basics
163 	Owner=iOwner;
164 	if (iController > NO_OWNER) Controller = iController; else Controller=iOwner;
165 	LastEnergyLossCausePlayer=NO_OWNER;
166 	Info=pInfo;
167 	Def=pDef->GetDef(); assert(Def);
168 	SetProperty(P_Prototype, C4VPropList(pDef));
169 	id=Def->id;
170 	if (Info) SetName(pInfo->Name);
171 	Category=Def->Category;
172 	Plane = Def->GetPlane(); assert(Plane);
173 	Def->Count++;
174 	if (pCreator) Layer=pCreator->Layer;
175 
176 	// graphics
177 	pGraphics = &Def->Graphics;
178 	if (pGraphics->Type == C4DefGraphics::TYPE_Mesh)
179 	{
180 		pMeshInstance = new StdMeshInstance(*pGraphics->Mesh, Def->GrowthType ? 1.0f : static_cast<float>(Con)/static_cast<float>(FullCon));
181 		pMeshInstance->SetFaceOrderingForClrModulation(ColorMod);
182 	}
183 	else
184 	{
185 		pMeshInstance = nullptr;
186 	}
187 	BlitMode = Def->BlitMode;
188 
189 	// Position
190 	if (!Def->Rotateable) { nr=0; nrdir=0; }
191 	fix_x=itofix(nx);
192 	fix_y=itofix(ny);
193 	fix_r=itofix(nr);
194 	xdir=nxdir; ydir=nydir; rdir=nrdir;
195 
196 	// Initial mobility
197 	if (!!xdir || !!ydir || !!rdir)
198 		Mobile=true;
199 
200 	// Mass
201 	Mass=std::max<int32_t>(Def->Mass*Con/FullCon,1);
202 
203 	// Life, energy, breath
204 	if (Category & C4D_Living) Alive=true;
205 	if (Alive) Energy=GetPropertyInt(P_MaxEnergy);
206 	Breath=GetPropertyInt(P_MaxBreath);
207 
208 	// Color
209 	if (Def->ColorByOwner)
210 	{
211 		if (ValidPlr(Owner))
212 			Color=::Players.Get(Owner)->ColorDw;
213 		else
214 			Color=0xff; // no-owner color: blue
215 	}
216 
217 	// Shape & face
218 	Shape=Def->Shape;
219 	SolidMask=Def->SolidMask;
220 	CheckSolidMaskRect();
221 	UpdateGraphics(false);
222 	UpdateFace(true);
223 
224 	// Initial audibility
225 	Audible=::Viewports.GetAudibility(GetX(), GetY(), &AudiblePan, 0, &AudiblePlayer);
226 
227 	// Initial OCF
228 	SetOCF();
229 
230 	// finished initializing
231 	Initializing=false;
232 
233 	return true;
234 }
235 
~C4Object()236 C4Object::~C4Object()
237 {
238 	Clear();
239 
240 #if defined(_DEBUG)
241 	// debug: mustn't be listed in any list now
242 	::Objects.Sectors.AssertObjectNotInList(this);
243 #endif
244 }
245 
ClearParticleLists()246 void C4Object::ClearParticleLists()
247 {
248 	if (FrontParticles != nullptr)
249 		Particles.ReleaseParticleList(FrontParticles);
250 	if (BackParticles != nullptr)
251 		Particles.ReleaseParticleList(BackParticles);
252 	FrontParticles = BackParticles = nullptr;
253 }
254 
AssignRemoval(bool fExitContents)255 void C4Object::AssignRemoval(bool fExitContents)
256 {
257 	// check status
258 	if (!Status) return;
259 	if (Config.General.DebugRec)
260 	{
261 		C4RCCreateObj rc;
262 		memset(&rc, '\0', sizeof(rc));
263 		rc.oei=Number;
264 		if (Def && Def->GetName()) strncpy(rc.id, Def->GetName(), 32+1);
265 		rc.x=GetX(); rc.y=GetY(); rc.ownr=Owner;
266 		AddDbgRec(RCT_DsObj, &rc, sizeof(rc));
267 	}
268 	// Destruction call in container
269 	if (Contained)
270 	{
271 		C4AulParSet pars(this);
272 		Contained->Call(PSF_ContentsDestruction, &pars);
273 		if (!Status) return;
274 	}
275 	// Destruction call
276 	Call(PSF_Destruction);
277 	// Destruction-callback might have deleted the object already
278 	if (!Status) return;
279 	// remove all effects (extinguishes as well)
280 	if (pEffects)
281 	{
282 		pEffects->ClearAll(C4FxCall_RemoveClear);
283 		// Effect-callback might actually have deleted the object already
284 		if (!Status) return;
285 	}
286 	// remove particles
287 	ClearParticleLists();
288 	// Action idle
289 	SetAction(nullptr);
290 	// Object system operation
291 	if (Status == C4OS_INACTIVE)
292 	{
293 		// object was inactive: activate first, then delete
294 		::Objects.InactiveObjects.Remove(this);
295 		Status = C4OS_NORMAL;
296 		::Objects.Add(this);
297 	}
298 	Status=0;
299 	// count decrease
300 	Def->Count--;
301 
302 	// get container for next actions
303 	C4Object *pCont = Contained;
304 	// remove or exit contents
305 	for (C4Object *cobj : Contents)
306 	{
307 		if (fExitContents)
308 		{
309 			// move objects to parent container or exit them completely
310 			bool fRejectCollect;
311 			if (!pCont || !cobj->Enter(pCont, true, false, &fRejectCollect))
312 				cobj->Exit(GetX(), GetY());
313 		}
314 		else
315 		{
316 			Contents.Remove(cobj);
317 			cobj->Contained = nullptr;
318 			cobj->AssignRemoval();
319 		}
320 	}
321 	// remove this object from container *after* its contents have been removed!
322 	if (pCont)
323 	{
324 		pCont->Contents.Remove(this);
325 		pCont->UpdateMass();
326 		pCont->SetOCF();
327 		Contained=nullptr;
328 	}
329 	// Object info
330 	if (Info) Info->Retire();
331 	Info = nullptr;
332 	// Object system operation
333 	ClearRefs();
334 	Game.ClearPointers(this);
335 	ClearCommands();
336 	if (pSolidMaskData)
337 	{
338 		delete pSolidMaskData;
339 		pSolidMaskData = nullptr;
340 	}
341 	SolidMask.Wdt = 0;
342 	RemovalDelay=2;
343 }
344 
UpdateShape(bool bUpdateVertices)345 void C4Object::UpdateShape(bool bUpdateVertices)
346 {
347 
348 	// Line shape independent
349 	if (Def->Line) return;
350 
351 	// Copy shape from def
352 	Shape.CopyFrom(Def->Shape, bUpdateVertices, !!fOwnVertices);
353 
354 	// Construction zoom
355 	if (Con!=FullCon)
356 	{
357 		if (Def->GrowthType)
358 			Shape.Stretch(Con, bUpdateVertices);
359 		else
360 			Shape.Jolt(Con, bUpdateVertices);
361 	}
362 
363 	// Rotation
364 	if (Def->Rotateable)
365 		if (fix_r != Fix0)
366 			Shape.Rotate(fix_r, bUpdateVertices);
367 
368 	// covered area changed? to be on the save side, update pos
369 	UpdatePos();
370 }
371 
UpdatePos()372 void C4Object::UpdatePos()
373 {
374 	// get new area covered
375 	// do *NOT* do this while initializing, because object cannot be sorted by main list
376 	if (!Initializing && Status == C4OS_NORMAL)
377 		::Objects.UpdatePos(this);
378 }
379 
UpdateFace(bool bUpdateShape,bool fTemp)380 void C4Object::UpdateFace(bool bUpdateShape, bool fTemp)
381 {
382 
383 	// Update shape - NOT for temp call, because temnp calls are done in drawing routine
384 	// must not change sync relevant data here (although the shape and pos *should* be updated at that time anyway,
385 	// because a runtime join would desync otherwise)
386 	if (!fTemp) { if (bUpdateShape) UpdateShape(); else UpdatePos(); }
387 
388 	// SolidMask
389 	if (!fTemp) UpdateSolidMask(false);
390 
391 	// Null defaults
392 	TopFace.Default();
393 
394 	// newgfx: TopFace only
395 	if (Con>=FullCon || Def->GrowthType)
396 		if (!Def->Rotateable || (fix_r == Fix0))
397 			if (Def->TopFace.Wdt>0) // Fullcon & no rotation
398 				TopFace.Set(GetGraphics()->GetBitmap(Color),
399 				            Def->TopFace.x,Def->TopFace.y,
400 				            Def->TopFace.Wdt,Def->TopFace.Hgt);
401 
402 	// Active face
403 	UpdateActionFace();
404 }
405 
UpdateGraphics(bool fGraphicsChanged,bool fTemp)406 void C4Object::UpdateGraphics(bool fGraphicsChanged, bool fTemp)
407 {
408 	// check color
409 	if (!fTemp) if (!pGraphics->IsColorByOwner()) Color=0;
410 	// new grafics: update face
411 	if (fGraphicsChanged)
412 	{
413 		// Keep mesh instance if it uses the same underlying mesh
414 		if(!pMeshInstance || pGraphics->Type != C4DefGraphics::TYPE_Mesh ||
415 		   &pMeshInstance->GetMesh() != pGraphics->Mesh)
416 		{
417 			// If this mesh is attached somewhere, detach it before deletion
418 			if(pMeshInstance && pMeshInstance->GetAttachParent() != nullptr)
419 			{
420 				// TODO: If the new mesh has a bone with the same name, we could try updating...
421 				StdMeshInstance::AttachedMesh* attach_parent = pMeshInstance->GetAttachParent();
422 				attach_parent->Parent->DetachMesh(attach_parent->Number);
423 			}
424 
425 			delete pMeshInstance;
426 
427 			if (pGraphics->Type == C4DefGraphics::TYPE_Mesh)
428 			{
429 				pMeshInstance = new StdMeshInstance(*pGraphics->Mesh, Def->GrowthType ? 1.0f : static_cast<float>(Con)/static_cast<float>(FullCon));
430 				pMeshInstance->SetFaceOrderingForClrModulation(ColorMod);
431 			}
432 			else
433 			{
434 				pMeshInstance = nullptr;
435 			}
436 		}
437 
438 		// update face - this also puts any SolidMask
439 		UpdateFace(false);
440 	}
441 }
442 
UpdateFlipDir()443 void C4Object::UpdateFlipDir()
444 {
445 	int32_t iFlipDir;
446 	// We're active
447 	C4PropList* pActionDef = GetAction();
448 	if (pActionDef)
449 		// Get flipdir value from action
450 		if ((iFlipDir = pActionDef->GetPropertyInt(P_FlipDir)))
451 			// Action dir is in flipdir range
452 			if (Action.Dir >= iFlipDir)
453 			{
454 				// Calculate flipped drawing dir (from the flipdir direction going backwards)
455 				Action.DrawDir = (iFlipDir - 1 - (Action.Dir - iFlipDir));
456 				// Set draw transform, creating one if necessary
457 				if (pDrawTransform)
458 					pDrawTransform->SetFlipDir(-1);
459 				else
460 					pDrawTransform = new C4DrawTransform(-1);
461 				// Done setting flipdir
462 				return;
463 			}
464 	// No flipdir necessary
465 	Action.DrawDir = Action.Dir;
466 	// Draw transform present?
467 	if (pDrawTransform)
468 	{
469 		// reset flip dir
470 		pDrawTransform->SetFlipDir(1);
471 		// if it's identity now, remove the matrix
472 		if (pDrawTransform->IsIdentity())
473 		{
474 			delete pDrawTransform;
475 			pDrawTransform=nullptr;
476 		}
477 	}
478 }
479 
DrawFaceImpl(C4TargetFacet & cgo,bool action,float fx,float fy,float fwdt,float fhgt,float tx,float ty,float twdt,float thgt,C4DrawTransform * transform) const480 void C4Object::DrawFaceImpl(C4TargetFacet &cgo, bool action, float fx, float fy, float fwdt, float fhgt, float tx, float ty, float twdt, float thgt, C4DrawTransform* transform) const
481 {
482 	C4Surface* sfc;
483 	switch (GetGraphics()->Type)
484 	{
485 	case C4DefGraphics::TYPE_None:
486 		// no graphics.
487 		break;
488 	case C4DefGraphics::TYPE_Bitmap:
489 		sfc = action ? Action.Facet.Surface : GetGraphics()->GetBitmap(Color);
490 
491 		pDraw->Blit(sfc,
492 		              fx, fy, fwdt, fhgt,
493 		              cgo.Surface, tx, ty, twdt, thgt,
494 		              true, transform);
495 		break;
496 	case C4DefGraphics::TYPE_Mesh:
497 		C4Value value;
498 		GetProperty(P_MeshTransformation, &value);
499 		StdMeshMatrix matrix;
500 		if (!C4ValueToMatrix(value, &matrix))
501 			matrix = StdMeshMatrix::Identity();
502 
503 		if (fix_r != Fix0)
504 		{
505 			// Rotation should happen around the mesh center after application of any mesh transformation
506 			// So translate back by the transformed mesh center before rotation
507 			auto mesh_center = pMeshInstance->GetMesh().GetBoundingBox().GetCenter();
508 			mesh_center = matrix * mesh_center;
509 			matrix = StdMeshMatrix::Translate(-mesh_center.x, -mesh_center.y, -mesh_center.z) * matrix;
510 			matrix = StdMeshMatrix::Rotate(fixtof(fix_r) * (M_PI / 180.0f), 0.0f, 0.0f, 1.0f) * matrix;
511 			matrix = StdMeshMatrix::Translate(mesh_center.x, mesh_center.y, mesh_center.z) * matrix;
512 		}
513 
514 		if(twdt != fwdt || thgt != fhgt)
515 		{
516 			// Also scale Z so that the mesh is not totally distorted and
517 			// so that normals halfway keep pointing into sensible directions.
518 			// We don't have a better guess so use the geometric mean for Z scale.
519 			matrix = StdMeshMatrix::Scale(twdt/fwdt,thgt/fhgt,std::sqrt(twdt*thgt/(fwdt*fhgt))) * matrix;
520 		}
521 
522 		pDraw->SetMeshTransform(&matrix);
523 
524 		pDraw->RenderMesh(*pMeshInstance, cgo.Surface, tx, ty, twdt, thgt, Color, transform);
525 		pDraw->SetMeshTransform(nullptr);
526 		break;
527 	}
528 }
529 
DrawFace(C4TargetFacet & cgo,float offX,float offY,int32_t iPhaseX,int32_t iPhaseY) const530 void C4Object::DrawFace(C4TargetFacet &cgo, float offX, float offY, int32_t iPhaseX, int32_t iPhaseY) const
531 {
532 	const auto swdt = float(Def->Shape.Wdt);
533 	const auto shgt = float(Def->Shape.Hgt);
534 	// Grow Type Display
535 	auto fx = float(swdt * iPhaseX);
536 	auto fy = float(shgt * iPhaseY);
537 	auto fwdt = float(swdt);
538 	auto fhgt = float(shgt);
539 
540 	float stretch_factor = static_cast<float>(Con) / FullCon;
541 	float tx = offX + Def->Shape.GetX() * stretch_factor;
542 	float ty = offY + Def->Shape.GetY() * stretch_factor;
543 	float twdt = swdt * stretch_factor;
544 	float thgt = shgt * stretch_factor;
545 
546 	// Construction Type Display
547 	if (!Def->GrowthType)
548 	{
549 		tx = offX + Def->Shape.GetX();
550 		twdt = swdt;
551 
552 		fy += fhgt - thgt;
553 		fhgt = thgt;
554 	}
555 
556 	C4DrawTransform transform;
557 	bool transform_active = false;
558 	if (pDrawTransform)
559 	{
560 		transform.SetTransformAt(*pDrawTransform, offX, offY);
561 		transform_active = true;
562 	}
563 
564 	// Meshes aren't rotated via DrawTransform to ensure lighting is applied correctly.
565 	if (GetGraphics()->Type != C4DefGraphics::TYPE_Mesh && Def->Rotateable && fix_r != Fix0)
566 	{
567 		if (pDrawTransform)
568 			transform.Rotate(fixtof(fix_r), offX, offY);
569 		else
570 			transform.SetRotate(fixtof(fix_r), offX, offY);
571 		transform_active = true;
572 	}
573 
574 	DrawFaceImpl(cgo, false, fx, fy, fwdt, fhgt, tx, ty, twdt, thgt, transform_active ? &transform : nullptr);
575 }
576 
DrawActionFace(C4TargetFacet & cgo,float offX,float offY) const577 void C4Object::DrawActionFace(C4TargetFacet &cgo, float offX, float offY) const
578 {
579 	// This should not be called for meshes since Facet has no meaning
580 	// for them. Only use DrawFace() with meshes!
581 	assert(GetGraphics()->Type == C4DefGraphics::TYPE_Bitmap);
582 	C4PropList* pActionDef = GetAction();
583 
584 	// Regular action facet
585 	const auto swdt = float(Action.Facet.Wdt);
586 	const auto shgt = float(Action.Facet.Hgt);
587 	int32_t iPhase = Action.Phase;
588 	if (pActionDef->GetPropertyInt(P_Reverse)) iPhase = pActionDef->GetPropertyInt(P_Length) - 1 - Action.Phase;
589 
590 	// Grow Type Display
591 	auto fx = float(Action.Facet.X + swdt * iPhase);
592 	auto fy = float(Action.Facet.Y + shgt * Action.DrawDir);
593 	auto fwdt = float(swdt);
594 	auto fhgt = float(shgt);
595 
596 	// draw stretched towards shape center with transform
597 	float stretch_factor = static_cast<float>(Con) / FullCon;
598 	float tx = (Def->Shape.GetX() + Action.FacetX) * stretch_factor + offX;
599 	float ty = (Def->Shape.GetY() + Action.FacetY) * stretch_factor + offY;
600 	float twdt = swdt * stretch_factor;
601 	float thgt = shgt * stretch_factor;
602 
603 	// Construction Type Display
604 	if (!Def->GrowthType)
605 	{
606 		// FIXME
607 		if (Con != FullCon)
608 		{
609 			// incomplete constructions do not show actions
610 			DrawFace(cgo, offX, offY);
611 			return;
612 		}
613 		tx = Def->Shape.GetX() + Action.FacetX + offX;
614 		twdt = swdt;
615 		float offset_from_top = shgt * std::max(FullCon - Con, 0) / FullCon;
616 		fy += offset_from_top;
617 		fhgt -= offset_from_top;
618 	}
619 
620 	C4DrawTransform transform;
621 	bool transform_active = false;
622 	if (pDrawTransform)
623 	{
624 		transform.SetTransformAt(*pDrawTransform, offX, offY);
625 		transform_active = true;
626 	}
627 
628 	// Meshes aren't rotated via DrawTransform to ensure lighting is applied correctly.
629 	if (GetGraphics()->Type != C4DefGraphics::TYPE_Mesh && Def->Rotateable && fix_r != Fix0)
630 	{
631 		if (pDrawTransform)
632 			transform.Rotate(fixtof(fix_r), offX, offY);
633 		else
634 			transform.SetRotate(fixtof(fix_r), offX, offY);
635 		transform_active = true;
636 	}
637 
638 	DrawFaceImpl(cgo, true, fx, fy, fwdt, fhgt, tx, ty, twdt, thgt, transform_active ? &transform : nullptr);
639 }
640 
UpdateMass()641 void C4Object::UpdateMass()
642 {
643 	Mass=std::max<int32_t>((Def->Mass+OwnMass)*Con/FullCon,1);
644 	if (!Def->NoMassFromContents) Mass+=Contents.Mass;
645 	if (Contained)
646 	{
647 		Contained->Contents.MassCount();
648 		Contained->UpdateMass();
649 	}
650 }
651 
UpdateInMat()652 void C4Object::UpdateInMat()
653 {
654 	// get new mat
655 	int32_t newmat;
656 	if (Contained)
657 		newmat = Contained->Def->ClosedContainer ? MNone : Contained->InMat;
658 	else
659 		newmat = GBackMat(GetX(), GetY());
660 
661 	// mat changed?
662 	if (newmat != InMat)
663 	{
664 		Call(PSF_OnMaterialChanged,&C4AulParSet(newmat,InMat));
665 		InMat = newmat;
666 	}
667 }
668 
SetOCF()669 void C4Object::SetOCF()
670 {
671 	C4PropList* pActionDef = GetAction();
672 	uint32_t dwOCFOld = OCF;
673 	// Update the object character flag according to the object's current situation
674 	C4Real cspeed=GetSpeed();
675 #ifdef _DEBUG
676 	if (Contained && !C4PropListNumbered::CheckPropList(Contained))
677 		{ LogF("Warning: contained in wild object %p!", static_cast<void*>(Contained)); }
678 	else if (Contained && !Contained->Status)
679 		{ LogF("Warning: contained in deleted object (#%d) (%s)!", Contained->Number, Contained->GetName()); }
680 #endif
681 	// OCF_Normal: The OCF is never zero
682 	OCF=OCF_Normal;
683 	// OCF_Construct: Can be built outside
684 	if (Def->Constructable && (Con<FullCon)
685 	    && (fix_r==Fix0) && !OnFire)
686 		OCF|=OCF_Construct;
687 	// OCF_Grab: Can be pushed
688 	if (GetPropertyInt(P_Touchable))
689 		OCF|=OCF_Grab;
690 	// OCF_Carryable: Can be picked up
691 	if (GetPropertyInt(P_Collectible))
692 		OCF|=OCF_Carryable;
693 	// OCF_OnFire: Is burning
694 	if (OnFire)
695 		OCF|=OCF_OnFire;
696 	// OCF_Inflammable: Is not burning and is inflammable
697 	if (!OnFire && GetPropertyInt(P_ContactIncinerate) > 0)
698 		OCF|=OCF_Inflammable;
699 	// OCF_FullCon: Is fully completed/grown
700 	if (Con>=FullCon)
701 		OCF|=OCF_FullCon;
702 	// OCF_Rotate: Can be rotated
703 	if (Def->Rotateable)
704 		// Don't rotate minimum (invisible) construction sites
705 		if (Con>100)
706 			OCF|=OCF_Rotate;
707 	// OCF_Exclusive: No action through this, no construction in front of this
708 	if (Def->Exclusive)
709 		OCF|=OCF_Exclusive;
710 	// OCF_Entrance: Can currently be entered/activated
711 	if ((Def->Entrance.Wdt>0) && (Def->Entrance.Hgt>0))
712 		if ((OCF & OCF_FullCon) && ((Def->RotatedEntrance == 1) || (GetR() <= Def->RotatedEntrance)))
713 			OCF|=OCF_Entrance;
714 	// HitSpeeds
715 	if (cspeed>=HitSpeed1) OCF|=OCF_HitSpeed1;
716 	if (cspeed>=HitSpeed2) OCF|=OCF_HitSpeed2;
717 	if (cspeed>=HitSpeed3) OCF|=OCF_HitSpeed3;
718 	if (cspeed>=HitSpeed4) OCF|=OCF_HitSpeed4;
719 	// OCF_Collection
720 	if ((OCF & OCF_FullCon) || Def->IncompleteActivity)
721 		if ((Def->Collection.Wdt>0) && (Def->Collection.Hgt>0))
722 			if (!pActionDef || (!pActionDef->GetPropertyInt(P_ObjectDisabled)))
723 					OCF|=OCF_Collection;
724 	// OCF_Alive
725 	if (Alive) OCF|=OCF_Alive;
726 	// OCF_CrewMember
727 	if (Def->CrewMember)
728 		if (Alive)
729 			OCF|=OCF_CrewMember;
730 	// OCF_NotContained
731 	if (!Contained)
732 		OCF|=OCF_NotContained;
733 	// OCF_InLiquid
734 	if (InLiquid)
735 		if (!Contained)
736 			OCF|=OCF_InLiquid;
737 	// OCF_InSolid
738 	if (!Contained)
739 		if (GBackSolid(GetX(), GetY()))
740 			OCF|=OCF_InSolid;
741 	// OCF_InFree
742 	if (!Contained)
743 		if (!GBackSemiSolid(GetX(), GetY()-1))
744 			OCF|=OCF_InFree;
745 	// OCF_Available
746 	if (!Contained || (Contained->Def->GrabPutGet & C4D_Grab_Get) || (Contained->OCF & OCF_Entrance))
747 		if (!GBackSemiSolid(GetX(), GetY()-1) || (!GBackSolid(GetX(), GetY()-1) && !GBackSemiSolid(GetX(), GetY()-8)))
748 			OCF|=OCF_Available;
749 	// OCF_Container
750 	if ((Def->GrabPutGet & C4D_Grab_Put) || (Def->GrabPutGet & C4D_Grab_Get) || (OCF & OCF_Entrance))
751 		OCF|=OCF_Container;
752 	if (DEBUGREC_OCF && Config.General.DebugRec)
753 	{
754 		C4RCOCF rc = { dwOCFOld, OCF, false };
755 		AddDbgRec(RCT_OCF, &rc, sizeof(rc));
756 	}
757 }
758 
759 
UpdateOCF()760 void C4Object::UpdateOCF()
761 {
762 	C4PropList* pActionDef = GetAction();
763 	uint32_t dwOCFOld = OCF;
764 	// Update the object character flag according to the object's current situation
765 	C4Real cspeed=GetSpeed();
766 #ifdef _DEBUG
767 	if (Contained && !C4PropListNumbered::CheckPropList(Contained))
768 		{ LogF("Warning: contained in wild object %p!", static_cast<void*>(Contained)); }
769 	else if (Contained && !Contained->Status)
770 		{ LogF("Warning: contained in deleted object %p (%s)!", static_cast<void*>(Contained), Contained->GetName()); }
771 #endif
772 	// Keep the bits that only have to be updated with SetOCF (def, category, con, alive, onfire)
773 	OCF=OCF & (OCF_Normal | OCF_Exclusive | OCF_FullCon | OCF_Rotate | OCF_OnFire
774 		| OCF_Alive | OCF_CrewMember);
775 	// OCF_inflammable: can catch fire and is not currently burning.
776 	if (!OnFire && GetPropertyInt(P_ContactIncinerate) > 0)
777 		OCF |= OCF_Inflammable;
778 	// OCF_Carryable: Can be picked up
779 	if (GetPropertyInt(P_Collectible))
780 		OCF|=OCF_Carryable;
781 	// OCF_Grab: Can be grabbed.
782 	if (GetPropertyInt(P_Touchable))
783 		OCF |= OCF_Grab;
784 	// OCF_Construct: Can be built outside
785 	if (Def->Constructable && (Con<FullCon)
786 	    && (fix_r == Fix0) && !OnFire)
787 		OCF|=OCF_Construct;
788 	// OCF_Entrance: Can currently be entered/activated
789 	if ((Def->Entrance.Wdt>0) && (Def->Entrance.Hgt>0))
790 		if ((OCF & OCF_FullCon) && ((Def->RotatedEntrance == 1) || (GetR() <= Def->RotatedEntrance)))
791 			OCF|=OCF_Entrance;
792 	// HitSpeeds
793 	if (cspeed>=HitSpeed1) OCF|=OCF_HitSpeed1;
794 	if (cspeed>=HitSpeed2) OCF|=OCF_HitSpeed2;
795 	if (cspeed>=HitSpeed3) OCF|=OCF_HitSpeed3;
796 	if (cspeed>=HitSpeed4) OCF|=OCF_HitSpeed4;
797 	// OCF_Collection
798 	if ((OCF & OCF_FullCon) || Def->IncompleteActivity)
799 		if ((Def->Collection.Wdt>0) && (Def->Collection.Hgt>0))
800 			if (!pActionDef || (!pActionDef->GetPropertyInt(P_ObjectDisabled)))
801 					OCF|=OCF_Collection;
802 	// OCF_NotContained
803 	if (!Contained)
804 		OCF|=OCF_NotContained;
805 	// OCF_InLiquid
806 	if (InLiquid)
807 		if (!Contained)
808 			OCF|=OCF_InLiquid;
809 	// OCF_InSolid
810 	if (!Contained)
811 		if (GBackSolid(GetX(), GetY()))
812 			OCF|=OCF_InSolid;
813 	// OCF_InFree
814 	if (!Contained)
815 		if (!GBackSemiSolid(GetX(), GetY()-1))
816 			OCF|=OCF_InFree;
817 	// OCF_Available
818 	if (!Contained || (Contained->Def->GrabPutGet & C4D_Grab_Get) || (Contained->OCF & OCF_Entrance))
819 		if (!GBackSemiSolid(GetX(), GetY()-1) || (!GBackSolid(GetX(), GetY()-1) && !GBackSemiSolid(GetX(), GetY()-8)))
820 			OCF|=OCF_Available;
821 	// OCF_Container
822 	if ((Def->GrabPutGet & C4D_Grab_Put) || (Def->GrabPutGet & C4D_Grab_Get) || (OCF & OCF_Entrance))
823 		OCF|=OCF_Container;
824 	if (DEBUGREC_OCF && Config.General.DebugRec)
825 	{
826 		C4RCOCF rc = { dwOCFOld, OCF, true };
827 		AddDbgRec(RCT_OCF, &rc, sizeof(rc));
828 	}
829 #ifdef _DEBUG
830 	DEBUGREC_OFF
831 	uint32_t updateOCF = OCF;
832 	SetOCF();
833 	assert (updateOCF == OCF);
834 	DEBUGREC_ON
835 #endif
836 }
837 
838 
ExecLife()839 bool C4Object::ExecLife()
840 {
841 	// Breathing
842 	if (!::Game.iTick5)
843 		if (Alive && !Def->NoBreath)
844 		{
845 			// Supply check
846 			bool Breathe=false;
847 			// Forcefields are breathable.
848 			if (GBackMat(GetX(), GetY()+Shape.GetY()/2)==MVehic)
849 				{ Breathe=true; }
850 			else if (GetPropertyInt(P_BreatheWater))
851 				{ if (GBackMat(GetX(), GetY())==MWater) Breathe=true; }
852 			else
853 				{ if (!GBackSemiSolid(GetX(), GetY()+Shape.GetY()/2)) Breathe=true; }
854 			if (Contained) Breathe=true;
855 			// No supply
856 			if (!Breathe)
857 			{
858 				// Reduce breath, then energy, bubble
859 				if (Breath > 0) DoBreath(-5);
860 				else DoEnergy(-1,false,C4FxCall_EngAsphyxiation, NO_OWNER);
861 			}
862 			// Supply
863 			else
864 			{
865 				// Take breath
866 				int32_t takebreath = GetPropertyInt(P_MaxBreath) - Breath;
867 				if (takebreath > 0) DoBreath(takebreath);
868 			}
869 		}
870 
871 	// Corrosion energy loss
872 	if (!::Game.iTick10)
873 		if (Alive)
874 			if (InMat!=MNone)
875 				if (::MaterialMap.Map[InMat].Corrosive)
876 					if (!GetPropertyInt(P_CorrosionResist))
877 						DoEnergy(-::MaterialMap.Map[InMat].Corrosive/15,false,C4FxCall_EngCorrosion, NO_OWNER);
878 
879 	// InMat incineration
880 	if (!::Game.iTick10)
881 		if (InMat!=MNone)
882 			if (::MaterialMap.Map[InMat].Incendiary)
883 				if (GetPropertyInt(P_ContactIncinerate) > 0 || GetPropertyBool(P_MaterialIncinerate) > 0)
884 				{
885 					Call(PSF_OnInIncendiaryMaterial, &C4AulParSet());
886 				}
887 
888 	// birthday
889 	if (!::Game.iTick255)
890 		if (Alive)
891 			if (Info)
892 			{
893 				int32_t iPlayingTime = Info->TotalPlayingTime + (Game.Time - Info->InActionTime);
894 
895 				int32_t iNewAge = iPlayingTime / 3600 / 5;
896 
897 				if (Info->Age != iNewAge && Info->Age)
898 				{
899 					// message
900 					GameMsgObject(FormatString(LoadResStr("IDS_OBJ_BIRTHDAY"),GetName (), Info->TotalPlayingTime / 3600 / 5).getData(),this);
901 					StartSoundEffect("UI::Trumpet",false,100,this);
902 				}
903 
904 				Info->Age = iNewAge;
905 
906 
907 			}
908 
909 	return true;
910 }
911 
Execute()912 void C4Object::Execute()
913 {
914 	if (Config.General.DebugRec)
915 	{
916 		// record debug
917 		C4RCExecObj rc;
918 		rc.Number=Number;
919 		rc.fx=fix_x;
920 		rc.fy=fix_y;
921 		rc.fr=fix_r;
922 		AddDbgRec(RCT_ExecObj, &rc, sizeof(rc));
923 	}
924 	// OCF
925 	UpdateOCF();
926 	// Command
927 	ExecuteCommand();
928 	// Action
929 	// need not check status, because dead objects have lost their action
930 	ExecAction();
931 	// commands and actions are likely to have removed the object, and movement
932 	// *must not* be executed for dead objects (SolidMask-errors)
933 	if (!Status) return;
934 	// Movement
935 	ExecMovement();
936 	if (!Status) return;
937 	// effects
938 	if (pEffects)
939 	{
940 		C4Effect::Execute(&pEffects);
941 		if (!Status) return;
942 	}
943 	// Life
944 	ExecLife();
945 	// Animation. If the mesh is attached, then don't execute animation here but let the parent object do it to make sure it is only executed once a frame.
946 	if (pMeshInstance && !pMeshInstance->GetAttachParent())
947 		pMeshInstance->ExecuteAnimation(1.0f/37.0f /* play smoothly at 37 FPS */);
948 	// Menu
949 	if (Menu) Menu->Execute();
950 }
951 
At(int32_t ctx,int32_t cty) const952 bool C4Object::At(int32_t ctx, int32_t cty) const
953 {
954 	if (Status) if (!Contained) if (Def)
955 				if (Inside<int32_t>(cty - (GetY() + Shape.GetY() - addtop()), 0, Shape.Hgt - 1 + addtop()))
956 					if (Inside<int32_t>(ctx - (GetX() + Shape.GetX()), 0, Shape.Wdt - 1))
957 						return true;
958 	return false;
959 }
960 
At(int32_t ctx,int32_t cty,DWORD & ocf) const961 bool C4Object::At(int32_t ctx, int32_t cty, DWORD &ocf) const
962 {
963 	if (Status) if (!Contained) if (Def)
964 				if (OCF & ocf)
965 					if (Inside<int32_t>(cty - (GetY() + Shape.GetY() - addtop()), 0, Shape.Hgt - 1 + addtop()))
966 						if (Inside<int32_t>(ctx - (GetX() + Shape.GetX()), 0, Shape.Wdt - 1))
967 						{
968 							// Set ocf return value
969 							GetOCFForPos(ctx, cty, ocf);
970 							return true;
971 						}
972 	return false;
973 }
974 
GetOCFForPos(int32_t ctx,int32_t cty,DWORD & ocf) const975 void C4Object::GetOCFForPos(int32_t ctx, int32_t cty, DWORD &ocf) const
976 {
977 	DWORD rocf=OCF;
978 	// Verify entrance area OCF return
979 	if (rocf & OCF_Entrance)
980 		if (!Inside<int32_t>(cty - (GetY() + Def->Entrance.y), 0, Def->Entrance.Hgt - 1)
981 		    || !Inside<int32_t>(ctx - (GetX() + Def->Entrance.x), 0, Def->Entrance.Wdt - 1))
982 			rocf &= (~OCF_Entrance);
983 	// Verify collection area OCF return
984 	if (rocf & OCF_Collection)
985 		if (!Inside<int32_t>(cty - (GetY() + Def->Collection.y), 0, Def->Collection.Hgt - 1)
986 		    || !Inside<int32_t>(ctx - (GetX() + Def->Collection.x), 0, Def->Collection.Wdt - 1))
987 			rocf &= (~OCF_Collection);
988 	ocf=rocf;
989 }
990 
AssignDeath(bool fForced)991 void C4Object::AssignDeath(bool fForced)
992 {
993 	C4Object *thing;
994 	// Alive objects only
995 	if (!Alive) return;
996 	// clear all effects
997 	// do not delete effects afterwards, because they might have denied removal
998 	// set alive-flag before, so objects know what's up
999 	// and prevent recursive death-calls this way
1000 	// get death causing player before doing effect calls, because those might meddle around with the flags
1001 	int32_t iDeathCausingPlayer = LastEnergyLossCausePlayer;
1002 	Alive=false;
1003 	if (pEffects) pEffects->ClearAll(C4FxCall_RemoveDeath);
1004 	// if the object is alive again, abort here if the kill is not forced
1005 	if (Alive && !fForced) return;
1006 	// Action
1007 	SetActionByName("Dead");
1008 	// Values
1009 	Alive=false;
1010 	ClearCommands();
1011 	C4ObjectInfo * pInfo = Info;
1012 	if (Info)
1013 	{
1014 		Info->HasDied=true;
1015 		++Info->DeathCount;
1016 		Info->Retire();
1017 	}
1018 	// Remove from crew/cursor/view
1019 	C4Player *pPlr = ::Players.Get(Owner);
1020 	if (pPlr) pPlr->ClearPointers(this, true);
1021 	// Remove from light sources
1022 	SetLightRange(0,0);
1023 	// Engine script call
1024 	C4AulParSet pars(iDeathCausingPlayer);
1025 	Call(PSF_Death, &pars);
1026 	// Lose contents
1027 	while ((thing=Contents.GetObject())) thing->Exit(thing->GetX(),thing->GetY());
1028 	// Update OCF. Done here because previously it would have been done in the next frame
1029 	// Whats worse: Having the OCF change because of some unrelated script-call like
1030 	// SetCategory, or slightly breaking compatibility?
1031 	SetOCF();
1032 	// Engine broadcast: relaunch player (in CR, this was called from clonk script.
1033 	// Now, it is done for every crew member)
1034 	if(pPlr)
1035 		if(!pPlr->Crew.ObjectCount())
1036 			::Game.GRBroadcast(PSF_RelaunchPlayer,
1037 			                   &C4AulParSet(Owner, iDeathCausingPlayer, Status ? this : nullptr));
1038 	if (pInfo)
1039 		pInfo->HasDied = false;
1040 }
1041 
ChangeDef(C4ID idNew)1042 bool C4Object::ChangeDef(C4ID idNew)
1043 {
1044 	// Get new definition
1045 	C4Def *pDef=C4Id2Def(idNew);
1046 	if (!pDef) return false;
1047 	// Containment storage
1048 	C4Object *pContainer=Contained;
1049 	// Exit container (no Ejection/Departure)
1050 	if (Contained) Exit(0,0,0,Fix0,Fix0,Fix0,false);
1051 	// Pre change resets
1052 	SetAction(nullptr);
1053 	ResetProperty(&Strings.P[P_Action]); // Enforce ActIdle because SetAction may have failed due to NoOtherAction
1054 	SetDir(0); // will drop any outdated flipdir
1055 	if (pSolidMaskData) { delete pSolidMaskData; pSolidMaskData=nullptr; }
1056 	Def->Count--;
1057 	// Def change
1058 	Def=pDef;
1059 	SetProperty(P_Prototype, C4VPropList(pDef));
1060 	id=pDef->id;
1061 	Def->Count++;
1062 	// new def: Needs to be resorted
1063 	Unsorted=true;
1064 	// graphics change
1065 	pGraphics = &pDef->Graphics;
1066 	// blit mode adjustment
1067 	if (!(BlitMode & C4GFXBLIT_CUSTOM)) BlitMode = Def->BlitMode;
1068 	// an object may have newly become an ColorByOwner-object
1069 	// if it had been ColorByOwner, but is not now, this will be caught in UpdateGraphics()
1070 	if (!Color && ValidPlr(Owner))
1071 		Color=::Players.Get(Owner)->ColorDw;
1072 	if (!Def->Rotateable) { fix_r=rdir=Fix0; }
1073 	// Reset solid mask
1074 	SolidMask=Def->SolidMask;
1075 	HalfVehicleSolidMask=false;
1076 	// Post change updates
1077 	UpdateGraphics(true);
1078 	UpdateMass();
1079 	UpdateFace(true);
1080 	SetOCF();
1081 	// Any effect callbacks to this object might need to reinitialize their target functions
1082 	// This is ugly, because every effect there is must be updated...
1083 	if (::ScriptEngine.pGlobalEffects)
1084 		::ScriptEngine.pGlobalEffects->OnObjectChangedDef(this);
1085 	if (::GameScript.pScenarioEffects)
1086 		::GameScript.pScenarioEffects->OnObjectChangedDef(this);
1087 	for (C4Object *obj : Objects)
1088 		if (obj->pEffects) obj->pEffects->OnObjectChangedDef(this);
1089 	// Containment (no Entrance)
1090 	if (pContainer) Enter(pContainer,false);
1091 	// Done
1092 	return true;
1093 }
1094 
DoDamage(int32_t iChange,int32_t iCausedBy,int32_t iCause)1095 void C4Object::DoDamage(int32_t iChange, int32_t iCausedBy, int32_t iCause)
1096 {
1097 	// non-living: ask effects first
1098 	if (pEffects && !Alive)
1099 	{
1100 		pEffects->DoDamage(iChange, iCause, iCausedBy);
1101 		if (!iChange) return;
1102 	}
1103 	// Change value
1104 	Damage = std::max<int32_t>( Damage+iChange, 0 );
1105 	// Engine script call
1106 	Call(PSF_Damage,&C4AulParSet(iChange, iCause, iCausedBy));
1107 }
1108 
DoEnergy(int32_t iChange,bool fExact,int32_t iCause,int32_t iCausedByPlr)1109 void C4Object::DoEnergy(int32_t iChange, bool fExact, int32_t iCause, int32_t iCausedByPlr)
1110 {
1111 	if (!fExact)
1112 	{
1113 		// Clamp range of change to prevent integer overflow errors
1114 		// Do not clamp directly to (0...MaxEnergy)-current_energy, because
1115 		// the change value calculated here may be reduced by effect callbacks
1116 		int32_t scale = C4MaxPhysical / 100; // iChange 100% = Physical 100000
1117 		iChange = Clamp<int32_t>(iChange, std::numeric_limits<int32_t>::min()/scale, std::numeric_limits<int32_t>::max()/scale)*scale;
1118 	}
1119 	// Was zero?
1120 	bool fWasZero=(Energy==0);
1121 	// Mark last damage causing player to trace kills
1122 	if (iChange < 0) UpdatLastEnergyLossCause(iCausedByPlr);
1123 	// Living things: ask effects for change first
1124 	if (pEffects && Alive)
1125 		pEffects->DoDamage(iChange, iCause, iCausedByPlr);
1126 	// Do change
1127 	iChange = Clamp<int32_t>(iChange, -Energy, GetPropertyInt(P_MaxEnergy) - Energy);
1128 	Energy += iChange;
1129 	// call to object
1130 	Call(PSF_EnergyChange,&C4AulParSet(iChange, iCause, iCausedByPlr));
1131 	// Alive and energy reduced to zero: death
1132 	if (Alive) if (Energy==0) if (!fWasZero) AssignDeath(false);
1133 }
1134 
UpdatLastEnergyLossCause(int32_t iNewCausePlr)1135 void C4Object::UpdatLastEnergyLossCause(int32_t iNewCausePlr)
1136 {
1137 	// Mark last damage causing player to trace kills
1138 	// do not regard self-administered damage if there was a previous damage causing player, because that would steal kills
1139 	// if people tumble themselves via stop-stop-(left/right)-throw  while falling into teh abyss
1140 	if (iNewCausePlr != Controller || LastEnergyLossCausePlayer < 0)
1141 	{
1142 		LastEnergyLossCausePlayer = iNewCausePlr;
1143 	}
1144 }
1145 
DoBreath(int32_t iChange)1146 void C4Object::DoBreath(int32_t iChange)
1147 {
1148 	// Do change
1149 	iChange = Clamp<int32_t>(iChange, -Breath, GetPropertyInt(P_MaxBreath) - Breath);
1150 	Breath += iChange;
1151 	// call to object
1152 	Call(PSF_BreathChange,&C4AulParSet(iChange));
1153 }
1154 
DoCon(int32_t iChange,bool grow_from_center)1155 void C4Object::DoCon(int32_t iChange, bool grow_from_center)
1156 {
1157 	C4Real strgt_con_b = fix_y + Shape.GetBottom();
1158 	bool fWasFull = (Con>=FullCon);
1159 	int32_t old_con = Con;
1160 
1161 	// Change con
1162 	if (Def->Oversize)
1163 		Con=std::max<int32_t>(Con+iChange,0);
1164 	else
1165 		Con=Clamp<int32_t>(Con+iChange,0,FullCon);
1166 
1167 	// Update OCF
1168 	SetOCF();
1169 
1170 	// Mass
1171 	UpdateMass();
1172 
1173 	// shape and position
1174 	UpdateShape();
1175 	// make the bottom-most vertex stay in place
1176 	if (!grow_from_center)
1177 	{
1178 		fix_y = strgt_con_b - Shape.GetBottom();
1179 	}
1180 	// Face (except for the shape)
1181 	UpdateFace(false);
1182 
1183 	// Do a callback on completion change.
1184 	if (iChange != 0)
1185 		Call(PSF_OnCompletionChange, &C4AulParSet(old_con, Con));
1186 
1187 	// Unfullcon
1188 	if (fWasFull && (Con<FullCon))
1189 	{
1190 		// Lose contents
1191 		if (!Def->IncompleteActivity)
1192 		{
1193 			C4Object *cobj;
1194 			while ((cobj=Contents.GetObject()))
1195 				if (Contained) cobj->Enter(Contained);
1196 				else cobj->Exit(cobj->GetX(),cobj->GetY());
1197 			SetAction(nullptr);
1198 		}
1199 	}
1200 
1201 	// Completion
1202 	if (!fWasFull && (Con>=FullCon))
1203 		Call(PSF_Initialize);
1204 
1205 	// Con Zero Removal
1206 	if (Con<=0)
1207 		AssignRemoval();
1208 	// Mesh Graphics Update
1209 	else if(pMeshInstance)
1210 		pMeshInstance->SetCompletion(Def->GrowthType ? 1.0f : static_cast<float>(Con)/static_cast<float>(FullCon));
1211 }
1212 
DoExperience(int32_t change)1213 void C4Object::DoExperience(int32_t change)
1214 {
1215 	const int32_t MaxExperience = 100000000;
1216 
1217 	if (!Info) return;
1218 
1219 	Info->Experience=Clamp<int32_t>(Info->Experience+change,0,MaxExperience);
1220 
1221 	// Promotion check
1222 	if (Info->Experience<MaxExperience)
1223 		if (Info->Experience>=::DefaultRanks.Experience(Info->Rank+1))
1224 			Promote(Info->Rank+1, false, false);
1225 }
1226 
Exit(int32_t iX,int32_t iY,int32_t iR,C4Real iXDir,C4Real iYDir,C4Real iRDir,bool fCalls)1227 bool C4Object::Exit(int32_t iX, int32_t iY, int32_t iR, C4Real iXDir, C4Real iYDir, C4Real iRDir, bool fCalls)
1228 {
1229 	// 1. Exit the current container.
1230 	// 2. Update Contents of container object and set Contained to nullptr.
1231 	// 3. Set offset position/motion if desired.
1232 	// 4. Call Ejection for container and Departure for object.
1233 
1234 	// Not contained
1235 	C4Object *pContainer=Contained;
1236 	if (!pContainer) return false;
1237 	// Remove object from container
1238 	pContainer->Contents.Remove(this);
1239 	pContainer->UpdateMass();
1240 	pContainer->SetOCF();
1241 	// No container
1242 	Contained=nullptr;
1243 	// Position/motion
1244 	fix_x=itofix(iX); fix_y=itofix(iY);
1245 	fix_r=itofix(iR);
1246 	BoundsCheck(fix_x, fix_y);
1247 	xdir=iXDir; ydir=iYDir; rdir=iRDir;
1248 	// Misc updates
1249 	Mobile=true;
1250 	InLiquid=false;
1251 	CloseMenu(true);
1252 	UpdateFace(true);
1253 	SetOCF();
1254 	// Object list callback (before script callbacks, because script callbacks may enter again)
1255 	ObjectListChangeListener.OnObjectContainerChanged(this, pContainer, nullptr);
1256 	// Engine calls
1257 	if (fCalls) pContainer->Call(PSF_Ejection,&C4AulParSet(this));
1258 	if (fCalls) Call(PSF_Departure,&C4AulParSet(pContainer));
1259 	// Success (if the obj wasn't "re-entered" by script)
1260 	return !Contained;
1261 }
1262 
Enter(C4Object * pTarget,bool fCalls,bool fCopyMotion,bool * pfRejectCollect)1263 bool C4Object::Enter(C4Object *pTarget, bool fCalls, bool fCopyMotion, bool *pfRejectCollect)
1264 {
1265 	// 0. Query entrance and collection
1266 	// 1. Exit if contained.
1267 	// 2. Set new container.
1268 	// 3. Update Contents and mass of the new container.
1269 	// 4. Call collection for container
1270 	// 5. Call entrance for object.
1271 
1272 	// No valid target or target is self
1273 	if (!pTarget || (pTarget==this)) return false;
1274 	// check if entrance is allowed
1275 	if (!! Call(PSF_RejectEntrance, &C4AulParSet(pTarget))) return false;
1276 	// check if we end up in an endless container-recursion
1277 	for (C4Object *pCnt=pTarget->Contained; pCnt; pCnt=pCnt->Contained)
1278 		if (pCnt==this) return false;
1279 	// Check RejectCollect, if desired
1280 	if (pfRejectCollect)
1281 	{
1282 		if (!!pTarget->Call(PSF_RejectCollection,&C4AulParSet(Def, this)))
1283 		{
1284 			*pfRejectCollect = true;
1285 			return false;
1286 		}
1287 		*pfRejectCollect = false;
1288 	}
1289 	// Exit if contained
1290 	if (Contained) if (!Exit(GetX(),GetY())) return false;
1291 	if (Contained || !Status || !pTarget->Status) return false;
1292 	// Failsafe updates
1293 	if (Menu)
1294 	{
1295 		CloseMenu(true);
1296 		// CloseMenu might do bad stuff
1297 		if (Contained || !Status || !pTarget->Status) return false;
1298 	}
1299 	SetOCF();
1300 	// Set container
1301 	Contained=pTarget;
1302 	// Enter
1303 	if (!Contained->Contents.Add(this, C4ObjectList::stContents))
1304 	{
1305 		Contained=nullptr;
1306 		return false;
1307 	}
1308 	// Assume that the new container controls this object, if it cannot control itself (i.e.: Alive)
1309 	// So it can be traced back who caused the damage, if a projectile hits its target
1310 	if (!Alive)
1311 		Controller = pTarget->Controller;
1312 	// Misc updates
1313 	// motion must be copied immediately, so the position will be correct when OCF is set, and
1314 	// OCF_Available will be set for newly bought items, even if 50/50 is solid in the landscape
1315 	// however, the motion must be preserved sometimes to keep flags like OCF_HitSpeed upon collection
1316 	if (fCopyMotion)
1317 	{
1318 		// remove any solidmask before copying the motion...
1319 		UpdateSolidMask(false);
1320 		CopyMotion(Contained);
1321 	}
1322 	SetOCF();
1323 	UpdateFace(true);
1324 	// Update container
1325 	Contained->UpdateMass();
1326 	Contained->SetOCF();
1327 	// Object list callback (before script callbacks, because script callbacks may exit again)
1328 	ObjectListChangeListener.OnObjectContainerChanged(this, nullptr, Contained);
1329 	// Collection call
1330 	if (fCalls) pTarget->Call(PSF_Collection2,&C4AulParSet(this));
1331 	if (!Contained || !Contained->Status || !pTarget->Status) return true;
1332 	// Entrance call
1333 	if (fCalls) Call(PSF_Entrance,&C4AulParSet(Contained));
1334 	if (!Contained || !Contained->Status || !pTarget->Status) return true;
1335 	// Success
1336 	return true;
1337 }
1338 
Fling(C4Real txdir,C4Real tydir,bool fAddSpeed)1339 void C4Object::Fling(C4Real txdir, C4Real tydir, bool fAddSpeed)
1340 {
1341 	if (fAddSpeed) { txdir+=xdir/2; tydir+=ydir/2; }
1342 	if (!ObjectActionTumble(this,(txdir<0),txdir,tydir))
1343 		if (!ObjectActionJump(this,txdir,tydir,false))
1344 		{
1345 			xdir=txdir; ydir=tydir;
1346 			Mobile=true;
1347 			Action.t_attach&=~CNAT_Bottom;
1348 		}
1349 }
1350 
ActivateEntrance(int32_t by_plr,C4Object * by_obj)1351 bool C4Object::ActivateEntrance(int32_t by_plr, C4Object *by_obj)
1352 {
1353 
1354 	// Try entrance activation
1355 	if (OCF & OCF_Entrance)
1356 		if (!! Call(PSF_ActivateEntrance,&C4AulParSet(by_obj)))
1357 			return true;
1358 	// Failure
1359 	return false;
1360 }
1361 
Push(C4Real txdir,C4Real dforce,bool fStraighten)1362 bool C4Object::Push(C4Real txdir, C4Real dforce, bool fStraighten)
1363 {
1364 	// Valid check
1365 	if (!Status || !Def || Contained || !(OCF & OCF_Grab)) return false;
1366 	// Grabbing okay, no pushing
1367 	if (GetPropertyInt(P_Touchable)==2) return true;
1368 	// Mobilization check (pre-mobilization zero)
1369 	if (!Mobile)
1370 		{ xdir=ydir=Fix0; }
1371 	// General pushing force vs. object mass
1372 	dforce=dforce*100/Mass;
1373 	// Set dir
1374 	if (xdir<0) SetDir(DIR_Left);
1375 	if (xdir>0) SetDir(DIR_Right);
1376 	// Work towards txdir
1377 	if (Abs(xdir-txdir)<=dforce) // Close-enough-set
1378 		{ xdir=txdir; }
1379 	else // Work towards
1380 	{
1381 		if (xdir<txdir) xdir+=dforce;
1382 		if (xdir>txdir) xdir-=dforce;
1383 	}
1384 	// Straighten
1385 	if (fStraighten)
1386 	{
1387 		if (Inside<int32_t>(GetR(),-StableRange,+StableRange))
1388 		{
1389 			rdir=0; // cheap way out
1390 		}
1391 		else
1392 		{
1393 			if (fix_r > Fix0) { if (rdir>-RotateAccel) rdir-=dforce; }
1394 			else { if (rdir<+RotateAccel) rdir+=dforce; }
1395 		}
1396 	}
1397 
1398 	// Mobilization check
1399 	if (!!xdir || !!ydir || !!rdir) Mobile=true;
1400 
1401 	// Stuck check
1402 	if (!::Game.iTick35) if (txdir) if (!Def->NoHorizontalMove)
1403 				if (ContactCheck(GetX(), GetY())) // Resets t_contact
1404 				{
1405 					GameMsgObjectError(FormatString(LoadResStr("IDS_OBJ_STUCK"),GetName()).getData(),this);
1406 					Call(PSF_Stuck);
1407 				}
1408 
1409 	return true;
1410 }
1411 
Lift(C4Real tydir,C4Real dforce)1412 bool C4Object::Lift(C4Real tydir, C4Real dforce)
1413 {
1414 	// Valid check
1415 	if (!Status || !Def || Contained) return false;
1416 	// Mobilization check
1417 	if (!Mobile)
1418 		{ xdir=ydir=Fix0; Mobile=true; }
1419 	// General pushing force vs. object mass
1420 	dforce=dforce*100/Mass;
1421 	// If close enough, set tydir
1422 	if (Abs(tydir-ydir)<=Abs(dforce))
1423 		ydir=tydir;
1424 	else // Work towards tydir
1425 	{
1426 		if (ydir<tydir) ydir+=dforce;
1427 		if (ydir>tydir) ydir-=dforce;
1428 	}
1429 	// Stuck check
1430 	if (tydir != -GravAccel)
1431 		if (ContactCheck(GetX(), GetY())) // Resets t_contact
1432 		{
1433 			GameMsgObjectError(FormatString(LoadResStr("IDS_OBJ_STUCK"),GetName()).getData(),this);
1434 			Call(PSF_Stuck);
1435 		}
1436 	return true;
1437 }
1438 
CreateContents(C4PropList * PropList)1439 C4Object* C4Object::CreateContents(C4PropList * PropList)
1440 {
1441 	C4Object *nobj;
1442 	if (!(nobj=Game.CreateObject(PropList,this,Owner))) return nullptr;
1443 	if (!nobj->Enter(this)) { nobj->AssignRemoval(); return nullptr; }
1444 	return nobj;
1445 }
1446 
CreateContentsByList(C4IDList & idlist)1447 bool C4Object::CreateContentsByList(C4IDList &idlist)
1448 {
1449 	int32_t cnt,cnt2;
1450 	for (cnt=0; idlist.GetID(cnt); cnt++)
1451 		for (cnt2=0; cnt2<idlist.GetCount(cnt); cnt2++)
1452 			if (!CreateContents(C4Id2Def(idlist.GetID(cnt))))
1453 				return false;
1454 	return true;
1455 }
1456 
DrawMenuSymbol(int32_t iMenu,C4Facet & cgo,int32_t iOwner)1457 static void DrawMenuSymbol(int32_t iMenu, C4Facet &cgo, int32_t iOwner)
1458 {
1459 	C4Facet ccgo;
1460 
1461 	DWORD dwColor=0;
1462 	if (ValidPlr(iOwner)) dwColor=::Players.Get(iOwner)->ColorDw;
1463 
1464 	switch (iMenu)
1465 	{
1466 	case C4MN_Buy:
1467 		::GraphicsResource.fctFlagClr.DrawClr(ccgo = cgo.GetFraction(75, 75), true, dwColor);
1468 		::GraphicsResource.fctWealth.Draw(ccgo = cgo.GetFraction(100, 50, C4FCT_Left, C4FCT_Bottom));
1469 		::GraphicsResource.fctArrow.Draw(ccgo = cgo.GetFraction(70, 70, C4FCT_Right, C4FCT_Center), false, 0);
1470 		break;
1471 	case C4MN_Sell:
1472 		::GraphicsResource.fctFlagClr.DrawClr(ccgo = cgo.GetFraction(75, 75), true, dwColor);
1473 		::GraphicsResource.fctWealth.Draw(ccgo = cgo.GetFraction(100, 50, C4FCT_Left, C4FCT_Bottom));
1474 		::GraphicsResource.fctArrow.Draw(ccgo = cgo.GetFraction(70, 70, C4FCT_Right, C4FCT_Center), false, 1);
1475 		break;
1476 	}
1477 }
1478 
ActivateMenu(int32_t iMenu,int32_t iMenuSelect,int32_t iMenuData,int32_t iMenuPosition,C4Object * pTarget)1479 bool C4Object::ActivateMenu(int32_t iMenu, int32_t iMenuSelect,
1480                             int32_t iMenuData, int32_t iMenuPosition,
1481                             C4Object *pTarget)
1482 {
1483 	// Variables
1484 	C4FacetSurface fctSymbol;
1485 	C4IDList ListItems;
1486 	// Close any other menu
1487 	if (Menu && Menu->IsActive()) if (!Menu->TryClose(true, false)) return false;
1488 	// Create menu
1489 	if (!Menu) Menu = new C4ObjectMenu; else Menu->ClearItems();
1490 	// Open menu
1491 	switch (iMenu)
1492 	{
1493 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1494 	case C4MN_Activate:
1495 		// No target specified: use own container as target
1496 		if (!pTarget) if (!(pTarget=Contained)) break;
1497 		// Opening contents menu blocked by RejectContents
1498 		if (!!pTarget->Call(PSF_RejectContents)) return false;
1499 		// Create symbol
1500 		fctSymbol.Create(C4SymbolSize,C4SymbolSize);
1501 		pTarget->Def->Draw(fctSymbol,false,pTarget->Color,pTarget);
1502 		// Init
1503 		Menu->Init(fctSymbol,FormatString(LoadResStr("IDS_OBJ_EMPTY"),pTarget->GetName()).getData(),this,C4MN_Extra_None,0,iMenu);
1504 		Menu->SetPermanent(true);
1505 		Menu->SetRefillObject(pTarget);
1506 		// Success
1507 		return true;
1508 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1509 	case C4MN_Buy:
1510 		// No target specified: container is base
1511 		if (!pTarget) if (!(pTarget=Contained)) break;
1512 		// Create symbol
1513 		fctSymbol.Create(C4SymbolSize,C4SymbolSize);
1514 		DrawMenuSymbol(C4MN_Buy, fctSymbol, pTarget->Owner);
1515 		// Init menu
1516 		Menu->Init(fctSymbol,LoadResStr("IDS_PLR_NOBUY"),this,C4MN_Extra_Value,0,iMenu);
1517 		Menu->SetPermanent(true);
1518 		Menu->SetRefillObject(pTarget);
1519 		// Success
1520 		return true;
1521 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1522 	case C4MN_Sell:
1523 		// No target specified: container is base
1524 		if (!pTarget) if (!(pTarget=Contained)) break;
1525 		// Create symbol & init
1526 		fctSymbol.Create(C4SymbolSize,C4SymbolSize);
1527 		DrawMenuSymbol(C4MN_Sell, fctSymbol, pTarget->Owner);
1528 		Menu->Init(fctSymbol,FormatString(LoadResStr("IDS_OBJ_EMPTY"),pTarget->GetName()).getData(),this,C4MN_Extra_Value,0,iMenu);
1529 		Menu->SetPermanent(true);
1530 		Menu->SetRefillObject(pTarget);
1531 		// Success
1532 		return true;
1533 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1534 	case C4MN_Get:
1535 	case C4MN_Contents:
1536 		// No target specified
1537 		if (!pTarget) break;
1538 		// Opening contents menu blocked by RejectContents
1539 		if (!!pTarget->Call(PSF_RejectContents)) return false;
1540 		// Create symbol & init
1541 		fctSymbol.Create(C4SymbolSize,C4SymbolSize);
1542 		pTarget->Def->Draw(fctSymbol,false,pTarget->Color,pTarget);
1543 		Menu->Init(fctSymbol,FormatString(LoadResStr("IDS_OBJ_EMPTY"),pTarget->GetName()).getData(),this,C4MN_Extra_None,0,iMenu);
1544 		Menu->SetPermanent(true);
1545 		Menu->SetRefillObject(pTarget);
1546 		// Success
1547 		return true;
1548 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1549 	case C4MN_Info:
1550 		// Target by parameter
1551 		if (!pTarget) break;
1552 		// Create symbol & init menu
1553 		fctSymbol.Create(C4SymbolSize, C4SymbolSize); GfxR->fctOKCancel.Draw(fctSymbol,true,0,1);
1554 		Menu->Init(fctSymbol, pTarget->GetName(), this, C4MN_Extra_None, 0, iMenu, C4MN_Style_Info);
1555 		Menu->SetPermanent(true);
1556 		Menu->SetAlignment(C4MN_Align_Free);
1557 		C4Viewport *pViewport = ::Viewports.GetViewport(Controller); // Hackhackhack!!!
1558 		if (pViewport) Menu->SetLocation((pTarget->GetX() + pTarget->Shape.GetX() + pTarget->Shape.Wdt + 10 - pViewport->GetViewX()) * pViewport->GetZoom(),
1559 			                                 (pTarget->GetY() + pTarget->Shape.GetY() - pViewport->GetViewY()) * pViewport->GetZoom());
1560 		// Add info item
1561 		fctSymbol.Create(C4PictureSize, C4PictureSize); pTarget->Def->Draw(fctSymbol, false, pTarget->Color, pTarget);
1562 		Menu->Add(pTarget->GetName(), fctSymbol, "", C4MN_Item_NoCount, nullptr, pTarget->GetInfoString().getData());
1563 		fctSymbol.Default();
1564 		// Success
1565 		return true;
1566 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1567 	}
1568 	// Invalid menu identification
1569 	CloseMenu(true);
1570 	return false;
1571 }
1572 
CloseMenu(bool fForce)1573 bool C4Object::CloseMenu(bool fForce)
1574 {
1575 	if (Menu)
1576 	{
1577 		if (Menu->IsActive()) if (!Menu->TryClose(fForce, false)) return false;
1578 		if (!Menu->IsCloseQuerying()) { delete Menu; Menu=nullptr; } // protect menu deletion from recursive menu operation calls
1579 	}
1580 	return true;
1581 }
1582 
GetArea(int32_t & aX,int32_t & aY,int32_t & aWdt,int32_t & aHgt) const1583 BYTE C4Object::GetArea(int32_t &aX, int32_t &aY, int32_t &aWdt, int32_t &aHgt) const
1584 {
1585 	if (!Status || !Def) return 0;
1586 	aX = GetX() + Shape.GetX(); aY = GetY() + Shape.GetY();
1587 	aWdt=Shape.Wdt; aHgt=Shape.Hgt;
1588 	return 1;
1589 }
1590 
GetEntranceArea(int32_t & aX,int32_t & aY,int32_t & aWdt,int32_t & aHgt) const1591 BYTE C4Object::GetEntranceArea(int32_t &aX, int32_t &aY, int32_t &aWdt, int32_t &aHgt) const
1592 {
1593 	if (!Status || !Def) return 0;
1594 	// Return actual entrance
1595 	if (OCF & OCF_Entrance)
1596 	{
1597 		aX=GetX() + Def->Entrance.x;
1598 		aY=GetY() + Def->Entrance.y;
1599 		aWdt=Def->Entrance.Wdt;
1600 		aHgt=Def->Entrance.Hgt;
1601 	}
1602 	// Return object center
1603 	else
1604 	{
1605 		aX=GetX(); aY=GetY();
1606 		aWdt=0; aHgt=0;
1607 	}
1608 	// Done
1609 	return 1;
1610 }
1611 
GetMomentum(C4Real & rxdir,C4Real & rydir) const1612 BYTE C4Object::GetMomentum(C4Real &rxdir, C4Real &rydir) const
1613 {
1614 	rxdir=rydir=0;
1615 	if (!Status || !Def) return 0;
1616 	rxdir=xdir; rydir=ydir;
1617 	return 1;
1618 }
1619 
GetSpeed() const1620 C4Real C4Object::GetSpeed() const
1621 {
1622 	C4Real cobjspd=Fix0;
1623 	if (xdir<0) cobjspd-=xdir; else cobjspd+=xdir;
1624 	if (ydir<0) cobjspd-=ydir; else cobjspd+=ydir;
1625 	return cobjspd;
1626 }
1627 
GetDataString()1628 StdStrBuf C4Object::GetDataString()
1629 {
1630 	StdStrBuf Output;
1631 	// Type
1632 	Output.AppendFormat(LoadResStr("IDS_CNS_TYPE"),GetName(),Def->id.ToString());
1633 	// Owner
1634 	if (ValidPlr(Owner))
1635 	{
1636 		Output.Append(LineFeed);
1637 		Output.AppendFormat(LoadResStr("IDS_CNS_OWNER"),::Players.Get(Owner)->GetName());
1638 	}
1639 	// Contents
1640 	if (Contents.ObjectCount())
1641 	{
1642 		Output.Append(LineFeed);
1643 		Output.Append(LoadResStr("IDS_CNS_CONTENTS"));
1644 		Output.Append(Contents.GetNameList(::Definitions));
1645 	}
1646 	// Action
1647 	if (GetAction())
1648 	{
1649 		Output.Append(LineFeed);
1650 		Output.Append(LoadResStr("IDS_CNS_ACTION"));
1651 		Output.Append(GetAction()->GetName());
1652 	}
1653 	// Properties
1654 	Output.Append(LineFeed);
1655 	Output.Append(LoadResStr("IDS_CNS_PROPERTIES"));
1656 	Output.Append(LineFeed "  ");
1657 	AppendDataString(&Output, LineFeed "  ");
1658 	// Effects
1659 	if (pEffects)
1660 	{
1661 		Output.Append(LineFeed);
1662 		Output.Append(LoadResStr("IDS_CNS_EFFECTS"));
1663 		Output.Append(": ");
1664 	}
1665 	for (C4Effect *pEffect = pEffects; pEffect; pEffect = pEffect->pNext)
1666 	{
1667 		Output.Append(LineFeed);
1668 		// Effect name
1669 		Output.AppendFormat("  %s: Priority %d, Interval %d", pEffect->GetName(), pEffect->iPriority, pEffect->iInterval);
1670 	}
1671 
1672 	StdStrBuf Output2;
1673 	C4ValueNumbers numbers;
1674 	DecompileToBuf_Log<StdCompilerINIWrite>(mkNamingAdapt(mkInsertAdapt(mkParAdapt(*this, &numbers),
1675 	                                                                    mkNamingAdapt(numbers, "Values"), false),
1676 	                                                      "Object"), &Output2, "C4Object::GetDataString");
1677 	Output.Append(LineFeed);
1678 	Output.Append(Output2);
1679 	return Output;
1680 }
1681 
SetName(const char * NewName)1682 void C4Object::SetName(const char * NewName)
1683 {
1684 	if (!NewName && Info)
1685 		C4PropList::SetName(Info->Name);
1686 	else
1687 		C4PropList::SetName(NewName);
1688 }
1689 
GetValue(C4Object * pInBase,int32_t iForPlayer)1690 int32_t C4Object::GetValue(C4Object *pInBase, int32_t iForPlayer)
1691 {
1692 	C4Value r = Call(PSF_CalcValue, &C4AulParSet(pInBase, iForPlayer));
1693 	int32_t iValue;
1694 	if (r != C4VNull)
1695 		iValue = r.getInt();
1696 	else
1697 	{
1698 		// get value of def
1699 		// Caution: Do not pass pInBase here, because the def base value is to be queried
1700 		//  - and not the value if you had to buy the object in this particular base
1701 		iValue = Def->GetValue(nullptr, iForPlayer);
1702 	}
1703 	// Con percentage
1704 	iValue = iValue * Con / FullCon;
1705 	// do any adjustments based on where the item is bought
1706 	if (pInBase)
1707 	{
1708 		r = pInBase->Call(PSF_CalcSellValue, &C4AulParSet(this, iValue));
1709 		if (r != C4VNull)
1710 			iValue = r.getInt();
1711 	}
1712 	return iValue;
1713 }
1714 
Promote(int32_t torank,bool exception,bool fForceRankName)1715 bool C4Object::Promote(int32_t torank, bool exception, bool fForceRankName)
1716 {
1717 	if (!Info) return false;
1718 	// get rank system
1719 	C4Def *pUseDef = C4Id2Def(Info->id);
1720 	C4RankSystem *pRankSys;
1721 	if (pUseDef && pUseDef->pRankNames)
1722 		pRankSys = pUseDef->pRankNames;
1723 	else
1724 		pRankSys = &::DefaultRanks;
1725 	// always promote info
1726 	Info->Promote(torank,*pRankSys, fForceRankName);
1727 	// silent update?
1728 	if (!pRankSys->GetRankName(torank,false)) return false;
1729 	GameMsgObject(FormatString(LoadResStr("IDS_OBJ_PROMOTION"),GetName (),Info->sRankName.getData()).getData(),this);
1730 
1731 	// call to object
1732 	Call(PSF_Promotion);
1733 
1734 	StartSoundEffect("UI::Trumpet",false,100,this);
1735 	return true;
1736 }
1737 
ClearPointers(C4Object * pObj)1738 void C4Object::ClearPointers(C4Object *pObj)
1739 {
1740 	// mesh attachments and animation nodes
1741 	if(pMeshInstance) pMeshInstance->ClearPointers(pObj);
1742 	// effects
1743 	if (pEffects) pEffects->ClearPointers(pObj);
1744 	// contents/contained: although normally not necessery because it's done in AssignRemoval and StatusDeactivate,
1745 	// it is also required during game destruction (because ClearPointers might do script callbacks)
1746 	// Perform silent exit to avoid additional callbacks
1747 	if (Contained == pObj)
1748 	{
1749 		Contained->Contents.Remove(this);
1750 		Contained = nullptr;
1751 	}
1752 	Contents.Remove(pObj);
1753 	// Action targets
1754 	if (Action.Target==pObj) Action.Target=nullptr;
1755 	if (Action.Target2==pObj) Action.Target2=nullptr;
1756 	// Commands
1757 	C4Command *cCom;
1758 	for (cCom=Command; cCom; cCom=cCom->Next)
1759 		cCom->ClearPointers(pObj);
1760 	// Menu
1761 	if (Menu) Menu->ClearPointers(pObj);
1762 	// Layer
1763 	if (Layer==pObj) Layer=nullptr;
1764 	// gfx overlays
1765 	if (pGfxOverlay)
1766 	{
1767 		C4GraphicsOverlay *pNextGfxOvrl = pGfxOverlay, *pGfxOvrl;
1768 		while ((pGfxOvrl = pNextGfxOvrl))
1769 		{
1770 			pNextGfxOvrl = pGfxOvrl->GetNext();
1771 			if (pGfxOvrl->GetOverlayObject() == pObj)
1772 				// overlay relying on deleted object: Delete!
1773 				RemoveGraphicsOverlay(pGfxOvrl->GetID());
1774 		}
1775 	}
1776 }
1777 
SetPhase(int32_t iPhase)1778 bool C4Object::SetPhase(int32_t iPhase)
1779 {
1780 	C4PropList* pActionDef = GetAction();
1781 	if (!pActionDef) return false;
1782 	const int32_t length = pActionDef->GetPropertyInt(P_Length);
1783 	Action.Phase=Clamp<int32_t>(iPhase,0,length);
1784 	Action.PhaseDelay = 0;
1785 	return true;
1786 }
1787 
Draw(C4TargetFacet & cgo,int32_t iByPlayer,DrawMode eDrawMode,float offX,float offY)1788 void C4Object::Draw(C4TargetFacet &cgo, int32_t iByPlayer, DrawMode eDrawMode, float offX, float offY)
1789 {
1790 #ifndef USE_CONSOLE
1791 	C4Facet ccgo;
1792 
1793 	// Status
1794 	if (!Status || !Def) return;
1795 
1796 	// visible?
1797 	if (!IsVisible(iByPlayer, !!eDrawMode)) return;
1798 
1799 	// Set up custom uniforms.
1800 	auto uniform_popper = pDraw->scriptUniform.Push(this);
1801 
1802 	// Line
1803 	if (Def->Line) { DrawLine(cgo, iByPlayer); return; }
1804 
1805 	// background particles (bounds not checked)
1806 	if (BackParticles) BackParticles->Draw(cgo, this);
1807 
1808 	// Object output position
1809 	float newzoom = cgo.Zoom;
1810 	if (eDrawMode!=ODM_Overlay)
1811 	{
1812 		if (!GetDrawPosition(cgo, offX, offY, newzoom)) return;
1813 	}
1814 	ZoomDataStackItem zdsi(newzoom);
1815 
1816 	bool fYStretchObject=false;
1817 	C4PropList* pActionDef = GetAction();
1818 	if (pActionDef)
1819 		if (pActionDef->GetPropertyInt(P_FacetTargetStretch))
1820 			fYStretchObject=true;
1821 
1822 	// Set audibility
1823 	if (!eDrawMode) SetAudibilityAt(cgo, GetX(), GetY(), iByPlayer);
1824 
1825 	// Output boundary
1826 	if (!fYStretchObject && !eDrawMode && !(Category & C4D_Parallax))
1827 	{
1828 		// For actions with a custom facet set, check against that action facet. Otherwise (or with oversize objects), just check against shape.
1829 		if (pActionDef && fix_r == Fix0 && !pActionDef->GetPropertyInt(P_FacetBase) && Con <= FullCon && Action.Facet.Wdt)
1830 		{
1831 			// active
1832 			if ( !Inside<float>(offX+Shape.GetX()+Action.FacetX,cgo.X-Action.Facet.Wdt,cgo.X+cgo.Wdt)
1833 			     || (!Inside<float>(offY+Shape.GetY()+Action.FacetY,cgo.Y-Action.Facet.Hgt,cgo.Y+cgo.Hgt)) )
1834 				{
1835 					if (FrontParticles && !Contained) FrontParticles->Draw(cgo, this);
1836 					return;
1837 				}
1838 		}
1839 		else
1840 			// idle
1841 			if ( !Inside<float>(offX+Shape.GetX(),cgo.X-Shape.Wdt,cgo.X+cgo.Wdt)
1842 			     || (!Inside<float>(offY+Shape.GetY(),cgo.Y-Shape.Hgt,cgo.Y+cgo.Hgt)) )
1843 				{
1844 					if (FrontParticles && !Contained) FrontParticles->Draw(cgo, this);
1845 					return;
1846 				}
1847 	}
1848 
1849 	// ensure correct color is set
1850 	if (GetGraphics()->Type == C4DefGraphics::TYPE_Bitmap)
1851 		if (GetGraphics()->Bmp.BitmapClr) GetGraphics()->Bmp.BitmapClr->SetClr(Color);
1852 
1853 	// Debug Display //////////////////////////////////////////////////////////////////////
1854 	if (::GraphicsSystem.ShowCommand && !eDrawMode)
1855 	{
1856 		C4Command *pCom;
1857 		int32_t ccx=GetX(),ccy=GetY();
1858 		float offX1, offY1, offX2, offY2, newzoom;
1859 		char szCommand[200];
1860 		StdStrBuf Cmds;
1861 		int32_t iMoveTos=0;
1862 		for (pCom=Command; pCom; pCom=pCom->Next)
1863 		{
1864 			switch (pCom->Command)
1865 			{
1866 			case C4CMD_MoveTo:
1867 				// Angle
1868 				int32_t iAngle; iAngle=Angle(ccx,ccy,pCom->Tx._getInt(),pCom->Ty); while (iAngle>180) iAngle-=360;
1869 				// Path
1870 				if(GetDrawPosition(cgo, ccx, ccy, cgo.Zoom, offX1, offY1, newzoom) &&
1871 				   GetDrawPosition(cgo, pCom->Tx._getInt(), pCom->Ty, cgo.Zoom, offX2, offY2, newzoom))
1872 				{
1873 					ZoomDataStackItem zdsi(newzoom);
1874 					pDraw->DrawLineDw(cgo.Surface,offX1,offY1,offX2,offY2,C4RGB(0xca,0,0));
1875 					pDraw->DrawFrameDw(cgo.Surface,offX2-1,offY2-1,offX2+1,offY2+1,C4RGB(0xca,0,0));
1876 				}
1877 
1878 				ccx=pCom->Tx._getInt(); ccy=pCom->Ty;
1879 				// Message
1880 				iMoveTos++; szCommand[0]=0;
1881 				break;
1882 			case C4CMD_Put:
1883 				sprintf(szCommand,"%s %s to %s",CommandName(pCom->Command),pCom->Target2 ? pCom->Target2->GetName() : pCom->Data ? pCom->Data.GetDataString().getData() : "Content",pCom->Target ? pCom->Target->GetName() : "");
1884 				break;
1885 			case C4CMD_Buy: case C4CMD_Sell:
1886 				sprintf(szCommand,"%s %s at %s",CommandName(pCom->Command),pCom->Data.GetDataString().getData(),pCom->Target ? pCom->Target->GetName() : "closest base");
1887 				break;
1888 			case C4CMD_Acquire:
1889 				sprintf(szCommand,"%s %s",CommandName(pCom->Command),pCom->Data.GetDataString().getData());
1890 				break;
1891 			case C4CMD_Call:
1892 				sprintf(szCommand,"%s %s in %s",CommandName(pCom->Command),pCom->Text->GetCStr(),pCom->Target ? pCom->Target->GetName() : "(null)");
1893 				break;
1894 			case C4CMD_None:
1895 				szCommand[0]=0;
1896 				break;
1897 			case C4CMD_Transfer:
1898 				// Path
1899 				if(GetDrawPosition(cgo, ccx, ccy, cgo.Zoom, offX1, offY1, newzoom) &&
1900 				   GetDrawPosition(cgo, pCom->Tx._getInt(), pCom->Ty, cgo.Zoom, offX2, offY2, newzoom))
1901 				{
1902 					ZoomDataStackItem zdsi(newzoom);
1903 					pDraw->DrawLineDw(cgo.Surface,offX1,offY1,offX2,offY2,C4RGB(0,0xca,0));
1904 					pDraw->DrawFrameDw(cgo.Surface,offX2-1,offY2-1,offX2+1,offY2+1,C4RGB(0,0xca,0));
1905 				}
1906 
1907 				ccx=pCom->Tx._getInt(); ccy=pCom->Ty;
1908 				// Message
1909 				sprintf(szCommand,"%s %s",CommandName(pCom->Command),pCom->Target ? pCom->Target->GetName() : "");
1910 				break;
1911 			default:
1912 				sprintf(szCommand,"%s %s",CommandName(pCom->Command),pCom->Target ? pCom->Target->GetName() : "");
1913 				break;
1914 			}
1915 			// Compose command stack message
1916 			if (szCommand[0])
1917 			{
1918 				// End MoveTo stack first
1919 				if (iMoveTos) { Cmds.AppendChar('|'); Cmds.AppendFormat("%dx MoveTo",iMoveTos); iMoveTos=0; }
1920 				// Current message
1921 				Cmds.AppendChar('|');
1922 				if (pCom->Finished) Cmds.Append("<i>");
1923 				Cmds.Append(szCommand);
1924 				if (pCom->Finished) Cmds.Append("</i>");
1925 			}
1926 		}
1927 		// Open MoveTo stack
1928 		if (iMoveTos) { Cmds.AppendChar('|'); Cmds.AppendFormat("%dx MoveTo",iMoveTos); iMoveTos=0; }
1929 		// Draw message
1930 		int32_t cmwdt,cmhgt;  ::GraphicsResource.FontRegular.GetTextExtent(Cmds.getData(),cmwdt,cmhgt,true);
1931 		pDraw->TextOut(Cmds.getData(), ::GraphicsResource.FontRegular, 1.0, cgo.Surface,offX,offY+Shape.GetY()-10-cmhgt,C4Draw::DEFAULT_MESSAGE_COLOR,ACenter);
1932 	}
1933 	// Debug Display ///////////////////////////////////////////////////////////////////////////////
1934 
1935 	// Don't draw (show solidmask)
1936 	if (::GraphicsSystem.Show8BitSurface != 0)
1937 		if (SolidMask.Wdt)
1938 		{
1939 			// DrawSolidMask(cgo); - no need to draw it, because the 8bit-surface will be shown
1940 			return;
1941 		}
1942 
1943 	// Contained check
1944 	if (Contained && !eDrawMode) return;
1945 
1946 	// Visibility inside FoW
1947 	const C4FoWRegion* pOldFoW = pDraw->GetFoW();
1948 	if(pOldFoW && (Category & C4D_IgnoreFoW))
1949 		pDraw->SetFoW(nullptr);
1950 
1951 	// color modulation (including construction sign...)
1952 	if (ColorMod != 0xffffffff || BlitMode) if (!eDrawMode) PrepareDrawing();
1953 
1954 	// Not active or rotated: BaseFace only
1955 	if (!pActionDef)
1956 	{
1957 		DrawFace(cgo, offX, offY);
1958 	}
1959 
1960 	// Active
1961 	else
1962 	{
1963 		// FacetBase
1964 		if (pActionDef->GetPropertyInt(P_FacetBase) || GetGraphics()->Type != C4DefGraphics::TYPE_Bitmap)
1965 			DrawFace(cgo, offX, offY, 0, Action.DrawDir);
1966 
1967 		// Special: stretched action facet
1968 		if (Action.Facet.Surface && pActionDef->GetPropertyInt(P_FacetTargetStretch))
1969 		{
1970 			if (Action.Target)
1971 				pDraw->Blit(Action.Facet.Surface,
1972 				              float(Action.Facet.X),float(Action.Facet.Y),float(Action.Facet.Wdt),float(Action.Facet.Hgt),
1973 				              cgo.Surface,
1974 				              offX + Shape.GetX() + Action.FacetX, offY + Shape.GetY() + Action.FacetY,Action.Facet.Wdt,
1975 				              (fixtof(Action.Target->fix_y) + Action.Target->Shape.GetY()) - (fixtof(fix_y) + Shape.GetY() + Action.FacetY),
1976 				              true);
1977 		}
1978 		else if (Action.Facet.Surface)
1979 			DrawActionFace(cgo, offX, offY);
1980 	}
1981 
1982 	// end of color modulation
1983 	if (ColorMod != 0xffffffff || BlitMode) if (!eDrawMode) FinishedDrawing();
1984 
1985 	// draw overlays - after blit mode changes, because overlay gfx set their own
1986 	if (pGfxOverlay) if (eDrawMode!=ODM_BaseOnly)
1987 			for (C4GraphicsOverlay *pGfxOvrl = pGfxOverlay; pGfxOvrl; pGfxOvrl = pGfxOvrl->GetNext())
1988 				if (!pGfxOvrl->IsPicture())
1989 					pGfxOvrl->Draw(cgo, this, iByPlayer);
1990 
1991 	// local particles in front of the object
1992 	if (eDrawMode!=ODM_BaseOnly)
1993 	{
1994 		if (FrontParticles)
1995 			FrontParticles->Draw(cgo, this);
1996 	}
1997 
1998 	// Debug Display ////////////////////////////////////////////////////////////////////////
1999 	if (::GraphicsSystem.ShowVertices) if (eDrawMode!=ODM_BaseOnly)
2000 		{
2001 			int32_t cnt;
2002 			if (Shape.VtxNum>1)
2003 				for (cnt=0; cnt<Shape.VtxNum; cnt++)
2004 				{
2005 					DrawVertex(cgo,
2006 					           offX+Shape.VtxX[cnt],
2007 					           offY+Shape.VtxY[cnt],
2008 					           (Shape.VtxCNAT[cnt] & CNAT_NoCollision) ? C4RGB(0, 0, 0xff) : (Mobile ? C4RGB(0xff, 0, 0) : C4RGB(0xef, 0xef, 0)),
2009 					           Shape.VtxContactCNAT[cnt]);
2010 				}
2011 		}
2012 
2013 	if (::GraphicsSystem.ShowEntrance) if (eDrawMode!=ODM_BaseOnly)
2014 		{
2015 			if (OCF & OCF_Entrance)
2016 				pDraw->DrawFrameDw(cgo.Surface,offX+Def->Entrance.x,
2017 				                             offY+Def->Entrance.y,
2018 				                             offX+Def->Entrance.x+Def->Entrance.Wdt-1,
2019 				                             offY+Def->Entrance.y+Def->Entrance.Hgt-1,
2020 				                             C4RGB(0, 0, 0xff));
2021 			if (OCF & OCF_Collection)
2022 				pDraw->DrawFrameDw(cgo.Surface,offX+Def->Collection.x,
2023 				                             offY+Def->Collection.y,
2024 				                             offX+Def->Collection.x+Def->Collection.Wdt-1,
2025 				                             offY+Def->Collection.y+Def->Collection.Hgt-1,
2026 				                             C4RGB(0xca, 0, 0));
2027 		}
2028 
2029 	if (::GraphicsSystem.ShowAction) if (eDrawMode!=ODM_BaseOnly)
2030 		{
2031 			if (pActionDef)
2032 			{
2033 				StdStrBuf str;
2034 				str.Format("%s (%d)",pActionDef->GetName(),Action.Phase);
2035 				int32_t cmwdt,cmhgt; ::GraphicsResource.FontRegular.GetTextExtent(str.getData(),cmwdt,cmhgt,true);
2036 				pDraw->TextOut(str.getData(), ::GraphicsResource.FontRegular,
2037 				                           1.0, cgo.Surface, offX, offY + Shape.GetY() - cmhgt,
2038 				                           InLiquid ? 0xfa0000FF : C4Draw::DEFAULT_MESSAGE_COLOR, ACenter);
2039 			}
2040 		}
2041 	// Debug Display ///////////////////////////////////////////////////////////////////////
2042 
2043 	// Restore visibility inside FoW
2044 	if (pOldFoW) pDraw->SetFoW(pOldFoW);
2045 #endif
2046 }
2047 
DrawTopFace(C4TargetFacet & cgo,int32_t iByPlayer,DrawMode eDrawMode,float offX,float offY)2048 void C4Object::DrawTopFace(C4TargetFacet &cgo, int32_t iByPlayer, DrawMode eDrawMode, float offX, float offY)
2049 {
2050 #ifndef USE_CONSOLE
2051 	// Status
2052 	if (!Status || !Def) return;
2053 	// visible?
2054 	if (!IsVisible(iByPlayer, eDrawMode==ODM_Overlay)) return;
2055 	// target pos (parallax)
2056 	float newzoom = cgo.Zoom;
2057 	if (eDrawMode!=ODM_Overlay) GetDrawPosition(cgo, offX, offY, newzoom);
2058 	ZoomDataStackItem zdsi(newzoom);
2059 	// TopFace
2060 	if (!(TopFace.Surface || (OCF & OCF_Construct))) return;
2061 	// Output bounds check
2062 	if (!Inside<float>(offX, cgo.X - Shape.Wdt, cgo.X + cgo.Wdt)
2063 	    || !Inside<float>(offY, cgo.Y - Shape.Hgt, cgo.Y + cgo.Hgt))
2064 		return;
2065 	// Don't draw (show solidmask)
2066 	if (::GraphicsSystem.Show8BitSurface != 0 && SolidMask.Wdt) return;
2067 	// Contained
2068 	if (Contained) if (eDrawMode!=ODM_Overlay) return;
2069 	// Construction sign
2070 	if (OCF & OCF_Construct && fix_r == Fix0)
2071 		if (eDrawMode!=ODM_BaseOnly)
2072 		{
2073 			C4Facet &fctConSign = ::GraphicsResource.fctConstruction;
2074 			pDraw->Blit(fctConSign.Surface,
2075 			              fctConSign.X, fctConSign.Y,
2076 			              fctConSign.Wdt, fctConSign.Hgt,
2077 			              cgo.Surface,
2078 			              offX + Shape.GetX(), offY + Shape.GetY() + Shape.Hgt - fctConSign.Hgt,
2079 			              fctConSign.Wdt, fctConSign.Hgt, true);
2080 		}
2081 	if(TopFace.Surface)
2082 	{
2083 		// FacetTopFace: Override TopFace.GetX()/GetY()
2084 		C4PropList* pActionDef = GetAction();
2085 		if (pActionDef && pActionDef->GetPropertyInt(P_FacetTopFace))
2086 		{
2087 			int32_t iPhase = Action.Phase;
2088 			if (pActionDef->GetPropertyInt(P_Reverse)) iPhase = pActionDef->GetPropertyInt(P_Length) - 1 - Action.Phase;
2089 			TopFace.X = pActionDef->GetPropertyInt(P_X) + Def->TopFace.x + pActionDef->GetPropertyInt(P_Wdt) * iPhase;
2090 			TopFace.Y = pActionDef->GetPropertyInt(P_Y) + Def->TopFace.y + pActionDef->GetPropertyInt(P_Hgt) * Action.DrawDir;
2091 		}
2092 		// ensure correct color is set
2093 		if (GetGraphics()->Bmp.BitmapClr) GetGraphics()->Bmp.BitmapClr->SetClr(Color);
2094 		// color modulation
2095 		if (!eDrawMode) PrepareDrawing();
2096 		// Draw top face bitmap
2097 		if (Con!=FullCon && Def->GrowthType)
2098 			// stretched
2099 			pDraw->Blit(TopFace.Surface,
2100 				            TopFace.X, TopFace.Y, TopFace.Wdt, TopFace.Hgt,
2101 				            cgo.Surface,
2102 				            offX + Shape.GetX() + float(Def->TopFace.tx * Con) / FullCon, offY + Shape.GetY() + float(Def->TopFace.ty * Con) / FullCon,
2103 				            float(TopFace.Wdt * Con) / FullCon, float(TopFace.Hgt * Con) / FullCon,
2104 				            true, pDrawTransform ? &C4DrawTransform(*pDrawTransform, offX, offY) : nullptr);
2105 		else
2106 			// normal
2107 			pDraw->Blit(TopFace.Surface,
2108 				            TopFace.X,TopFace.Y,
2109 				            TopFace.Wdt,TopFace.Hgt,
2110 				            cgo.Surface,
2111 				            offX + Shape.GetX() + Def->TopFace.tx, offY + Shape.GetY() + Def->TopFace.ty,
2112 				            TopFace.Wdt, TopFace.Hgt,
2113 				            true, pDrawTransform ? &C4DrawTransform(*pDrawTransform, offX, offY) : nullptr);
2114 	}
2115 	// end of color modulation
2116 	if (!eDrawMode) FinishedDrawing();
2117 #endif
2118 }
2119 
DrawLine(C4TargetFacet & cgo,int32_t at_player)2120 void C4Object::DrawLine(C4TargetFacet &cgo, int32_t at_player)
2121 {
2122 	// Nothing to draw if the object has less than two vertices
2123 	if (Shape.VtxNum < 2)
2124 		return;
2125 #ifndef USE_CONSOLE
2126 	// Audibility
2127 	SetAudibilityAt(cgo, Shape.VtxX[0], Shape.VtxY[0], at_player);
2128 	SetAudibilityAt(cgo, Shape.VtxX[Shape.VtxNum - 1], Shape.VtxY[Shape.VtxNum - 1], at_player);
2129 	// additive mode?
2130 	PrepareDrawing();
2131 	// Draw line segments
2132 	C4Value colorsV; GetProperty(P_LineColors, &colorsV);
2133 	C4ValueArray *colors = colorsV.getArray();
2134 	// TODO: Edge color (color1) is currently ignored.
2135 	int32_t color0 = 0xFFFF00FF;// , color1 = 0xFFFF00FF; // use bright colors so author notices
2136 	if (colors)
2137 	{
2138 		color0 = colors->GetItem(0).getInt();
2139 	}
2140 
2141 	std::vector<C4BltVertex> vertices;
2142 	vertices.resize( (Shape.VtxNum - 1) * 2);
2143 	for (int32_t vtx=0; vtx+1<Shape.VtxNum; vtx++)
2144 	{
2145 		DwTo4UB(color0, vertices[2*vtx].color);
2146 		DwTo4UB(color0, vertices[2*vtx+1].color);
2147 
2148 		vertices[2*vtx].ftx = Shape.VtxX[vtx] + cgo.X - cgo.TargetX;
2149 		vertices[2*vtx].fty = Shape.VtxY[vtx] + cgo.Y - cgo.TargetY;
2150 		vertices[2*vtx+1].ftx = Shape.VtxX[vtx+1] + cgo.X - cgo.TargetX;
2151 		vertices[2*vtx+1].fty = Shape.VtxY[vtx+1] + cgo.Y - cgo.TargetY;
2152 	}
2153 
2154 	pDraw->PerformMultiLines(cgo.Surface, &vertices[0], vertices.size(), 1.0f, nullptr);
2155 
2156 	// reset blit mode
2157 	FinishedDrawing();
2158 #endif
2159 }
2160 
CompileFunc(StdCompiler * pComp,C4ValueNumbers * numbers)2161 void C4Object::CompileFunc(StdCompiler *pComp, C4ValueNumbers * numbers)
2162 {
2163 	bool deserializing = pComp->isDeserializer();
2164 	if (deserializing)
2165 		Clear();
2166 
2167 	// Compile ID, search definition
2168 	pComp->Value(mkNamingAdapt(id,                  "id",                 C4ID::None          ));
2169 	if (deserializing)
2170 	{
2171 		Def = ::Definitions.ID2Def(id);
2172 		if (!Def)
2173 			{ pComp->excNotFound(LoadResStr("IDS_PRC_UNDEFINEDOBJECT"),id.ToString()); return; }
2174 	}
2175 
2176 	pComp->Value(mkNamingAdapt( mkParAdapt(static_cast<C4PropListNumbered&>(*this), numbers), "Properties"));
2177 	pComp->Value(mkNamingAdapt( Status,                           "Status",             1                 ));
2178 	if (Info) nInfo = Info->Name; else nInfo.Clear();
2179 	pComp->Value(mkNamingAdapt( toC4CStrBuf(nInfo),               "Info",               ""                ));
2180 	pComp->Value(mkNamingAdapt( Owner,                            "Owner",              NO_OWNER          ));
2181 	pComp->Value(mkNamingAdapt( Controller,                       "Controller",         NO_OWNER          ));
2182 	pComp->Value(mkNamingAdapt( LastEnergyLossCausePlayer,        "LastEngLossPlr",     NO_OWNER          ));
2183 	pComp->Value(mkNamingAdapt( Category,                         "Category",           0                 ));
2184 	pComp->Value(mkNamingAdapt( Plane,                            "Plane",              0                 ));
2185 
2186 	pComp->Value(mkNamingAdapt( iLastAttachMovementFrame,         "LastSolidAtchFrame", -1                ));
2187 	pComp->Value(mkNamingAdapt( Con,                              "Size",               0                 ));
2188 	pComp->Value(mkNamingAdapt( OwnMass,                          "OwnMass",            0                 ));
2189 	pComp->Value(mkNamingAdapt( Mass,                             "Mass",               0                 ));
2190 	pComp->Value(mkNamingAdapt( Damage,                           "Damage",             0                 ));
2191 	pComp->Value(mkNamingAdapt( Energy,                           "Energy",             0                 ));
2192 	pComp->Value(mkNamingAdapt( Alive,                            "Alive",              false             ));
2193 	pComp->Value(mkNamingAdapt( Breath,                           "Breath",             0                 ));
2194 	pComp->Value(mkNamingAdapt( Color,                            "Color",              0u                ));
2195 	pComp->Value(mkNamingAdapt( fix_x,                            "X",                  Fix0              ));
2196 	pComp->Value(mkNamingAdapt( fix_y,                            "Y",                  Fix0              ));
2197 	pComp->Value(mkNamingAdapt( fix_r,                            "R",                  Fix0              ));
2198 	pComp->Value(mkNamingAdapt( xdir,                             "XDir",               0                 ));
2199 	pComp->Value(mkNamingAdapt( ydir,                             "YDir",               0                 ));
2200 	pComp->Value(mkNamingAdapt( rdir,                             "RDir",               0                 ));
2201 	pComp->Value(mkParAdapt(Shape, &Def->Shape));
2202 	pComp->Value(mkNamingAdapt( fOwnVertices,                     "OwnVertices",        false             ));
2203 	pComp->Value(mkNamingAdapt( SolidMask,                        "SolidMask",          Def->SolidMask    ));
2204 	pComp->Value(mkNamingAdapt( HalfVehicleSolidMask,             "HalfVehicleSolidMask", false           ));
2205 	pComp->Value(mkNamingAdapt( PictureRect,                      "Picture"                               ));
2206 	pComp->Value(mkNamingAdapt( Mobile,                           "Mobile",             false             ));
2207 	pComp->Value(mkNamingAdapt( OnFire,                           "OnFire",             false             ));
2208 	pComp->Value(mkNamingAdapt( InLiquid,                         "InLiquid",           false             ));
2209 	pComp->Value(mkNamingAdapt( EntranceStatus,                   "EntranceStatus",     false             ));
2210 	pComp->Value(mkNamingAdapt( OCF,                              "OCF",                0u                ));
2211 	pComp->Value(Action);
2212 	pComp->Value(mkNamingAdapt( Contained,                        "Contained",          C4ObjectPtr::Null ));
2213 	pComp->Value(mkNamingAdapt( Action.Target,                    "ActionTarget1",      C4ObjectPtr::Null ));
2214 	pComp->Value(mkNamingAdapt( Action.Target2,                   "ActionTarget2",      C4ObjectPtr::Null ));
2215 	pComp->Value(mkNamingAdapt( mkParAdapt(Contents, numbers),    "Contents"                              ));
2216 	pComp->Value(mkNamingAdapt( lightRange,                       "LightRange",         0                 ));
2217 	pComp->Value(mkNamingAdapt( lightFadeoutRange,                "LightFadeoutRange",  0                 ));
2218 	pComp->Value(mkNamingAdapt( lightColor,                       "lightColor",         0xffffffffu       ));
2219 	pComp->Value(mkNamingAdapt( ColorMod,                         "ColorMod",           0xffffffffu       ));
2220 	pComp->Value(mkNamingAdapt( BlitMode,                         "BlitMode",           0u                ));
2221 	pComp->Value(mkNamingAdapt( CrewDisabled,                     "CrewDisabled",       false             ));
2222 	pComp->Value(mkNamingAdapt( Layer,                            "Layer",              C4ObjectPtr::Null ));
2223 	pComp->Value(mkNamingAdapt( C4DefGraphicsAdapt(pGraphics),    "Graphics",           &Def->Graphics    ));
2224 	pComp->Value(mkNamingPtrAdapt( pDrawTransform,                "DrawTransform"                         ));
2225 	pComp->Value(mkParAdapt(mkNamingPtrAdapt( pEffects,           "Effects"                               ), this, numbers));
2226 	pComp->Value(mkNamingAdapt( C4GraphicsOverlayListAdapt(pGfxOverlay),"GfxOverlay",   (C4GraphicsOverlay *)nullptr));
2227 
2228 	// Serialize mesh instance if we have a mesh graphics
2229 	if(pGraphics->Type == C4DefGraphics::TYPE_Mesh)
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 
2237 		pComp->Value(mkNamingAdapt(mkParAdapt(*pMeshInstance, C4MeshDenumeratorFactory), "Mesh"));
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
2299 			Action.Animation = pMeshInstance->GetRootAnimationForSlot(0);
2300 			pMeshInstance->SetFaceOrderingForClrModulation(ColorMod);
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 
Denumerate(C4ValueNumbers * numbers)2312 void C4Object::Denumerate(C4ValueNumbers * numbers)
2313 {
2314 	C4PropList::Denumerate(numbers);
2315 	// Standard enumerated pointers
2316 	Contained.DenumeratePointers();
2317 	Action.Target.DenumeratePointers();
2318 	Action.Target2.DenumeratePointers();
2319 	Layer.DenumeratePointers();
2320 
2321 	// Post-compile object list
2322 	Contents.DenumeratePointers();
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
2337 	if (pMeshInstance) pMeshInstance->DenumeratePointers();
2338 }
2339 
DrawPicture(C4Facet & cgo,bool fSelected,C4DrawTransform * transform)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 
Picture2Facet(C4FacetSurface & cgo)2346 void C4Object::Picture2Facet(C4FacetSurface &cgo)
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 
ValidateOwner()2380 bool C4Object::ValidateOwner()
2381 {
2382 	// Check owner and controller
2383 	if (!ValidPlr(Owner)) Owner=NO_OWNER;
2384 	if (!ValidPlr(Controller)) Controller=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 
AssignInfo()2390 bool C4Object::AssignInfo()
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 
AssignLightRange()2420 bool C4Object::AssignLightRange()
2421 {
2422 	if (!lightRange && !lightFadeoutRange) return true;
2423 
2424 	UpdateLight();
2425 	return true;
2426 }
2427 
ClearInfo(C4ObjectInfo * pInfo)2428 void C4Object::ClearInfo(C4ObjectInfo *pInfo)
2429 {
2430 	if (Info==pInfo)
2431 	{
2432 		Info=nullptr;
2433 	}
2434 }
2435 
Clear()2436 void C4Object::Clear()
2437 {
2438 	ClearParticleLists();
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 
MenuCommand(const char * szCommand)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 
SetSolidMask(int32_t iX,int32_t iY,int32_t iWdt,int32_t iHgt,int32_t iTX,int32_t iTY)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 
SetHalfVehicleSolidMask(bool set)2472 void C4Object::SetHalfVehicleSolidMask(bool set)
2473 {
2474 	if (!pSolidMaskData) return;
2475 	HalfVehicleSolidMask = set;
2476 	pSolidMaskData->SetHalfVehicle(set);
2477 }
2478 
CheckSolidMaskRect()2479 bool C4Object::CheckSolidMaskRect()
2480 {
2481 	// Ensure SolidMask rect lies within bounds of SolidMask bitmap in definition
2482 	CSurface8 *sfcGraphics = Def->pSolidMask;
2483 	if (!sfcGraphics)
2484 	{
2485 		// no graphics to set solid in
2486 		SolidMask.Set(0,0,0,0,0,0);
2487 		return false;
2488 	}
2489 	SolidMask.Set(std::max<int32_t>(SolidMask.x,0), std::max<int32_t>(SolidMask.y,0),
2490 	              std::min<int32_t>(SolidMask.Wdt,sfcGraphics->Wdt-SolidMask.x), std::min<int32_t>(SolidMask.Hgt, sfcGraphics->Hgt-SolidMask.y),
2491 	              SolidMask.tx, SolidMask.ty);
2492 	if (SolidMask.Hgt<=0) SolidMask.Wdt=0;
2493 	return SolidMask.Wdt>0;
2494 }
2495 
SyncClearance()2496 void C4Object::SyncClearance()
2497 {
2498 	// Misc. no-save safeties
2499 	Action.t_attach = CNAT_None;
2500 	InMat = MNone;
2501 	t_contact = 0;
2502 	// Update OCF
2503 	SetOCF();
2504 	// Menu
2505 	CloseMenu(true);
2506 	// Material contents
2507 	if (MaterialContents) delete MaterialContents; MaterialContents=nullptr;
2508 	// reset speed of staticback-objects
2509 	if (Category & C4D_StaticBack)
2510 	{
2511 		xdir = ydir = 0;
2512 	}
2513 }
2514 
DrawSelectMark(C4TargetFacet & cgo) const2515 void C4Object::DrawSelectMark(C4TargetFacet &cgo) const
2516 {
2517 	// Status
2518 	if (!Status) return;
2519 	// No select marks in film playback
2520 	if (Game.C4S.Head.Film && Game.C4S.Head.Replay) return;
2521 	// target pos (parallax)
2522 	float offX, offY, newzoom;
2523 	GetDrawPosition(cgo, offX, offY, newzoom);
2524 	// Output boundary
2525 	if (!Inside<float>(offX, cgo.X, cgo.X + cgo.Wdt)
2526 	    || !Inside<float>(offY, cgo.Y, cgo.Y + cgo.Hgt)) return;
2527 	// Draw select marks
2528 	float cox = offX + Shape.GetX() - cgo.X + cgo.X - 2;
2529 	float coy = offY + Shape.GetY() - cgo.Y + cgo.Y - 2;
2530 	GfxR->fctSelectMark.Draw(cgo.Surface,cox,coy,0);
2531 	GfxR->fctSelectMark.Draw(cgo.Surface,cox+Shape.Wdt,coy,1);
2532 	GfxR->fctSelectMark.Draw(cgo.Surface,cox,coy+Shape.Hgt,2);
2533 	GfxR->fctSelectMark.Draw(cgo.Surface,cox+Shape.Wdt,coy+Shape.Hgt,3);
2534 }
2535 
ClearCommands()2536 void C4Object::ClearCommands()
2537 {
2538 	C4Command *pNext;
2539 	while (Command)
2540 	{
2541 		pNext=Command->Next;
2542 		if (!Command->iExec)
2543 			delete Command;
2544 		else
2545 			Command->iExec = 2;
2546 		Command=pNext;
2547 	}
2548 }
2549 
ClearCommand(C4Command * pUntil)2550 void C4Object::ClearCommand(C4Command *pUntil)
2551 {
2552 	C4Command *pCom,*pNext;
2553 	for (pCom=Command; pCom; pCom=pNext)
2554 	{
2555 		// Last one to clear
2556 		if (pCom==pUntil) pNext=nullptr;
2557 		// Next one to clear after this
2558 		else pNext=pCom->Next;
2559 		Command=pCom->Next;
2560 		if (!pCom->iExec)
2561 			delete pCom;
2562 		else
2563 			pCom->iExec = 2;
2564 	}
2565 }
2566 
AddCommand(int32_t iCommand,C4Object * pTarget,C4Value iTx,int32_t iTy,int32_t iUpdateInterval,C4Object * pTarget2,bool fInitEvaluation,C4Value iData,bool fAppend,int32_t iRetries,C4String * szText,int32_t iBaseMode)2567 bool C4Object::AddCommand(int32_t iCommand, C4Object *pTarget, C4Value iTx, int32_t iTy,
2568                           int32_t iUpdateInterval, C4Object *pTarget2,
2569                           bool fInitEvaluation, C4Value iData, bool fAppend,
2570                           int32_t iRetries, C4String *szText, int32_t iBaseMode)
2571 {
2572 	// Command stack size safety
2573 	const int32_t MaxCommandStack = 35;
2574 	C4Command *pCom,*pLast; int32_t iCommands;
2575 	for (pCom=Command,iCommands=0; pCom; pCom=pCom->Next,iCommands++) {}
2576 	if (iCommands>=MaxCommandStack) return false;
2577 	// Valid command safety
2578 	if (!Inside(iCommand,C4CMD_First,C4CMD_Last)) return false;
2579 	// Allocate and set new command
2580 	if (!(pCom=new C4Command)) return false;
2581 	pCom->Set(iCommand,this,pTarget,iTx,iTy,pTarget2,iData,
2582 	          iUpdateInterval,!fInitEvaluation,iRetries,szText,iBaseMode);
2583 	// Append to bottom of stack
2584 	if (fAppend)
2585 	{
2586 		for (pLast=Command; pLast && pLast->Next; pLast=pLast->Next) {}
2587 		if (pLast) pLast->Next=pCom;
2588 		else Command=pCom;
2589 	}
2590 	// Add to top of command stack
2591 	else
2592 	{
2593 		pCom->Next=Command;
2594 		Command=pCom;
2595 	}
2596 	// Success
2597 	return true;
2598 }
2599 
SetCommand(int32_t iCommand,C4Object * pTarget,C4Value iTx,int32_t iTy,C4Object * pTarget2,bool fControl,C4Value iData,int32_t iRetries,C4String * szText)2600 void C4Object::SetCommand(int32_t iCommand, C4Object *pTarget, C4Value iTx, int32_t iTy,
2601                           C4Object *pTarget2, bool fControl, C4Value iData,
2602                           int32_t iRetries, C4String *szText)
2603 {
2604 	// Clear stack
2605 	ClearCommands();
2606 	// Close menu
2607 	if (fControl)
2608 		if (!CloseMenu(false)) return;
2609 	// Script overload
2610 	if (fControl)
2611 		if (!!Call(PSF_ControlCommand,&C4AulParSet(CommandName(iCommand),
2612 		           pTarget,
2613 		           iTx,
2614 		           iTy,
2615 		           pTarget2,
2616 		           iData)))
2617 			return;
2618 	// Inside vehicle control overload
2619 	if (Contained)
2620 		if (Contained->Def->VehicleControl & C4D_VehicleControl_Inside)
2621 		{
2622 			Contained->Controller=Controller;
2623 			if (!!Contained->Call(PSF_ControlCommand,&C4AulParSet(CommandName(iCommand),
2624 			                      pTarget,
2625 			                      iTx,
2626 			                      iTy,
2627 			                      pTarget2,
2628 			                      iData,
2629 			                      this)))
2630 				return;
2631 		}
2632 	// Outside vehicle control overload
2633 	if (GetProcedure()==DFA_PUSH)
2634 		if (Action.Target)  if (Action.Target->Def->VehicleControl & C4D_VehicleControl_Outside)
2635 			{
2636 				Action.Target->Controller=Controller;
2637 				if (!!Action.Target->Call(PSF_ControlCommand,&C4AulParSet(CommandName(iCommand),
2638 				                          pTarget,
2639 				                          iTx,
2640 				                          iTy,
2641 				                          pTarget2,
2642 				                          iData)))
2643 					return;
2644 			}
2645 	// Add new command
2646 	AddCommand(iCommand,pTarget,iTx,iTy,0,pTarget2,true,iData,false,iRetries,szText,C4CMD_Mode_Base);
2647 }
2648 
FindCommand(int32_t iCommandType) const2649 C4Command *C4Object::FindCommand(int32_t iCommandType) const
2650 {
2651 	// seek all commands
2652 	for (C4Command *pCom = Command; pCom; pCom=pCom->Next)
2653 		if (pCom->Command == iCommandType) return pCom;
2654 	// nothing found
2655 	return nullptr;
2656 }
2657 
ExecuteCommand()2658 bool C4Object::ExecuteCommand()
2659 {
2660 	// Execute first command
2661 	if (Command) Command->Execute();
2662 	// Command finished: engine call
2663 	if (Command && Command->Finished)
2664 		Call(PSF_ControlCommandFinished,&C4AulParSet(CommandName(Command->Command), Command->Target, Command->Tx, Command->Ty, Command->Target2, Command->Data));
2665 	// Clear finished commands
2666 	while (Command && Command->Finished) ClearCommand(Command);
2667 	// Done
2668 	return true;
2669 }
2670 
Resort()2671 void C4Object::Resort()
2672 {
2673 	// Flag resort
2674 	Unsorted=true;
2675 	Game.fResortAnyObject = true;
2676 	// Must not immediately resort - link change/removal would crash Game::ExecObjects
2677 }
2678 
GetAction() const2679 C4PropList* C4Object::GetAction() const
2680 {
2681 	C4Value value;
2682 	GetProperty(P_Action, &value);
2683 	return value.getPropList();
2684 }
2685 
SetAction(C4PropList * Act,C4Object * pTarget,C4Object * pTarget2,int32_t iCalls,bool fForce)2686 bool C4Object::SetAction(C4PropList * Act, C4Object *pTarget, C4Object *pTarget2, int32_t iCalls, bool fForce)
2687 {
2688 	C4Value vLastAction;
2689 	GetProperty(P_Action, &vLastAction);
2690 	C4PropList * LastAction = vLastAction.getPropList();
2691 	int32_t iLastPhase=Action.Phase;
2692 	C4Object *pLastTarget = Action.Target;
2693 	C4Object *pLastTarget2 = Action.Target2;
2694 	// No other action
2695 	if (LastAction)
2696 		if (LastAction->GetPropertyInt(P_NoOtherAction) && !fForce)
2697 			if (Act != LastAction)
2698 				return false;
2699 	// Set animation on instance. Abort if the mesh does not have
2700 	// such an animation.
2701 	if (pMeshInstance)
2702 	{
2703 		if (Action.Animation) pMeshInstance->StopAnimation(Action.Animation);
2704 		Action.Animation = nullptr;
2705 
2706 		C4String* Animation = Act ? Act->GetPropertyStr(P_Animation) : nullptr;
2707 		if (Animation)
2708 		{
2709 			// note that weight is ignored
2710 			Action.Animation = pMeshInstance->PlayAnimation(Animation->GetData(), 0, nullptr, new C4ValueProviderAction(this), new C4ValueProviderConst(itofix(1)), true);
2711 		}
2712 	}
2713 	// Stop previous act sound
2714 	if (LastAction)
2715 		if (Act != LastAction)
2716 			if (LastAction->GetPropertyStr(P_Sound))
2717 				StopSoundEffect(LastAction->GetPropertyStr(P_Sound)->GetCStr(),this);
2718 	// Unfullcon objects no action
2719 	if (Con<FullCon)
2720 		if (!Def->IncompleteActivity)
2721 			Act = nullptr;
2722 	// Reset action time on change
2723 	if (Act!=LastAction)
2724 	{
2725 		Action.Time=0;
2726 		// reset action data and targets if procedure is changed
2727 		if ((Act ? Act->GetPropertyP(P_Procedure) : -1)
2728 			!= (LastAction ? LastAction->GetPropertyP(P_Procedure) : -1))
2729 		{
2730 			Action.Data = 0;
2731 			Action.Target = nullptr;
2732 			Action.Target2 = nullptr;
2733 		}
2734 	}
2735 	// Set new action
2736 	SetProperty(P_Action, C4VPropList(Act));
2737 	Action.Phase=Action.PhaseDelay=0;
2738 	// Set target if specified
2739 	if (pTarget) Action.Target=pTarget;
2740 	if (pTarget2) Action.Target2=pTarget2;
2741 	// Set Action Facet
2742 	UpdateActionFace();
2743 	// update flipdir
2744 	if ((LastAction ? LastAction->GetPropertyInt(P_FlipDir) : 0)
2745 	    != (Act ? Act->GetPropertyInt(P_FlipDir) : 0)) UpdateFlipDir();
2746 	// Start act sound
2747 	if (Act)
2748 		if (Act != LastAction)
2749 			if (Act->GetPropertyStr(P_Sound))
2750 				StartSoundEffect(Act->GetPropertyStr(P_Sound)->GetCStr(),+1,100,this);
2751 	// Reset OCF
2752 	SetOCF();
2753 	// issue calls
2754 	// Execute EndCall for last action
2755 	if (iCalls & SAC_EndCall && !fForce)
2756 		if (LastAction)
2757 		{
2758 			if (LastAction->GetPropertyStr(P_EndCall))
2759 			{
2760 				C4Def *pOldDef = Def;
2761 				Call(LastAction->GetPropertyStr(P_EndCall)->GetCStr());
2762 				// abort exeution if def changed
2763 				if (Def != pOldDef || !Status) return true;
2764 			}
2765 		}
2766 	// Execute AbortCall for last action
2767 	if (iCalls & SAC_AbortCall && !fForce)
2768 		if (LastAction)
2769 		{
2770 			if (LastAction->GetPropertyStr(P_AbortCall))
2771 			{
2772 				C4Def *pOldDef = Def;
2773 				if (pLastTarget && !pLastTarget->Status) pLastTarget = nullptr;
2774 				if (pLastTarget2 && !pLastTarget2->Status) pLastTarget2 = nullptr;
2775 				Call(LastAction->GetPropertyStr(P_AbortCall)->GetCStr(), &C4AulParSet(iLastPhase, pLastTarget, pLastTarget2));
2776 				// abort exeution if def changed
2777 				if (Def != pOldDef || !Status) return true;
2778 			}
2779 		}
2780 	// Execute StartCall for new action
2781 	if (iCalls & SAC_StartCall)
2782 		if (Act)
2783 		{
2784 			if (Act->GetPropertyStr(P_StartCall))
2785 			{
2786 				C4Def *pOldDef = Def;
2787 				Call(Act->GetPropertyStr(P_StartCall)->GetCStr());
2788 				// abort exeution if def changed
2789 				if (Def != pOldDef || !Status) return true;
2790 			}
2791 		}
2792 
2793 	C4Def *pOldDef = Def;
2794 	Call(PSF_OnActionChanged, &C4AulParSet(LastAction ? LastAction->GetName() : "Idle"));
2795 	if (Def != pOldDef || !Status) return true;
2796 
2797 	return true;
2798 }
2799 
UpdateActionFace()2800 void C4Object::UpdateActionFace()
2801 {
2802 	// Default: no action face
2803 	Action.Facet.Default();
2804 	// Active: get action facet from action definition
2805 	C4PropList* pActionDef = GetAction();
2806 	if (pActionDef)
2807 	{
2808 		if (pActionDef->GetPropertyInt(P_Wdt)>0)
2809 		{
2810 			Action.Facet.Set(GetGraphics()->GetBitmap(Color),
2811 			                 pActionDef->GetPropertyInt(P_X),pActionDef->GetPropertyInt(P_Y),
2812 			                 pActionDef->GetPropertyInt(P_Wdt),pActionDef->GetPropertyInt(P_Hgt));
2813 			Action.FacetX=pActionDef->GetPropertyInt(P_OffX);
2814 			Action.FacetY=pActionDef->GetPropertyInt(P_OffY);
2815 		}
2816 	}
2817 }
2818 
SetActionByName(C4String * ActName,C4Object * pTarget,C4Object * pTarget2,int32_t iCalls,bool fForce)2819 bool C4Object::SetActionByName(C4String *ActName,
2820                                C4Object *pTarget, C4Object *pTarget2,
2821                                int32_t iCalls, bool fForce)
2822 {
2823 	assert(ActName);
2824 	// If we get the null string or ActIdle by name, set ActIdle
2825 	if (!ActName || ActName == &Strings.P[P_Idle])
2826 		return SetAction(nullptr,nullptr,nullptr,iCalls,fForce);
2827 	C4Value ActMap; GetProperty(P_ActMap, &ActMap);
2828 	if (!ActMap.getPropList()) return false;
2829 	C4Value Action; ActMap.getPropList()->GetPropertyByS(ActName, &Action);
2830 	if (!Action.getPropList()) return false;
2831 	return SetAction(Action.getPropList(),pTarget,pTarget2,iCalls,fForce);
2832 }
2833 
SetActionByName(const char * szActName,C4Object * pTarget,C4Object * pTarget2,int32_t iCalls,bool fForce)2834 bool C4Object::SetActionByName(const char * szActName,
2835                                C4Object *pTarget, C4Object *pTarget2,
2836                                int32_t iCalls, bool fForce)
2837 {
2838 	C4String * ActName = Strings.RegString(szActName);
2839 	ActName->IncRef();
2840 	bool r = SetActionByName(ActName, pTarget, pTarget2, iCalls, fForce);
2841 	ActName->DecRef();
2842 	return r;
2843 }
2844 
SetDir(int32_t iDir)2845 void C4Object::SetDir(int32_t iDir)
2846 {
2847 	// Not active
2848 	C4PropList* pActionDef = GetAction();
2849 	if (!pActionDef) return;
2850 	// Invalid direction
2851 	if (!Inside<int32_t>(iDir,0,pActionDef->GetPropertyInt(P_Directions)-1)) return;
2852 	// Execute turn action
2853 	if (iDir != Action.Dir)
2854 		if (pActionDef->GetPropertyStr(P_TurnAction))
2855 			{ SetActionByName(pActionDef->GetPropertyStr(P_TurnAction)); }
2856 	// Set dir
2857 	Action.Dir=iDir;
2858 	// update by flipdir?
2859 	if (pActionDef->GetPropertyInt(P_FlipDir))
2860 		UpdateFlipDir();
2861 	else
2862 		Action.DrawDir=iDir;
2863 }
2864 
GetProcedure() const2865 int32_t C4Object::GetProcedure() const
2866 {
2867 	C4PropList* pActionDef = GetAction();
2868 	if (!pActionDef) return -1;
2869 	return pActionDef->GetPropertyP(P_Procedure);
2870 }
2871 
GrabLost(C4Object * cObj,C4Object * prev_target)2872 void GrabLost(C4Object *cObj, C4Object *prev_target)
2873 {
2874 	// Grab lost script call on target (quite hacky stuff...)
2875 	if (prev_target && prev_target->Status) prev_target->Call(PSF_GrabLost);
2876 	// Clear commands down to first PushTo (if any) in command stack
2877 	for (C4Command *pCom=cObj->Command; pCom; pCom=pCom->Next)
2878 		if (pCom->Next && pCom->Next->Command==C4CMD_PushTo)
2879 		{
2880 			cObj->ClearCommand(pCom);
2881 			break;
2882 		}
2883 }
2884 
2885 static void DoGravity(C4Object *cobj);
2886 
NoAttachAction()2887 void C4Object::NoAttachAction()
2888 {
2889 	// Active objects
2890 	if (GetAction())
2891 	{
2892 		int32_t iProcedure = GetProcedure();
2893 		C4Object *prev_target = Action.Target;
2894 		// Scaling upwards: corner scale
2895 		if (iProcedure == DFA_SCALE && Action.ComDir != COMD_Stop && ComDirLike(Action.ComDir, COMD_Up))
2896 			if (ObjectActionCornerScale(this)) return;
2897 		if (iProcedure == DFA_SCALE && Action.ComDir == COMD_Left && Action.Dir == DIR_Left)
2898 			if (ObjectActionCornerScale(this)) return;
2899 		if (iProcedure == DFA_SCALE && Action.ComDir == COMD_Right && Action.Dir == DIR_Right)
2900 			if (ObjectActionCornerScale(this)) return;
2901 		// Scaling and stopped: fall off to side (avoid zuppel)
2902 		if ((iProcedure == DFA_SCALE) && (Action.ComDir == COMD_Stop))
2903 		{
2904 			if (Action.Dir == DIR_Left)
2905 				{ if (ObjectActionJump(this,itofix(1),Fix0,false)) return; }
2906 			else
2907 				{ if (ObjectActionJump(this,itofix(-1),Fix0,false)) return; }
2908 		}
2909 		// Pushing: grab loss
2910 		if (iProcedure==DFA_PUSH) GrabLost(this, prev_target);
2911 		// Else jump
2912 		ObjectActionJump(this,xdir,ydir,false);
2913 	}
2914 	// Inactive objects, simple mobile natural gravity
2915 	else
2916 	{
2917 		DoGravity(this);
2918 		Mobile=true;
2919 	}
2920 }
2921 
ContactAction()2922 void C4Object::ContactAction()
2923 {
2924 	// Take certain action on contact. Evaluate t_contact-CNAT and Procedure.
2925 
2926 	// Determine Procedure
2927 	C4PropList* pActionDef = GetAction();
2928 	if (!pActionDef) return;
2929 	int32_t iProcedure=pActionDef->GetPropertyP(P_Procedure);
2930 	int32_t fDisabled=pActionDef->GetPropertyInt(P_ObjectDisabled);
2931 
2932 	//------------------------------- Hit Bottom ---------------------------------------------
2933 	if (t_contact & CNAT_Bottom)
2934 		switch (iProcedure)
2935 		{
2936 		case DFA_FLIGHT:
2937 			if (ydir < 0) return;
2938 			// Jump: FlatHit / HardHit / Walk
2939 			if ((OCF & OCF_HitSpeed4) || fDisabled)
2940 				if (ObjectActionFlat(this,Action.Dir)) return;
2941 			if (OCF & OCF_HitSpeed3)
2942 				if (ObjectActionKneel(this)) return;
2943 			ObjectActionWalk(this);
2944 			ydir = 0;
2945 			return;
2946 		case DFA_SCALE:
2947 			// Scale down: stand
2948 			if (ComDirLike(Action.ComDir, COMD_Down))
2949 			{
2950 				ObjectActionStand(this);
2951 				return;
2952 			}
2953 			break;
2954 		case DFA_DIG:
2955 			// no special action
2956 			break;
2957 		case DFA_SWIM:
2958 			// Try corner scale out
2959 			if (!GBackSemiSolid(GetX(),GetY()-1+Def->Float*Con/FullCon-1))
2960 				if (ObjectActionCornerScale(this)) return;
2961 			break;
2962 		}
2963 
2964 	//------------------------------- Hit Ceiling -----------------------------------------
2965 	if (t_contact & CNAT_Top)
2966 		switch (iProcedure)
2967 		{
2968 		case DFA_WALK:
2969 			// Walk: Stop
2970 			ObjectActionStand(this); return;
2971 		case DFA_SCALE:
2972 			// Scale: Try hangle, else stop if going upward
2973 			if (ComDirLike(Action.ComDir, COMD_Up))
2974 			{
2975 				if (ObjectActionHangle(this))
2976 				{
2977 					SetDir(Action.Dir == DIR_Left ? DIR_Right : DIR_Left);
2978 					return;
2979 				}
2980 				Action.ComDir=COMD_Stop;
2981 			}
2982 			break;
2983 		case DFA_FLIGHT:
2984 			// Jump: Try hangle, else bounce off
2985 			// High Speed Flight: Tumble
2986 			if ((OCF & OCF_HitSpeed3) || fDisabled)
2987 				{ ObjectActionTumble(this, Action.Dir, xdir, ydir); break; }
2988 			if (ObjectActionHangle(this)) return;
2989 			break;
2990 		case DFA_DIG:
2991 			// No action
2992 			break;
2993 		case DFA_HANGLE:
2994 			Action.ComDir=COMD_Stop;
2995 			break;
2996 		}
2997 
2998 	//---------------------------- Hit Left Wall ----------------------------------------
2999 	if (t_contact & CNAT_Left)
3000 	{
3001 		switch (iProcedure)
3002 		{
3003 		case DFA_FLIGHT:
3004 			// High Speed Flight: Tumble
3005 			if ((OCF & OCF_HitSpeed3) || fDisabled)
3006 				{ ObjectActionTumble(this, DIR_Left, xdir, ydir); break; }
3007 			// Else
3008 			else if (!ComDirLike(Action.ComDir, COMD_Right) && ObjectActionScale(this,DIR_Left)) return;
3009 			break;
3010 		case DFA_WALK:
3011 			// Walk: Try scale
3012 			if (ComDirLike(Action.ComDir, COMD_Left))
3013 			{
3014 				if (ObjectActionScale(this,DIR_Left))
3015 				{
3016 					ydir = C4REAL100(-1);
3017 					return;
3018 				}
3019 			}
3020 			// Heading away from solid
3021 			if (ComDirLike(Action.ComDir, COMD_Right))
3022 			{
3023 				// Slide off
3024 				ObjectActionJump(this,xdir/2,ydir,false);
3025 			}
3026 			return;
3027 		case DFA_SWIM:
3028 			// Only scale if swimming at the surface
3029 			if (!GBackSemiSolid(GetX(),GetY()-1+Def->Float*Con/FullCon-1))
3030 			{
3031 				// Try scale, only if swimming at the surface.
3032 				if (ComDirLike(Action.ComDir, COMD_Left))
3033 					if (ObjectActionScale(this,DIR_Left)) return;
3034 				// Try corner scale out
3035 				if (ObjectActionCornerScale(this)) return;
3036 			}
3037 			return;
3038 		case DFA_HANGLE:
3039 			// Hangle: Try scale
3040 			if (ObjectActionScale(this,DIR_Left))
3041 			{
3042 				ydir = C4REAL100(1);
3043 				return;
3044 			}
3045 			return;
3046 		case DFA_DIG:
3047 			// Dig: no action
3048 			break;
3049 		}
3050 	}
3051 
3052 	//------------------------------ Hit Right Wall --------------------------------------
3053 	if (t_contact & CNAT_Right)
3054 	{
3055 		switch (iProcedure)
3056 		{
3057 		case DFA_FLIGHT:
3058 			// High Speed Flight: Tumble
3059 			if ((OCF & OCF_HitSpeed3) || fDisabled)
3060 				{ ObjectActionTumble(this, DIR_Right, xdir, ydir); break; }
3061 			// Else Scale
3062 			else if (!ComDirLike(Action.ComDir, COMD_Left) && ObjectActionScale(this,DIR_Right)) return;
3063 			break;
3064 		case DFA_WALK:
3065 			// Walk: Try scale
3066 			if (ComDirLike(Action.ComDir, COMD_Right))
3067 			{
3068 				if (ObjectActionScale(this,DIR_Right))
3069 				{
3070 					ydir = C4REAL100(-1);
3071 					return;
3072 				}
3073 			}
3074 			// Heading away from solid
3075 			if (ComDirLike(Action.ComDir, COMD_Left))
3076 			{
3077 				// Slide off
3078 				ObjectActionJump(this,xdir/2,ydir,false);
3079 			}
3080 			return;
3081 		case DFA_SWIM:
3082 			// Only scale if swimming at the surface
3083 			if (!GBackSemiSolid(GetX(),GetY()-1+Def->Float*Con/FullCon-1))
3084 			{
3085 				// Try scale
3086 				if (ComDirLike(Action.ComDir, COMD_Right))
3087 					if (ObjectActionScale(this,DIR_Right)) return;
3088 				// Try corner scale out
3089 				if (ObjectActionCornerScale(this)) return;
3090 			}
3091 			return;
3092 		case DFA_HANGLE:
3093 			// Hangle: Try scale
3094 			if (ObjectActionScale(this,DIR_Right))
3095 			{
3096 				ydir = C4REAL100(1);
3097 				return;
3098 			}
3099 			return;
3100 		case DFA_DIG:
3101 			// Dig: no action
3102 			break;
3103 		}
3104 	}
3105 
3106 	//---------------------------- Unresolved Cases ---------------------------------------
3107 
3108 	// Flight stuck
3109 	if (iProcedure==DFA_FLIGHT)
3110 	{
3111 		// Enforce slide free (might slide through tiny holes this way)
3112 		if (!ydir)
3113 		{
3114 			int fAllowDown = !(t_contact & CNAT_Bottom);
3115 			if (t_contact & CNAT_Right)
3116 			{
3117 				ForcePosition(fix_x - 1, fix_y + fAllowDown);
3118 				xdir=ydir=0;
3119 			}
3120 			if (t_contact & CNAT_Left)
3121 			{
3122 				ForcePosition(fix_x + 1, fix_y + fAllowDown);
3123 				xdir=ydir=0;
3124 			}
3125 		}
3126 		if (!xdir)
3127 		{
3128 			if (t_contact & CNAT_Top)
3129 			{
3130 				ForcePosition(fix_x, fix_y + 1);
3131 				xdir=ydir=0;
3132 			}
3133 		}
3134 	}
3135 }
3136 
Towards(C4Real & val,C4Real target,C4Real step)3137 void Towards(C4Real &val, C4Real target, C4Real step)
3138 {
3139 	if (val==target) return;
3140 	if (Abs(val-target)<=step) { val=target; return; }
3141 	if (val<target) val+=step; else val-=step;
3142 }
3143 
DoBridge(C4Object * clk)3144 bool DoBridge(C4Object *clk)
3145 {
3146 	int32_t iBridgeTime; bool fMoveClonk, fWall; int32_t iBridgeMaterial;
3147 	clk->Action.GetBridgeData(iBridgeTime, fMoveClonk, fWall, iBridgeMaterial);
3148 	if (!iBridgeTime) iBridgeTime = 100; // default bridge time
3149 	if (clk->Action.Time>=iBridgeTime) { ObjectActionStand(clk); return false; }
3150 	// get bridge advancement
3151 	int32_t dtp;
3152 	if (fWall) switch (clk->Action.ComDir)
3153 		{
3154 		case COMD_Left: case COMD_Right: dtp = 4; fMoveClonk = false; break; // vertical wall: default 25 pixels
3155 		case COMD_UpLeft: case COMD_UpRight: dtp = 5; fMoveClonk = false; break; // diagonal roof over Clonk: default 20 pixels up and 20 pixels side (28 pixels - optimized to close tunnels completely)
3156 		case COMD_Up: dtp = 5; break; // horizontal roof over Clonk
3157 		default: return true; // bridge procedure just for show
3158 		}
3159 	else switch (clk->Action.ComDir)
3160 		{
3161 		case COMD_Left: case COMD_Right: dtp = 5; break; // horizontal bridges: default 20 pixels
3162 		case COMD_Up: dtp = 4; break; // vertical bridges: default 25 pixels (same as
3163 		case COMD_UpLeft: case COMD_UpRight: dtp = 6; break; // diagonal bridges: default 16 pixels up and 16 pixels side (23 pixels)
3164 		default: return true; // bridge procedure  just for show
3165 		}
3166 	if (clk->Action.Time % dtp) return true; // no advancement in this frame
3167 	// get target pos for Clonk and bridge
3168 	int32_t cx=clk->GetX(), cy=clk->GetY(), cw=clk->Shape.Wdt, ch=clk->Shape.Hgt;
3169 	int32_t tx=cx,ty=cy+ch/2;
3170 	int32_t dt;
3171 	if (fMoveClonk) dt = 0; else dt = clk->Action.Time / dtp;
3172 	if (fWall) switch (clk->Action.ComDir)
3173 		{
3174 		case COMD_Left: tx-=cw/2; ty+=-dt; break;
3175 		case COMD_Right: tx+=cw/2; ty+=-dt; break;
3176 		case COMD_Up:
3177 		{
3178 			int32_t x0;
3179 			if (fMoveClonk) x0=-3; else x0=(iBridgeTime/dtp)/-2;
3180 			tx+=(x0+dt)*((clk->Action.Dir==DIR_Right)*2-1); cx+=((clk->Action.Dir==DIR_Right)*2-1); ty-=ch+3; break;
3181 		}
3182 		case COMD_UpLeft: tx-=-4+dt; ty+=-ch-7+dt; break;
3183 		case COMD_UpRight: tx+=-4+dt; ty+=-ch-7+dt; break;
3184 		}
3185 	else switch (clk->Action.ComDir)
3186 		{
3187 		case COMD_Left: tx+=-3-dt; --cx; break;
3188 		case COMD_Right: tx+=+2+dt; ++cx; break;
3189 		case COMD_Up: tx+=(-cw/2+(cw-1)*(clk->Action.Dir==DIR_Right))*(!fMoveClonk); ty+=-dt-fMoveClonk; --cy; break;
3190 		case COMD_UpLeft: tx+=-5-dt+fMoveClonk*3; ty+=2-dt-fMoveClonk*3; --cx; --cy; break;
3191 		case COMD_UpRight: tx+=+5+dt-fMoveClonk*2; ty+=2-dt-fMoveClonk*3; ++cx; --cy; break;
3192 		}
3193 	// check if Clonk movement is posible
3194 	if (fMoveClonk)
3195 	{
3196 		int32_t cx2=cx, cy2=cy;
3197 		if (/*!clk->Shape.Attach(cx2, cy2, (clk->Action.t_attach & CNAT_Flags) | CNAT_Bottom) ||*/ clk->Shape.CheckContact(cx2, cy2-1))
3198 		{
3199 			// Clonk would collide here: Change to nonmoving Clonk mode and redo bridging
3200 			iBridgeTime -= clk->Action.Time;
3201 			clk->Action.Time = 0;
3202 			if (fWall && clk->Action.ComDir==COMD_Up)
3203 			{
3204 				// special for roof above Clonk: The nonmoving roof is started at bridgelength before the Clonk
3205 				// so, when interrupted, an action time halfway through the action must be set
3206 				clk->Action.Time = iBridgeTime;
3207 				iBridgeTime += iBridgeTime;
3208 			}
3209 			clk->Action.SetBridgeData(iBridgeTime, false, fWall, iBridgeMaterial);
3210 			return DoBridge(clk);
3211 		}
3212 	}
3213 	// draw bridge into landscape
3214 	::Landscape.DrawMaterialRect(iBridgeMaterial,tx-2,ty,4,3);
3215 	// Move Clonk
3216 	if (fMoveClonk) clk->MovePosition(cx-clk->GetX(), cy-clk->GetY());
3217 	return true;
3218 }
3219 
DoGravity(C4Object * cobj)3220 static void DoGravity(C4Object *cobj)
3221 {
3222 	// Floatation in liquids
3223 	if (cobj->InLiquid && cobj->Def->Float)
3224 	{
3225 		cobj->ydir-=GravAccel * C4REAL100(80);
3226 		if (cobj->ydir<C4REAL100(-160)) cobj->ydir=C4REAL100(-160);
3227 		if (cobj->xdir<-FloatFriction) cobj->xdir+=FloatFriction;
3228 		if (cobj->xdir>+FloatFriction) cobj->xdir-=FloatFriction;
3229 		if (cobj->rdir<-FloatFriction) cobj->rdir+=FloatFriction;
3230 		if (cobj->rdir>+FloatFriction) cobj->rdir-=FloatFriction;
3231 		// Reduce upwards speed when about to leave liquid to prevent eternal moving up and down.
3232 		// Check both for no liquid one and two pixels above the surface, because the object could
3233 		// skip one pixel as its speed can be more than 100.
3234 		int32_t y_float = cobj->GetY() - 1 + cobj->Def->Float * cobj->GetCon() / FullCon;
3235 		if (!GBackLiquid(cobj->GetX(), y_float - 1) || !GBackLiquid(cobj->GetX(), y_float - 2))
3236 			if (cobj->ydir < 0)
3237 			{
3238 				// Reduce the upwards speed and set to zero for small values to prevent fluctuations.
3239 				cobj->ydir /= 2;
3240 				if (Abs(cobj->ydir) < C4REAL100(10))
3241 					cobj->ydir = 0;
3242 			}
3243 	}
3244 	// Free fall gravity
3245 	else if (~cobj->Category & C4D_StaticBack)
3246 		cobj->ydir+=GravAccel;
3247 }
3248 
StopActionDelayCommand(C4Object * cobj)3249 void StopActionDelayCommand(C4Object *cobj)
3250 {
3251 	ObjectComStop(cobj);
3252 	cobj->AddCommand(C4CMD_Wait,nullptr,0,0,50);
3253 }
3254 
ReduceLineSegments(C4Shape & rShape,bool fAlternate)3255 bool ReduceLineSegments(C4Shape &rShape, bool fAlternate)
3256 {
3257 	// try if line could go by a path directly when skipping on evertex. If fAlternate is true, try by skipping two vertices
3258 	for (int32_t cnt=0; cnt+2+fAlternate<rShape.VtxNum; cnt++)
3259 		if (PathFree(rShape.VtxX[cnt],rShape.VtxY[cnt],
3260 		             rShape.VtxX[cnt+2+fAlternate],rShape.VtxY[cnt+2+fAlternate]))
3261 		{
3262 			if (fAlternate) rShape.RemoveVertex(cnt+2);
3263 			rShape.RemoveVertex(cnt+1);
3264 			return true;
3265 		}
3266 	return false;
3267 }
3268 
ExecAction()3269 void C4Object::ExecAction()
3270 {
3271 	C4Real iTXDir;
3272 	C4Real lftspeed,tydir;
3273 	int32_t iTargetX;
3274 	int32_t iPushRange,iPushDistance;
3275 
3276 	// Standard phase advance
3277 	int32_t iPhaseAdvance=1;
3278 
3279 	// Upright attachment check
3280 	if (!Mobile)
3281 		if (Def->UprightAttach)
3282 			if (Inside<int32_t>(GetR(),-StableRange,+StableRange))
3283 			{
3284 				Action.t_attach|=Def->UprightAttach;
3285 				Mobile=true;
3286 			}
3287 
3288 	C4PropList* pActionDef = GetAction();
3289 	// No IncompleteActivity? Reset action if there was one
3290 	if (!(OCF & OCF_FullCon) && !Def->IncompleteActivity && pActionDef)
3291 	{
3292 		SetAction(nullptr);
3293 		pActionDef = nullptr;
3294 	}
3295 
3296 	// InLiquidAction check
3297 	if (InLiquid)
3298 		if (pActionDef && pActionDef->GetPropertyStr(P_InLiquidAction))
3299 		{
3300 			SetActionByName(pActionDef->GetPropertyStr(P_InLiquidAction));
3301 			pActionDef = GetAction();
3302 		}
3303 
3304 	// Idle objects do natural gravity only
3305 	if (!pActionDef)
3306 	{
3307 		Action.t_attach = CNAT_None;
3308 		if (Mobile) DoGravity(this);
3309 		return;
3310 	}
3311 
3312 	C4Real fWalk,fMove;
3313 
3314 	// Action time advance
3315 	Action.Time++;
3316 
3317 	C4Value Attach;
3318 	pActionDef->GetProperty(P_Attach, &Attach);
3319 	if (Attach.GetType() != C4V_Nil)
3320 	{
3321 		Action.t_attach = Attach.getInt();
3322 	}
3323 	else switch (pActionDef->GetPropertyP(P_Procedure))
3324 	{
3325 	case DFA_SCALE:
3326 		if (Action.Dir == DIR_Left)  Action.t_attach = CNAT_Left;
3327 		if (Action.Dir == DIR_Right) Action.t_attach = CNAT_Right;
3328 		break;
3329 	case DFA_HANGLE:
3330 		Action.t_attach = CNAT_Top;
3331 		break;
3332 	case DFA_WALK:
3333 	case DFA_KNEEL:
3334 	case DFA_THROW:
3335 	case DFA_BRIDGE:
3336 	case DFA_PUSH:
3337 	case DFA_PULL:
3338 	case DFA_DIG:
3339 		Action.t_attach = CNAT_Bottom;
3340 		break;
3341 	default:
3342 		Action.t_attach = CNAT_None;
3343 	}
3344 
3345 	// if an object is in controllable state, so it can be assumed that if it dies later because of NO_OWNER's cause,
3346 	// it has been its own fault and not the fault of the last one who threw a flint on it
3347 	// do not reset for burning objects to make sure the killer is set correctly if they fall out of the map while burning
3348 	if (!pActionDef->GetPropertyInt(P_ObjectDisabled) && pActionDef->GetPropertyP(P_Procedure) != DFA_FLIGHT && !OnFire)
3349 		LastEnergyLossCausePlayer = NO_OWNER;
3350 
3351 	// Handle Default Action Procedure: evaluates Procedure and Action.ComDir
3352 	// Update xdir,ydir,Action.Dir,attachment,iPhaseAdvance
3353 	int32_t dir = Action.Dir;
3354 	C4Real accel = C4REAL100(pActionDef->GetPropertyInt(P_Accel));
3355 	C4Real decel = accel;
3356 	{
3357 		C4Value decel_val;
3358 		pActionDef->GetProperty(P_Decel, &decel_val);
3359 		if (decel_val.GetType() != C4V_Nil)
3360 			decel = C4REAL100(decel_val.getInt());
3361 	}
3362 	C4Real limit = C4REAL100(pActionDef->GetPropertyInt(P_Speed));
3363 
3364 	switch (pActionDef->GetPropertyP(P_Procedure))
3365 	{
3366 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3367 	case DFA_WALK:
3368 		switch (Action.ComDir)
3369 		{
3370 		case COMD_Left: case COMD_UpLeft: case COMD_DownLeft:
3371 			// breaaak!!!
3372 			if (dir == DIR_Right)
3373 				xdir-=decel;
3374 			else
3375 				xdir-=accel;
3376 			if (xdir<-limit) xdir=-limit;
3377 			break;
3378 		case COMD_Right: case COMD_UpRight: case COMD_DownRight:
3379 			if (dir == DIR_Left)
3380 				xdir+=decel;
3381 			else
3382 				xdir+=accel;
3383 			if (xdir>+limit) xdir=+limit;
3384 			break;
3385 		case COMD_Stop: case COMD_Up: case COMD_Down:
3386 			if (xdir<0) xdir+=decel;
3387 			if (xdir>0) xdir-=decel;
3388 			if ((xdir>-decel) && (xdir<+decel)) xdir=0;
3389 			break;
3390 		}
3391 		iPhaseAdvance=0;
3392 		if (xdir<0)
3393 		{
3394 			if (dir != DIR_Left) { SetDir(DIR_Left); xdir = -1; }
3395 			iPhaseAdvance=-fixtoi(xdir*10);
3396 		}
3397 		if (xdir>0)
3398 		{
3399 			if (dir != DIR_Right) { SetDir(DIR_Right); xdir = 1; }
3400 			iPhaseAdvance=+fixtoi(xdir*10);
3401 		}
3402 
3403 		Mobile=true;
3404 		// object is rotateable? adjust to ground, if in horizontal movement or not attached to the center vertex
3405 		if (Def->Rotateable && Shape.AttachMat != MNone && (!!xdir || Def->Shape.VtxX[Shape.iAttachVtx]))
3406 			AdjustWalkRotation(20, 20, 100);
3407 		else
3408 			rdir=0;
3409 		break;
3410 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3411 	case DFA_KNEEL:
3412 		ydir=0;
3413 		Mobile=true;
3414 		break;
3415 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3416 	case DFA_SCALE:
3417 	{
3418 		int ComDir = Action.ComDir;
3419 		if (Shape.CheckScaleToWalk(GetX(), GetY()))
3420 		{
3421 			ObjectActionWalk(this);
3422 			return;
3423 		}
3424 		if ((Action.Dir == DIR_Left && ComDir == COMD_Left) || (Action.Dir == DIR_Right && ComDir == COMD_Right))
3425 		{
3426 			ComDir = COMD_Up;
3427 		}
3428 		switch (ComDir)
3429 		{
3430 		case COMD_Up: case COMD_UpRight:  case COMD_UpLeft:
3431 			if (ydir > 0) ydir -= decel;
3432 			else ydir -= accel;
3433 			if (ydir < -limit) ydir = -limit; break;
3434 		case COMD_Down: case COMD_DownRight: case COMD_DownLeft:
3435 			if (ydir < 0) ydir += decel;
3436 			else ydir += accel;
3437 			if (ydir > +limit) ydir = +limit; break;
3438 		case COMD_Left: case COMD_Right: case COMD_Stop:
3439 			if (ydir < 0) ydir += decel;
3440 			if (ydir > 0) ydir -= decel;
3441 			if ((ydir > -decel) && (ydir < +decel)) ydir = 0;
3442 			break;
3443 		}
3444 		iPhaseAdvance=0;
3445 		if (ydir<0) iPhaseAdvance=-fixtoi(ydir*14);
3446 		if (ydir>0) iPhaseAdvance=+fixtoi(ydir*14);
3447 		xdir=0;
3448 		Mobile=true;
3449 		break;
3450 	}
3451 	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3452 	case DFA_HANGLE:
3453 		switch (Action.ComDir)
3454 		{
3455 		case COMD_Left: case COMD_UpLeft: case COMD_DownLeft:
3456 			if (xdir > 0) xdir -= decel;
3457 			else xdir -= accel;
3458 			if (xdir < -limit) xdir = -limit;
3459 			break;
3460 		case COMD_Right: case COMD_UpRight: case COMD_DownRight:
3461 			if (xdir < 0) xdir += decel;
3462 			else xdir += accel;
3463 			if (xdir > +limit) xdir = +limit;
3464 			break;
3465 		case COMD_Up:
3466 			if (Action.Dir == DIR_Left)
3467 				if (xdir > 0) xdir -= decel;
3468 				else xdir -= accel;
3469 			else
3470 				if (xdir < 0) xdir += decel;
3471 				else xdir += accel;
3472 			if (xdir < -limit) xdir = -limit;
3473 			if (xdir > +limit) xdir = +limit;
3474 			break;
3475 		case COMD_Stop: case COMD_Down:
3476 			if (xdir < 0) xdir += decel;
3477 			if (xdir > 0) xdir -= decel;
3478 			if ((xdir > -decel) && (xdir < +decel)) xdir = 0;
3479 			break;
3480 		}
3481 		iPhaseAdvance=0;
3482 		if (xdir<0) { iPhaseAdvance=-fixtoi(xdir*10); SetDir(DIR_Left); }
3483 		if (xdir>0) { iPhaseAdvance=+fixtoi(xdir*10); SetDir(DIR_Right); }
3484 		ydir=0;
3485 		Mobile=true;
3486 		break;
3487 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3488 	case DFA_FLIGHT:
3489 		// Contained: fall out (one try only)
3490 		if (!::Game.iTick10)
3491 			if (Contained)
3492 			{
3493 				StopActionDelayCommand(this);
3494 				SetCommand(C4CMD_Exit);
3495 			}
3496 
3497 		switch (Action.ComDir)
3498 		{
3499 		case COMD_Left: case COMD_UpLeft: case COMD_DownLeft:
3500 			xdir -= std::max(std::min(limit + xdir, xdir > 0 ? decel : accel), itofix(0));
3501 			break;
3502 		case COMD_Right: case COMD_UpRight: case COMD_DownRight:
3503 			xdir += std::max(std::min(limit - xdir, xdir < 0 ? decel : accel), itofix(0));
3504 			break;
3505 		}
3506 
3507 		// Gravity/mobile
3508 		DoGravity(this);
3509 		Mobile=true;
3510 		break;
3511 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3512 	case DFA_DIG:
3513 	{
3514 		int32_t smpx = GetX(), smpy = GetY();
3515 		bool fAttachOK = false;
3516 		if (Action.t_attach & CNAT_Bottom && Shape.Attach(smpx,smpy,CNAT_Bottom)) fAttachOK = true;
3517 		else if (Action.t_attach & CNAT_Left && Shape.Attach(smpx,smpy,CNAT_Left)) { fAttachOK = true; }
3518 		else if (Action.t_attach & CNAT_Right && Shape.Attach(smpx,smpy,CNAT_Right)) { fAttachOK = true; }
3519 		else if (Action.t_attach & CNAT_Top && Shape.Attach(smpx,smpy,CNAT_Top)) fAttachOK = true;
3520 		if (!fAttachOK)
3521 			{ ObjectComStopDig(this); return; }
3522 		iPhaseAdvance=fixtoi(itofix(40)*limit);
3523 
3524 		if (xdir < 0) SetDir(DIR_Left); else if (xdir > 0) SetDir(DIR_Right);
3525 		Action.t_attach=CNAT_None;
3526 		Mobile=true;
3527 		break;
3528 	}
3529 	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3530 	case DFA_SWIM:
3531 		// ComDir changes xdir/ydir
3532 		switch (Action.ComDir)
3533 		{
3534 		case COMD_Up:       ydir-=accel; break;
3535 		case COMD_UpRight:  ydir-=accel;  xdir+=accel; break;
3536 		case COMD_Right:                  xdir+=accel; break;
3537 		case COMD_DownRight:ydir+=accel;  xdir+=accel; break;
3538 		case COMD_Down:     ydir+=accel; break;
3539 		case COMD_DownLeft: ydir+=accel;  xdir-=accel; break;
3540 		case COMD_Left:                   xdir-=accel; break;
3541 		case COMD_UpLeft:   ydir-=accel;  xdir-=accel; break;
3542 		case COMD_Stop:
3543 			if (xdir<0) xdir+=decel;
3544 			if (xdir>0) xdir-=decel;
3545 			if ((xdir>-decel) && (xdir<+decel)) xdir=0;
3546 			if (ydir<0) ydir+=decel;
3547 			if (ydir>0) ydir-=decel;
3548 			if ((ydir>-decel) && (ydir<+decel)) ydir=0;
3549 			break;
3550 		}
3551 
3552 		// Out of liquid check
3553 		if (!InLiquid)
3554 		{
3555 			// Just above liquid: move down
3556 			if (GBackLiquid(GetX(),GetY()+1+Def->Float*Con/FullCon-1)) ydir=+accel;
3557 			// Free fall: walk
3558 			else { ObjectActionWalk(this); return; }
3559 		}
3560 
3561 		// xdir/ydir bounds, don't apply if COMD_None
3562 		if (Action.ComDir != COMD_None)
3563 		{
3564 			if (ydir<-limit) ydir=-limit; if (ydir>+limit) ydir=+limit;
3565 			if (xdir>+limit) xdir=+limit; if (xdir<-limit) xdir=-limit;
3566 		}
3567 		// Surface dir bound
3568 		if (!GBackLiquid(GetX(),GetY()-1+Def->Float*Con/FullCon-1)) if (ydir<0) ydir=0;
3569 		// Dir, Phase, Attach
3570 		if (xdir<0) SetDir(DIR_Left);
3571 		if (xdir>0) SetDir(DIR_Right);
3572 		iPhaseAdvance=fixtoi(limit*10);
3573 		Mobile=true;
3574 
3575 		break;
3576 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3577 	case DFA_THROW:
3578 		Mobile=true;
3579 		break;
3580 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3581 	case DFA_BRIDGE:
3582 	{
3583 		if (!DoBridge(this)) return;
3584 		switch (Action.ComDir)
3585 		{
3586 		case COMD_Left:  case COMD_UpLeft: SetDir(DIR_Left); break;
3587 		case COMD_Right: case COMD_UpRight: SetDir(DIR_Right); break;
3588 		}
3589 		ydir=0; xdir=0;
3590 		Mobile=true;
3591 	}
3592 	break;
3593 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3594 	case DFA_PUSH:
3595 		// No target
3596 		if (!Action.Target) { StopActionDelayCommand(this); return; }
3597 		// Inside target
3598 		if (Contained==Action.Target) { StopActionDelayCommand(this); return; }
3599 		// Target pushing force
3600 		bool fStraighten;
3601 		iTXDir=0; fStraighten=false;
3602 		switch (Action.ComDir)
3603 		{
3604 		case COMD_Left: case COMD_DownLeft:   iTXDir=-limit; break;
3605 		case COMD_UpLeft:  fStraighten=true;     iTXDir=-limit; break;
3606 		case COMD_Right: case COMD_DownRight: iTXDir=+limit; break;
3607 		case COMD_UpRight: fStraighten=true;     iTXDir=+limit; break;
3608 		case COMD_Up:      fStraighten=true; break;
3609 		case COMD_Stop: case COMD_Down:       iTXDir=0;       break;
3610 		}
3611 		// Push object
3612 		if (!Action.Target->Push(iTXDir,accel,fStraighten))
3613 			{ StopActionDelayCommand(this); return; }
3614 		// Set target controller
3615 		Action.Target->Controller=Controller;
3616 		// ObjectAction got hold check
3617 		iPushDistance = std::max(Shape.Wdt/2-8,0);
3618 		iPushRange = iPushDistance + 10;
3619 		int32_t sax,say,sawdt,sahgt;
3620 		Action.Target->GetArea(sax,say,sawdt,sahgt);
3621 		// Object lost
3622 		if (!Inside(GetX()-sax,-iPushRange,sawdt-1+iPushRange)
3623 		    || !Inside(GetY()-say,-iPushRange,sahgt-1+iPushRange))
3624 		{
3625 			C4Object *prev_target = Action.Target;
3626 			// Wait command (why, anyway?)
3627 			StopActionDelayCommand(this);
3628 			// Grab lost action
3629 			GrabLost(this, prev_target);
3630 			// Done
3631 			return;
3632 		}
3633 		// Follow object (full xdir reset)
3634 		// Vertical follow: If object moves out at top, assume it's being pushed upwards and the Clonk must run after it
3635 		if (GetY()-iPushDistance > say+sahgt && iTXDir) { if (iTXDir>0) sax+=sawdt/2; sawdt/=2; }
3636 		// Horizontal follow
3637 		iTargetX=Clamp(GetX(),sax-iPushDistance,sax+sawdt-1+iPushDistance);
3638 		if (GetX()==iTargetX) xdir=0;
3639 		else { if (GetX()<iTargetX) xdir=+limit; if (GetX()>iTargetX) xdir=-limit; }
3640 		// Phase by XDir
3641 		if (xdir<0) { iPhaseAdvance=-fixtoi(xdir*10); SetDir(DIR_Left);  }
3642 		if (xdir>0) { iPhaseAdvance=+fixtoi(xdir*10); SetDir(DIR_Right); }
3643 		// No YDir
3644 		ydir=0;
3645 		Mobile=true;
3646 		break;
3647 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3648 	case DFA_PULL:
3649 		// No target
3650 		if (!Action.Target) { StopActionDelayCommand(this); return; }
3651 		// Inside target
3652 		if (Contained==Action.Target) { StopActionDelayCommand(this); return; }
3653 		// Target contained
3654 		if (Action.Target->Contained) { StopActionDelayCommand(this); return; }
3655 
3656 		int32_t iPullDistance;
3657 		int32_t iPullX;
3658 
3659 		iPullDistance = Action.Target->Shape.Wdt/2 + Shape.Wdt/2;
3660 
3661 		iTargetX=GetX();
3662 		if (Action.ComDir==COMD_Right) iTargetX = Action.Target->GetX()+iPullDistance;
3663 		if (Action.ComDir==COMD_Left) iTargetX = Action.Target->GetX()-iPullDistance;
3664 
3665 		iPullX=Action.Target->GetX();
3666 		if (Action.ComDir==COMD_Right) iPullX = GetX()-iPullDistance;
3667 		if (Action.ComDir==COMD_Left) iPullX = GetX()+iPullDistance;
3668 
3669 		fWalk = limit;
3670 
3671 		fMove = 0;
3672 		if (Action.ComDir==COMD_Right) fMove = +fWalk;
3673 		if (Action.ComDir==COMD_Left) fMove = -fWalk;
3674 
3675 		iTXDir = fMove + fWalk * Clamp<int32_t>(iPullX-Action.Target->GetX(),-10,+10) / 10;
3676 
3677 		// Push object
3678 		if (!Action.Target->Push(iTXDir,accel,false))
3679 			{ StopActionDelayCommand(this); return; }
3680 		// Set target controller
3681 		Action.Target->Controller=Controller;
3682 
3683 		// Train pulling: com dir transfer
3684 		if ( (Action.Target->GetProcedure()==DFA_WALK)
3685 		     || (Action.Target->GetProcedure()==DFA_PULL) )
3686 		{
3687 			Action.Target->Action.ComDir=COMD_Stop;
3688 			if (iTXDir<0) Action.Target->Action.ComDir=COMD_Left;
3689 			if (iTXDir>0) Action.Target->Action.ComDir=COMD_Right;
3690 		}
3691 
3692 		// Pulling range
3693 		iPushDistance = std::max(Shape.Wdt/2-8,0);
3694 		iPushRange = iPushDistance + 20;
3695 		Action.Target->GetArea(sax,say,sawdt,sahgt);
3696 		// Object lost
3697 		if (!Inside(GetX()-sax,-iPushRange,sawdt-1+iPushRange)
3698 		    || !Inside(GetY()-say,-iPushRange,sahgt-1+iPushRange))
3699 		{
3700 			// Remember target. Will be lost on changing action.
3701 			C4Object *prev_target = Action.Target;
3702 			// Wait command (why, anyway?)
3703 			StopActionDelayCommand(this);
3704 			// Grab lost action
3705 			GrabLost(this, prev_target);
3706 			// Lose target
3707 			Action.Target=nullptr;
3708 			// Done
3709 			return;
3710 		}
3711 
3712 		// Move to pulling position
3713 		xdir = fMove + fWalk * Clamp<int32_t>(iTargetX-GetX(),-10,+10) / 10;
3714 
3715 		// Phase by XDir
3716 		iPhaseAdvance=0;
3717 		if (xdir<0) { iPhaseAdvance=-fixtoi(xdir*10); SetDir(DIR_Left);  }
3718 		if (xdir>0) { iPhaseAdvance=+fixtoi(xdir*10); SetDir(DIR_Right); }
3719 		// No YDir
3720 		ydir=0;
3721 		Mobile=true;
3722 
3723 		break;
3724 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3725 	case DFA_LIFT:
3726 		// Valid check
3727 		if (!Action.Target) { SetAction(nullptr); return; }
3728 		// Target lifting force
3729 		lftspeed=itofix(2); tydir=0;
3730 		switch (Action.ComDir)
3731 		{
3732 		case COMD_Up:   tydir=-lftspeed; break;
3733 		case COMD_Stop: tydir=-GravAccel; break;
3734 		case COMD_Down: tydir=+lftspeed; break;
3735 		}
3736 		// Lift object
3737 		if (!Action.Target->Lift(tydir,C4REAL100(50)))
3738 			{ SetAction(nullptr); return; }
3739 		// Check LiftTop
3740 		if (Def->LiftTop)
3741 			if (Action.Target->GetY()<=(GetY()+Def->LiftTop))
3742 				if (Action.ComDir==COMD_Up)
3743 					Call(PSF_LiftTop);
3744 		// General
3745 		DoGravity(this);
3746 		break;
3747 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3748 	case DFA_FLOAT:
3749 		// ComDir changes xdir/ydir
3750 		switch (Action.ComDir)
3751 		{
3752 		case COMD_Up:
3753 			ydir-=accel;
3754 			if (xdir<0) xdir+=decel;
3755 			if (xdir>0) xdir-=decel;
3756 			if ((xdir>-decel) && (xdir<+decel)) xdir=0;
3757 			break;
3758 		case COMD_UpRight:
3759 			ydir-=accel; xdir+=accel; break;
3760 		case COMD_Right:
3761 			xdir+=accel;
3762 			if (ydir<0) ydir+=decel;
3763 			if (ydir>0) ydir-=decel;
3764 			if ((ydir>-decel) && (ydir<+decel)) ydir=0;
3765 			break;
3766 		case COMD_DownRight:
3767 			ydir+=accel; xdir+=accel; break;
3768 		case COMD_Down:
3769 			ydir+=accel;
3770 			if (xdir<0) xdir+=decel;
3771 			if (xdir>0) xdir-=decel;
3772 			if ((xdir>-decel) && (xdir<+decel)) xdir=0;
3773 			break;
3774 		case COMD_DownLeft:
3775 			ydir+=accel; xdir-=accel; break;
3776 		case COMD_Left:
3777 			xdir-=accel;
3778 			if (ydir<0) ydir+=decel;
3779 			if (ydir>0) ydir-=decel;
3780 			if ((ydir>-decel) && (ydir<+decel)) ydir=0;
3781 			break;
3782 		case COMD_UpLeft:
3783 			ydir-=accel; xdir-=accel; break;
3784 		case COMD_Stop:
3785 			if (xdir<0) xdir+=decel;
3786 			if (xdir>0) xdir-=decel;
3787 			if ((xdir>-decel) && (xdir<+decel)) xdir=0;
3788 			if (ydir<0) ydir+=decel;
3789 			if (ydir>0) ydir-=decel;
3790 			if ((ydir>-decel) && (ydir<+decel)) ydir=0;
3791 			break;
3792 		}
3793 		// xdir/ydir bounds, don't apply if COMD_None
3794 		if (Action.ComDir != COMD_None)
3795 		{
3796 			if (ydir<-limit) ydir=-limit; if (ydir>+limit) ydir=+limit;
3797 			if (xdir>+limit) xdir=+limit; if (xdir<-limit) xdir=-limit;
3798 		}
3799 
3800 		Mobile=true;
3801 		break;
3802 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3803 		// ATTACH: Force position to target object
3804 		//   own vertex index is determined by high-order byte of action data
3805 		//   target vertex index is determined by low-order byte of action data
3806 	case DFA_ATTACH:
3807 
3808 		// No target
3809 		if (!Action.Target)
3810 		{
3811 			if (Status)
3812 			{
3813 				SetAction(nullptr);
3814 				Call(PSF_AttachTargetLost);
3815 			}
3816 			return;
3817 		}
3818 
3819 		// Target incomplete and no incomplete activity
3820 		if (!(Action.Target->OCF & OCF_FullCon))
3821 			if (!Action.Target->Def->IncompleteActivity)
3822 				{ SetAction(nullptr); return; }
3823 
3824 		// Force containment
3825 		if (Action.Target->Contained!=Contained)
3826 		{
3827 			if (Action.Target->Contained)
3828 				Enter(Action.Target->Contained);
3829 			else
3830 				Exit(GetX(),GetY(),GetR());
3831 		}
3832 
3833 		// Object might have detached in Enter/Exit call
3834 		if (!Action.Target) break;
3835 
3836 		// Move position (so objects on solidmask move)
3837 		MovePosition(Action.Target->fix_x + Action.Target->Shape.VtxX[Action.Data&255]
3838 		              -Shape.VtxX[Action.Data>>8] - fix_x,
3839 		              Action.Target->fix_y + Action.Target->Shape.VtxY[Action.Data&255]
3840 		              -Shape.VtxY[Action.Data>>8] - fix_y);
3841 		// must zero motion...
3842 		xdir=ydir=0;
3843 
3844 		break;
3845 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3846 	case DFA_CONNECT:
3847 		{
3848 			bool fBroke=false;
3849 			bool fLineChange = false;
3850 
3851 			// Line destruction check: Target missing or incomplete
3852 			if (!Action.Target || (Action.Target->Con<FullCon)) fBroke=true;
3853 			if (!Action.Target2 || (Action.Target2->Con<FullCon)) fBroke=true;
3854 			if (fBroke)
3855 			{
3856 				Call(PSF_OnLineBreak,&C4AulParSet(true));
3857 				AssignRemoval();
3858 				return;
3859 			}
3860 
3861 			// Movement by Target
3862 			// Connect to attach vertex
3863 			C4Value lineAttachV; C4ValueArray *lineAttach;
3864 			Action.Target->GetProperty(P_LineAttach, &lineAttachV);
3865 			lineAttach = lineAttachV.getArray();
3866 			int32_t iConnectX1, iConnectY1;
3867 			iConnectX1 = Action.Target->GetX();
3868 			iConnectY1 = Action.Target->GetY();
3869 			if (lineAttach)
3870 			{
3871 				iConnectX1 += lineAttach->GetItem(0).getInt();
3872 				iConnectY1 += lineAttach->GetItem(1).getInt();
3873 			}
3874 			if ((iConnectX1!=Shape.VtxX[0]) || (iConnectY1!=Shape.VtxY[0]))
3875 			{
3876 				// Regular wrapping line
3877 				if (Def->LineIntersect == 0)
3878 					if (!Shape.LineConnect(iConnectX1,iConnectY1,0,+1,
3879 											Shape.VtxX[0],Shape.VtxY[0])) fBroke=true;
3880 				// No-intersection line
3881 				if (Def->LineIntersect == 1)
3882 					{ Shape.VtxX[0]=iConnectX1; Shape.VtxY[0]=iConnectY1; }
3883 
3884 				fLineChange = true;
3885 			}
3886 
3887 			// Movement by Target2
3888 			// Connect to attach vertex
3889 			Action.Target2->GetProperty(P_LineAttach, &lineAttachV);
3890 			lineAttach = lineAttachV.getArray();
3891 			int32_t iConnectX2, iConnectY2;
3892 			iConnectX2 = Action.Target2->GetX();
3893 			iConnectY2 = Action.Target2->GetY();
3894 			if (lineAttach)
3895 			{
3896 				iConnectX2 += lineAttach->GetItem(0).getInt();
3897 				iConnectY2 += lineAttach->GetItem(1).getInt();
3898 			}
3899 			if ((iConnectX2!=Shape.VtxX[Shape.VtxNum-1]) || (iConnectY2!=Shape.VtxY[Shape.VtxNum-1]))
3900 			{
3901 				// Regular wrapping line
3902 				if (Def->LineIntersect == 0)
3903 					if (!Shape.LineConnect(iConnectX2,iConnectY2,Shape.VtxNum-1,-1,
3904 											Shape.VtxX[Shape.VtxNum-1],Shape.VtxY[Shape.VtxNum-1])) fBroke=true;
3905 				// No-intersection line
3906 				if (Def->LineIntersect == 1)
3907 					{ Shape.VtxX[Shape.VtxNum-1]=iConnectX2; Shape.VtxY[Shape.VtxNum-1]=iConnectY2; }
3908 
3909 				fLineChange = true;
3910 			}
3911 
3912 			// Line fBroke
3913 			if (fBroke)
3914 			{
3915 				Call(PSF_OnLineBreak,nullptr);
3916 				AssignRemoval();
3917 				return;
3918 			}
3919 
3920 			// Reduce line segments
3921 			if (!::Game.iTick35)
3922 				if (ReduceLineSegments(Shape, !::Game.iTick2))
3923 					fLineChange = true;
3924 
3925 			// Line change callback
3926 			if (fLineChange)
3927 				Call(PSF_OnLineChange);
3928 		}
3929 		break;
3930 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3931 	default:
3932 		// Attach
3933 		if (Action.t_attach)
3934 		{
3935 			xdir = ydir = 0;
3936 			Mobile = true;
3937 		}
3938 		// Free gravity
3939 		else
3940 			DoGravity(this);
3941 		break;
3942 		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
3943 	}
3944 
3945 	// Phase Advance (zero delay means no phase advance)
3946 	if (pActionDef->GetPropertyInt(P_Delay))
3947 	{
3948 		Action.PhaseDelay+=iPhaseAdvance;
3949 		if (Action.PhaseDelay >= pActionDef->GetPropertyInt(P_Delay))
3950 		{
3951 			// Advance Phase
3952 			Action.PhaseDelay=0;
3953 			Action.Phase += pActionDef->GetPropertyInt(P_Step);
3954 			// Phase call
3955 			if (pActionDef->GetPropertyStr(P_PhaseCall))
3956 			{
3957 				Call(pActionDef->GetPropertyStr(P_PhaseCall)->GetCStr());
3958 			}
3959 			// Phase end
3960 			if (Action.Phase>=pActionDef->GetPropertyInt(P_Length))
3961 			{
3962 				C4String *next_action = pActionDef->GetPropertyStr(P_NextAction);
3963 				// Keep current action if there is no NextAction
3964 				if (!next_action)
3965 					Action.Phase = 0;
3966 				// set new action if it's not Hold
3967 				else if (next_action == &Strings.P[P_Hold])
3968 				{
3969 					Action.Phase = pActionDef->GetPropertyInt(P_Length)-1;
3970 					Action.PhaseDelay = pActionDef->GetPropertyInt(P_Delay)-1;
3971 				}
3972 				else
3973 				{
3974 					// Set new action
3975 					SetActionByName(next_action, nullptr, nullptr, SAC_StartCall | SAC_EndCall);
3976 				}
3977 			}
3978 		}
3979 	}
3980 
3981 	return;
3982 }
3983 
3984 
SetOwner(int32_t iOwner)3985 bool C4Object::SetOwner(int32_t iOwner)
3986 {
3987 	// Check valid owner
3988 	if (!(ValidPlr(iOwner) || iOwner == NO_OWNER)) return false;
3989 	// always set color, even if no owner-change is done
3990 	if (iOwner != NO_OWNER)
3991 		if (GetGraphics()->IsColorByOwner())
3992 		{
3993 			Color=::Players.Get(iOwner)->ColorDw;
3994 			UpdateFace(false);
3995 		}
3996 	// no change?
3997 	if (Owner == iOwner) return true;
3998 	// set new owner
3999 	int32_t iOldOwner=Owner;
4000 	Owner=iOwner;
4001 	// this automatically updates controller
4002 	Controller = Owner;
4003 	// script callback
4004 	Call(PSF_OnOwnerChanged, &C4AulParSet(Owner, iOldOwner));
4005 	// done
4006 	return true;
4007 }
4008 
4009 
SetLightRange(int32_t iToRange,int32_t iToFadeoutRange)4010 bool C4Object::SetLightRange(int32_t iToRange, int32_t iToFadeoutRange)
4011 {
4012 	// set new range
4013 	lightRange = iToRange;
4014 	lightFadeoutRange = iToFadeoutRange;
4015 	// resort into player's FoW-repeller-list
4016 	UpdateLight();
4017 	// success
4018 	return true;
4019 }
4020 
4021 
SetLightColor(uint32_t iValue)4022 bool C4Object::SetLightColor(uint32_t iValue)
4023 {
4024 	// set new color value
4025 	lightColor = iValue;
4026 	// resort into player's FoW-repeller-list
4027 	UpdateLight();
4028 	// success
4029 	return true;
4030 }
4031 
4032 
UpdateLight()4033 void C4Object::UpdateLight()
4034 {
4035 	if (Landscape.HasFoW()) Landscape.GetFoW()->Add(this);
4036 }
4037 
SetAudibilityAt(C4TargetFacet & cgo,int32_t iX,int32_t iY,int32_t player)4038 void C4Object::SetAudibilityAt(C4TargetFacet &cgo, int32_t iX, int32_t iY, int32_t player)
4039 {
4040 	// target pos (parallax)
4041 	float offX, offY, newzoom;
4042 	GetDrawPosition(cgo, iX, iY, cgo.Zoom, offX, offY, newzoom);
4043 	int32_t audible_at_pos = Clamp(100 - 100 * Distance(cgo.X + cgo.Wdt / 2, cgo.Y + cgo.Hgt / 2, offX, offY) / 700, 0, 100);
4044 	if (audible_at_pos > Audible)
4045 	{
4046 		Audible = audible_at_pos;
4047 		AudiblePan = Clamp<int>(200 * (offX - cgo.X - (cgo.Wdt / 2)) / cgo.Wdt, -100, 100);
4048 		AudiblePlayer = player;
4049 	}
4050 }
4051 
IsVisible(int32_t iForPlr,bool fAsOverlay) const4052 bool C4Object::IsVisible(int32_t iForPlr, bool fAsOverlay) const
4053 {
4054 	bool fDraw;
4055 	C4Value vis;
4056 	if (!GetProperty(P_Visibility, &vis))
4057 		return true;
4058 
4059 	int32_t Visibility;
4060 	C4ValueArray *parameters = vis.getArray();
4061 	if (parameters && parameters->GetSize())
4062 	{
4063 		Visibility = parameters->GetItem(0).getInt();
4064 	}
4065 	else
4066 	{
4067 		Visibility = vis.getInt();
4068 	}
4069 	// check layer
4070 	if (Layer && Layer != this && !fAsOverlay)
4071 	{
4072 		fDraw = Layer->IsVisible(iForPlr, false);
4073 		if (Layer->GetPropertyInt(P_Visibility) & VIS_LayerToggle) fDraw = !fDraw;
4074 		if (!fDraw) return false;
4075 	}
4076 	// no flags set?
4077 	if (!Visibility) return true;
4078 	// check overlay
4079 	if (Visibility & VIS_OverlayOnly)
4080 	{
4081 		if (!fAsOverlay) return false;
4082 		if (Visibility == VIS_OverlayOnly) return true;
4083 	}
4084 	// editor visibility
4085 	if (::Application.isEditor)
4086 	{
4087 		if (Visibility & VIS_Editor) return true;
4088 	}
4089 	// check visibility
4090 	fDraw=false;
4091 	if (Visibility & VIS_Owner) fDraw = fDraw || (iForPlr==Owner);
4092 	if (iForPlr!=NO_OWNER)
4093 	{
4094 		// check all
4095 		if (Visibility & VIS_Allies)  fDraw = fDraw || (iForPlr!=Owner && !Hostile(iForPlr, Owner));
4096 		if (Visibility & VIS_Enemies) fDraw = fDraw || (iForPlr!=Owner && Hostile(iForPlr, Owner));
4097 		if (parameters)
4098 		{
4099 			if (Visibility & VIS_Select)  fDraw = fDraw || parameters->GetItem(1+iForPlr).getBool();
4100 		}
4101 	}
4102 	else fDraw = fDraw || (Visibility & VIS_God);
4103 	return fDraw;
4104 }
4105 
IsInLiquidCheck() const4106 bool C4Object::IsInLiquidCheck() const
4107 {
4108 	return GBackLiquid(GetX(),GetY()+Def->Float*Con/FullCon-1);
4109 }
4110 
SetRotation(int32_t nr)4111 void C4Object::SetRotation(int32_t nr)
4112 {
4113 	while (nr<0) nr+=360; nr%=360;
4114 	// remove solid mask
4115 	if (pSolidMaskData) pSolidMaskData->Remove(false);
4116 	// set rotation
4117 	fix_r=itofix(nr);
4118 	// Update face
4119 	UpdateFace(true);
4120 }
4121 
PrepareDrawing() const4122 void C4Object::PrepareDrawing() const
4123 {
4124 	// color modulation
4125 	if (ColorMod != 0xffffffff || (BlitMode & (C4GFXBLIT_MOD2 | C4GFXBLIT_CLRSFC_MOD2))) pDraw->ActivateBlitModulation(ColorMod);
4126 	// other blit modes
4127 	pDraw->SetBlitMode(BlitMode);
4128 }
4129 
FinishedDrawing() const4130 void C4Object::FinishedDrawing() const
4131 {
4132 	// color modulation
4133 	pDraw->DeactivateBlitModulation();
4134 	// extra blitting flags
4135 	pDraw->ResetBlitMode();
4136 }
4137 
DrawSolidMask(C4TargetFacet & cgo) const4138 void C4Object::DrawSolidMask(C4TargetFacet &cgo) const
4139 {
4140 	// mask must exist
4141 	if (!pSolidMaskData) return;
4142 	// draw it
4143 	pSolidMaskData->Draw(cgo);
4144 }
4145 
UpdateSolidMask(bool fRestoreAttachedObjects)4146 void C4Object::UpdateSolidMask(bool fRestoreAttachedObjects)
4147 {
4148 	// solidmask doesn't make sense with non-existant objects
4149 	// (the solidmask has already been destroyed in AssignRemoval -
4150 	//  do not reset it!)
4151 	if (!Status) return;
4152 	// Determine necessity, update cSolidMask, put or remove mask
4153 	// Mask if enabled, fullcon, not contained
4154 	if (SolidMask.Wdt > 0 && Con >= FullCon && !Contained)
4155 	{
4156 		// Recheck and put mask
4157 		if (!pSolidMaskData)
4158 		{
4159 			pSolidMaskData = new C4SolidMask(this);
4160 		}
4161 		else
4162 			pSolidMaskData->Remove(false);
4163 		pSolidMaskData->Put(true, nullptr, fRestoreAttachedObjects);
4164 		SetHalfVehicleSolidMask(HalfVehicleSolidMask);
4165 	}
4166 	// Otherwise, remove and destroy mask
4167 	else if (pSolidMaskData)
4168 	{
4169 		delete pSolidMaskData; pSolidMaskData = nullptr;
4170 	}
4171 }
4172 
Collect(C4Object * pObj)4173 bool C4Object::Collect(C4Object *pObj)
4174 {
4175 	// Object enter container
4176 	bool fRejectCollect;
4177 	if (!pObj->Enter(this, true, false, &fRejectCollect))
4178 		return false;
4179 	// Cancel attach (hacky)
4180 	ObjectComCancelAttach(pObj);
4181 	// Container Collection call
4182 	Call(PSF_Collection,&C4AulParSet(pObj));
4183 	// Object Hit call
4184 	if (pObj->Status && pObj->OCF & OCF_HitSpeed1) pObj->Call(PSF_Hit);
4185 	if (pObj->Status && pObj->OCF & OCF_HitSpeed2) pObj->Call(PSF_Hit2);
4186 	if (pObj->Status && pObj->OCF & OCF_HitSpeed3) pObj->Call(PSF_Hit3);
4187 	// post-copy the motion of the new container
4188 	if (pObj->Contained == this) pObj->CopyMotion(this);
4189 	// done, success
4190 	return true;
4191 }
4192 
GrabInfo(C4Object * pFrom)4193 bool C4Object::GrabInfo(C4Object *pFrom)
4194 {
4195 	// safety
4196 	if (!pFrom) return false; if (!Status || !pFrom->Status) return false;
4197 	// even more safety (own info: success)
4198 	if (pFrom == this) return true;
4199 	// only if other object has info
4200 	if (!pFrom->Info) return false;
4201 	// clear own info object
4202 	if (Info)
4203 	{
4204 		Info->Retire();
4205 		ClearInfo (Info);
4206 	}
4207 	// remove objects from any owning crews
4208 	::Players.ClearPointers(pFrom);
4209 	::Players.ClearPointers(this);
4210 	// set info
4211 	Info = pFrom->Info; pFrom->ClearInfo (pFrom->Info);
4212 	// set name
4213 	SetName(Info->Name);
4214 	// retire from old crew
4215 	Info->Retire();
4216 	// if alive, recruit to new crew
4217 	if (Alive) Info->Recruit();
4218 	// make new crew member
4219 	C4Player *pPlr = ::Players.Get(Owner);
4220 	if (pPlr) pPlr->MakeCrewMember(this);
4221 	// done, success
4222 	return true;
4223 }
4224 
ShiftContents(bool fShiftBack,bool fDoCalls)4225 bool C4Object::ShiftContents(bool fShiftBack, bool fDoCalls)
4226 {
4227 	// get current object
4228 	C4Object *c_obj = Contents.GetObject();
4229 	if (!c_obj) return false;
4230 	// get next/previous
4231 	auto it = fShiftBack ? Contents.reverse().begin() : ++Contents.begin();
4232 	while (!it.atEnd())
4233 	{
4234 		auto pObj = (*it);
4235 		// check object
4236 		if (pObj->Status)
4237 			if (!c_obj->CanConcatPictureWith(pObj))
4238 			{
4239 				// object different: shift to this
4240 				DirectComContents(pObj, !!fDoCalls);
4241 				return true;
4242 			}
4243 		// next/prev item
4244 		it++;
4245 	}
4246 	return false;
4247 }
4248 
DirectComContents(C4Object * pTarget,bool fDoCalls)4249 void C4Object::DirectComContents(C4Object *pTarget, bool fDoCalls)
4250 {
4251 	// safety
4252 	if (!pTarget || !pTarget->Status || pTarget->Contained != this) return;
4253 	// Desired object already at front?
4254 	if (Contents.GetObject() == pTarget) return;
4255 	// select object via script?
4256 	if (fDoCalls)
4257 		if (Call("~ControlContents", &C4AulParSet(pTarget)))
4258 			return;
4259 	// default action
4260 	if (!(Contents.ShiftContents(pTarget))) return;
4261 	// Selection sound
4262 	if (fDoCalls) if (!Contents.GetObject()->Call("~Selection", &C4AulParSet(this))) StartSoundEffect("Clonk::Action::Grab",false,100,this);
4263 	// update menu with the new item in "put" entry
4264 	if (Menu && Menu->IsActive() && Menu->IsContextMenu())
4265 	{
4266 		Menu->Refill();
4267 	}
4268 	// Done
4269 	return;
4270 }
4271 
GetParallaxity(int32_t * parX,int32_t * parY) const4272 void C4Object::GetParallaxity(int32_t *parX, int32_t *parY) const
4273 {
4274 	assert(parX); assert(parY);
4275 	*parX = 100; *parY = 100;
4276 	if (Category & C4D_Foreground)
4277 	{
4278 		*parX = 0; *parY = 0;
4279 		return;
4280 	}
4281 	if (!(Category & C4D_Parallax)) return;
4282 	C4Value parV; GetProperty(P_Parallaxity, &parV);
4283 	C4ValueArray *par = parV.getArray();
4284 	if (!par) return;
4285 	*parX = par->GetItem(0).getInt();
4286 	*parY = par->GetItem(1).getInt();
4287 }
4288 
GetDragImage(C4Object ** drag_object,C4Def ** drag_def) const4289 bool C4Object::GetDragImage(C4Object **drag_object, C4Def **drag_def) const
4290 {
4291 	// drag is possible if MouseDragImage is assigned
4292 	C4Value parV; GetProperty(P_MouseDragImage, &parV);
4293 	if (!parV) return false;
4294 	// determine drag object/id
4295 	C4Object *obj = parV.getObj();
4296 	C4Def * def = nullptr;
4297 	if (!obj) def = parV.getDef();
4298 	if (drag_object) *drag_object = obj;
4299 	if (drag_def) *drag_def = def;
4300 	// drag possible, even w./o image
4301 	return true;
4302 }
4303 
AddObjectAndContentsToArray(C4ValueArray * target_array,int32_t index)4304 int32_t C4Object::AddObjectAndContentsToArray(C4ValueArray *target_array, int32_t index)
4305 {
4306 	// add self, contents and child contents count recursively to value array. Return index after last added item.
4307 	target_array->SetItem(index++, C4VObj(this));
4308 	for (C4Object *cobj : Contents)
4309 	{
4310 		index = cobj->AddObjectAndContentsToArray(target_array, index);
4311 	}
4312 	return index;
4313 }
4314 
DoSelect()4315 bool C4Object::DoSelect()
4316 {
4317 	// selection allowed?
4318 	if (CrewDisabled) return false;
4319 	// do callback
4320 	Call(PSF_CrewSelection, &C4AulParSet(false));
4321 	// done
4322 	return true;
4323 }
4324 
UnSelect()4325 void C4Object::UnSelect()
4326 {
4327 	// do callback
4328 	Call(PSF_CrewSelection, &C4AulParSet(true));
4329 }
4330 
GetViewPos(float & riX,float & riY,float tx,float ty,const C4Facet & fctViewport) const4331 void C4Object::GetViewPos(float & riX, float & riY, float tx, float ty, const C4Facet & fctViewport) const       // get position this object is seen at (for given scroll)
4332 {
4333 	if (Category & C4D_Parallax) GetViewPosPar(riX, riY, tx, ty, fctViewport); else { riX = float(GetX()); riY = float(GetY()); }
4334 }
4335 
GetDrawPosition(const C4TargetFacet & cgo,float & resultx,float & resulty,float & resultzoom) const4336 bool C4Object::GetDrawPosition(const C4TargetFacet & cgo,
4337 	float & resultx, float & resulty, float & resultzoom) const
4338 {
4339 	return GetDrawPosition(cgo, fixtof(fix_x), fixtof(fix_y), cgo.Zoom, resultx, resulty, resultzoom);
4340 }
4341 
GetDrawPosition(const C4TargetFacet & cgo,float objx,float objy,float zoom,float & resultx,float & resulty,float & resultzoom) const4342 bool C4Object::GetDrawPosition(const C4TargetFacet & cgo, float objx, float objy, float zoom, float & resultx, float & resulty, float & resultzoom) const
4343 {
4344 	// for HUD
4345 	if(Category & C4D_Foreground)
4346 	{
4347 		resultzoom = zoom;
4348 
4349 		if(fix_x < 0)
4350 			resultx = cgo.X + objx + cgo.Wdt;
4351 		else
4352 			resultx = cgo.X + objx;
4353 
4354 		if(fix_y < 0)
4355 			resulty = cgo.Y + objy + cgo.Hgt;
4356 		else
4357 			resulty = cgo.Y + objy;
4358 
4359 		return true;
4360 	}
4361 
4362 	// zoom with parallaxity
4363 	int iParX, iParY;
4364 	GetParallaxity(&iParX, &iParY);
4365 	float targetx = cgo.TargetX; float targety = cgo.TargetY;
4366 	float parx = iParX / 100.0f; float pary = iParY / 100.0f;
4367 	float par = parx; // and pary?
4368 
4369 	// Step 1: project to landscape coordinates
4370 	resultzoom = 1.0 / (1.0 - (par - par/zoom));
4371 	// it would be par / (1.0 - (par - par/zoom)) if objects would get smaller farther away
4372 	if (resultzoom <= 0 || resultzoom > 100) // FIXME: optimize treshhold
4373 		return false;
4374 
4375 	float rx = ((1 - parx) * cgo.ParRefX) * resultzoom + objx / (parx + zoom - parx * zoom);
4376 	float ry = ((1 - pary) * cgo.ParRefY) * resultzoom + objy / (pary + zoom - pary * zoom);
4377 
4378 	// Step 2: convert to screen coordinates
4379 	if(parx == 0 && fix_x < 0)
4380 		resultx = cgo.X + (objx + cgo.Wdt) * zoom / resultzoom;
4381 	else
4382 		resultx = cgo.X + (rx - targetx) * zoom / resultzoom;
4383 
4384 	if(pary == 0 && fix_y < 0)
4385 		resulty = cgo.Y + (objy + cgo.Hgt) * zoom / resultzoom;
4386 	else
4387 		resulty = cgo.Y + (ry - targety) * zoom / resultzoom;
4388 
4389 	return true;
4390 }
4391 
GetViewPosPar(float & riX,float & riY,float tx,float ty,const C4Facet & fctViewport) const4392 void C4Object::GetViewPosPar(float &riX, float &riY, float tx, float ty, const C4Facet &fctViewport) const
4393 {
4394 	int iParX, iParY;
4395 	GetParallaxity(&iParX, &iParY);
4396 	// get drawing pos, then subtract original target pos to get drawing pos on landscape
4397 	if (!iParX && GetX()<0)
4398 		// HUD element at right viewport pos
4399 		riX=fixtof(fix_x)+tx+fctViewport.Wdt;
4400 	else
4401 		// regular parallaxity
4402 		riX=fixtof(fix_x)-(tx*(iParX-100)/100);
4403 	if (!iParY && GetY()<0)
4404 		// HUD element at bottom viewport pos
4405 		riY=fixtof(fix_y)+ty+fctViewport.Hgt;
4406 	else
4407 		// regular parallaxity
4408 		riY=fixtof(fix_y)-(ty*(iParY-100)/100);
4409 }
4410 
PutAwayUnusedObject(C4Object * pToMakeRoomForObject)4411 bool C4Object::PutAwayUnusedObject(C4Object *pToMakeRoomForObject)
4412 {
4413 	// get unused object
4414 	C4Object *pUnusedObject;
4415 	C4AulFunc *pFnObj2Drop = GetFunc(PSF_GetObject2Drop);
4416 	if (pFnObj2Drop)
4417 		pUnusedObject = pFnObj2Drop->Exec(this, &C4AulParSet(pToMakeRoomForObject)).getObj();
4418 	else
4419 	{
4420 		// is there any unused object to put away?
4421 		if (!Contents.GetLastObject()) return false;
4422 		// defaultly, it's the last object in the list
4423 		// (contents list cannot have invalid status-objects)
4424 		pUnusedObject = Contents.GetLastObject();
4425 	}
4426 	// no object to put away? fail
4427 	if (!pUnusedObject) return false;
4428 	// grabbing something?
4429 	bool fPushing = (GetProcedure()==DFA_PUSH);
4430 	if (fPushing)
4431 		// try to put it in there
4432 		if (ObjectComPut(this, Action.Target, pUnusedObject))
4433 			return true;
4434 	// in container? put in there
4435 	if (Contained)
4436 	{
4437 		// try to put it in directly
4438 		// note that this works too, if an object is grabbed inside the container
4439 		if (ObjectComPut(this, Contained, pUnusedObject))
4440 			return true;
4441 		// now putting didn't work - drop it outside
4442 		AddCommand(C4CMD_Drop, pUnusedObject);
4443 		AddCommand(C4CMD_Exit);
4444 		return true;
4445 	}
4446 	else
4447 		// if uncontained, simply try to drop it
4448 		// if this doesn't work, it won't ever
4449 		return !!ObjectComDrop(this, pUnusedObject);
4450 }
4451 
SetGraphics(const char * szGraphicsName,C4Def * pSourceDef)4452 bool C4Object::SetGraphics(const char *szGraphicsName, C4Def *pSourceDef)
4453 {
4454 	// safety
4455 	if (!Status) return false;
4456 	// default def
4457 	if (!pSourceDef) pSourceDef = Def;
4458 	// get graphics
4459 	C4DefGraphics *pGrp = pSourceDef->Graphics.Get(szGraphicsName);
4460 	if (!pGrp) return false;
4461 	// set new graphics
4462 	pGraphics = pGrp;
4463 	// update Color, etc.
4464 	UpdateGraphics(true);
4465 	// success
4466 	return true;
4467 }
4468 
SetGraphics(C4DefGraphics * pNewGfx,bool fTemp)4469 bool C4Object::SetGraphics(C4DefGraphics *pNewGfx, bool fTemp)
4470 {
4471 	// safety
4472 	if (!pNewGfx) return false;
4473 	// set it and update related stuff
4474 	pGraphics = pNewGfx;
4475 	UpdateGraphics(true, fTemp);
4476 	return true;
4477 }
4478 
GetGraphicsOverlay(int32_t iForID) const4479 C4GraphicsOverlay *C4Object::GetGraphicsOverlay(int32_t iForID) const
4480 {
4481 	// search in list until ID is found or passed
4482 	C4GraphicsOverlay *pOverlay = pGfxOverlay;
4483 	while (pOverlay && pOverlay->GetID() < iForID) pOverlay = pOverlay->GetNext();
4484 	// exact match found?
4485 	if (pOverlay && pOverlay->GetID() == iForID) return pOverlay;
4486 	// none found
4487 	return nullptr;
4488 }
4489 
GetGraphicsOverlay(int32_t iForID,bool fCreate)4490 C4GraphicsOverlay *C4Object::GetGraphicsOverlay(int32_t iForID, bool fCreate)
4491 {
4492 	// search in list until ID is found or passed
4493 	C4GraphicsOverlay *pOverlay = pGfxOverlay, *pPrevOverlay = nullptr;
4494 	while (pOverlay && pOverlay->GetID() < iForID) { pPrevOverlay = pOverlay; pOverlay = pOverlay->GetNext(); }
4495 	// exact match found?
4496 	if (pOverlay && pOverlay->GetID() == iForID) return pOverlay;
4497 	// ID has been passed: Create new if desired
4498 	if (!fCreate) return nullptr;
4499 	C4GraphicsOverlay *pNewOverlay = new C4GraphicsOverlay();
4500 	pNewOverlay->SetID(iForID);
4501 	pNewOverlay->SetNext(pOverlay);
4502 	if (pPrevOverlay) pPrevOverlay->SetNext(pNewOverlay); else pGfxOverlay = pNewOverlay;
4503 	// return newly created overlay
4504 	return pNewOverlay;
4505 }
4506 
RemoveGraphicsOverlay(int32_t iOverlayID)4507 bool C4Object::RemoveGraphicsOverlay(int32_t iOverlayID)
4508 {
4509 	// search in list until ID is found or passed
4510 	C4GraphicsOverlay *pOverlay = pGfxOverlay, *pPrevOverlay = nullptr;
4511 	while (pOverlay && pOverlay->GetID() < iOverlayID) { pPrevOverlay = pOverlay; pOverlay = pOverlay->GetNext(); }
4512 	// exact match found?
4513 	if (pOverlay && pOverlay->GetID() == iOverlayID)
4514 	{
4515 		// remove it
4516 		if (pPrevOverlay) pPrevOverlay->SetNext(pOverlay->GetNext()); else pGfxOverlay = pOverlay->GetNext();
4517 		pOverlay->SetNext(nullptr); // prevents deletion of following overlays
4518 		delete pOverlay;
4519 		// removed
4520 		return true;
4521 	}
4522 	// no match found
4523 	return false;
4524 }
4525 
HasGraphicsOverlayRecursion(const C4Object * pCheckObj) const4526 bool C4Object::HasGraphicsOverlayRecursion(const C4Object *pCheckObj) const
4527 {
4528 	C4Object *pGfxOvrlObj;
4529 	if (pGfxOverlay)
4530 		for (C4GraphicsOverlay *pGfxOvrl = pGfxOverlay; pGfxOvrl; pGfxOvrl = pGfxOvrl->GetNext())
4531 			if ((pGfxOvrlObj = pGfxOvrl->GetOverlayObject()))
4532 			{
4533 				if (pGfxOvrlObj == pCheckObj) return true;
4534 				if (pGfxOvrlObj->HasGraphicsOverlayRecursion(pCheckObj)) return true;
4535 			}
4536 	return false;
4537 }
4538 
StatusActivate()4539 bool C4Object::StatusActivate()
4540 {
4541 	// readd to main list
4542 	::Objects.InactiveObjects.Remove(this);
4543 	Status = C4OS_NORMAL;
4544 	::Objects.Add(this);
4545 	// update some values
4546 	UpdateGraphics(false);
4547 	UpdateFace(true);
4548 	UpdatePos();
4549 	UpdateLight();
4550 	Call(PSF_OnSynchronized);
4551 	// done, success
4552 	return true;
4553 }
4554 
StatusDeactivate(bool fClearPointers)4555 bool C4Object::StatusDeactivate(bool fClearPointers)
4556 {
4557 	// clear particles
4558 	ClearParticleLists();
4559 
4560 	// put into inactive list
4561 	::Objects.Remove(this);
4562 	Status = C4OS_INACTIVE;
4563 	if (Landscape.HasFoW()) Landscape.GetFoW()->Remove(this);
4564 	::Objects.InactiveObjects.Add(this, C4ObjectList::stMain);
4565 	// if desired, clear game pointers
4566 	if (fClearPointers)
4567 	{
4568 		// in this case, the object must also exit any container, and any contained objects must be exited
4569 		ClearContentsAndContained();
4570 		Game.ClearPointers(this);
4571 	}
4572 	else
4573 	{
4574 		// always clear transfer
4575 		Game.TransferZones.ClearPointers(this);
4576 	}
4577 	// done, success
4578 	return true;
4579 }
4580 
ClearContentsAndContained(bool fDoCalls)4581 void C4Object::ClearContentsAndContained(bool fDoCalls)
4582 {
4583 	// exit contents from container
4584 	for (C4Object *cobj : Contents)
4585 	{
4586 		cobj->Exit(GetX(), GetY(), 0,Fix0,Fix0,Fix0, fDoCalls);
4587 	}
4588 	// remove from container *after* contents have been removed!
4589 	if (Contained) Exit(GetX(), GetY(), 0, Fix0, Fix0, Fix0, fDoCalls);
4590 }
4591 
AdjustWalkRotation(int32_t iRangeX,int32_t iRangeY,int32_t iSpeed)4592 bool C4Object::AdjustWalkRotation(int32_t iRangeX, int32_t iRangeY, int32_t iSpeed)
4593 {
4594 	int32_t iDestAngle;
4595 	// attachment at middle (bottom) vertex?
4596 	if (Shape.iAttachVtx<0 || !Def->Shape.VtxX[Shape.iAttachVtx])
4597 	{
4598 		// evaluate floor around attachment pos
4599 		int32_t iSolidLeft=0, iSolidRight=0;
4600 		// left
4601 		int32_t iXCheck = Shape.iAttachX-iRangeX;
4602 		if (GBackSolid(iXCheck, Shape.iAttachY))
4603 		{
4604 			// up
4605 			while (--iSolidLeft>-iRangeY)
4606 				if (GBackSolid(iXCheck, Shape.iAttachY+iSolidLeft))
4607 					{ ++iSolidLeft; break; }
4608 		}
4609 		else
4610 			// down
4611 			while (++iSolidLeft<iRangeY)
4612 				if (GBackSolid(iXCheck, Shape.iAttachY+iSolidLeft))
4613 					{ --iSolidLeft; break; }
4614 		// right
4615 		iXCheck += 2*iRangeX;
4616 		if (GBackSolid(iXCheck, Shape.iAttachY))
4617 		{
4618 			// up
4619 			while (--iSolidRight>-iRangeY)
4620 				if (GBackSolid(iXCheck, Shape.iAttachY+iSolidRight))
4621 					{ ++iSolidRight; break; }
4622 		}
4623 		else
4624 			// down
4625 			while (++iSolidRight<iRangeY)
4626 				if (GBackSolid(iXCheck, Shape.iAttachY+iSolidRight))
4627 					{ --iSolidRight; break; }
4628 		// calculate destination angle
4629 		// 100% accurate for large values of Pi ;)
4630 		iDestAngle=(iSolidRight-iSolidLeft)*(35/std::max<int32_t>(iRangeX, 1));
4631 	}
4632 	else
4633 	{
4634 		// attachment at other than horizontal middle vertex: get your feet to the ground!
4635 		// rotate target to large angle is OK, because rotation will stop once the real
4636 		// bottom vertex hits solid ground
4637 		if (Shape.VtxX[Shape.iAttachVtx] > 0)
4638 			iDestAngle = -50;
4639 		else
4640 			iDestAngle = 50;
4641 	}
4642 	// move to destination angle
4643 	if (Abs(iDestAngle-GetR())>2)
4644 	{
4645 		rdir = itofix(Clamp<int32_t>(iDestAngle-GetR(), -15,+15));
4646 		rdir/=(10000/iSpeed);
4647 	}
4648 	else rdir=0;
4649 	// done, success
4650 	return true;
4651 }
4652 
BubbleOut(int32_t tx,int32_t ty)4653 static void BubbleOut(int32_t tx, int32_t ty)
4654 {
4655 	// No bubbles from nowhere
4656 	if (!GBackSemiSolid(tx,ty)) return;
4657 	// Enough bubbles out there already
4658 	if (::Objects.ObjectCount(C4ID::Bubble) >= 150) return;
4659 	// Create bubble
4660 	Game.CreateObject(C4ID::Bubble,nullptr,NO_OWNER,tx,ty);
4661 }
4662 
Splash()4663 void C4Object::Splash()
4664 {
4665 	int32_t tx = GetX(); int32_t ty = GetY()+1;
4666 	int32_t amt = std::min(Shape.Wdt*Shape.Hgt/10,20);
4667 	// Splash only if there is free space above
4668 	if (GBackSemiSolid(tx, ty - 15)) return;
4669 	// get back mat
4670 	int32_t iMat = GBackMat(tx, ty);
4671 	// check liquid
4672 	if (MatValid(iMat))
4673 		if (DensityLiquid(::MaterialMap.Map[iMat].Density) && ::MaterialMap.Map[iMat].Instable)
4674 		{
4675 			int32_t sy = ty;
4676 			while (GBackLiquid(tx, sy) && sy > ty - 20 && sy >= 0) sy--;
4677 			// Splash bubbles and liquid
4678 			for (int32_t cnt=0; cnt<amt; cnt++)
4679 			{
4680 				int32_t bubble_x = tx+Random(16)-8;
4681 				int32_t bubble_y = ty+Random(16)-6;
4682 				BubbleOut(bubble_x,bubble_y);
4683 				if (GBackLiquid(tx,ty) && !GBackSemiSolid(tx, sy))
4684 				{
4685 					C4Real xdir = C4REAL100(Random(151)-75);
4686 					C4Real ydir = C4REAL100(-int32_t(Random(200)));
4687 					::PXS.Create(::Landscape.ExtractMaterial(tx,ty,false),
4688 					             itofix(tx),itofix(sy),
4689 					             xdir,
4690 					             ydir);
4691 				}
4692 			}
4693 		}
4694 	// Splash sound
4695 	if (amt>=20)
4696 		StartSoundEffect("Liquids::Splash2", false, 50, this);
4697 	else if (amt>1) StartSoundEffect("Liquids::Splash1", false, 50, this);
4698 }
4699 
UpdateInLiquid()4700 void C4Object::UpdateInLiquid()
4701 {
4702 	// InLiquid check
4703 	if (IsInLiquidCheck()) // In Liquid
4704 	{
4705 		if (!InLiquid) // Enter liquid
4706 		{
4707 			if (OCF & OCF_HitSpeed2)
4708 				if (Mass>3) Splash();
4709 			InLiquid=true;
4710 		}
4711 	}
4712 	else // Out of liquid
4713 	{
4714 		if (InLiquid) // Leave liquid
4715 			InLiquid=false;
4716 	}
4717 }
4718 
GetInfoString()4719 StdStrBuf C4Object::GetInfoString()
4720 {
4721 	StdStrBuf sResult;
4722 	// no info for invalid objects
4723 	if (!Status) return sResult;
4724 	// go through all effects and add their desc
4725 	for (C4Effect *pEff = pEffects; pEff; pEff = pEff->pNext)
4726 	{
4727 		C4Value par[7];
4728 		C4Value vInfo = pEff->DoCall(this, PSFS_FxInfo, par[0], par[1], par[2], par[3], par[4], par[5], par[6]);
4729 		if (!vInfo) continue;
4730 		// debug: warn for wrong return types
4731 		if (vInfo.GetType() != C4V_String)
4732 			DebugLogF("Effect %s(#%d) on object %s (#%d) returned wrong info type %d.", pEff->GetName(), pEff->Number, GetName(), Number, vInfo.GetType());
4733 		// get string val
4734 		C4String *psInfo = vInfo.getStr(); const char *szEffInfo;
4735 		if (psInfo && (szEffInfo = psInfo->GetCStr()))
4736 			if (*szEffInfo)
4737 			{
4738 				// OK; this effect has a desc. Add it!
4739 				if (sResult.getLength()) sResult.AppendChar('|');
4740 				sResult.Append(szEffInfo);
4741 			}
4742 	}
4743 	// done
4744 	return sResult;
4745 }
4746 
GrabContents(C4Object * pFrom)4747 void C4Object::GrabContents(C4Object *pFrom)
4748 {
4749 	// create a temp list of all objects and transfer it
4750 	// this prevents nasty deadlocks caused by RejectEntrance-scripts
4751 	C4ObjectList tmpList; tmpList.Copy(pFrom->Contents);
4752 	for (C4Object *obj : tmpList)
4753 		if (obj->Status)
4754 			obj->Enter(this);
4755 }
4756 
CanConcatPictureWith(C4Object * pOtherObject) const4757 bool C4Object::CanConcatPictureWith(C4Object *pOtherObject) const
4758 {
4759 	// check current definition ID
4760 	if (id != pOtherObject->id) return false;
4761 	// def overwrite of stack conditions
4762 	int32_t allow_picture_stack = Def->AllowPictureStack;
4763 	if (!(allow_picture_stack & APS_Color))
4764 	{
4765 		// check color if ColorByOwner (flags)
4766 		if (Color != pOtherObject->Color && Def->ColorByOwner) return false;
4767 		// check modulation
4768 		if (ColorMod != pOtherObject->ColorMod) return false;
4769 		if (BlitMode != pOtherObject->BlitMode) return false;
4770 	}
4771 	if (!(allow_picture_stack & APS_Graphics))
4772 	{
4773 		// check graphics
4774 		if (pGraphics != pOtherObject->pGraphics) return false;
4775 		// check any own picture rect
4776 		if (PictureRect != pOtherObject->PictureRect) return false;
4777 	}
4778 	if (!(allow_picture_stack & APS_Name))
4779 	{
4780 		// check name, so zagabar's sandwiches don't stack
4781 		if (GetName() != pOtherObject->GetName()) return false;
4782 	}
4783 	if (!(allow_picture_stack & APS_Overlay))
4784 	{
4785 		// check overlay graphics
4786 		for (C4GraphicsOverlay *pOwnOverlay = pGfxOverlay; pOwnOverlay; pOwnOverlay = pOwnOverlay->GetNext())
4787 			if (pOwnOverlay->IsPicture())
4788 			{
4789 				C4GraphicsOverlay *pOtherOverlay = pOtherObject->GetGraphicsOverlay(pOwnOverlay->GetID(), false);
4790 				if (!pOtherOverlay || !(*pOtherOverlay == *pOwnOverlay)) return false;
4791 			}
4792 		for (C4GraphicsOverlay *pOtherOverlay = pOtherObject->pGfxOverlay; pOtherOverlay; pOtherOverlay = pOtherOverlay->GetNext())
4793 			if (pOtherOverlay->IsPicture())
4794 				if (!GetGraphicsOverlay(pOtherOverlay->GetID())) return false;
4795 	}
4796 	// concat OK
4797 	return true;
4798 }
4799 
IsMoveableBySolidMask(int ComparisonPlane) const4800 bool C4Object::IsMoveableBySolidMask(int ComparisonPlane) const
4801 {
4802 	return (Status == C4OS_NORMAL)
4803 		&& !(Category & C4D_StaticBack)
4804 		&& (ComparisonPlane < GetPlane())
4805 		&& !Contained
4806 		;
4807 }
4808 
UpdateScriptPointers()4809 void C4Object::UpdateScriptPointers()
4810 {
4811 	if (pEffects)
4812 		pEffects->ReAssignAllCallbackFunctions();
4813 }
4814 
IsPlayerObject(int32_t iPlayerNumber) const4815 bool C4Object::IsPlayerObject(int32_t iPlayerNumber) const
4816 {
4817 	bool fAnyPlr = (iPlayerNumber == NO_OWNER);
4818 	// if an owner is specified: only owned objects
4819 	if (fAnyPlr && !ValidPlr(Owner)) return false;
4820 	// and crew objects
4821 	if (fAnyPlr || Owner == iPlayerNumber)
4822 	{
4823 		C4Player *pOwner = ::Players.Get(Owner);
4824 		if (pOwner)
4825 		{
4826 			if (pOwner && pOwner->Crew.IsContained(this)) return true;
4827 		}
4828 		else
4829 		{
4830 			// Do not force that the owner exists because the function must work for unjoined players (savegame resume)
4831 			if (Def->CrewMember)
4832 				return true;
4833 		}
4834 	}
4835 	// otherwise, not a player object
4836 	return false;
4837 }
4838 
IsUserPlayerObject()4839 bool C4Object::IsUserPlayerObject()
4840 {
4841 	// must be a player object at all
4842 	if (!IsPlayerObject()) return false;
4843 	// and the owner must not be a script player
4844 	C4Player *pOwner = ::Players.Get(Owner);
4845 	if (!pOwner || pOwner->GetType() != C4PT_User) return false;
4846 	// otherwise, it's a user playeer object
4847 	return true;
4848 }
4849 
SetPropertyByS(C4String * k,const C4Value & to)4850 void C4Object::SetPropertyByS(C4String * k, const C4Value & to)
4851 {
4852 	if (k >= &Strings.P[0] && k < &Strings.P[P_LAST])
4853 	{
4854 		switch(k - &Strings.P[0])
4855 		{
4856 			case P_Plane:
4857 				if (!to.getInt()) throw C4AulExecError("invalid Plane 0");
4858 				SetPlane(to.getInt());
4859 				return;
4860 		}
4861 	}
4862 	C4PropListNumbered::SetPropertyByS(k, to);
4863 }
4864 
ResetProperty(C4String * k)4865 void C4Object::ResetProperty(C4String * k)
4866 {
4867 	if (k >= &Strings.P[0] && k < &Strings.P[P_LAST])
4868 	{
4869 		switch(k - &Strings.P[0])
4870 		{
4871 			case P_Plane:
4872 				SetPlane(GetPropertyInt(P_Plane));
4873 				return;
4874 		}
4875 	}
4876 	return C4PropListNumbered::ResetProperty(k);
4877 }
4878 
GetPropertyByS(const C4String * k,C4Value * pResult) const4879 bool C4Object::GetPropertyByS(const C4String *k, C4Value *pResult) const
4880 {
4881 	if (k >= &Strings.P[0] && k < &Strings.P[P_LAST])
4882 	{
4883 		switch(k - &Strings.P[0])
4884 		{
4885 			case P_Plane: *pResult = C4VInt(Plane); return true;
4886 		}
4887 	}
4888 	return C4PropListNumbered::GetPropertyByS(k, pResult);
4889 }
4890 
GetProperties() const4891 C4ValueArray * C4Object::GetProperties() const
4892 {
4893 	C4ValueArray * a = C4PropList::GetProperties();
4894 	int i;
4895 	i = a->GetSize();
4896 	a->SetSize(i + 1);
4897 	(*a)[i++] = C4VString(&::Strings.P[P_Plane]);
4898 	return a;
4899 }
4900 
GetSolidMaskPlane() const4901 int32_t C4Object::GetSolidMaskPlane() const
4902 {
4903 	// use SolidMaskPlane property. Fallback to object plane if unassigned.
4904 	int32_t plane = GetPropertyInt(P_SolidMaskPlane);
4905 	return plane ? plane : GetPlane();
4906 }
4907