/*
 * OpenClonk, http://www.openclonk.org
 *
 * Copyright (c) 1998-2001, 2003-2004, 2007-2008  Matthes Bender
 * Copyright (c) 2001-2010, 2012  Sven Eberhardt
 * Copyright (c) 2001-2007  Peter Wortmann
 * Copyright (c) 2001  Carlo Teubner
 * Copyright (c) 2001  Michael Käser
 * Copyright (c) 2004-2012  Günther Brammer
 * Copyright (c) 2005, 2009-2011  Tobias Zwick
 * Copyright (c) 2006, 2009-2012  Armin Burgmeier
 * Copyright (c) 2009-2010, 2012  Nicolas Hake
 * Copyright (c) 2010  Benjamin Herr
 * Copyright (c) 2010-2011  Maikel de Vries
 * Copyright (c) 2011  David Dormagen
 * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de
 *
 * Portions might be copyrighted by other authors who have contributed
 * to OpenClonk.
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 * See isc_license.txt for full license and disclaimer.
 *
 * "Clonk" is a registered trademark of Matthes Bender.
 * See clonk_trademark_license.txt for full license.
 */

/* That which fills the world with life */

#include <C4Include.h>
#include <C4Object.h>

#include <C4DefList.h>
#include <C4Effect.h>
#include <C4ObjectInfo.h>
#include <C4Physics.h>
#include <C4ObjectCom.h>
#include <C4Command.h>
#include <C4Viewport.h>
#include <C4MaterialList.h>
#ifdef DEBUGREC
#include <C4Record.h>
#endif
#include <C4SolidMask.h>
#include <C4Random.h>
#include <C4Log.h>
#include <C4Player.h>
#include <C4ObjectMenu.h>
#include <C4RankSystem.h>
#include <C4GameMessage.h>
#include <C4GraphicsResource.h>
#include <C4GraphicsSystem.h>
#include <C4Game.h>
#include <C4PlayerList.h>
#include <C4GameObjects.h>
#include <C4Record.h>
#include <C4MeshAnimation.h>

namespace
{
	const StdMeshInstance::AttachedMesh::DenumeratorFactoryFunc C4MeshDenumeratorFactory = StdMeshInstance::AttachedMesh::DenumeratorFactory<C4MeshDenumerator>;
}

void C4MeshDenumerator::CompileFunc(StdCompiler* pComp, StdMeshInstance::AttachedMesh* attach)
{
	if(pComp->isCompiler())
	{
		int32_t def;
		pComp->Value(mkNamingCountAdapt(def, "ChildInstance"));

		if(def)
		{
			C4DefGraphics* pGfx = NULL;
			pComp->Value(mkNamingAdapt(C4DefGraphicsAdapt(pGfx), "ChildMesh"));
			Def = pGfx->pDef;

			if(pGfx->Type != C4DefGraphics::TYPE_Mesh)
				pComp->excCorrupt("ChildMesh points to non-mesh graphics");
			assert(!attach->Child);
			pComp->Value(mkParAdapt(mkNamingContextPtrAdapt(attach->Child, *pGfx->Mesh, "ChildInstance"), C4MeshDenumeratorFactory));
			assert(attach->Child != NULL);
			attach->OwnChild = true; // Delete the newly allocated child instance when the parent instance is gone

			// TODO: Do we leak pGfx?
		}
		else
		{
			pComp->Value(mkNamingAdapt(Object, "ChildObject"));
			attach->OwnChild = false; // Keep child instance when parent instance is gone since it belongs to a different object
		}
	}
	else
	{
		int32_t def = 0;
		if(Def) ++def;
		pComp->Value(mkNamingCountAdapt(def, "ChildInstance"));

		if(Def)
		{
			assert(attach->OwnChild);
			C4DefGraphics* pGfx = &Def->Graphics;
			assert(pGfx->Type == C4DefGraphics::TYPE_Mesh);
			pComp->Value(mkNamingAdapt(C4DefGraphicsAdapt(pGfx), "ChildMesh"));
			pComp->Value(mkParAdapt(mkNamingContextPtrAdapt(attach->Child, *pGfx->Mesh, "ChildInstance"), C4MeshDenumeratorFactory));
		}
		else
		{
			assert(!attach->OwnChild);
			pComp->Value(mkNamingAdapt(Object, "ChildObject"));
		}
	}
}

void C4MeshDenumerator::DenumeratePointers(StdMeshInstance::AttachedMesh* attach)
{
	Object.DenumeratePointers();

	// Set child instance of attach after denumeration
	if(Object)
	{
		assert(!attach->OwnChild);
		assert(!attach->Child || attach->Child == Object->pMeshInstance);
		if(!attach->Child)
			attach->Child = Object->pMeshInstance;
	}
}

static void DrawVertex(C4Facet &cgo, float tx, float ty, int32_t col, int32_t contact)
{
	if (Inside<int32_t>(tx,cgo.X,cgo.X+cgo.Wdt) && Inside<int32_t>(ty,cgo.Y,cgo.Y+cgo.Hgt))
	{
		pDraw->DrawLineDw(cgo.Surface, tx - 1, ty, tx + 1, ty, col, 0.5f);
		pDraw->DrawLineDw(cgo.Surface, tx, ty - 1, tx, ty + 1, col, 0.5f);
		if (contact) pDraw->DrawFrameDw(cgo.Surface,tx-1.5,ty-1.5,tx+1.5,ty+1.5,C4RGB(0xff, 0xff, 0xff));
	}
}

void C4Action::SetBridgeData(int32_t iBridgeTime, bool fMoveClonk, bool fWall, int32_t iBridgeMaterial)
{
	// validity
	iBridgeMaterial = Min<int32_t>(iBridgeMaterial, ::MaterialMap.Num-1);
	if (iBridgeMaterial < 0) iBridgeMaterial = 0xff;
	iBridgeTime = BoundBy<int32_t>(iBridgeTime, 0, 0xffff);
	// mask in this->Data
	Data = (uint32_t(iBridgeTime) << 16) + (uint32_t(fMoveClonk) << 8) + (uint32_t(fWall) << 9) + iBridgeMaterial;
}

void C4Action::GetBridgeData(int32_t &riBridgeTime, bool &rfMoveClonk, bool &rfWall, int32_t &riBridgeMaterial)
{
	// mask from this->Data
	uint32_t uiData = Data;
	riBridgeTime = (uint32_t(uiData) >> 16);
	rfMoveClonk = !!(uiData & 0x100);
	rfWall = !!(uiData & 0x200);
	riBridgeMaterial = (uiData & 0xff);
	if (riBridgeMaterial == 0xff) riBridgeMaterial = -1;
}

C4Object::C4Object()
{
	Default();
}

void C4Object::Default()
{
	id=C4ID::None;
	nInfo.Clear();
	RemovalDelay=0;
	Owner=NO_OWNER;
	Controller=NO_OWNER;
	LastEnergyLossCausePlayer=NO_OWNER;
	Category=0;
	NoCollectDelay=0;
	Con=0;
	Mass=OwnMass=0;
	Damage=0;
	Energy=0;
	Alive=0;
	Breath=0;
	InMat=MNone;
	Color=0;
	PlrViewRange=0;
	fix_x=fix_y=fix_r=0;
	xdir=ydir=rdir=0;
	Mobile=0;
	Unsorted=false;
	Initializing=false;
	OnFire=0;
	InLiquid=0;
	EntranceStatus=0;
	Audible=0;
	t_contact=0;
	OCF=0;
	Action.Default();
	Shape.Default();
	fOwnVertices=0;
	Contents.Default();
	Component.Default();
	SolidMask.Default();
	PictureRect.Default();
	Def=NULL;
	Info=NULL;
	Command=NULL;
	Contained=NULL;
	TopFace.Default();
	Menu=NULL;
	MaterialContents=NULL;
	Marker=0;
	ColorMod=0xffffffff;
	BlitMode=0;
	CrewDisabled=false;
	Layer=NULL;
	pSolidMaskData=NULL;
	pGraphics=NULL;
	pMeshInstance=NULL;
	pDrawTransform=NULL;
	pEffects=NULL;
	pGfxOverlay=NULL;
	iLastAttachMovementFrame=-1;
}

bool C4Object::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)
{
	C4PropListNumbered::AcquireNumber();
	// currently initializing
	Initializing=true;

	// Def & basics
	Owner=iOwner;
	if (iController > NO_OWNER) Controller = iController; else Controller=iOwner;
	LastEnergyLossCausePlayer=NO_OWNER;
	Info=pInfo;
	Def=pDef->GetDef(); assert(Def);
	SetProperty(P_Prototype, C4VPropList(pDef));
	id=Def->id;
	if (Info) SetName(pInfo->Name);
	Category=Def->Category;
	Plane = Def->GetPlane(); assert(Plane);
	Def->Count++;
	if (pCreator) Layer=pCreator->Layer;

	// graphics
	pGraphics = &Def->Graphics;
	if (pGraphics->Type == C4DefGraphics::TYPE_Mesh)
	{
		pMeshInstance = new StdMeshInstance(*pGraphics->Mesh, Def->GrowthType ? 1.0f : static_cast<float>(Con)/static_cast<float>(FullCon));
		pMeshInstance->SetFaceOrderingForClrModulation(ColorMod);
	}
	else
	{
		pMeshInstance = NULL;
	}
	BlitMode = Def->BlitMode;

	// Position
	if (!Def->Rotateable) { nr=0; nrdir=0; }
	fix_x=itofix(nx);
	fix_y=itofix(ny);
	r=nr;
	fix_r=itofix(r);
	xdir=nxdir; ydir=nydir; rdir=nrdir;

	// Initial mobility
	if (!!xdir || !!ydir || !!rdir)
		Mobile=1;

	// Mass
	Mass=Max<int32_t>(Def->Mass*Con/FullCon,1);

	// Life, energy, breath
	if (Category & C4D_Living) Alive=1;
	if (Alive) Energy=GetPropertyInt(P_MaxEnergy);
	Breath=GetPropertyInt(P_MaxBreath);

	// Components
	Component=Def->Component;
	ComponentConCutoff();

	// Color
	if (Def->ColorByOwner)
	{
		if (ValidPlr(Owner))
			Color=::Players.Get(Owner)->ColorDw;
		else
			Color=0xff; // no-owner color: blue
	}

	// Shape & face
	Shape=Def->Shape;
	SolidMask=Def->SolidMask;
	CheckSolidMaskRect();
	UpdateGraphics(false);
	UpdateFace(true);

	// Initial audibility
	Audible=::Viewports.GetAudibility(GetX(), GetY(), &AudiblePan);

	// Initial OCF
	SetOCF();

	// finished initializing
	Initializing=false;

	return true;
}

C4Object::~C4Object()
{
	Clear();

#if defined(_DEBUG)
	// debug: mustn't be listed in any list now
	::Objects.Sectors.AssertObjectNotInList(this);
#endif
}

void C4Object::AssignRemoval(bool fExitContents)
{
	// check status
	if (!Status) return;
#ifdef DEBUGREC
	C4RCCreateObj rc;
	memset(&rc, '\0', sizeof(rc));
	rc.oei=Number;
	if (Def && Def->GetName()) strncpy(rc.id, Def->GetName(), 32+1);
	rc.x=GetX(); rc.y=GetY(); rc.ownr=Owner;
	AddDbgRec(RCT_DsObj, &rc, sizeof(rc));
#endif
	// Destruction call in container
	if (Contained)
	{
		C4AulParSet pars(C4VObj(this));
		Contained->Call(PSF_ContentsDestruction, &pars);
		if (!Status) return;
	}
	// Destruction call
	Call(PSF_Destruction);
	// Destruction-callback might have deleted the object already
	if (!Status) return;
	// remove all effects (extinguishes as well)
	if (pEffects)
	{
		pEffects->ClearAll(this, C4FxCall_RemoveClear);
		// Effect-callback might actually have deleted the object already
		if (!Status) return;
	}
	// remove particles
	if (FrontParticles) FrontParticles.Clear();
	if (BackParticles) BackParticles.Clear();
	// Action idle
	SetAction(0);
	// Object system operation
	if (Status == C4OS_INACTIVE)
	{
		// object was inactive: activate first, then delete
		::Objects.InactiveObjects.Remove(this);
		Status = C4OS_NORMAL;
		::Objects.Add(this);
	}
	Status=0;
	// count decrease
	Def->Count--;
	// Kill contents
	C4Object *cobj; C4ObjectLink *clnk,*next;
	for (clnk=Contents.First; clnk && (cobj=clnk->Obj); clnk=next)
	{
		next=clnk->Next;
		if (fExitContents)
			cobj->Exit(GetX(), GetY());
		else
		{
			Contents.Remove(cobj);
			cobj->AssignRemoval();
		}
	}
	// remove from container *after* contents have been removed!
	C4Object *pCont;
	if ((pCont=Contained))
	{
		pCont->Contents.Remove(this);
		pCont->UpdateMass();
		pCont->SetOCF();
		Contained=NULL;
	}
	// Object info
	if (Info) Info->Retire();
	Info = NULL;
	// Object system operation
	while (FirstRef) FirstRef->Set0();
	Game.ClearPointers(this);
	ClearCommands();
	if (pSolidMaskData)
	{
		delete pSolidMaskData;
		pSolidMaskData = NULL;
	}
	SolidMask.Wdt = 0;
	RemovalDelay=2;
}

void C4Object::UpdateShape(bool bUpdateVertices)
{

	// Line shape independent
	if (Def->Line) return;

	// Copy shape from def
	Shape.CopyFrom(Def->Shape, bUpdateVertices, !!fOwnVertices);

	// Construction zoom
	if (Con!=FullCon)
	{
		if (Def->GrowthType)
			Shape.Stretch(Con, bUpdateVertices);
		else
			Shape.Jolt(Con, bUpdateVertices);
	}

	// Rotation
	if (Def->Rotateable)
		if (r!=0)
			Shape.Rotate(r, bUpdateVertices);

	// covered area changed? to be on the save side, update pos
	UpdatePos();
}

void C4Object::UpdatePos()
{
	// get new area covered
	// do *NOT* do this while initializing, because object cannot be sorted by main list
	if (!Initializing && Status == C4OS_NORMAL)
		::Objects.UpdatePos(this);
}

void C4Object::UpdateFace(bool bUpdateShape, bool fTemp)
{

	// Update shape - NOT for temp call, because temnp calls are done in drawing routine
	// must not change sync relevant data here (although the shape and pos *should* be updated at that time anyway,
	// because a runtime join would desync otherwise)
	if (!fTemp) { if (bUpdateShape) UpdateShape(); else UpdatePos(); }

	// SolidMask
	if (!fTemp) UpdateSolidMask(false);

	// Null defaults
	TopFace.Default();

	// newgfx: TopFace only
	if (Con>=FullCon || Def->GrowthType)
		if (!Def->Rotateable || (r==0))
			if (Def->TopFace.Wdt>0) // Fullcon & no rotation
				TopFace.Set(GetGraphics()->GetBitmap(Color),
				            Def->TopFace.x,Def->TopFace.y,
				            Def->TopFace.Wdt,Def->TopFace.Hgt);

	// Active face
	UpdateActionFace();
}

void C4Object::UpdateGraphics(bool fGraphicsChanged, bool fTemp)
{
	// check color
	if (!fTemp) if (!pGraphics->IsColorByOwner()) Color=0;
	// new grafics: update face + solidmask
	if (fGraphicsChanged)
	{
		// update solid
		if (pSolidMaskData && !fTemp)
		{
			delete pSolidMaskData; pSolidMaskData = NULL;
			// ensure SolidMask-rect lies within new graphics-rect
			CheckSolidMaskRect();
		}

		// Keep mesh instance if it uses the same underlying mesh
		if(!pMeshInstance || !pGraphics->Type == C4DefGraphics::TYPE_Mesh ||
		   &pMeshInstance->GetMesh() != pGraphics->Mesh)
		{
			delete pMeshInstance;
			if (pGraphics->Type == C4DefGraphics::TYPE_Mesh)
			{
				pMeshInstance = new StdMeshInstance(*pGraphics->Mesh, Def->GrowthType ? 1.0f : static_cast<float>(Con)/static_cast<float>(FullCon));
				pMeshInstance->SetFaceOrderingForClrModulation(ColorMod);
			}
			else
			{
				pMeshInstance = NULL;
			}
		}

		// update face - this also puts any SolidMask
		UpdateFace(false);
	}
}

void C4Object::UpdateFlipDir()
{
	int32_t iFlipDir;
	// We're active
	C4PropList* pActionDef = GetAction();
	if (pActionDef)
		// Get flipdir value from action
		if ((iFlipDir = pActionDef->GetPropertyInt(P_FlipDir)))
			// Action dir is in flipdir range
			if (Action.Dir >= iFlipDir)
			{
				// Calculate flipped drawing dir (from the flipdir direction going backwards)
				Action.DrawDir = (iFlipDir - 1 - (Action.Dir - iFlipDir));
				// Set draw transform, creating one if necessary
				if (pDrawTransform)
					pDrawTransform->SetFlipDir(-1);
				else
					pDrawTransform = new C4DrawTransform(-1);
				// Done setting flipdir
				return;
			}
	// No flipdir necessary
	Action.DrawDir = Action.Dir;
	// Draw transform present?
	if (pDrawTransform)
	{
		// reset flip dir
		pDrawTransform->SetFlipDir(1);
		// if it's identity now, remove the matrix
		if (pDrawTransform->IsIdentity())
		{
			delete pDrawTransform;
			pDrawTransform=NULL;
		}
	}
}

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)
{
	C4Surface* sfc;
	switch (GetGraphics()->Type)
	{
	case C4DefGraphics::TYPE_Bitmap:
		sfc = action ? Action.Facet.Surface : GetGraphics()->GetBitmap(Color);

		pDraw->Blit(sfc,
		              fx, fy, fwdt, fhgt,
		              cgo.Surface, tx, ty, twdt, thgt,
		              true, transform);
		break;
	case C4DefGraphics::TYPE_Mesh:
		C4Value value;
		GetProperty(P_MeshTransformation, &value);
		StdMeshMatrix matrix;
		if (!C4ValueToMatrix(value, &matrix))
			matrix = StdMeshMatrix::Identity();

		if(twdt != fwdt || thgt != fhgt)
		{
			// Also scale Z so that the mesh is not totally distorted and
			// so that normals halfway keep pointing into sensible directions.
			// We don't have a better guess so use the geometric mean for Z scale.
			matrix = StdMeshMatrix::Scale(twdt/fwdt,thgt/fhgt,std::sqrt(twdt*thgt/(fwdt*fhgt))) * matrix;
		}

		pDraw->SetMeshTransform(&matrix);

		pDraw->RenderMesh(*pMeshInstance, cgo.Surface, tx, ty, twdt, thgt, Color, transform);
		pDraw->SetMeshTransform(NULL);
		break;
	}
}

void C4Object::DrawFace(C4TargetFacet &cgo, float offX, float offY, int32_t iPhaseX, int32_t iPhaseY)
{
	const float swdt = float(Def->Shape.Wdt);
	const float shgt = float(Def->Shape.Hgt);
	// Grow Type Display
	float fx = float(swdt * iPhaseX);
	float fy = float(shgt * iPhaseY);
	float fwdt = float(swdt);
	float fhgt = float(shgt);

	float stretch_factor = static_cast<float>(Con) / FullCon;
	float tx = offX + Def->Shape.GetX() * stretch_factor;
	float ty = offY + Def->Shape.GetY() * stretch_factor;
	float twdt = swdt * stretch_factor;
	float thgt = shgt * stretch_factor;

	// Construction Type Display
	if (!Def->GrowthType)
	{
		tx = offX + Def->Shape.GetX();
		twdt = swdt;

		fy += fhgt - thgt;
		fhgt = thgt;
	}

	// Straight
	if ((!Def->Rotateable || (r==0)) && !pDrawTransform)
	{
		DrawFaceImpl(cgo, false, fx, fy, fwdt, fhgt, tx, ty, twdt, thgt, NULL);
		/*    pDraw->Blit(GetGraphics()->GetBitmap(Color),
		      fx, fy, fwdt, fhgt,
		      cgo.Surface, tx, ty, twdt, thgt,
		      true, NULL);*/
	}
	// Rotated or transformed
	else
	{
		C4DrawTransform rot;
		if (pDrawTransform)
		{
			rot.SetTransformAt(*pDrawTransform, offX, offY);
			if (r) rot.Rotate(r * 100, offX, offY);
		}
		else
		{
			rot.SetRotate(r * 100, offX, offY);
		}
		DrawFaceImpl(cgo, false, fx, fy, fwdt, fhgt, tx, ty, twdt, thgt, &rot);
		/*    pDraw->Blit(GetGraphics()->GetBitmap(Color),
		      fx, fy, fwdt, fhgt,
		      cgo.Surface, tx, ty, twdt, thgt,
		      true, &rot);*/
	}
}

void C4Object::DrawActionFace(C4TargetFacet &cgo, float offX, float offY)
{
	// This should not be called for meshes since Facet has no meaning
	// for them. Only use DrawFace() with meshes!
	assert(GetGraphics()->Type == C4DefGraphics::TYPE_Bitmap);
	C4PropList* pActionDef = GetAction();

	// Regular action facet
	const float swdt = float(Action.Facet.Wdt);
	const float shgt = float(Action.Facet.Hgt);
	int32_t iPhase = Action.Phase;
	if (pActionDef->GetPropertyInt(P_Reverse)) iPhase = pActionDef->GetPropertyInt(P_Length) - 1 - Action.Phase;

	// Grow Type Display
	float fx = float(Action.Facet.X + swdt * iPhase);
	float fy = float(Action.Facet.Y + shgt * Action.DrawDir);
	float fwdt = float(swdt);
	float fhgt = float(shgt);

	// draw stretched towards shape center with transform
	float stretch_factor = static_cast<float>(Con) / FullCon;
	float tx = (Def->Shape.GetX() + Action.FacetX) * stretch_factor + offX;
	float ty = (Def->Shape.GetY() + Action.FacetY) * stretch_factor + offY;
	float twdt = swdt * stretch_factor;
	float thgt = shgt * stretch_factor;

	// Construction Type Display
	if (!Def->GrowthType)
	{
		// FIXME
		if (Con != FullCon)
		{
			// incomplete constructions do not show actions
			DrawFace(cgo, offX, offY);
			return;
		}
		tx = Def->Shape.GetX() + Action.FacetX + offX;
		twdt = swdt;
		float offset_from_top = shgt * Max<float>(float(FullCon - Con), 0) / FullCon;
		fy += offset_from_top;
		fhgt -= offset_from_top;
	}

	// Straight
	if ((!Def->Rotateable || (r==0)) && !pDrawTransform)
	{
		DrawFaceImpl(cgo, true, fx, fy, fwdt, fhgt, tx, ty, twdt, thgt, NULL);
		/*pDraw->Blit(Action.Facet.Surface,
		  fx, fy, fwdt, fhgt,
		  cgo.Surface, tx, ty, twdt, thgt,
		  true, NULL);*/
	}
	// Rotated or transformed
	else
	{
		// rotate midpoint of action facet around center of shape
		// combine with existing transform if necessary
		C4DrawTransform rot;
		if (pDrawTransform)
		{
			rot.SetTransformAt(*pDrawTransform, offX, offY);
			if (r) rot.Rotate(r * 100, offX, offY);
		}
		else
		{
			rot.SetRotate(r * 100, offX, offY);
		}
		DrawFaceImpl(cgo, true, fx, fy, fwdt, fhgt, tx, ty, twdt, thgt, &rot);
		/*    pDraw->Blit(Action.Facet.Surface,
		      fx, fy, fwdt, fhgt,
		      cgo.Surface, tx, ty, twdt, thgt,
		      true, &rot);*/
	}
}

void C4Object::UpdateMass()
{
	Mass=Max<int32_t>((Def->Mass+OwnMass)*Con/FullCon,1);
	if (!Def->NoComponentMass) Mass+=Contents.Mass;
	if (Contained)
	{
		Contained->Contents.MassCount();
		Contained->UpdateMass();
	}
}

void C4Object::ComponentConCutoff()
{
	// this is not ideal, since it does not know about custom builder components
	int32_t cnt;
	for (cnt=0; Component.GetID(cnt); cnt++)
		Component.SetCount(cnt,
		                   Min<int32_t>(Component.GetCount(cnt),Def->Component.GetCount(cnt)*Con/FullCon));
}

void C4Object::ComponentConGain()
{
	// this is not ideal, since it does not know about custom builder components
	int32_t cnt;
	for (cnt=0; Component.GetID(cnt); cnt++)
		Component.SetCount(cnt,
		                   Max<int32_t>(Component.GetCount(cnt),Def->Component.GetCount(cnt)*Con/FullCon));
}

void C4Object::UpdateInMat()
{
	// get new mat
	int32_t newmat;
	if (Contained)
		newmat = Contained->Def->ClosedContainer ? MNone : Contained->InMat;
	else
		newmat = GBackMat(GetX(), GetY());

	// mat changed?
	if (newmat != InMat)
	{
		Call(PSF_OnMaterialChanged,&C4AulParSet(C4VInt(newmat),C4VInt(InMat)));
		InMat = newmat;
	}
}

void C4Object::SetOCF()
{
	C4PropList* pActionDef = GetAction();
#ifdef DEBUGREC_OCF
	uint32_t dwOCFOld = OCF;
#endif
	// Update the object character flag according to the object's current situation
	C4Real cspeed=GetSpeed();
#ifdef _DEBUG
	if (Contained && !C4PropListNumbered::CheckPropList(Contained))
		{ LogF("Warning: contained in wild object %p!", static_cast<void*>(Contained)); }
	else if (Contained && !Contained->Status)
		{ LogF("Warning: contained in deleted object (#%d) (%s)!", Contained->Number, Contained->GetName()); }
#endif
	// OCF_Normal: The OCF is never zero
	OCF=OCF_Normal;
	// OCF_Construct: Can be built outside
	if (Def->Constructable && (Con<FullCon)
	    && (r==0) && !OnFire)
		OCF|=OCF_Construct;
	// OCF_Grab: Can be pushed
	if (GetPropertyInt(P_Touchable))
		OCF|=OCF_Grab;
	// OCF_Carryable: Can be picked up
	if (GetPropertyInt(P_Collectible))
		OCF|=OCF_Carryable;
	// OCF_OnFire: Is burning
	if (OnFire)
		OCF|=OCF_OnFire;
	// OCF_Inflammable: Is not burning and is inflammable
	if (!OnFire && GetPropertyInt(P_ContactIncinerate) > 0)
		OCF|=OCF_Inflammable;
	// OCF_FullCon: Is fully completed/grown
	if (Con>=FullCon)
		OCF|=OCF_FullCon;
	// OCF_Rotate: Can be rotated
	if (Def->Rotateable)
		// Don't rotate minimum (invisible) construction sites
		if (Con>100)
			OCF|=OCF_Rotate;
	// OCF_Exclusive: No action through this, no construction in front of this
	if (Def->Exclusive)
		OCF|=OCF_Exclusive;
	// OCF_Entrance: Can currently be entered/activated
	if ((Def->Entrance.Wdt>0) && (Def->Entrance.Hgt>0))
		if ((OCF & OCF_FullCon) && ((Def->RotatedEntrance == 1) || (r <= Def->RotatedEntrance)))
			OCF|=OCF_Entrance;
	// HitSpeeds
	if (cspeed>=HitSpeed1) OCF|=OCF_HitSpeed1;
	if (cspeed>=HitSpeed2) OCF|=OCF_HitSpeed2;
	if (cspeed>=HitSpeed3) OCF|=OCF_HitSpeed3;
	if (cspeed>=HitSpeed4) OCF|=OCF_HitSpeed4;
	// OCF_Collection
	if ((OCF & OCF_FullCon) || Def->IncompleteActivity)
		if ((Def->Collection.Wdt>0) && (Def->Collection.Hgt>0))
			if (!pActionDef || (!pActionDef->GetPropertyInt(P_ObjectDisabled)))
				if (NoCollectDelay==0)
					OCF|=OCF_Collection;
	// OCF_Alive
	if (Alive) OCF|=OCF_Alive;
	// OCF_CrewMember
	if (Def->CrewMember)
		if (Alive)
			OCF|=OCF_CrewMember;
	// OCF_AttractLightning
	if (Def->AttractLightning)
		if (OCF & OCF_FullCon)
			OCF|=OCF_AttractLightning;
	// OCF_NotContained
	if (!Contained)
		OCF|=OCF_NotContained;
	// OCF_InLiquid
	if (InLiquid)
		if (!Contained)
			OCF|=OCF_InLiquid;
	// OCF_InSolid
	if (!Contained)
		if (GBackSolid(GetX(), GetY()))
			OCF|=OCF_InSolid;
	// OCF_InFree
	if (!Contained)
		if (!GBackSemiSolid(GetX(), GetY()-1))
			OCF|=OCF_InFree;
	// OCF_Available
	if (!Contained || (Contained->Def->GrabPutGet & C4D_Grab_Get) || (Contained->OCF & OCF_Entrance))
		if (!GBackSemiSolid(GetX(), GetY()-1) || (!GBackSolid(GetX(), GetY()-1) && !GBackSemiSolid(GetX(), GetY()-8)))
			OCF|=OCF_Available;
	// OCF_Container
	if ((Def->GrabPutGet & C4D_Grab_Put) || (Def->GrabPutGet & C4D_Grab_Get) || (OCF & OCF_Entrance))
		OCF|=OCF_Container;
#ifdef DEBUGREC_OCF
	C4RCOCF rc = { dwOCFOld, OCF, false };
	AddDbgRec(RCT_OCF, &rc, sizeof(rc));
#endif
}


void C4Object::UpdateOCF()
{
	C4PropList* pActionDef = GetAction();
#ifdef DEBUGREC_OCF
	uint32_t dwOCFOld = OCF;
#endif
	// Update the object character flag according to the object's current situation
	C4Real cspeed=GetSpeed();
#ifdef _DEBUG
	if (Contained && !C4PropListNumbered::CheckPropList(Contained))
		{ LogF("Warning: contained in wild object %p!", static_cast<void*>(Contained)); }
	else if (Contained && !Contained->Status)
		{ LogF("Warning: contained in deleted object %p (%s)!", static_cast<void*>(Contained), Contained->GetName()); }
#endif
	// Keep the bits that only have to be updated with SetOCF (def, category, con, alive, onfire)
	OCF=OCF & (OCF_Normal | OCF_Exclusive | OCF_Grab | OCF_FullCon | OCF_Rotate | OCF_OnFire
		| OCF_Inflammable | OCF_Alive | OCF_CrewMember | OCF_AttractLightning);
	// OCF_Carryable: Can be picked up
	if (GetPropertyInt(P_Collectible))
		OCF|=OCF_Carryable;
	// OCF_Construct: Can be built outside
	if (Def->Constructable && (Con<FullCon)
	    && (r==0) && !OnFire)
		OCF|=OCF_Construct;
	// OCF_Entrance: Can currently be entered/activated
	if ((Def->Entrance.Wdt>0) && (Def->Entrance.Hgt>0))
		if ((OCF & OCF_FullCon) && ((Def->RotatedEntrance == 1) || (r <= Def->RotatedEntrance)))
			OCF|=OCF_Entrance;
	// HitSpeeds
	if (cspeed>=HitSpeed1) OCF|=OCF_HitSpeed1;
	if (cspeed>=HitSpeed2) OCF|=OCF_HitSpeed2;
	if (cspeed>=HitSpeed3) OCF|=OCF_HitSpeed3;
	if (cspeed>=HitSpeed4) OCF|=OCF_HitSpeed4;
	// OCF_Collection
	if ((OCF & OCF_FullCon) || Def->IncompleteActivity)
		if ((Def->Collection.Wdt>0) && (Def->Collection.Hgt>0))
			if (!pActionDef || (!pActionDef->GetPropertyInt(P_ObjectDisabled)))
				if (NoCollectDelay==0)
					OCF|=OCF_Collection;
	// OCF_NotContained
	if (!Contained)
		OCF|=OCF_NotContained;
	// OCF_InLiquid
	if (InLiquid)
		if (!Contained)
			OCF|=OCF_InLiquid;
	// OCF_InSolid
	if (!Contained)
		if (GBackSolid(GetX(), GetY()))
			OCF|=OCF_InSolid;
	// OCF_InFree
	if (!Contained)
		if (!GBackSemiSolid(GetX(), GetY()-1))
			OCF|=OCF_InFree;
	// OCF_Available
	if (!Contained || (Contained->Def->GrabPutGet & C4D_Grab_Get) || (Contained->OCF & OCF_Entrance))
		if (!GBackSemiSolid(GetX(), GetY()-1) || (!GBackSolid(GetX(), GetY()-1) && !GBackSemiSolid(GetX(), GetY()-8)))
			OCF|=OCF_Available;
	// OCF_Container
	if ((Def->GrabPutGet & C4D_Grab_Put) || (Def->GrabPutGet & C4D_Grab_Get) || (OCF & OCF_Entrance))
		OCF|=OCF_Container;
#ifdef DEBUGREC_OCF
	C4RCOCF rc = { dwOCFOld, OCF, true };
	AddDbgRec(RCT_OCF, &rc, sizeof(rc));
#endif
#ifdef _DEBUG
	DEBUGREC_OFF
	uint32_t updateOCF = OCF;
	SetOCF();
	assert (updateOCF == OCF);
	DEBUGREC_ON
#endif
}


bool C4Object::ExecLife()
{
	// Breathing
	if (!::Game.iTick5)
		if (Alive && !Def->NoBreath)
		{
			// Supply check
			bool Breathe=false;
			// Forcefields are breathable.
			if (GBackMat(GetX(), GetY()+Shape.GetY()/2)==MVehic)
				{ Breathe=true; }
			else if (GetPropertyInt(P_BreatheWater))
				{ if (GBackMat(GetX(), GetY())==MWater) Breathe=true; }
			else
				{ if (!GBackSemiSolid(GetX(), GetY()+Shape.GetY()/2)) Breathe=true; }
			if (Contained) Breathe=true;
			// No supply
			if (!Breathe)
			{
				// Reduce breath, then energy, bubble
				if (Breath > 0) DoBreath(-5);
				else DoEnergy(-1,false,C4FxCall_EngAsphyxiation, NO_OWNER);
			}
			// Supply
			else
			{
				// Take breath
				int32_t takebreath = GetPropertyInt(P_MaxBreath) - Breath;
				if (takebreath > 0) DoBreath(takebreath);
			}
		}

	// Corrosion energy loss
	if (!::Game.iTick10)
		if (Alive)
			if (InMat!=MNone)
				if (::MaterialMap.Map[InMat].Corrosive)
					if (!GetPropertyInt(P_CorrosionResist))
						DoEnergy(-::MaterialMap.Map[InMat].Corrosive/15,false,C4FxCall_EngCorrosion, NO_OWNER);

	// InMat incineration
	if (!::Game.iTick10)
		if (InMat!=MNone)
			if (::MaterialMap.Map[InMat].Incindiary)
				if (GetPropertyInt(P_ContactIncinerate) > 0)
				{
					C4AulFunc *pCallFunc = GetFunc(PSF_OnInIncendiaryMaterial);
					if (pCallFunc)
					{
						pCallFunc->Exec(this, &C4AulParSet());
					}
				}

	// birthday
	if (!::Game.iTick255)
		if (Alive)
			if (Info)
			{
				int32_t iPlayingTime = Info->TotalPlayingTime + (Game.Time - Info->InActionTime);

				int32_t iNewAge = iPlayingTime / 3600 / 5;

				if (Info->Age != iNewAge && Info->Age)
				{
					// message
					GameMsgObject(FormatString(LoadResStr("IDS_OBJ_BIRTHDAY"),GetName (), Info->TotalPlayingTime / 3600 / 5).getData(),this);
					StartSoundEffect("Trumpet",false,100,this);
				}

				Info->Age = iNewAge;


			}

	return true;
}

void C4Object::Execute()
{
#ifdef DEBUGREC
	// record debug
	C4RCExecObj rc;
	rc.Number=Number;
	rc.fx=fix_x;
	rc.fy=fix_y;
	rc.fr=fix_r;
	AddDbgRec(RCT_ExecObj, &rc, sizeof(rc));
#endif
	// reset temporary marker
	Marker = 0;
	// OCF
	UpdateOCF();
	// Command
	ExecuteCommand();
	// Action
	// need not check status, because dead objects have lost their action
	ExecAction();
	// commands and actions are likely to have removed the object, and movement
	// *must not* be executed for dead objects (SolidMask-errors)
	if (!Status) return;
	// Movement
	ExecMovement();
	if (!Status) return;
	// particles
	if (BackParticles) BackParticles.Exec(this);
	if (FrontParticles) FrontParticles.Exec(this);
	// effects
	if (pEffects)
	{
		pEffects->Execute(this);
		if (!Status) return;
	}
	// Life
	ExecLife();
	// 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.
	if (pMeshInstance && !pMeshInstance->GetAttachParent())
		pMeshInstance->ExecuteAnimation(1.0f/37.0f /* play smoothly at 37 FPS */);
	// Menu
	if (Menu) Menu->Execute();
}

bool C4Object::At(int32_t ctx, int32_t cty)
{
	if (Status) if (!Contained) if (Def)
				if (Inside<int32_t>(cty - (GetY() + Shape.GetY() - addtop()), 0, Shape.Hgt - 1 + addtop()))
					if (Inside<int32_t>(ctx - (GetX() + Shape.GetX()), 0, Shape.Wdt - 1))
						return true;
	return false;
}

bool C4Object::At(int32_t ctx, int32_t cty, DWORD &ocf)
{
	if (Status) if (!Contained) if (Def)
				if (OCF & ocf)
					if (Inside<int32_t>(cty - (GetY() + Shape.GetY() - addtop()), 0, Shape.Hgt - 1 + addtop()))
						if (Inside<int32_t>(ctx - (GetX() + Shape.GetX()), 0, Shape.Wdt - 1))
						{
							// Set ocf return value
							GetOCFForPos(ctx, cty, ocf);
							return true;
						}
	return false;
}

void C4Object::GetOCFForPos(int32_t ctx, int32_t cty, DWORD &ocf)
{
	DWORD rocf=OCF;
	// Verify entrance area OCF return
	if (rocf & OCF_Entrance)
		if (!Inside<int32_t>(cty - (GetY() + Def->Entrance.y), 0, Def->Entrance.Hgt - 1)
		    || !Inside<int32_t>(ctx - (GetX() + Def->Entrance.x), 0, Def->Entrance.Wdt - 1))
			rocf &= (~OCF_Entrance);
	// Verify collection area OCF return
	if (rocf & OCF_Collection)
		if (!Inside<int32_t>(cty - (GetY() + Def->Collection.y), 0, Def->Collection.Hgt - 1)
		    || !Inside<int32_t>(ctx - (GetX() + Def->Collection.x), 0, Def->Collection.Wdt - 1))
			rocf &= (~OCF_Collection);
	ocf=rocf;
}

void C4Object::AssignDeath(bool fForced)
{
	C4Object *thing;
	// Alive objects only
	if (!Alive) return;
	// clear all effects
	// do not delete effects afterwards, because they might have denied removal
	// set alive-flag before, so objects know what's up
	// and prevent recursive death-calls this way
	// get death causing player before doing effect calls, because those might meddle around with the flags
	int32_t iDeathCausingPlayer = LastEnergyLossCausePlayer;
	Alive=0;
	if (pEffects) pEffects->ClearAll(this, C4FxCall_RemoveDeath);
	// if the object is alive again, abort here if the kill is not forced
	if (Alive && !fForced) return;
	// Action
	SetActionByName("Dead");
	// Values
	Alive=0;
	ClearCommands();
	C4ObjectInfo * pInfo = Info;
	if (Info)
	{
		Info->HasDied=true;
		++Info->DeathCount;
		Info->Retire();
	}
	// Lose contents
	while ((thing=Contents.GetObject())) thing->Exit(thing->GetX(),thing->GetY());
	// Remove from crew/cursor/view
	C4Player *pPlr = ::Players.Get(Owner);
	if (pPlr) pPlr->ClearPointers(this, true);
	// ensure objects that won't be affected by dead-plrview-decay are handled properly
	if (!pPlr || !(Category & C4D_Living) || !pPlr->FoWViewObjs.IsContained(this))
		SetPlrViewRange(0);
	// Engine script call
	C4AulParSet pars(C4VInt(iDeathCausingPlayer));
	Call(PSF_Death, &pars);
	// Update OCF. Done here because previously it would have been done in the next frame
	// Whats worse: Having the OCF change because of some unrelated script-call like
	// SetCategory, or slightly breaking compatibility?
	SetOCF();

	// Engine broadcast: relaunch player (in CR, this was called from clonk script.
	// Now, it is done for every crew member)
	if(pPlr)
		if(!pPlr->Crew.ObjectCount())
			::GameScript.GRBroadcast(PSF_RelaunchPlayer,
			                         &C4AulParSet(C4VInt(Owner),C4VInt(iDeathCausingPlayer)));
	if (pInfo)
		pInfo->HasDied = false;
}

bool C4Object::ChangeDef(C4ID idNew)
{
	// Get new definition
	C4Def *pDef=C4Id2Def(idNew);
	if (!pDef) return false;
	// Containment storage
	C4Object *pContainer=Contained;
	// Exit container (no Ejection/Departure)
	if (Contained) Exit(0,0,0,Fix0,Fix0,Fix0,false);
	// Pre change resets
	SetAction(0);
	ResetProperty(&Strings.P[P_Action]); // Enforce ActIdle because SetAction may have failed due to NoOtherAction
	SetDir(0); // will drop any outdated flipdir
	if (pSolidMaskData) { delete pSolidMaskData; pSolidMaskData=NULL; }
	Def->Count--;
	// Def change
	Def=pDef;
	SetProperty(P_Prototype, C4VPropList(pDef));
	id=pDef->id;
	Def->Count++;
	// new def: Needs to be resorted
	Unsorted=true;
	// graphics change
	pGraphics = &pDef->Graphics;
	// blit mode adjustment
	if (!(BlitMode & C4GFXBLIT_CUSTOM)) BlitMode = Def->BlitMode;
	// an object may have newly become an ColorByOwner-object
	// if it had been ColorByOwner, but is not now, this will be caught in UpdateGraphics()
	if (!Color && ValidPlr(Owner))
		Color=::Players.Get(Owner)->ColorDw;
	if (!Def->Rotateable) { r=0; fix_r=rdir=Fix0; }
	// Reset solid mask
	SolidMask=Def->SolidMask;
	// Post change updates
	UpdateGraphics(true);
	UpdateMass();
	UpdateFace(true);
	SetOCF();
	// Any effect callbacks to this object might need to reinitialize their target functions
	// This is ugly, because every effect there is must be updated...
	if (Game.pGlobalEffects) Game.pGlobalEffects->OnObjectChangedDef(this);
	for (C4ObjectLink *pLnk = ::Objects.First; pLnk; pLnk = pLnk->Next)
		if (pLnk->Obj->pEffects) pLnk->Obj->pEffects->OnObjectChangedDef(this);
	// Containment (no Entrance)
	if (pContainer) Enter(pContainer,false);
	// Done
	return true;
}

void C4Object::DoDamage(int32_t iChange, int32_t iCausedBy, int32_t iCause)
{
	// non-living: ask effects first
	if (pEffects && !Alive)
	{
		pEffects->DoDamage(this, iChange, iCause, iCausedBy);
		if (!iChange) return;
	}
	// Change value
	Damage = Max<int32_t>( Damage+iChange, 0 );
	// Engine script call
	Call(PSF_Damage,&C4AulParSet(C4VInt(iChange), C4VInt(iCause), C4VInt(iCausedBy)));
}

void C4Object::DoEnergy(int32_t iChange, bool fExact, int32_t iCause, int32_t iCausedByPlr)
{
	// iChange 100% = Physical 100000
	if (!fExact) iChange=iChange*C4MaxPhysical/100;
	// Was zero?
	bool fWasZero=(Energy==0);
	// Mark last damage causing player to trace kills
	if (iChange < 0) UpdatLastEnergyLossCause(iCausedByPlr);
	// Living things: ask effects for change first
	if (pEffects && Alive)
		pEffects->DoDamage(this, iChange, iCause, iCausedByPlr);
	// Do change
	iChange = BoundBy<int32_t>(iChange, -Energy, GetPropertyInt(P_MaxEnergy) - Energy);
	Energy += iChange;
	// call to object
	Call(PSF_EnergyChange,&C4AulParSet(C4VInt(iChange), C4VInt(iCause), C4VInt(iCausedByPlr)));
	// Alive and energy reduced to zero: death
	if (Alive) if (Energy==0) if (!fWasZero) AssignDeath(false);
}

void C4Object::UpdatLastEnergyLossCause(int32_t iNewCausePlr)
{
	// Mark last damage causing player to trace kills
	// do not regard self-administered damage if there was a previous damage causing player, because that would steal kills
	// if people tumble themselves via stop-stop-(left/right)-throw  while falling into teh abyss
	if (iNewCausePlr != Controller || LastEnergyLossCausePlayer < 0)
	{
		LastEnergyLossCausePlayer = iNewCausePlr;
	}
}

void C4Object::DoBreath(int32_t iChange)
{
	// Do change
	iChange = BoundBy<int32_t>(iChange, -Breath, GetPropertyInt(P_MaxBreath) - Breath);
	Breath += iChange;
	// call to object
	Call(PSF_BreathChange,&C4AulParSet(C4VInt(iChange)));
}

void C4Object::DoCon(int32_t iChange)
{
	C4Real strgt_con_b = fix_y + Shape.GetBottom();
	bool fWasFull = (Con>=FullCon);

	// Change con
	if (Def->Oversize)
		Con=Max<int32_t>(Con+iChange,0);
	else
		Con=BoundBy<int32_t>(Con+iChange,0,FullCon);

	// Update OCF
	SetOCF();

	// Mass
	UpdateMass();

	// shape and position
	UpdateShape();
	// make the bottom-most vertex stay in place
	fix_y = strgt_con_b - Shape.GetBottom();
	// Face (except for the shape)
	UpdateFace(false);

	// component update
	// Decay: reduce components
	if (iChange<0)
		ComponentConCutoff();
	// Growth: gain components
	else
		ComponentConGain();

	// Unfullcon
	if (fWasFull && (Con<FullCon))
	{
		// Lose contents
		if (!Def->IncompleteActivity)
		{
			C4Object *cobj;
			while ((cobj=Contents.GetObject()))
				if (Contained) cobj->Enter(Contained);
				else cobj->Exit(cobj->GetX(),cobj->GetY());
			SetAction(0);
		}
	}

	// Completion
	if (!fWasFull && (Con>=FullCon))
		Call(PSF_Initialize);

	// Con Zero Removal
	if (Con<=0)
		AssignRemoval();
	// Mesh Graphics Update
	else if(pMeshInstance)
		pMeshInstance->SetCompletion(Def->GrowthType ? 1.0f : static_cast<float>(Con)/static_cast<float>(FullCon));
}

void C4Object::DoExperience(int32_t change)
{
	const int32_t MaxExperience = 100000000;

	if (!Info) return;

	Info->Experience=BoundBy<int32_t>(Info->Experience+change,0,MaxExperience);

	// Promotion check
	if (Info->Experience<MaxExperience)
		if (Info->Experience>=::DefaultRanks.Experience(Info->Rank+1))
			Promote(Info->Rank+1, false, false);
}

bool C4Object::Exit(int32_t iX, int32_t iY, int32_t iR, C4Real iXDir, C4Real iYDir, C4Real iRDir, bool fCalls)
{
	// 1. Exit the current container.
	// 2. Update Contents of container object and set Contained to NULL.
	// 3. Set offset position/motion if desired.
	// 4. Call Ejection for container and Departure for object.

	// Not contained
	C4Object *pContainer=Contained;
	if (!pContainer) return false;
	// Remove object from container
	pContainer->Contents.Remove(this);
	pContainer->UpdateMass();
	pContainer->SetOCF();
	// No container
	Contained=NULL;
	// Position/motion
	r=iR;
	fix_x=itofix(iX); fix_y=itofix(iY); fix_r=itofix(r);
	BoundsCheck(fix_x, fix_y);
	xdir=iXDir; ydir=iYDir; rdir=iRDir;
	// Misc updates
	Mobile=1;
	InLiquid=0;
	CloseMenu(true);
	UpdateFace(true);
	SetOCF();
	// Engine calls
	if (fCalls) pContainer->Call(PSF_Ejection,&C4AulParSet(C4VObj(this)));
	if (fCalls) Call(PSF_Departure,&C4AulParSet(C4VObj(pContainer)));
	// Success (if the obj wasn't "re-entered" by script)
	return !Contained;
}

bool C4Object::Enter(C4Object *pTarget, bool fCalls, bool fCopyMotion, bool *pfRejectCollect)
{
	// 0. Query entrance and collection
	// 1. Exit if contained.
	// 2. Set new container.
	// 3. Update Contents and mass of the new container.
	// 4. Call collection for container
	// 5. Call entrance for object.

	// No target or target is self
	if (!pTarget || (pTarget==this)) return false;
	// check if entrance is allowed
	if (!! Call(PSF_RejectEntrance, &C4AulParSet(C4VObj(pTarget)))) return false;
	// check if we end up in an endless container-recursion
	for (C4Object *pCnt=pTarget->Contained; pCnt; pCnt=pCnt->Contained)
		if (pCnt==this) return false;
	// Check RejectCollect, if desired
	if (pfRejectCollect)
	{
		if (!!pTarget->Call(PSF_RejectCollection,&C4AulParSet(C4VPropList(Def), C4VObj(this))))
		{
			*pfRejectCollect = true;
			return false;
		}
		*pfRejectCollect = false;
	}
	// Exit if contained
	if (Contained) if (!Exit(GetX(),GetY())) return false;
	if (Contained || !Status || !pTarget->Status) return false;
	// Failsafe updates
	CloseMenu(true);
	SetOCF();
	// Set container
	Contained=pTarget;
	// Enter
	if (!Contained->Contents.Add(this, C4ObjectList::stContents))
	{
		Contained=NULL;
		return false;
	}
	// Assume that the new container controls this object, if it cannot control itself (i.e.: Alive)
	// So it can be traced back who caused the damage, if a projectile hits its target
	if (!Alive)
		Controller = pTarget->Controller;
	// Misc updates
	// motion must be copied immediately, so the position will be correct when OCF is set, and
	// OCF_Available will be set for newly bought items, even if 50/50 is solid in the landscape
	// however, the motion must be preserved sometimes to keep flags like OCF_HitSpeed upon collection
	if (fCopyMotion)
	{
		// remove any solidmask before copying the motion...
		UpdateSolidMask(false);
		CopyMotion(Contained);
	}
	SetOCF();
	UpdateFace(true);
	// Update container
	Contained->UpdateMass();
	Contained->SetOCF();
	// Collection call
	if (fCalls) pTarget->Call(PSF_Collection2,&C4AulParSet(C4VObj(this)));
	if (!Contained || !Contained->Status || !pTarget->Status) return true;
	// Entrance call
	if (fCalls) Call(PSF_Entrance,&C4AulParSet(C4VObj(Contained)));
	if (!Contained || !Contained->Status || !pTarget->Status) return true;
	// Success
	return true;
}

void C4Object::Fling(C4Real txdir, C4Real tydir, bool fAddSpeed)
{
	if (fAddSpeed) { txdir+=xdir/2; tydir+=ydir/2; }
	if (!ObjectActionTumble(this,(txdir<0),txdir,tydir))
		if (!ObjectActionJump(this,txdir,tydir,false))
		{
			xdir=txdir; ydir=tydir;
			Mobile=1;
			Action.t_attach&=~CNAT_Bottom;
		}
}

bool C4Object::ActivateEntrance(int32_t by_plr, C4Object *by_obj)
{

	// Try entrance activation
	if (OCF & OCF_Entrance)
		if (!! Call(PSF_ActivateEntrance,&C4AulParSet(C4VObj(by_obj))))
			return true;
	// Failure
	return false;
}

bool C4Object::Push(C4Real txdir, C4Real dforce, bool fStraighten)
{
	// Valid check
	if (!Status || !Def || Contained || !(OCF & OCF_Grab)) return false;
	// Grabbing okay, no pushing
	if (GetPropertyInt(P_Touchable)==2) return true;
	// Mobilization check (pre-mobilization zero)
	if (!Mobile)
		{ xdir=ydir=Fix0; }
	// General pushing force vs. object mass
	dforce=dforce*100/Mass;
	// Set dir
	if (xdir<0) SetDir(DIR_Left);
	if (xdir>0) SetDir(DIR_Right);
	// Work towards txdir
	if (Abs(xdir-txdir)<=dforce) // Close-enough-set
		{ xdir=txdir; }
	else // Work towards
	{
		if (xdir<txdir) xdir+=dforce;
		if (xdir>txdir) xdir-=dforce;
	}
	// Straighten
	if (fStraighten)
	{
		if (Inside<int32_t>(r,-StableRange,+StableRange))
		{
			rdir=0; // cheap way out
		}
		else
		{
			if (r>0) { if (rdir>-RotateAccel) rdir-=dforce; }
			else { if (rdir<+RotateAccel) rdir+=dforce; }
		}
	}

	// Mobilization check
	if (!!xdir || !!ydir || !!rdir) Mobile=1;

	// Stuck check
	if (!::Game.iTick35) if (txdir) if (!Def->NoHorizontalMove)
				if (ContactCheck(GetX(), GetY())) // Resets t_contact
				{
					GameMsgObjectError(FormatString(LoadResStr("IDS_OBJ_STUCK"),GetName()).getData(),this);
					Call(PSF_Stuck);
				}

	return true;
}

bool C4Object::Lift(C4Real tydir, C4Real dforce)
{
	// Valid check
	if (!Status || !Def || Contained) return false;
	// Mobilization check
	if (!Mobile)
		{ xdir=ydir=Fix0; Mobile=1; }
	// General pushing force vs. object mass
	dforce=dforce*100/Mass;
	// If close enough, set tydir
	if (Abs(tydir-ydir)<=Abs(dforce))
		ydir=tydir;
	else // Work towards tydir
	{
		if (ydir<tydir) ydir+=dforce;
		if (ydir>tydir) ydir-=dforce;
	}
	// Stuck check
	if (tydir != -GravAccel)
		if (ContactCheck(GetX(), GetY())) // Resets t_contact
		{
			GameMsgObjectError(FormatString(LoadResStr("IDS_OBJ_STUCK"),GetName()).getData(),this);
			Call(PSF_Stuck);
		}
	return true;
}

C4Object* C4Object::CreateContents(C4PropList * PropList)
{
	C4Object *nobj;
	if (!(nobj=Game.CreateObject(PropList,this,Owner))) return NULL;
	if (!nobj->Enter(this)) { nobj->AssignRemoval(); return NULL; }
	return nobj;
}

bool C4Object::CreateContentsByList(C4IDList &idlist)
{
	int32_t cnt,cnt2;
	for (cnt=0; idlist.GetID(cnt); cnt++)
		for (cnt2=0; cnt2<idlist.GetCount(cnt); cnt2++)
			if (!CreateContents(C4Id2Def(idlist.GetID(cnt))))
				return false;
	return true;
}

static void DrawMenuSymbol(int32_t iMenu, C4Facet &cgo, int32_t iOwner)
{
	C4Facet ccgo;

	DWORD dwColor=0;
	if (ValidPlr(iOwner)) dwColor=::Players.Get(iOwner)->ColorDw;

	switch (iMenu)
	{
	case C4MN_Buy:
		::GraphicsResource.fctFlagClr.DrawClr(ccgo = cgo.GetFraction(75, 75), true, dwColor);
		::GraphicsResource.fctWealth.Draw(ccgo = cgo.GetFraction(100, 50, C4FCT_Left, C4FCT_Bottom));
		::GraphicsResource.fctArrow.Draw(ccgo = cgo.GetFraction(70, 70, C4FCT_Right, C4FCT_Center), false, 0);
		break;
	case C4MN_Sell:
		::GraphicsResource.fctFlagClr.DrawClr(ccgo = cgo.GetFraction(75, 75), true, dwColor);
		::GraphicsResource.fctWealth.Draw(ccgo = cgo.GetFraction(100, 50, C4FCT_Left, C4FCT_Bottom));
		::GraphicsResource.fctArrow.Draw(ccgo = cgo.GetFraction(70, 70, C4FCT_Right, C4FCT_Center), false, 1);
		break;
	}
}

bool C4Object::ActivateMenu(int32_t iMenu, int32_t iMenuSelect,
                            int32_t iMenuData, int32_t iMenuPosition,
                            C4Object *pTarget)
{
	// Variables
	C4FacetSurface fctSymbol;
	C4IDList ListItems;
	// Close any other menu
	//CloseMenu(true);
	if (Menu && Menu->IsActive()) if (!Menu->TryClose(true, false)) return false;
	// Create menu
	if (!Menu) Menu = new C4ObjectMenu; else Menu->ClearItems();
	// Open menu
	switch (iMenu)
	{
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	case C4MN_Activate:
		// No target specified: use own container as target
		if (!pTarget) if (!(pTarget=Contained)) break;
		// Opening contents menu blocked by RejectContents
		if (!!pTarget->Call(PSF_RejectContents)) return false;
		// Create symbol
		fctSymbol.Create(C4SymbolSize,C4SymbolSize);
		pTarget->Def->Draw(fctSymbol,false,pTarget->Color,pTarget);
		// Init
		Menu->Init(fctSymbol,FormatString(LoadResStr("IDS_OBJ_EMPTY"),pTarget->GetName()).getData(),this,C4MN_Extra_None,0,iMenu);
		Menu->SetPermanent(true);
		Menu->SetRefillObject(pTarget);
		// Success
		return true;
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	case C4MN_Buy:
		// No target specified: container is base
		if (!pTarget) if (!(pTarget=Contained)) break;
		// Create symbol
		fctSymbol.Create(C4SymbolSize,C4SymbolSize);
		//pTarget->Def->Draw(fctSymbol,false,pTarget->Color,pTarget);
		DrawMenuSymbol(C4MN_Buy, fctSymbol, pTarget->Owner);
		// Init menu
		Menu->Init(fctSymbol,LoadResStr("IDS_PLR_NOBUY"),this,C4MN_Extra_Value,0,iMenu);
		Menu->SetPermanent(true);
		Menu->SetRefillObject(pTarget);
		// Success
		return true;
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	case C4MN_Sell:
		// No target specified: container is base
		if (!pTarget) if (!(pTarget=Contained)) break;
		// Create symbol & init
		fctSymbol.Create(C4SymbolSize,C4SymbolSize);
		//pTarget->Def->Draw(fctSymbol,false,pTarget->Color,pTarget);
		DrawMenuSymbol(C4MN_Sell, fctSymbol, pTarget->Owner);
		Menu->Init(fctSymbol,FormatString(LoadResStr("IDS_OBJ_EMPTY"),pTarget->GetName()).getData(),this,C4MN_Extra_Value,0,iMenu);
		Menu->SetPermanent(true);
		Menu->SetRefillObject(pTarget);
		// Success
		return true;
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	case C4MN_Get:
	case C4MN_Contents:
		// No target specified
		if (!pTarget) break;
		// Opening contents menu blocked by RejectContents
		if (!!pTarget->Call(PSF_RejectContents)) return false;
		// Create symbol & init
		fctSymbol.Create(C4SymbolSize,C4SymbolSize);
		pTarget->Def->Draw(fctSymbol,false,pTarget->Color,pTarget);
		Menu->Init(fctSymbol,FormatString(LoadResStr("IDS_OBJ_EMPTY"),pTarget->GetName()).getData(),this,C4MN_Extra_None,0,iMenu);
		Menu->SetPermanent(true);
		Menu->SetRefillObject(pTarget);
		// Success
		return true;
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	case C4MN_Info:
		// Target by parameter
		if (!pTarget) break;
		// Create symbol & init menu
		fctSymbol.Create(C4SymbolSize, C4SymbolSize); GfxR->fctOKCancel.Draw(fctSymbol,true,0,1);
		Menu->Init(fctSymbol, pTarget->GetName(), this, C4MN_Extra_None, 0, iMenu, C4MN_Style_Info);
		Menu->SetPermanent(true);
		Menu->SetAlignment(C4MN_Align_Free);
		C4Viewport *pViewport = ::Viewports.GetViewport(Controller); // Hackhackhack!!!
		if (pViewport) Menu->SetLocation((pTarget->GetX() + pTarget->Shape.GetX() + pTarget->Shape.Wdt + 10 - pViewport->ViewX) * pViewport->GetZoom(),
			                                 (pTarget->GetY() + pTarget->Shape.GetY() - pViewport->ViewY) * pViewport->GetZoom());
		// Add info item
		fctSymbol.Create(C4PictureSize, C4PictureSize); pTarget->Def->Draw(fctSymbol, false, pTarget->Color, pTarget);
		Menu->Add(pTarget->GetName(), fctSymbol, "", C4MN_Item_NoCount, NULL, pTarget->GetInfoString().getData());
		fctSymbol.Default();
		// Success
		return true;
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	}
	// Invalid menu identification
	CloseMenu(true);
	return false;
}

bool C4Object::CloseMenu(bool fForce)
{
	if (Menu)
	{
		if (Menu->IsActive()) if (!Menu->TryClose(fForce, false)) return false;
		if (!Menu->IsCloseQuerying()) { delete Menu; Menu=NULL; } // protect menu deletion from recursive menu operation calls
	}
	return true;
}

BYTE C4Object::GetArea(int32_t &aX, int32_t &aY, int32_t &aWdt, int32_t &aHgt)
{
	if (!Status || !Def) return 0;
	aX = GetX() + Shape.GetX(); aY = GetY() + Shape.GetY();
	aWdt=Shape.Wdt; aHgt=Shape.Hgt;
	return 1;
}

BYTE C4Object::GetEntranceArea(int32_t &aX, int32_t &aY, int32_t &aWdt, int32_t &aHgt)
{
	if (!Status || !Def) return 0;
	// Return actual entrance
	if (OCF & OCF_Entrance)
	{
		aX=GetX() + Def->Entrance.x;
		aY=GetY() + Def->Entrance.y;
		aWdt=Def->Entrance.Wdt;
		aHgt=Def->Entrance.Hgt;
	}
	// Return object center
	else
	{
		aX=GetX(); aY=GetY();
		aWdt=0; aHgt=0;
	}
	// Done
	return 1;
}

BYTE C4Object::GetMomentum(C4Real &rxdir, C4Real &rydir)
{
	rxdir=rydir=0;
	if (!Status || !Def) return 0;
	rxdir=xdir; rydir=ydir;
	return 1;
}

C4Real C4Object::GetSpeed()
{
	C4Real cobjspd=Fix0;
	if (xdir<0) cobjspd-=xdir; else cobjspd+=xdir;
	if (ydir<0) cobjspd-=ydir; else cobjspd+=ydir;
	return cobjspd;
}

StdStrBuf C4Object::GetDataString()
{
	StdStrBuf Output;
	// Type
	Output.AppendFormat(LoadResStr("IDS_CNS_TYPE"),GetName(),Def->id.ToString());
	// Owner
	if (ValidPlr(Owner))
	{
		Output.Append(LineFeed);
		Output.AppendFormat(LoadResStr("IDS_CNS_OWNER"),::Players.Get(Owner)->GetName());
	}
	// Contents
	if (Contents.ObjectCount())
	{
		Output.Append(LineFeed);
		Output.Append(LoadResStr("IDS_CNS_CONTENTS"));
		Output.Append(Contents.GetNameList(::Definitions));
	}
	// Action
	if (GetAction())
	{
		Output.Append(LineFeed);
		Output.Append(LoadResStr("IDS_CNS_ACTION"));
		Output.Append(GetAction()->GetName());
	}
	// Properties
	Output.Append(LineFeed);
	Output.Append(LoadResStr("IDS_CNS_PROPERTIES"));
	Output.Append(LineFeed "  ");
	AppendDataString(&Output, LineFeed "  ");
	// Effects
	if (pEffects)
	{
		Output.Append(LineFeed);
		Output.Append(LoadResStr("IDS_CNS_EFFECTS"));
	}
	for (C4Effect *pEffect = pEffects; pEffect; pEffect = pEffect->pNext)
	{
		Output.Append(LineFeed);
		// Effect name
		Output.AppendFormat("  %s: Priority %d, Interval %d", pEffect->GetName(), pEffect->iPriority, pEffect->iInterval);
	}

	StdStrBuf Output2;
	C4ValueNumbers numbers;
	DecompileToBuf_Log<StdCompilerINIWrite>(mkNamingAdapt(mkInsertAdapt(mkParAdapt(*this, &numbers),
	                                                                    mkNamingAdapt(numbers, "Values"), false),
	                                                      "Object"), &Output2, "C4Object::GetDataString");
	Output.Append(LineFeed);
	Output.Append(Output2);
	return Output;
}

void C4Object::SetName(const char * NewName)
{
	if (!NewName && Info)
		C4PropList::SetName(Info->Name);
	else
		C4PropList::SetName(NewName);
}

int32_t C4Object::GetValue(C4Object *pInBase, int32_t iForPlayer)
{
	C4Value r = Call(PSF_CalcValue, &C4AulParSet(C4VObj(pInBase), C4VInt(iForPlayer)));
	int32_t iValue;
	if (r != C4VNull)
		iValue = r.getInt();
	else
	{
		// get value of def
		// Caution: Do not pass pInBase here, because the def base value is to be queried
		//  - and not the value if you had to buy the object in this particular base
		iValue = Def->GetValue(NULL, iForPlayer);
	}
	// Con percentage
	iValue = iValue * Con / FullCon;
	// do any adjustments based on where the item is bought
	if (pInBase)
	{
		r = pInBase->Call(PSF_CalcSellValue, &C4AulParSet(C4VObj(this), C4VInt(iValue)));
		if (r != C4VNull)
			iValue = r.getInt();
	}
	return iValue;
}

bool C4Object::Promote(int32_t torank, bool exception, bool fForceRankName)
{
	if (!Info) return false;
	// get rank system
	C4Def *pUseDef = C4Id2Def(Info->id);
	C4RankSystem *pRankSys;
	if (pUseDef && pUseDef->pRankNames)
		pRankSys = pUseDef->pRankNames;
	else
		pRankSys = &::DefaultRanks;
	// always promote info
	Info->Promote(torank,*pRankSys, fForceRankName);
	// silent update?
	if (!pRankSys->GetRankName(torank,false)) return false;
	GameMsgObject(FormatString(LoadResStr("IDS_OBJ_PROMOTION"),GetName (),Info->sRankName.getData()).getData(),this);

	// call to object
	Call(PSF_Promotion);

	StartSoundEffect("Trumpet",0,100,this);
	return true;
}

void C4Object::ClearPointers(C4Object *pObj)
{
	// TODO: Clear pointers on mesh instance:
	// Check for attach children using pObj's mesh instance
	// Check for animation nodes refering to pObj (Anim_X, ...).

	// effects
	if (pEffects) pEffects->ClearPointers(pObj);
	// contents/contained: not necessary, because it's done in AssignRemoval and StatusDeactivate
	// Action targets
	if (Action.Target==pObj) Action.Target=NULL;
	if (Action.Target2==pObj) Action.Target2=NULL;
	// Commands
	C4Command *cCom;
	for (cCom=Command; cCom; cCom=cCom->Next)
		cCom->ClearPointers(pObj);
	// Menu
	if (Menu) Menu->ClearPointers(pObj);
	// Layer
	if (Layer==pObj) Layer=NULL;
	// gfx overlays
	if (pGfxOverlay)
	{
		C4GraphicsOverlay *pNextGfxOvrl = pGfxOverlay, *pGfxOvrl;
		while ((pGfxOvrl = pNextGfxOvrl))
		{
			pNextGfxOvrl = pGfxOvrl->GetNext();
			if (pGfxOvrl->GetOverlayObject() == pObj)
				// overlay relying on deleted object: Delete!
				RemoveGraphicsOverlay(pGfxOvrl->GetID());
		}
	}
}

bool C4Object::SetPhase(int32_t iPhase)
{
	C4PropList* pActionDef = GetAction();
	if (!pActionDef) return false;
	const int32_t length = pActionDef->GetPropertyInt(P_Length);
	Action.Phase=BoundBy<int32_t>(iPhase,0,length);
	Action.PhaseDelay = 0;
	return true;
}

void C4Object::Draw(C4TargetFacet &cgo, int32_t iByPlayer, DrawMode eDrawMode, float offX, float offY)
{
	C4Facet ccgo;

	// Status
	if (!Status || !Def) return;

	// visible?
	if (!IsVisible(iByPlayer, !!eDrawMode)) return;

	// Line
	if (Def->Line) { DrawLine(cgo); return; }

	// background particles (bounds not checked)
	if (BackParticles && !Contained && eDrawMode!=ODM_BaseOnly) BackParticles.Draw(cgo,this);

	// Object output position
	float newzoom;
	if (eDrawMode!=ODM_Overlay)
	{
		if (!GetDrawPosition(cgo, offX, offY, newzoom)) return;
	}
	ZoomDataStackItem zdsi(newzoom);

	bool fYStretchObject=false;
	C4PropList* pActionDef = GetAction();
	if (pActionDef)
		if (pActionDef->GetPropertyInt(P_FacetTargetStretch))
			fYStretchObject=true;

	// Set audibility
	if (!eDrawMode) SetAudibilityAt(cgo, GetX(), GetY());

	// Output boundary
	if (!fYStretchObject && !eDrawMode && !(Category & C4D_Parallax))
	{
		if (pActionDef && !r && !pActionDef->GetPropertyInt(P_FacetBase) && Con<=FullCon)
		{
			// active
			if ( !Inside<float>(offX+Shape.GetX()+Action.FacetX,cgo.X-Action.Facet.Wdt,cgo.X+cgo.Wdt)
			     || (!Inside<float>(offY+Shape.GetY()+Action.FacetY,cgo.Y-Action.Facet.Hgt,cgo.Y+cgo.Hgt)) )
				{ if (FrontParticles && !Contained) FrontParticles.Draw(cgo,this); return; }
		}
		else
			// idle
			if ( !Inside<float>(offX+Shape.GetX(),cgo.X-Shape.Wdt,cgo.X+cgo.Wdt)
			     || (!Inside<float>(offY+Shape.GetY(),cgo.Y-Shape.Hgt,cgo.Y+cgo.Hgt)) )
				{ if (FrontParticles && !Contained) FrontParticles.Draw(cgo,this); return; }
	}

	// ensure correct color is set
	if (GetGraphics()->Type == C4DefGraphics::TYPE_Bitmap)
		if (GetGraphics()->Bmp.BitmapClr) GetGraphics()->Bmp.BitmapClr->SetClr(Color);

	// Debug Display //////////////////////////////////////////////////////////////////////
	if (::GraphicsSystem.ShowCommand && !eDrawMode)
	{
		C4Command *pCom;
		int32_t ccx=GetX(),ccy=GetY();
		float offX1, offY1, offX2, offY2, newzoom;
		char szCommand[200];
		StdStrBuf Cmds;
		int32_t iMoveTos=0;
		for (pCom=Command; pCom; pCom=pCom->Next)
		{
			switch (pCom->Command)
			{
			case C4CMD_MoveTo:
				// Angle
				int32_t iAngle; iAngle=Angle(ccx,ccy,pCom->Tx._getInt(),pCom->Ty); while (iAngle>180) iAngle-=360;
				// Path
				if(GetDrawPosition(cgo, ccx, ccy, cgo.Zoom, offX1, offY1, newzoom) &&
				   GetDrawPosition(cgo, pCom->Tx._getInt(), pCom->Ty, cgo.Zoom, offX2, offY2, newzoom))
				{
					ZoomDataStackItem zdsi(newzoom);
					pDraw->DrawLineDw(cgo.Surface,offX1,offY1,offX2,offY2,C4RGB(0xca,0,0));
					pDraw->DrawFrameDw(cgo.Surface,offX2-1,offY2-1,offX2+1,offY2+1,C4RGB(0xca,0,0));
				}

				ccx=pCom->Tx._getInt(); ccy=pCom->Ty;
				// Message
				iMoveTos++; szCommand[0]=0;
				//sprintf(szCommand,"%s %d/%d",CommandName(pCom->Command),pCom->Tx,pCom->Ty,iAngle);
				break;
			case C4CMD_Put:
				sprintf(szCommand,"%s %s to %s",CommandName(pCom->Command),pCom->Target2 ? pCom->Target2->GetName() : pCom->Data ? pCom->Data.getC4ID().ToString() : "Content",pCom->Target ? pCom->Target->GetName() : "");
				break;
			case C4CMD_Buy: case C4CMD_Sell:
				sprintf(szCommand,"%s %s at %s",CommandName(pCom->Command),pCom->Data.getC4ID().ToString(),pCom->Target ? pCom->Target->GetName() : "closest base");
				break;
			case C4CMD_Acquire:
				sprintf(szCommand,"%s %s",CommandName(pCom->Command),pCom->Data.getC4ID().ToString());
				break;
			case C4CMD_Call:
				sprintf(szCommand,"%s %s in %s",CommandName(pCom->Command),pCom->Text->GetCStr(),pCom->Target ? pCom->Target->GetName() : "(null)");
				break;
			case C4CMD_None:
				szCommand[0]=0;
				break;
			case C4CMD_Transfer:
				// Path
				if(GetDrawPosition(cgo, ccx, ccy, cgo.Zoom, offX1, offY1, newzoom) &&
				   GetDrawPosition(cgo, pCom->Tx._getInt(), pCom->Ty, cgo.Zoom, offX2, offY2, newzoom))
				{
					ZoomDataStackItem zdsi(newzoom);
					pDraw->DrawLineDw(cgo.Surface,offX1,offY1,offX2,offY2,C4RGB(0,0xca,0));
					pDraw->DrawFrameDw(cgo.Surface,offX2-1,offY2-1,offX2+1,offY2+1,C4RGB(0,0xca,0));
				}

				ccx=pCom->Tx._getInt(); ccy=pCom->Ty;
				// Message
				sprintf(szCommand,"%s %s",CommandName(pCom->Command),pCom->Target ? pCom->Target->GetName() : "");
				break;
			default:
				sprintf(szCommand,"%s %s",CommandName(pCom->Command),pCom->Target ? pCom->Target->GetName() : "");
				break;
			}
			// Compose command stack message
			if (szCommand[0])
			{
				// End MoveTo stack first
				if (iMoveTos) { Cmds.AppendChar('|'); Cmds.AppendFormat("%dx MoveTo",iMoveTos); iMoveTos=0; }
				// Current message
				Cmds.AppendChar('|');
				if (pCom->Finished) Cmds.Append("<i>");
				Cmds.Append(szCommand);
				if (pCom->Finished) Cmds.Append("</i>");
			}
		}
		// Open MoveTo stack
		if (iMoveTos) { Cmds.AppendChar('|'); Cmds.AppendFormat("%dx MoveTo",iMoveTos); iMoveTos=0; }
		// Draw message
		int32_t cmwdt,cmhgt;  ::GraphicsResource.FontRegular.GetTextExtent(Cmds.getData(),cmwdt,cmhgt,true);
		pDraw->TextOut(Cmds.getData(), ::GraphicsResource.FontRegular, 1.0, cgo.Surface,offX,offY+Shape.GetY()-10-cmhgt,C4Draw::DEFAULT_MESSAGE_COLOR,ACenter);
	}
	// Debug Display ///////////////////////////////////////////////////////////////////////////////

	// Don't draw (show solidmask)
	if (::GraphicsSystem.ShowSolidMask)
		if (SolidMask.Wdt)
		{
			// DrawSolidMask(cgo); - no need to draw it, because the 8bit-surface will be shown
			return;
		}

	// Contained check
	if (Contained && !eDrawMode) return;

	// Visibility inside FoW
	bool fOldClrModEnabled = !!(Category & C4D_IgnoreFoW);
	if (fOldClrModEnabled)
	{
		fOldClrModEnabled = pDraw->GetClrModMapEnabled();
		pDraw->SetClrModMapEnabled(false);
	}

	// Fire facet - always draw, even if particles are drawn as well
	if (OnFire /*&& !::Particles.IsFireParticleLoaded()*/) if (eDrawMode!=ODM_BaseOnly)
		{
			C4Facet fgo;
			// Straight: Full Shape.Rect on fire
			if (r==0)
			{
				fgo.Set(cgo.Surface,offX + Shape.GetX(),offY + Shape.GetY(),
				        Shape.Wdt,Shape.Hgt-Shape.FireTop);
			}
			// Rotated: Reduced fire rect
			else
			{
				C4Rect fr;
				Shape.GetVertexOutline(fr);
				fgo.Set(cgo.Surface,
				        offX + fr.x,
				        offY + fr.y,
				        fr.Wdt, fr.Hgt);
			}
			::GraphicsResource.fctFire.Draw(fgo,false,(Number + Game.FrameCounter) % MaxFirePhase);
		}

	// color modulation (including construction sign...)
	if (ColorMod != 0xffffffff || BlitMode) if (!eDrawMode) PrepareDrawing();

	// Not active or rotated: BaseFace only
	if (!pActionDef)
	{
		DrawFace(cgo, offX, offY);
	}

	// Active
	else
	{
		// FacetBase
		if (pActionDef->GetPropertyInt(P_FacetBase) || GetGraphics()->Type != C4DefGraphics::TYPE_Bitmap)
			DrawFace(cgo, offX, offY, 0, Action.DrawDir);

		// Special: stretched action facet
		if (Action.Facet.Surface && pActionDef->GetPropertyInt(P_FacetTargetStretch))
		{
			if (Action.Target)
				pDraw->Blit(Action.Facet.Surface,
				              float(Action.Facet.X),float(Action.Facet.Y),float(Action.Facet.Wdt),float(Action.Facet.Hgt),
				              cgo.Surface,
				              offX + Shape.GetX() + Action.FacetX, offY + Shape.GetY() + Action.FacetY,Action.Facet.Wdt,
				              (fixtof(Action.Target->fix_y) + Action.Target->Shape.GetY()) - (fixtof(fix_y) + Shape.GetY() + Action.FacetY),
				              true);
		}
		else if (Action.Facet.Surface)
			DrawActionFace(cgo, offX, offY);
	}

	// end of color modulation
	if (ColorMod != 0xffffffff || BlitMode) if (!eDrawMode) FinishedDrawing();

	// draw overlays - after blit mode changes, because overlay gfx set their own
	if (pGfxOverlay) if (eDrawMode!=ODM_BaseOnly)
			for (C4GraphicsOverlay *pGfxOvrl = pGfxOverlay; pGfxOvrl; pGfxOvrl = pGfxOvrl->GetNext())
				if (!pGfxOvrl->IsPicture())
					pGfxOvrl->Draw(cgo, this, iByPlayer);

	// local particles in front of the object
	if (FrontParticles) if (eDrawMode!=ODM_BaseOnly) FrontParticles.Draw(cgo,this);

	// Debug Display ////////////////////////////////////////////////////////////////////////
	if (::GraphicsSystem.ShowVertices) if (eDrawMode!=ODM_BaseOnly)
		{
			int32_t cnt;
			if (Shape.VtxNum>1)
				for (cnt=0; cnt<Shape.VtxNum; cnt++)
				{
					DrawVertex(cgo,
					           offX+Shape.VtxX[cnt],
					           offY+Shape.VtxY[cnt],
					           (Shape.VtxCNAT[cnt] & CNAT_NoCollision) ? C4RGB(0, 0, 0xff) : (Mobile ? C4RGB(0xff, 0, 0) : C4RGB(0xef, 0xef, 0)),
					           Shape.VtxContactCNAT[cnt]);
				}
		}

	if (::GraphicsSystem.ShowEntrance) if (eDrawMode!=ODM_BaseOnly)
		{
			if (OCF & OCF_Entrance)
				pDraw->DrawFrameDw(cgo.Surface,offX+Def->Entrance.x,
				                             offY+Def->Entrance.y,
				                             offX+Def->Entrance.x+Def->Entrance.Wdt-1,
				                             offY+Def->Entrance.y+Def->Entrance.Hgt-1,
				                             C4RGB(0, 0, 0xff));
			if (OCF & OCF_Collection)
				pDraw->DrawFrameDw(cgo.Surface,offX+Def->Collection.x,
				                             offY+Def->Collection.y,
				                             offX+Def->Collection.x+Def->Collection.Wdt-1,
				                             offY+Def->Collection.y+Def->Collection.Hgt-1,
				                             C4RGB(0xca, 0, 0));
		}

	if (::GraphicsSystem.ShowAction) if (eDrawMode!=ODM_BaseOnly)
		{
			if (pActionDef)
			{
				StdStrBuf str;
				str.Format("%s (%d)",pActionDef->GetName(),Action.Phase);
				int32_t cmwdt,cmhgt; ::GraphicsResource.FontRegular.GetTextExtent(str.getData(),cmwdt,cmhgt,true);
				pDraw->TextOut(str.getData(), ::GraphicsResource.FontRegular,
				                           1.0, cgo.Surface, offX, offY + Shape.GetY() - cmhgt,
				                           InLiquid ? 0xfa0000FF : C4Draw::DEFAULT_MESSAGE_COLOR, ACenter);
			}
		}
	// Debug Display ///////////////////////////////////////////////////////////////////////

	// Restore visibility inside FoW
	if (fOldClrModEnabled) pDraw->SetClrModMapEnabled(fOldClrModEnabled);

}

void C4Object::DrawTopFace(C4TargetFacet &cgo, int32_t iByPlayer, DrawMode eDrawMode, float offX, float offY)
{
	// Status
	if (!Status || !Def) return;
	// visible?
	if (!IsVisible(iByPlayer, eDrawMode==ODM_Overlay)) return;
	// target pos (parallax)
	float newzoom;
	if (eDrawMode!=ODM_Overlay) GetDrawPosition(cgo, offX, offY, newzoom);
	ZoomDataStackItem zdsi(newzoom);
	// Clonk name
	// Name of Owner/Clonk (only when Crew Member; never in films)
	if (OCF & OCF_CrewMember)
		if ((Config.Graphics.ShowCrewNames || Config.Graphics.ShowCrewCNames) && (!Game.C4S.Head.Film || !Game.C4S.Head.Replay))
			if (!eDrawMode)
				if (Owner != iByPlayer && !Contained)
				{
					// inside screen range?
					if (!Inside<int>(offX + Shape.GetX(), cgo.X - Shape.Wdt, cgo.X + cgo.Wdt)
					    || !Inside<int>(offY + Shape.GetY(), cgo.Y - Shape.Hgt, cgo.Y + cgo.Hgt)) return;
					// get player
					C4Player* pOwner = ::Players.Get(Owner);
					if (pOwner)
						if (!Hostile(Owner, iByPlayer))
							if (!pOwner->IsInvisible())
							{
								// compose string
								char szText[C4GM_MaxText+1];
								if (Config.Graphics.ShowCrewNames)
									if (Config.Graphics.ShowCrewCNames)
										sprintf(szText, "%s (%s)", GetName(), pOwner->GetName());
									else
										SCopy(pOwner->GetName(),szText);
								else
									SCopy(GetName(),szText);
								// Word wrap to cgo width
								int32_t iCharWdt, dummy; ::GraphicsResource.FontRegular.GetTextExtent("m", iCharWdt, dummy, false);
								int32_t iMaxLine = Max<int32_t>( cgo.Wdt / iCharWdt, 20 );
								SWordWrap(szText,' ','|',iMaxLine);
								// Adjust position by output boundaries
								float iTX,iTY;
								int iTWdt,iTHgt;
								::GraphicsResource.FontRegular.GetTextExtent(szText,iTWdt,iTHgt, true);
								iTX = BoundBy<int>(offX, cgo.X + iTWdt / 2, cgo.X + cgo.Wdt - iTWdt / 2);
								iTY = BoundBy<int>(offY - Def->Shape.Hgt / 2 - 20 - iTHgt, cgo.Y, cgo.Y + cgo.Hgt - iTHgt);
								// Draw
								pDraw->TextOut(szText, ::GraphicsResource.FontRegular, 1.0, cgo.Surface, iTX, iTY,
								                           pOwner->ColorDw|0x7f000000,ACenter);
							}
				}
	// TopFace
	if (!(TopFace.Surface || (OCF & OCF_Construct))) return;
	// Output bounds check
	if (!Inside<float>(offX, cgo.X - Shape.Wdt, cgo.X + cgo.Wdt)
	    || !Inside<float>(offY, cgo.Y - Shape.Hgt, cgo.Y + cgo.Hgt))
		return;
	// Don't draw (show solidmask)
	if (::GraphicsSystem.ShowSolidMask && SolidMask.Wdt) return;
	// Contained
	if (Contained) if (eDrawMode!=ODM_Overlay) return;
	// Construction sign
	if (OCF & OCF_Construct && r == 0)
		if (eDrawMode!=ODM_BaseOnly)
		{
			C4Facet &fctConSign = ::GraphicsResource.fctConstruction;
			pDraw->Blit(fctConSign.Surface,
			              fctConSign.X, fctConSign.Y,
			              fctConSign.Wdt, fctConSign.Hgt,
			              cgo.Surface,
			              offX + Shape.GetX(), offY + Shape.GetY() + Shape.Hgt - fctConSign.Hgt,
			              fctConSign.Wdt, fctConSign.Hgt, true);
		}
	if(TopFace.Surface)
	{
		// FacetTopFace: Override TopFace.GetX()/GetY()
		C4PropList* pActionDef = GetAction();
		if (pActionDef && pActionDef->GetPropertyInt(P_FacetTopFace))
		{
			int32_t iPhase = Action.Phase;
			if (pActionDef->GetPropertyInt(P_Reverse)) iPhase = pActionDef->GetPropertyInt(P_Length) - 1 - Action.Phase;
			TopFace.X = pActionDef->GetPropertyInt(P_X) + Def->TopFace.x + pActionDef->GetPropertyInt(P_Wdt) * iPhase;
			TopFace.Y = pActionDef->GetPropertyInt(P_Y) + Def->TopFace.y + pActionDef->GetPropertyInt(P_Hgt) * Action.DrawDir;
		}
		// ensure correct color is set
		if (GetGraphics()->Bmp.BitmapClr) GetGraphics()->Bmp.BitmapClr->SetClr(Color);
		// color modulation
		if (!eDrawMode) PrepareDrawing();
		// Draw top face bitmap
		if (Con!=FullCon && Def->GrowthType)
			// stretched
			pDraw->Blit(TopFace.Surface,
				            TopFace.X, TopFace.Y, TopFace.Wdt, TopFace.Hgt,
				            cgo.Surface,
				            offX + Shape.GetX() + float(Def->TopFace.tx * Con) / FullCon, offY + Shape.GetY() + float(Def->TopFace.ty * Con) / FullCon,
				            float(TopFace.Wdt * Con) / FullCon, float(TopFace.Hgt * Con) / FullCon,
				            true, pDrawTransform ? &C4DrawTransform(*pDrawTransform, offX, offY) : NULL);
		else
			// normal
			pDraw->Blit(TopFace.Surface,
				            TopFace.X,TopFace.Y,
				            TopFace.Wdt,TopFace.Hgt,
				            cgo.Surface,
				            offX + Shape.GetX() + Def->TopFace.tx, offY + Shape.GetY() + Def->TopFace.ty,
				            TopFace.Wdt, TopFace.Hgt,
				            true, pDrawTransform ? &C4DrawTransform(*pDrawTransform, offX, offY) : NULL);
	}
	// end of color modulation
	if (!eDrawMode) FinishedDrawing();
}

void C4Object::DrawLine(C4TargetFacet &cgo)
{
	// Audibility
	SetAudibilityAt(cgo, Shape.VtxX[0],Shape.VtxY[0]);
	SetAudibilityAt(cgo, Shape.VtxX[Shape.VtxNum-1],Shape.VtxY[Shape.VtxNum-1]);
	// additive mode?
	PrepareDrawing();
	// Draw line segments
	C4Value colorsV; GetProperty(P_LineColors, &colorsV);
	C4ValueArray *colors = colorsV.getArray();
	int32_t color0 = 0xFFFF00FF, color1 = 0xFFFF00FF; // use bright colors so author notices
	if (colors)
	{
		color0 = colors->GetItem(0).getInt();
		color1 = colors->GetItem(1).getInt();
	}
	for (int32_t vtx=0; vtx+1<Shape.VtxNum; vtx++)
		cgo.DrawLineDw(Shape.VtxX[vtx],Shape.VtxY[vtx],
						Shape.VtxX[vtx+1],Shape.VtxY[vtx+1],
						color0, color1);
	// reset blit mode
	FinishedDrawing();
}

void C4Object::CompileFunc(StdCompiler *pComp, C4ValueNumbers * numbers)
{
	bool fCompiler = pComp->isCompiler();
	if (fCompiler)
		Clear();

	// Compile ID, search definition
	pComp->Value(mkNamingAdapt(id,                  "id",                 C4ID::None          ));
	if (fCompiler)
	{
		Def = ::Definitions.ID2Def(id);
		if (!Def)
			{ pComp->excNotFound(LoadResStr("IDS_PRC_UNDEFINEDOBJECT"),id.ToString()); return; }
	}

	pComp->Value(mkNamingAdapt( mkParAdapt(static_cast<C4PropListNumbered&>(*this), numbers), "Properties"));
	pComp->Value(mkNamingAdapt( Status,                           "Status",             1                 ));
	if (Info) nInfo = Info->Name; else nInfo.Clear();
	pComp->Value(mkNamingAdapt( toC4CStrBuf(nInfo),               "Info",               ""                ));
	pComp->Value(mkNamingAdapt( Owner,                            "Owner",              NO_OWNER          ));
	pComp->Value(mkNamingAdapt( Controller,                       "Controller",         NO_OWNER          ));
	pComp->Value(mkNamingAdapt( LastEnergyLossCausePlayer,        "LastEngLossPlr",     NO_OWNER          ));
	pComp->Value(mkNamingAdapt( Category,                         "Category",           0                 ));
	pComp->Value(mkNamingAdapt( Plane,                            "Plane",              0                 ));

	pComp->Value(mkNamingAdapt( r,                                "Rotation",           0                 ));

	pComp->Value(mkNamingAdapt( iLastAttachMovementFrame,         "LastSolidAtchFrame", -1                ));
	pComp->Value(mkNamingAdapt( NoCollectDelay,                   "NoCollectDelay",     0                 ));
	pComp->Value(mkNamingAdapt( Con,                              "Size",               0                 ));
	pComp->Value(mkNamingAdapt( OwnMass,                          "OwnMass",            0                 ));
	pComp->Value(mkNamingAdapt( Mass,                             "Mass",               0                 ));
	pComp->Value(mkNamingAdapt( Damage,                           "Damage",             0                 ));
	pComp->Value(mkNamingAdapt( Energy,                           "Energy",             0                 ));
	pComp->Value(mkNamingAdapt( Alive,                            "Alive",              false             ));
	pComp->Value(mkNamingAdapt( Breath,                           "Breath",             0                 ));
	pComp->Value(mkNamingAdapt( Color,                            "Color",              0u                ));
	pComp->Value(mkNamingAdapt( fix_x,                            "X",                  Fix0              ));
	pComp->Value(mkNamingAdapt( fix_y,                            "Y",                  Fix0              ));
	pComp->Value(mkNamingAdapt( fix_r,                            "R",                  Fix0              ));
	pComp->Value(mkNamingAdapt( xdir,                             "XDir",               0                 ));
	pComp->Value(mkNamingAdapt( ydir,                             "YDir",               0                 ));
	pComp->Value(mkNamingAdapt( rdir,                             "RDir",               0                 ));
	pComp->Value(mkParAdapt(Shape, true));
	pComp->Value(mkNamingAdapt( fOwnVertices,                     "OwnVertices",        false             ));
	pComp->Value(mkNamingAdapt( SolidMask,                        "SolidMask",          Def->SolidMask    ));
	pComp->Value(mkNamingAdapt( PictureRect,                      "Picture"                               ));
	pComp->Value(mkNamingAdapt( Mobile,                           "Mobile",             false             ));
	pComp->Value(mkNamingAdapt( OnFire,                           "OnFire",             false             ));
	pComp->Value(mkNamingAdapt( InLiquid,                         "InLiquid",           false             ));
	pComp->Value(mkNamingAdapt( EntranceStatus,                   "EntranceStatus",     false             ));
	pComp->Value(mkNamingAdapt( OCF,                              "OCF",                0u                ));
	pComp->Value(Action);
	pComp->Value(mkNamingAdapt( Contained,                        "Contained",          C4ObjectPtr::Null ));
	pComp->Value(mkNamingAdapt( Action.Target,                    "ActionTarget1",      C4ObjectPtr::Null ));
	pComp->Value(mkNamingAdapt( Action.Target2,                   "ActionTarget2",      C4ObjectPtr::Null ));
	pComp->Value(mkNamingAdapt( Component,                        "Component"                             ));
	pComp->Value(mkNamingAdapt( mkParAdapt(Contents, numbers),    "Contents"                              ));
	pComp->Value(mkNamingAdapt( PlrViewRange,                     "PlrViewRange",       0                 ));
	pComp->Value(mkNamingAdapt( ColorMod,                         "ColorMod",           0xffffffffu       ));
	pComp->Value(mkNamingAdapt( BlitMode,                         "BlitMode",           0u                ));
	pComp->Value(mkNamingAdapt( CrewDisabled,                     "CrewDisabled",       false             ));
	pComp->Value(mkNamingAdapt( Layer,                            "Layer",              C4ObjectPtr::Null ));
	pComp->Value(mkNamingAdapt( C4DefGraphicsAdapt(pGraphics),    "Graphics",           &Def->Graphics    ));
	pComp->Value(mkNamingPtrAdapt( pDrawTransform,                "DrawTransform"                         ));
	pComp->Value(mkParAdapt(mkNamingPtrAdapt( pEffects,           "Effects"                               ), numbers));
	pComp->Value(mkNamingAdapt( C4GraphicsOverlayListAdapt(pGfxOverlay),"GfxOverlay",   (C4GraphicsOverlay *)NULL));

	// Serialize mesh instance if we have a mesh graphics
	if(pGraphics->Type == C4DefGraphics::TYPE_Mesh)
	{
		if(pComp->isCompiler())
		{
			assert(!pMeshInstance);
			pMeshInstance = new StdMeshInstance(*pGraphics->Mesh, Def->GrowthType ? 1.0f : static_cast<float>(Con)/static_cast<float>(FullCon));
		}

		pComp->Value(mkNamingAdapt(mkParAdapt(*pMeshInstance, C4MeshDenumeratorFactory), "Mesh"));

		// Does not work because unanimated meshes without attached meshes
		// do not even write a [Mesh] header so this does not create a mesh instance in that case
/*		pComp->Value(mkNamingContextPtrAdapt( pMeshInstance, *pGraphics->Mesh, "Mesh"));
		if(!pMeshInstance)
			pComp->excCorrupt("Mesh graphics without mesh instance");*/
	}

	// TODO: Animations / attached meshes

	// Commands
	if (pComp->FollowName("Commands"))
	{
		if (fCompiler)
		{
			C4Command *pCmd = NULL;
			for (int i = 1; ; i++)
			{
				// Every command has its own naming environment
				StdStrBuf Naming = FormatString("Command%d", i);
				pComp->Value(mkParAdapt(mkNamingPtrAdapt(pCmd ? pCmd->Next : Command, Naming.getData()), numbers));
				// Last command?
				pCmd = (pCmd ? pCmd->Next : Command);
				if (!pCmd)
					break;
				pCmd->cObj = this;
			}
		}
		else
		{
			C4Command *pCmd = Command;
			for (int i = 1; pCmd; i++, pCmd = pCmd->Next)
			{
				StdStrBuf Naming = FormatString("Command%d", i);
				pComp->Value(mkNamingAdapt(mkParAdapt(*pCmd, numbers), Naming.getData()));
			}
		}
	}

	// Compiling? Do initialization.
	if (fCompiler)
	{
		// add to def count
		Def->Count++;


		// Set action (override running data)
		/* FIXME
		int32_t iTime=Action.Time;
		int32_t iPhase=Action.Phase;
		int32_t iPhaseDelay=Action.PhaseDelay;
		if (SetActionByName(Action.pActionDef->GetName(),0,0,false))
		  {
		  Action.Time=iTime;
		  Action.Phase=iPhase; // No checking for valid phase
		  Action.PhaseDelay=iPhaseDelay;
		  }*/

		if (pMeshInstance)
		{
			// Set Action animation by slot 0
			Action.Animation = pMeshInstance->GetRootAnimationForSlot(0);
			pMeshInstance->SetFaceOrderingForClrModulation(ColorMod);
		}

		// blit mode not assigned? use definition default then
		if (!BlitMode) BlitMode = Def->BlitMode;

		// object needs to be resorted? May happen if there's unsorted objects in savegame
		if (Unsorted) Game.fResortAnyObject = true;

	}

}

void C4Object::Denumerate(C4ValueNumbers * numbers)
{
	C4PropList::Denumerate(numbers);
	// Standard enumerated pointers
	Contained.DenumeratePointers();
	Action.Target.DenumeratePointers();
	Action.Target2.DenumeratePointers();
	Layer.DenumeratePointers();

	// Post-compile object list
	Contents.DenumeratePointers();

	// Commands
	for (C4Command *pCom=Command; pCom; pCom=pCom->Next)
		pCom->Denumerate(numbers);

	// effects
	if (pEffects) pEffects->Denumerate(numbers);

	// gfx overlays
	if (pGfxOverlay)
		for (C4GraphicsOverlay *pGfxOvrl = pGfxOverlay; pGfxOvrl; pGfxOvrl = pGfxOvrl->GetNext())
			pGfxOvrl->DenumeratePointers();

	// mesh instance
	if (pMeshInstance) pMeshInstance->DenumeratePointers();
}

void C4Object::DrawPicture(C4Facet &cgo, bool fSelected, C4DrawTransform* transform)
{
	// Draw def picture with object color
	Def->Draw(cgo,fSelected,Color,this,0,0,transform);
}

void C4Object::Picture2Facet(C4FacetSurface &cgo)
{
	// set picture rect to facet
	C4Rect fctPicRect = PictureRect;
	if (!fctPicRect.Wdt) fctPicRect = Def->PictureRect;
	C4Facet fctPicture;
	fctPicture.Set(GetGraphics()->GetBitmap(Color),fctPicRect.x,fctPicRect.y,fctPicRect.Wdt,fctPicRect.Hgt);

	// use direct facet w/o own data if possible
	if (ColorMod == 0xffffffff && BlitMode == C4GFXBLIT_NORMAL && !pGfxOverlay)
	{
		cgo.Set(fctPicture);
		return;
	}

	// otherwise, draw to picture facet
	if (!cgo.Create(cgo.Wdt, cgo.Hgt)) return;

	// specific object color?
	PrepareDrawing();

	// draw picture itself
	fctPicture.Draw(cgo,true);

	// draw overlays
	if (pGfxOverlay)
		for (C4GraphicsOverlay *pGfxOvrl = pGfxOverlay; pGfxOvrl; pGfxOvrl = pGfxOvrl->GetNext())
			if (pGfxOvrl->IsPicture())
				pGfxOvrl->DrawPicture(cgo, this, NULL);

	// done; reset drawing states
	FinishedDrawing();
}

bool C4Object::ValidateOwner()
{
	// Check owner and controller
	if (!ValidPlr(Owner)) Owner=NO_OWNER;
	if (!ValidPlr(Controller)) Controller=NO_OWNER;
	// Color is not reset any more, because many scripts change colors to non-owner-colors these days
	// Additionally, player colors are now guarantueed to remain the same in savegame resumes
	return true;
}

bool C4Object::AssignInfo()
{
	if (Info || !ValidPlr(Owner)) return false;
	// In crew list?
	C4Player *pPlr = ::Players.Get(Owner);
	if (pPlr->Crew.GetLink(this))
	{
		// Register with player
		if (!::Players.Get(Owner)->MakeCrewMember(this, true, false))
			pPlr->Crew.Remove(this);
		return true;
	}
	// Info set, but not in crew list, so
	//    a) The savegame is old-style (without crew list)
	// or b) The clonk is dead
	// or c) The clonk belongs to a script player that's restored without Game.txt
	else if (nInfo.getLength())
	{
		if (!::Players.Get(Owner)->MakeCrewMember(this, true, false))
			return false;
		// Dead and gone (info flags, remove from crew/cursor)
		if (!Alive)
		{
			if (ValidPlr(Owner)) ::Players.Get(Owner)->ClearPointers(this, true);
		}
		return true;
	}
	return false;
}

bool C4Object::AssignPlrViewRange()
{
	// no range?
	if (!PlrViewRange) return true;
	// add to FoW-repellers
	PlrFoWActualize();
	// success
	return true;
}

void C4Object::ClearInfo(C4ObjectInfo *pInfo)
{
	if (Info==pInfo)
	{
		Info=NULL;
	}
}

void C4Object::Clear()
{
	if (pEffects) { delete pEffects; pEffects=NULL; }
	if (FrontParticles) FrontParticles.Clear();
	if (BackParticles) BackParticles.Clear();
	if (pSolidMaskData) { delete pSolidMaskData; pSolidMaskData=NULL; }
	if (Menu) delete Menu; Menu=NULL;
	if (MaterialContents) delete MaterialContents; MaterialContents=NULL;
	// clear commands!
	C4Command *pCom, *pNext;
	for (pCom=Command; pCom; pCom=pNext)
	{
		pNext=pCom->Next; delete pCom; pCom=pNext;
	}
	if (pDrawTransform) { delete pDrawTransform; pDrawTransform=NULL; }
	if (pGfxOverlay) { delete pGfxOverlay; pGfxOverlay=NULL; }
	if (pMeshInstance) { delete pMeshInstance; pMeshInstance = NULL; }
}



bool C4Object::MenuCommand(const char *szCommand)
{
	// Native script execution
	if (!Def || !Status) return false;
	return !! Def->Script.DirectExec(this, szCommand, "MenuCommand");
}

C4Object *C4Object::ComposeContents(C4ID id)
{
	int32_t cnt,cnt2;
	C4ID c_id;
	bool fInsufficient = false;
	C4Object *pObj;
	C4ID idNeeded=C4ID::None;
	int32_t iNeeded=0;
	// Get def
	C4Def *pDef = C4Id2Def(id); if (!pDef) return NULL;
	// get needed contents
	C4IDList NeededComponents;
	pDef->GetComponents(&NeededComponents, NULL);
	// Check for sufficient components
	StdStrBuf Needs; Needs.Format(LoadResStr("IDS_CON_BUILDMATNEED"),pDef->GetName());
	for (cnt=0; (c_id=NeededComponents.GetID(cnt)); cnt++)
		if (NeededComponents.GetCount(cnt) > Contents.ObjectCount(c_id))
		{
			Needs.AppendFormat("|%ix %s", NeededComponents.GetCount(cnt) - Contents.ObjectCount(c_id), C4Id2Def(c_id) ? C4Id2Def(c_id)->GetName() : c_id.ToString() );
			if (!idNeeded) { idNeeded=c_id; iNeeded=NeededComponents.GetCount(cnt)-Contents.ObjectCount(c_id); }
			fInsufficient = true;
		}
	// Insufficient
	if (fInsufficient)
	{
		// BuildNeedsMaterial call to object...
		if (!Call(PSF_BuildNeedsMaterial,&C4AulParSet(C4VPropList(C4Id2Def(idNeeded)), C4VInt(iNeeded))))
			// ...game message if not overloaded
			GameMsgObjectError(Needs.getData(),this);
		// Return
		return NULL;
	}
	// Remove components
	for (cnt=0; (c_id=NeededComponents.GetID(cnt)); cnt++)
		for (cnt2=0; cnt2<NeededComponents.GetCount(cnt); cnt2++)
			if (!( pObj = Contents.Find(c_id) ))
				return NULL;
			else
				pObj->AssignRemoval();
	// Create composed object
	// the object is created with default components instead of builder components
	// this is done because some objects (e.g. arrow packs) will set custom components during initialization, which should not be overriden
	return CreateContents(C4Id2Def(id));
}

void C4Object::SetSolidMask(int32_t iX, int32_t iY, int32_t iWdt, int32_t iHgt, int32_t iTX, int32_t iTY)
{
	// remove osld
	if (pSolidMaskData) { delete pSolidMaskData; pSolidMaskData=NULL; }
	// set new data
	SolidMask.Set(iX,iY,iWdt,iHgt,iTX,iTY);
	// re-put if valid
	if (CheckSolidMaskRect()) UpdateSolidMask(false);
}

bool C4Object::CheckSolidMaskRect()
{
	// SolidMasks are only supported for bitmap graphics
	if (GetGraphics()->Type != C4DefGraphics::TYPE_Bitmap) return false;

	// check NewGfx only, because invalid SolidMask-rects are OK in OldGfx
	// the bounds-check is done in C4Draw::GetPixel()
	C4Surface *sfcGraphics = GetGraphics()->GetBitmap();
	SolidMask.Set(Max<int32_t>(SolidMask.x,0), Max<int32_t>(SolidMask.y,0),
	              Min<int32_t>(SolidMask.Wdt,sfcGraphics->Wdt-SolidMask.x), Min<int32_t>(SolidMask.Hgt, sfcGraphics->Hgt-SolidMask.y),
	              SolidMask.tx, SolidMask.ty);
	if (SolidMask.Hgt<=0) SolidMask.Wdt=0;
	return SolidMask.Wdt>0;
}

void C4Object::SyncClearance()
{
	// Misc. no-save safeties
	Action.t_attach = CNAT_None;
	InMat = MNone;
	t_contact = 0;
	// Fixed point values - precision reduction
	fix_r = itofix( r );
	// Update OCF
	SetOCF();
	// Menu
	CloseMenu(true);
	// Material contents
	if (MaterialContents) delete MaterialContents; MaterialContents=NULL;
	// reset speed of staticback-objects
	if (Category & C4D_StaticBack)
	{
		xdir = ydir = 0;
	}
}

void C4Object::DrawSelectMark(C4TargetFacet &cgo)
{
	// Status
	if (!Status) return;
	// No select marks in film playback
	if (Game.C4S.Head.Film && Game.C4S.Head.Replay) return;
	// target pos (parallax)
	float offX, offY, newzoom;
	GetDrawPosition(cgo, offX, offY, newzoom);
	// Output boundary
	if (!Inside<float>(offX, cgo.X, cgo.X + cgo.Wdt)
	    || !Inside<float>(offY, cgo.Y, cgo.Y + cgo.Hgt)) return;
	// Draw select marks
	float cox = offX + Shape.GetX() - cgo.X + cgo.X - 2;
	float coy = offY + Shape.GetY() - cgo.Y + cgo.Y - 2;
	GfxR->fctSelectMark.Draw(cgo.Surface,cox,coy,0);
	GfxR->fctSelectMark.Draw(cgo.Surface,cox+Shape.Wdt,coy,1);
	GfxR->fctSelectMark.Draw(cgo.Surface,cox,coy+Shape.Hgt,2);
	GfxR->fctSelectMark.Draw(cgo.Surface,cox+Shape.Wdt,coy+Shape.Hgt,3);
}

void C4Object::ClearCommands()
{
	C4Command *pNext;
	while (Command)
	{
		pNext=Command->Next;
		if (!Command->iExec)
			delete Command;
		else
			Command->iExec = 2;
		Command=pNext;
	}
}

void C4Object::ClearCommand(C4Command *pUntil)
{
	C4Command *pCom,*pNext;
	for (pCom=Command; pCom; pCom=pNext)
	{
		// Last one to clear
		if (pCom==pUntil) pNext=NULL;
		// Next one to clear after this
		else pNext=pCom->Next;
		Command=pCom->Next;
		if (!pCom->iExec)
			delete pCom;
		else
			pCom->iExec = 2;
	}
}

bool C4Object::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)
{
	// Command stack size safety
	const int32_t MaxCommandStack = 35;
	C4Command *pCom,*pLast; int32_t iCommands;
	for (pCom=Command,iCommands=0; pCom; pCom=pCom->Next,iCommands++) {}
	if (iCommands>=MaxCommandStack) return false;
	// Valid command safety
	if (!Inside(iCommand,C4CMD_First,C4CMD_Last)) return false;
	// Allocate and set new command
	if (!(pCom=new C4Command)) return false;
	pCom->Set(iCommand,this,pTarget,iTx,iTy,pTarget2,iData,
	          iUpdateInterval,!fInitEvaluation,iRetries,szText,iBaseMode);
	// Append to bottom of stack
	if (fAppend)
	{
		for (pLast=Command; pLast && pLast->Next; pLast=pLast->Next) {}
		if (pLast) pLast->Next=pCom;
		else Command=pCom;
	}
	// Add to top of command stack
	else
	{
		pCom->Next=Command;
		Command=pCom;
	}
	// Success
	//sprintf(OSTR,"%s command %s added: %i/%i %s %s (%i)",GetName(),CommandName(iCommand),iTx,iTy,pTarget ? pTarget->GetName() : "O",pTarget2 ? pTarget2->GetName() : "O",iUpdateInterval); Log(OSTR);
	return true;
}

void C4Object::SetCommand(int32_t iCommand, C4Object *pTarget, C4Value iTx, int32_t iTy,
                          C4Object *pTarget2, bool fControl, C4Value iData,
                          int32_t iRetries, C4String *szText)
{
	// Decrease NoCollectDelay
	if (NoCollectDelay>0) NoCollectDelay--;
	// Clear stack
	ClearCommands();
	// Close menu
	if (fControl)
		if (!CloseMenu(false)) return;
	// Script overload
	if (fControl)
		if (!!Call(PSF_ControlCommand,&C4AulParSet(C4VString(CommandName(iCommand)),
		           C4VObj(pTarget),
		           iTx,
		           C4VInt(iTy),
		           C4VObj(pTarget2),
		           iData)))
			return;
	// Inside vehicle control overload
	if (Contained)
		if (Contained->Def->VehicleControl & C4D_VehicleControl_Inside)
		{
			Contained->Controller=Controller;
			if (!!Contained->Call(PSF_ControlCommand,&C4AulParSet(C4VString(CommandName(iCommand)),
			                      C4VObj(pTarget),
			                      iTx,
			                      C4VInt(iTy),
			                      C4VObj(pTarget2),
			                      iData,
			                      C4VObj(this))))
				return;
		}
	// Outside vehicle control overload
	if (GetProcedure()==DFA_PUSH)
		if (Action.Target)  if (Action.Target->Def->VehicleControl & C4D_VehicleControl_Outside)
			{
				Action.Target->Controller=Controller;
				if (!!Action.Target->Call(PSF_ControlCommand,&C4AulParSet(C4VString(CommandName(iCommand)),
				                          C4VObj(pTarget),
				                          iTx,
				                          C4VInt(iTy),
				                          C4VObj(pTarget2),
				                          iData)))
					return;
			}
	// Add new command
	AddCommand(iCommand,pTarget,iTx,iTy,0,pTarget2,true,iData,false,iRetries,szText,C4CMD_Mode_Base);
}

C4Command *C4Object::FindCommand(int32_t iCommandType)
{
	// seek all commands
	for (C4Command *pCom = Command; pCom; pCom=pCom->Next)
		if (pCom->Command == iCommandType) return pCom;
	// nothing found
	return NULL;
}

bool C4Object::ExecuteCommand()
{
	// Execute first command
	if (Command) Command->Execute();
	// Command finished: engine call
	if (Command && Command->Finished)
		Call(PSF_ControlCommandFinished,&C4AulParSet(C4VString(CommandName(Command->Command)), C4VObj(Command->Target), Command->Tx, C4VInt(Command->Ty), C4VObj(Command->Target2), Command->Data));
	// Clear finished commands
	while (Command && Command->Finished) ClearCommand(Command);
	// Done
	return true;
}

void C4Object::Resort()
{
	// Flag resort
	Unsorted=true;
	Game.fResortAnyObject = true;
	// Must not immediately resort - link change/removal would crash Game::ExecObjects
}

C4PropList* C4Object::GetAction() const
{
	C4Value value;
	GetProperty(P_Action, &value);
	return value.getPropList();
}

bool C4Object::SetAction(C4PropList * Act, C4Object *pTarget, C4Object *pTarget2, int32_t iCalls, bool fForce)
{
	C4Value vLastAction;
	GetProperty(P_Action, &vLastAction);
	C4PropList * LastAction = vLastAction.getPropList();
	int32_t iLastPhase=Action.Phase;
	C4Object *pLastTarget = Action.Target;
	C4Object *pLastTarget2 = Action.Target2;
	// No other action
	if (LastAction)
		if (LastAction->GetPropertyInt(P_NoOtherAction) && !fForce)
			if (Act != LastAction)
				return false;
	// Set animation on instance. Abort if the mesh does not have
	// such an animation.
	if (pMeshInstance)
	{
		if (Action.Animation) pMeshInstance->StopAnimation(Action.Animation);
		Action.Animation = NULL;

		C4String* Animation = Act ? Act->GetPropertyStr(P_Animation) : NULL;
		if (Animation)
		{
			// note that weight is ignored
			Action.Animation = pMeshInstance->PlayAnimation(Animation->GetData(), 0, NULL, new C4ValueProviderAction(this), new C4ValueProviderConst(itofix(1)));
		}
	}
	// Stop previous act sound
	if (LastAction)
		if (Act != LastAction)
			if (LastAction->GetPropertyStr(P_Sound))
				StopSoundEffect(LastAction->GetPropertyStr(P_Sound)->GetCStr(),this);
	// Unfullcon objects no action
	if (Con<FullCon)
		if (!Def->IncompleteActivity)
			Act = 0;
	// Reset action time on change
	if (Act!=LastAction)
	{
		Action.Time=0;
		// reset action data if procedure is changed
		if ((Act ? Act->GetPropertyP(P_Procedure) : -1)
		    != (LastAction ? LastAction->GetPropertyP(P_Procedure) : -1))
			Action.Data = 0;
	}
	// Set new action

	SetProperty(P_Action, C4VPropList(Act));
	Action.Phase=Action.PhaseDelay=0;
	// Set target if specified
	if (pTarget) Action.Target=pTarget;
	if (pTarget2) Action.Target2=pTarget2;
	// Set Action Facet
	UpdateActionFace();
	// update flipdir
	if ((LastAction ? LastAction->GetPropertyInt(P_FlipDir) : 0)
	    != (Act ? Act->GetPropertyInt(P_FlipDir) : 0)) UpdateFlipDir();
	// Start act sound
	if (Act)
		if (Act != LastAction)
			if (Act->GetPropertyStr(P_Sound))
				StartSoundEffect(Act->GetPropertyStr(P_Sound)->GetCStr(),+1,100,this);
	// Reset OCF
	SetOCF();
	// issue calls
	// Execute EndCall for last action
	if (iCalls & SAC_EndCall && !fForce)
		if (LastAction)
		{
			if (LastAction->GetPropertyStr(P_EndCall))
			{
				C4Def *pOldDef = Def;
				Call(LastAction->GetPropertyStr(P_EndCall)->GetCStr());
				// abort exeution if def changed
				if (Def != pOldDef || !Status) return true;
			}
		}
	// Execute AbortCall for last action
	if (iCalls & SAC_AbortCall && !fForce)
		if (LastAction)
		{
			if (LastAction->GetPropertyStr(P_AbortCall))
			{
				C4Def *pOldDef = Def;
				if (pLastTarget && !pLastTarget->Status) pLastTarget = NULL;
				if (pLastTarget2 && !pLastTarget2->Status) pLastTarget2 = NULL;
				Call(LastAction->GetPropertyStr(P_AbortCall)->GetCStr(), &C4AulParSet(C4VInt(iLastPhase), C4VObj(pLastTarget), C4VObj(pLastTarget2)));
				// abort exeution if def changed
				if (Def != pOldDef || !Status) return true;
			}
		}
	// Execute StartCall for new action
	if (iCalls & SAC_StartCall)
		if (Act)
		{
			if (Act->GetPropertyStr(P_StartCall))
			{
				C4Def *pOldDef = Def;
				Call(Act->GetPropertyStr(P_StartCall)->GetCStr());
				// abort exeution if def changed
				if (Def != pOldDef || !Status) return true;
			}
		}

	C4Def *pOldDef = Def;
	Call(PSF_OnActionChanged, &C4AulParSet(C4VString(LastAction ? LastAction->GetName() : "Idle")));
	if (Def != pOldDef || !Status) return true;

	return true;
}

void C4Object::UpdateActionFace()
{
	// Default: no action face
	Action.Facet.Default();
	// Active: get action facet from action definition
	C4PropList* pActionDef = GetAction();
	if (pActionDef)
	{
		if (pActionDef->GetPropertyInt(P_Wdt)>0)
		{
			Action.Facet.Set(GetGraphics()->GetBitmap(Color),
			                 pActionDef->GetPropertyInt(P_X),pActionDef->GetPropertyInt(P_Y),
			                 pActionDef->GetPropertyInt(P_Wdt),pActionDef->GetPropertyInt(P_Hgt));
			Action.FacetX=pActionDef->GetPropertyInt(P_OffX);
			Action.FacetY=pActionDef->GetPropertyInt(P_OffY);
		}
	}
}

bool C4Object::SetActionByName(C4String *ActName,
                               C4Object *pTarget, C4Object *pTarget2,
                               int32_t iCalls, bool fForce)
{
	assert(ActName);
	// If we get the null string or ActIdle by name, set ActIdle
	if (!ActName || ActName == &Strings.P[P_Idle])
		return SetAction(0,0,0,iCalls,fForce);
	C4Value ActMap; GetProperty(P_ActMap, &ActMap);
	if (!ActMap.getPropList()) return false;
	C4Value Action; ActMap.getPropList()->GetPropertyByS(ActName, &Action);
	if (!Action.getPropList()) return false;
	return SetAction(Action.getPropList(),pTarget,pTarget2,iCalls,fForce);
}

C4PropList *C4Object::GetActionByName(C4PropertyName &act_name, C4Value *rval_holder)
{
	C4Value ActMap; GetProperty(P_ActMap, &ActMap);
	if (!ActMap.getPropList()) return false;
	ActMap.getPropList()->GetProperty(act_name, rval_holder);
	return rval_holder->getPropList();
}

bool C4Object::SetActionByName(const char * szActName,
                               C4Object *pTarget, C4Object *pTarget2,
                               int32_t iCalls, bool fForce)
{
	C4String * ActName = Strings.RegString(szActName);
	ActName->IncRef();
	bool r = SetActionByName(ActName, pTarget, pTarget2, iCalls, fForce);
	ActName->DecRef();
	return r;
}

void C4Object::SetDir(int32_t iDir)
{
	// Not active
	C4PropList* pActionDef = GetAction();
	if (!pActionDef) return;
	// Invalid direction
	if (!Inside<int32_t>(iDir,0,pActionDef->GetPropertyInt(P_Directions)-1)) return;
	// Execute turn action
	if (iDir != Action.Dir)
		if (pActionDef->GetPropertyStr(P_TurnAction))
			{ SetActionByName(pActionDef->GetPropertyStr(P_TurnAction)); }
	// Set dir
	Action.Dir=iDir;
	// update by flipdir?
	if (pActionDef->GetPropertyInt(P_FlipDir))
		UpdateFlipDir();
	else
		Action.DrawDir=iDir;
}

int32_t C4Object::GetProcedure()
{
	C4PropList* pActionDef = GetAction();
	if (!pActionDef) return -1;
	return pActionDef->GetPropertyP(P_Procedure);
}

void GrabLost(C4Object *cObj)
{
	// Grab lost script call on target (quite hacky stuff...)
	cObj->Action.Target->Call(PSF_GrabLost);
	// Also, delete the target from the clonk's action (Newton)
	cObj->Action.Target = NULL;
	// Clear commands down to first PushTo (if any) in command stack
	for (C4Command *pCom=cObj->Command; pCom; pCom=pCom->Next)
		if (pCom->Next && pCom->Next->Command==C4CMD_PushTo)
		{
			cObj->ClearCommand(pCom);
			break;
		}
}

static void DoGravity(C4Object *cobj);

void C4Object::NoAttachAction()
{
	// Active objects
	if (GetAction())
	{
		int32_t iProcedure = GetProcedure();
		// Scaling upwards: corner scale
		if (iProcedure == DFA_SCALE && Action.ComDir != COMD_Stop && ComDirLike(Action.ComDir, COMD_Up))
			if (ObjectActionCornerScale(this)) return;
		if (iProcedure == DFA_SCALE && Action.ComDir == COMD_Left && Action.Dir == DIR_Left)
			if (ObjectActionCornerScale(this)) return;
		if (iProcedure == DFA_SCALE && Action.ComDir == COMD_Right && Action.Dir == DIR_Right)
			if (ObjectActionCornerScale(this)) return;
		// Scaling and stopped: fall off to side (avoid zuppel)
		if ((iProcedure == DFA_SCALE) && (Action.ComDir == COMD_Stop))
		{
			if (Action.Dir == DIR_Left)
				{ if (ObjectActionJump(this,itofix(1),Fix0,false)) return; }
			else
				{ if (ObjectActionJump(this,itofix(-1),Fix0,false)) return; }
		}
		// Pushing: grab loss
		if (iProcedure==DFA_PUSH) GrabLost(this);
		// Else jump
		ObjectActionJump(this,xdir,ydir,false);
	}
	// Inactive objects, simple mobile natural gravity
	else
	{
		DoGravity(this);
		Mobile=1;
	}
}

void C4Object::ContactAction()
{
	// Take certain action on contact. Evaluate t_contact-CNAT and Procedure.

	// Determine Procedure
	C4PropList* pActionDef = GetAction();
	if (!pActionDef) return;
	int32_t iProcedure=pActionDef->GetPropertyP(P_Procedure);
	int32_t fDisabled=pActionDef->GetPropertyInt(P_ObjectDisabled);

	//------------------------------- Hit Bottom ---------------------------------------------
	if (t_contact & CNAT_Bottom)
		switch (iProcedure)
		{
		case DFA_FLIGHT:
			if (ydir < 0) return;
			// Jump: FlatHit / HardHit / Walk
			if ((OCF & OCF_HitSpeed4) || fDisabled)
				if (ObjectActionFlat(this,Action.Dir)) return;
			if (OCF & OCF_HitSpeed3)
				if (ObjectActionKneel(this)) return;
			ObjectActionWalk(this);
			ydir = 0;
			return;
		case DFA_SCALE:
			// Scale down: stand
			if (ComDirLike(Action.ComDir, COMD_Down))
			{
				ObjectActionStand(this);
				return;
			}
			break;
		case DFA_DIG:
			// no special action
			break;
		case DFA_SWIM:
			// Try corner scale out
			if (!GBackLiquid(GetX(), GetY() - 1))
				if (ObjectActionCornerScale(this)) return;
			break;
		}

	//------------------------------- Hit Ceiling -----------------------------------------
	if (t_contact & CNAT_Top)
		switch (iProcedure)
		{
		case DFA_WALK:
			// Walk: Stop
			ObjectActionStand(this); return;
		case DFA_SCALE:
			// Scale: Try hangle, else stop if going upward
			if (ComDirLike(Action.ComDir, COMD_Up))
			{
				if (ObjectActionHangle(this))
				{
					SetDir(Action.Dir == DIR_Left ? DIR_Right : DIR_Left);
					return;
				}
				Action.ComDir=COMD_Stop;
			}
			break;
		case DFA_FLIGHT:
			// Jump: Try hangle, else bounce off
			// High Speed Flight: Tumble
			if ((OCF & OCF_HitSpeed3) || fDisabled)
				{ ObjectActionTumble(this, Action.Dir, xdir, ydir); break; }
			if (ObjectActionHangle(this)) return;
			break;
		case DFA_DIG:
			// No action
			break;
		case DFA_HANGLE:
			Action.ComDir=COMD_Stop;
			break;
		}

	//---------------------------- Hit Left Wall ----------------------------------------
	if (t_contact & CNAT_Left)
	{
		switch (iProcedure)
		{
		case DFA_FLIGHT:
			// High Speed Flight: Tumble
			if ((OCF & OCF_HitSpeed3) || fDisabled)
				{ ObjectActionTumble(this, DIR_Left, xdir, ydir); break; }
			// Else
			else if (!ComDirLike(Action.ComDir, COMD_Right) && ObjectActionScale(this,DIR_Left)) return;
			break;
		case DFA_WALK:
			// Walk: Try scale
			if (ComDirLike(Action.ComDir, COMD_Left))
			{
				if (ObjectActionScale(this,DIR_Left))
				{
					ydir = C4REAL100(-1);
					return;
				}
			}
			// Heading away from solid
			if (ComDirLike(Action.ComDir, COMD_Right))
			{
				// Slide off
				ObjectActionJump(this,xdir/2,ydir,false);
			}
			return;
		case DFA_SWIM:
			// Try scale
			if (ComDirLike(Action.ComDir, COMD_Left))
				if (ObjectActionScale(this,DIR_Left)) return;
			// Try corner scale out
			if (ObjectActionCornerScale(this)) return;
			return;
		case DFA_HANGLE:
			// Hangle: Try scale
			if (ObjectActionScale(this,DIR_Left))
			{
				ydir = C4REAL100(1);
				return;
			}
			return;
		case DFA_DIG:
			// Dig: no action
			break;
		}
	}

	//------------------------------ Hit Right Wall --------------------------------------
	if (t_contact & CNAT_Right)
	{
		switch (iProcedure)
		{
		case DFA_FLIGHT:
			// High Speed Flight: Tumble
			if ((OCF & OCF_HitSpeed3) || fDisabled)
				{ ObjectActionTumble(this, DIR_Right, xdir, ydir); break; }
			// Else Scale
			else if (!ComDirLike(Action.ComDir, COMD_Left) && ObjectActionScale(this,DIR_Right)) return;
			break;
		case DFA_WALK:
			// Walk: Try scale
			if (ComDirLike(Action.ComDir, COMD_Right))
			{
				if (ObjectActionScale(this,DIR_Right))
				{
					ydir = C4REAL100(-1);
					return;
				}
			}
			// Heading away from solid
			if (ComDirLike(Action.ComDir, COMD_Left))
			{
				// Slide off
				ObjectActionJump(this,xdir/2,ydir,false);
			}
			return;
		case DFA_SWIM:
			// Try scale
			if (ComDirLike(Action.ComDir, COMD_Right))
				if (ObjectActionScale(this,DIR_Right)) return;
			// Try corner scale out
			if (ObjectActionCornerScale(this)) return;
			// Skip to enable walk out
			return;
		case DFA_HANGLE:
			// Hangle: Try scale
			if (ObjectActionScale(this,DIR_Right))
			{
				ydir = C4REAL100(1);
				return;
			}
			return;
		case DFA_DIG:
			// Dig: no action
			break;
		}
	}

	//---------------------------- Unresolved Cases ---------------------------------------

	// Flight stuck
	if (iProcedure==DFA_FLIGHT)
	{
		// Enforce slide free (might slide through tiny holes this way)
		if (!ydir)
		{
			int fAllowDown = !(t_contact & CNAT_Bottom);
			if (t_contact & CNAT_Right)
			{
				ForcePosition(fix_x - 1, fix_y + fAllowDown);
				xdir=ydir=0;
			}
			if (t_contact & CNAT_Left)
			{
				ForcePosition(fix_x + 1, fix_y + fAllowDown);
				xdir=ydir=0;
			}
		}
		if (!xdir)
		{
			if (t_contact & CNAT_Top)
			{
				ForcePosition(fix_x, fix_y + 1);
				xdir=ydir=0;
			}
		}
	}
}

void Towards(C4Real &val, C4Real target, C4Real step)
{
	if (val==target) return;
	if (Abs(val-target)<=step) { val=target; return; }
	if (val<target) val+=step; else val-=step;
}

bool DoBridge(C4Object *clk)
{
	int32_t iBridgeTime; bool fMoveClonk, fWall; int32_t iBridgeMaterial;
	clk->Action.GetBridgeData(iBridgeTime, fMoveClonk, fWall, iBridgeMaterial);
	if (!iBridgeTime) iBridgeTime = 100; // default bridge time
	if (clk->Action.Time>=iBridgeTime) { ObjectActionStand(clk); return false; }
	// get bridge advancement
	int32_t dtp;
	if (fWall) switch (clk->Action.ComDir)
		{
		case COMD_Left: case COMD_Right: dtp = 4; fMoveClonk = false; break; // vertical wall: default 25 pixels
		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)
		case COMD_Up: dtp = 5; break; // horizontal roof over Clonk
		default: return true; // bridge procedure just for show
		}
	else switch (clk->Action.ComDir)
		{
		case COMD_Left: case COMD_Right: dtp = 5; break; // horizontal bridges: default 20 pixels
		case COMD_Up: dtp = 4; break; // vertical bridges: default 25 pixels (same as
		case COMD_UpLeft: case COMD_UpRight: dtp = 6; break; // diagonal bridges: default 16 pixels up and 16 pixels side (23 pixels)
		default: return true; // bridge procedure  just for show
		}
	if (clk->Action.Time % dtp) return true; // no advancement in this frame
	// get target pos for Clonk and bridge
	int32_t cx=clk->GetX(), cy=clk->GetY(), cw=clk->Shape.Wdt, ch=clk->Shape.Hgt;
	int32_t tx=cx,ty=cy+ch/2;
	int32_t dt;
	if (fMoveClonk) dt = 0; else dt = clk->Action.Time / dtp;
	if (fWall) switch (clk->Action.ComDir)
		{
		case COMD_Left: tx-=cw/2; ty+=-dt; break;
		case COMD_Right: tx+=cw/2; ty+=-dt; break;
		case COMD_Up:
		{
			int32_t x0;
			if (fMoveClonk) x0=-3; else x0=(iBridgeTime/dtp)/-2;
			tx+=(x0+dt)*((clk->Action.Dir==DIR_Right)*2-1); cx+=((clk->Action.Dir==DIR_Right)*2-1); ty-=ch+3; break;
		}
		case COMD_UpLeft: tx-=-4+dt; ty+=-ch-7+dt; break;
		case COMD_UpRight: tx+=-4+dt; ty+=-ch-7+dt; break;
		}
	else switch (clk->Action.ComDir)
		{
		case COMD_Left: tx+=-3-dt; --cx; break;
		case COMD_Right: tx+=+2+dt; ++cx; break;
		case COMD_Up: tx+=(-cw/2+(cw-1)*(clk->Action.Dir==DIR_Right))*(!fMoveClonk); ty+=-dt-fMoveClonk; --cy; break;
		case COMD_UpLeft: tx+=-5-dt+fMoveClonk*3; ty+=2-dt-fMoveClonk*3; --cx; --cy; break;
		case COMD_UpRight: tx+=+5+dt-fMoveClonk*2; ty+=2-dt-fMoveClonk*3; ++cx; --cy; break;
		}
	// check if Clonk movement is posible
	if (fMoveClonk)
	{
		int32_t cx2=cx, cy2=cy;
		if (/*!clk->Shape.Attach(cx2, cy2, (clk->Action.t_attach & CNAT_Flags) | CNAT_Bottom) ||*/ clk->Shape.CheckContact(cx2, cy2-1))
		{
			// Clonk would collide here: Change to nonmoving Clonk mode and redo bridging
			iBridgeTime -= clk->Action.Time;
			clk->Action.Time = 0;
			if (fWall && clk->Action.ComDir==COMD_Up)
			{
				// special for roof above Clonk: The nonmoving roof is started at bridgelength before the Clonkl
				// so, when interrupted, an action time halfway through the action must be set
				clk->Action.Time = iBridgeTime;
				iBridgeTime += iBridgeTime;
			}
			clk->Action.SetBridgeData(iBridgeTime, false, fWall, iBridgeMaterial);
			return DoBridge(clk);
		}
	}
	// draw bridge into landscape
	::Landscape.DrawMaterialRect(iBridgeMaterial,tx-2,ty,4,3);
	// Move Clonk
	if (fMoveClonk) clk->MovePosition(cx-clk->GetX(), cy-clk->GetY());
	return true;
}

static void DoGravity(C4Object *cobj)
{
	// Floatation in liquids
	if (cobj->InLiquid && cobj->Def->Float)
	{
		cobj->ydir-=GravAccel * C4REAL100(80);
		if (cobj->ydir<C4REAL100(-160)) cobj->ydir=C4REAL100(-160);
		if (cobj->xdir<-FloatFriction) cobj->xdir+=FloatFriction;
		if (cobj->xdir>+FloatFriction) cobj->xdir-=FloatFriction;
		if (cobj->rdir<-FloatFriction) cobj->rdir+=FloatFriction;
		if (cobj->rdir>+FloatFriction) cobj->rdir-=FloatFriction;
		if (!GBackLiquid(cobj->GetX(),cobj->GetY()-1+ cobj->Def->Float*cobj->GetCon()/FullCon -1 ))
			if (cobj->ydir<0) cobj->ydir=0;
	}
	// Free fall gravity
	else if (~cobj->Category & C4D_StaticBack)
		cobj->ydir+=GravAccel;
}

void StopActionDelayCommand(C4Object *cobj)
{
	ObjectComStop(cobj);
	cobj->AddCommand(C4CMD_Wait,NULL,0,0,50);
}

bool ReduceLineSegments(C4Shape &rShape, bool fAlternate)
{
	// try if line could go by a path directly when skipping on evertex. If fAlternate is true, try by skipping two vertices
	for (int32_t cnt=0; cnt+2+fAlternate<rShape.VtxNum; cnt++)
		if (PathFree(rShape.VtxX[cnt],rShape.VtxY[cnt],
		             rShape.VtxX[cnt+2+fAlternate],rShape.VtxY[cnt+2+fAlternate]))
		{
			if (fAlternate) rShape.RemoveVertex(cnt+2);
			rShape.RemoveVertex(cnt+1);
			return true;
		}
	return false;
}

void C4Object::ExecAction()
{
	C4Real iTXDir;
	C4Real lftspeed,tydir;
	int32_t iTargetX;
	int32_t iPushRange,iPushDistance;

	// Standard phase advance
	int32_t iPhaseAdvance=1;

	// Upright attachment check
	if (!Mobile)
		if (Def->UprightAttach)
			if (Inside<int32_t>(r,-StableRange,+StableRange))
			{
				Action.t_attach|=Def->UprightAttach;
				Mobile=1;
			}

	C4PropList* pActionDef = GetAction();
	// No IncompleteActivity? Reset action if there was one
	if (!(OCF & OCF_FullCon) && !Def->IncompleteActivity && pActionDef)
	{
		SetAction(0);
		pActionDef = 0;
	}

	// InLiquidAction check
	if (InLiquid)
		if (pActionDef && pActionDef->GetPropertyStr(P_InLiquidAction))
		{
			SetActionByName(pActionDef->GetPropertyStr(P_InLiquidAction));
			pActionDef = GetAction();
		}
	
	// Idle objects do natural gravity only
	if (!pActionDef)
	{
		Action.t_attach = CNAT_None;
		if (Mobile) DoGravity(this);
		return;
	}

	C4Real fWalk,fMove;

	// Action time advance
	Action.Time++;

	C4Value Attach;
	pActionDef->GetProperty(P_Attach, &Attach);
	if (Attach.GetType() != C4V_Nil)
	{
		Action.t_attach = Attach.getInt();
	}
	else switch (pActionDef->GetPropertyP(P_Procedure))
	{
	case DFA_SCALE:
		if (Action.Dir == DIR_Left)  Action.t_attach = CNAT_Left;
		if (Action.Dir == DIR_Right) Action.t_attach = CNAT_Right;
		break;
	case DFA_HANGLE:
		Action.t_attach = CNAT_Top;
		break;
	case DFA_WALK:
	case DFA_KNEEL:
	case DFA_THROW:
	case DFA_BRIDGE:
	case DFA_PUSH:
	case DFA_PULL:
	case DFA_DIG:
		Action.t_attach = CNAT_Bottom;
		break;
	default:
		Action.t_attach = CNAT_None;
	}

	// if an object is in controllable state, so it can be assumed that if it dies later because of NO_OWNER's cause,
	// it has been its own fault and not the fault of the last one who threw a flint on it
	// do not reset for burning objects to make sure the killer is set correctly if they fall out of the map while burning
	if (!pActionDef->GetPropertyInt(P_ObjectDisabled) && pActionDef->GetPropertyP(P_Procedure) != DFA_FLIGHT && !OnFire)
		LastEnergyLossCausePlayer = NO_OWNER;

	// Handle Default Action Procedure: evaluates Procedure and Action.ComDir
	// Update xdir,ydir,Action.Dir,attachment,iPhaseAdvance
	int32_t dir = Action.Dir;
	C4Real accel = C4REAL100(pActionDef->GetPropertyInt(P_Accel));
	C4Real decel = accel;
	{
		C4Value decel_val;
		pActionDef->GetProperty(P_Decel, &decel_val);
		if (decel_val.GetType() != C4V_Nil)
			decel = C4REAL100(decel_val.getInt());
	}
	C4Real limit = C4REAL100(pActionDef->GetPropertyInt(P_Speed));

	switch (pActionDef->GetPropertyP(P_Procedure))
	{
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	case DFA_WALK:
		switch (Action.ComDir)
		{
		case COMD_Left: case COMD_UpLeft: case COMD_DownLeft:
			// breaaak!!!
			if (dir == DIR_Right)
				xdir-=decel;
			else
				xdir-=accel;
			if (xdir<-limit) xdir=-limit;
			break;
		case COMD_Right: case COMD_UpRight: case COMD_DownRight:
			if (dir == DIR_Left)
				xdir+=decel;
			else
				xdir+=accel;
			if (xdir>+limit) xdir=+limit;
			break;
		case COMD_Stop: case COMD_Up: case COMD_Down:
			if (xdir<0) xdir+=decel;
			if (xdir>0) xdir-=decel;
			if ((xdir>-decel) && (xdir<+decel)) xdir=0;
			break;
		}
		iPhaseAdvance=0;
		if (xdir<0)
		{
			if (dir != DIR_Left) { SetDir(DIR_Left); xdir = -1; }
			iPhaseAdvance=-fixtoi(xdir*10);
		}
		if (xdir>0)
		{
			if (dir != DIR_Right) { SetDir(DIR_Right); xdir = 1; }
			iPhaseAdvance=+fixtoi(xdir*10);
		}

		Mobile=1;
		// object is rotateable? adjust to ground, if in horizontal movement or not attached to the center vertex
		if (Def->Rotateable && Shape.AttachMat != MNone && (!!xdir || Def->Shape.VtxX[Shape.iAttachVtx]))
			AdjustWalkRotation(20, 20, 100);
		else
			rdir=0;
		break;
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	case DFA_KNEEL:
		ydir=0;
		Mobile=1;
		break;
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	case DFA_SCALE:
	{
		int ComDir = Action.ComDir;
		if (Shape.CheckScaleToWalk(GetX(), GetY()))
		{
			ObjectActionWalk(this);
			return;
		}
		if ((Action.Dir == DIR_Left && ComDir == COMD_Left) || (Action.Dir == DIR_Right && ComDir == COMD_Right))
		{
			/*if (ydir > 0)
				ComDir = COMD_Down;
			else
				ComDir = COMD_Up;*/
			ComDir = COMD_Up;
		}
		switch (ComDir)
		{
		case COMD_Up: case COMD_UpRight:  case COMD_UpLeft:
			if (ydir > 0) ydir -= decel;
			else ydir -= accel;
			if (ydir < -limit) ydir = -limit; break;
		case COMD_Down: case COMD_DownRight: case COMD_DownLeft:
			if (ydir < 0) ydir += decel;
			else ydir += accel;
			if (ydir > +limit) ydir = +limit; break;
		case COMD_Left: case COMD_Right: case COMD_Stop:
			if (ydir < 0) ydir += decel;
			if (ydir > 0) ydir -= decel;
			if ((ydir > -decel) && (ydir < +decel)) ydir = 0;
			break;
		}
		iPhaseAdvance=0;
		if (ydir<0) iPhaseAdvance=-fixtoi(ydir*14);
		if (ydir>0) iPhaseAdvance=+fixtoi(ydir*14);
		xdir=0;
		Mobile=1;
		break;
	}
	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	case DFA_HANGLE:
		switch (Action.ComDir)
		{
		case COMD_Left: case COMD_UpLeft: case COMD_DownLeft:
			if (xdir > 0) xdir -= decel;
			else xdir -= accel;
			if (xdir < -limit) xdir = -limit;
			break;
		case COMD_Right: case COMD_UpRight: case COMD_DownRight:
			if (xdir < 0) xdir += decel;
			else xdir += accel;
			if (xdir > +limit) xdir = +limit;
			break;
		case COMD_Up:
			if (Action.Dir == DIR_Left)
				if (xdir > 0) xdir -= decel;
				else xdir -= accel;
			else
				if (xdir < 0) xdir += decel;
				else xdir += accel;
			if (xdir < -limit) xdir = -limit;
			if (xdir > +limit) xdir = +limit;
			break;
		case COMD_Stop: case COMD_Down:
			if (xdir < 0) xdir += decel;
			if (xdir > 0) xdir -= decel;
			if ((xdir > -decel) && (xdir < +decel)) xdir = 0;
			break;
		}
		iPhaseAdvance=0;
		if (xdir<0) { iPhaseAdvance=-fixtoi(xdir*10); SetDir(DIR_Left); }
		if (xdir>0) { iPhaseAdvance=+fixtoi(xdir*10); SetDir(DIR_Right); }
		ydir=0;
		Mobile=1;
		break;
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	case DFA_FLIGHT:
		// Contained: fall out (one try only)
		if (!::Game.iTick10)
			if (Contained)
			{
				StopActionDelayCommand(this);
				SetCommand(C4CMD_Exit);
			}

		switch (Action.ComDir)
		{
		case COMD_Left: case COMD_UpLeft: case COMD_DownLeft:
			xdir -= Max(Min(limit + xdir, xdir > 0 ? decel : accel), itofix(0));
			break;
		case COMD_Right: case COMD_UpRight: case COMD_DownRight:
			xdir += Max(Min(limit - xdir, xdir < 0 ? decel : accel), itofix(0));
			break;
		}

		// Gravity/mobile
		DoGravity(this);
		Mobile=1;
		break;
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	case DFA_DIG:
	{
		int32_t smpx = GetX(), smpy = GetY();
		bool fAttachOK = false;
		if (Action.t_attach & CNAT_Bottom && Shape.Attach(smpx,smpy,CNAT_Bottom)) fAttachOK = true;
		else if (Action.t_attach & CNAT_Left && Shape.Attach(smpx,smpy,CNAT_Left)) { fAttachOK = true; }
		else if (Action.t_attach & CNAT_Right && Shape.Attach(smpx,smpy,CNAT_Right)) { fAttachOK = true; }
		else if (Action.t_attach & CNAT_Top && Shape.Attach(smpx,smpy,CNAT_Top)) fAttachOK = true;
		if (!fAttachOK)
			{ ObjectComStopDig(this); return; }
		iPhaseAdvance=40*limit;

		if (xdir < 0) SetDir(DIR_Left); else if (xdir > 0) SetDir(DIR_Right);
		Action.t_attach=CNAT_None;
		Mobile=1;
		break;
	}
	// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	case DFA_SWIM:
		// ComDir changes xdir/ydir
		switch (Action.ComDir)
		{
		case COMD_Up:       ydir-=accel; break;
		case COMD_UpRight:  ydir-=accel;  xdir+=accel; break;
		case COMD_Right:                  xdir+=accel; break;
		case COMD_DownRight:ydir+=accel;  xdir+=accel; break;
		case COMD_Down:     ydir+=accel; break;
		case COMD_DownLeft: ydir+=accel;  xdir-=accel; break;
		case COMD_Left:                   xdir-=accel; break;
		case COMD_UpLeft:   ydir-=accel;  xdir-=accel; break;
		case COMD_Stop:
			if (xdir<0) xdir+=decel;
			if (xdir>0) xdir-=decel;
			if ((xdir>-decel) && (xdir<+decel)) xdir=0;
			if (ydir<0) ydir+=decel;
			if (ydir>0) ydir-=decel;
			if ((ydir>-decel) && (ydir<+decel)) ydir=0;
			break;
		}

		// Out of liquid check
		if (!InLiquid)
		{
			// Just above liquid: move down
			if (GBackLiquid(GetX(),GetY()+1+Def->Float*Con/FullCon-1)) ydir=+accel;
			// Free fall: walk
			else { ObjectActionWalk(this); return; }
		}

		// xdir/ydir bounds, don't apply if COMD_None
		if (Action.ComDir != COMD_None)
		{
			if (ydir<-limit) ydir=-limit; if (ydir>+limit) ydir=+limit;
			if (xdir>+limit) xdir=+limit; if (xdir<-limit) xdir=-limit;
		}
		// Surface dir bound
		if (!GBackLiquid(GetX(),GetY()-1+Def->Float*Con/FullCon-1)) if (ydir<0) ydir=0;
		// Dir, Phase, Attach
		if (xdir<0) SetDir(DIR_Left);
		if (xdir>0) SetDir(DIR_Right);
		iPhaseAdvance=fixtoi(limit*10);
		Mobile=1;

		break;
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	case DFA_THROW:
		Mobile=1;
		break;
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	case DFA_BRIDGE:
	{
		if (!DoBridge(this)) return;
		switch (Action.ComDir)
		{
		case COMD_Left:  case COMD_UpLeft: SetDir(DIR_Left); break;
		case COMD_Right: case COMD_UpRight: SetDir(DIR_Right); break;
		}
		ydir=0; xdir=0;
		Mobile=1;
	}
	break;
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	case DFA_PUSH:
		// No target
		if (!Action.Target) { StopActionDelayCommand(this); return; }
		// Inside target
		if (Contained==Action.Target) { StopActionDelayCommand(this); return; }
		// Target pushing force
		bool fStraighten;
		iTXDir=0; fStraighten=false;
		switch (Action.ComDir)
		{
		case COMD_Left: case COMD_DownLeft:   iTXDir=-limit; break;
		case COMD_UpLeft:  fStraighten=1;     iTXDir=-limit; break;
		case COMD_Right: case COMD_DownRight: iTXDir=+limit; break;
		case COMD_UpRight: fStraighten=1;     iTXDir=+limit; break;
		case COMD_Up:      fStraighten=1; break;
		case COMD_Stop: case COMD_Down:       iTXDir=0;       break;
		}
		// Push object
		if (!Action.Target->Push(iTXDir,accel,fStraighten))
			{ StopActionDelayCommand(this); return; }
		// Set target controller
		Action.Target->Controller=Controller;
		// ObjectAction got hold check
		iPushDistance = Max(Shape.Wdt/2-8,0);
		iPushRange = iPushDistance + 10;
		int32_t sax,say,sawdt,sahgt;
		Action.Target->GetArea(sax,say,sawdt,sahgt);
		// Object lost
		if (!Inside(GetX()-sax,-iPushRange,sawdt-1+iPushRange)
		    || !Inside(GetY()-say,-iPushRange,sahgt-1+iPushRange))
		{
			// Wait command (why, anyway?)
			StopActionDelayCommand(this);
			// Grab lost action
			GrabLost(this);
			// Done
			return;
		}
		// Follow object (full xdir reset)
		// Vertical follow: If object moves out at top, assume it's being pushed upwards and the Clonk must run after it
		if (GetY()-iPushDistance > say+sahgt && iTXDir) { if (iTXDir>0) sax+=sawdt/2; sawdt/=2; }
		// Horizontal follow
		iTargetX=BoundBy(GetX(),sax-iPushDistance,sax+sawdt-1+iPushDistance);
		if (GetX()==iTargetX) xdir=0;
		else { if (GetX()<iTargetX) xdir=+limit; if (GetX()>iTargetX) xdir=-limit; }
		// Phase by XDir
		if (xdir<0) { iPhaseAdvance=-fixtoi(xdir*10); SetDir(DIR_Left);  }
		if (xdir>0) { iPhaseAdvance=+fixtoi(xdir*10); SetDir(DIR_Right); }
		// No YDir
		ydir=0;
		Mobile=1;
		break;
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	case DFA_PULL:
		// No target
		if (!Action.Target) { StopActionDelayCommand(this); return; }
		// Inside target
		if (Contained==Action.Target) { StopActionDelayCommand(this); return; }
		// Target contained
		if (Action.Target->Contained) { StopActionDelayCommand(this); return; }

		int32_t iPullDistance;
		int32_t iPullX;

		iPullDistance = Action.Target->Shape.Wdt/2 + Shape.Wdt/2;

		iTargetX=GetX();
		if (Action.ComDir==COMD_Right) iTargetX = Action.Target->GetX()+iPullDistance;
		if (Action.ComDir==COMD_Left) iTargetX = Action.Target->GetX()-iPullDistance;

		iPullX=Action.Target->GetX();
		if (Action.ComDir==COMD_Right) iPullX = GetX()-iPullDistance;
		if (Action.ComDir==COMD_Left) iPullX = GetX()+iPullDistance;

		fWalk = limit;

		fMove = 0;
		if (Action.ComDir==COMD_Right) fMove = +fWalk;
		if (Action.ComDir==COMD_Left) fMove = -fWalk;

		iTXDir = fMove + fWalk * BoundBy<int32_t>(iPullX-Action.Target->GetX(),-10,+10) / 10;

		// Push object
		if (!Action.Target->Push(iTXDir,accel,false))
			{ StopActionDelayCommand(this); return; }
		// Set target controller
		Action.Target->Controller=Controller;

		// Train pulling: com dir transfer
		if ( (Action.Target->GetProcedure()==DFA_WALK)
		     || (Action.Target->GetProcedure()==DFA_PULL) )
		{
			Action.Target->Action.ComDir=COMD_Stop;
			if (iTXDir<0) Action.Target->Action.ComDir=COMD_Left;
			if (iTXDir>0) Action.Target->Action.ComDir=COMD_Right;
		}

		// Pulling range
		iPushDistance = Max(Shape.Wdt/2-8,0);
		iPushRange = iPushDistance + 20;
		Action.Target->GetArea(sax,say,sawdt,sahgt);
		// Object lost
		if (!Inside(GetX()-sax,-iPushRange,sawdt-1+iPushRange)
		    || !Inside(GetY()-say,-iPushRange,sahgt-1+iPushRange))
		{
			// Wait command (why, anyway?)
			StopActionDelayCommand(this);
			// Grab lost action
			GrabLost(this);
			// Lose target
			Action.Target=NULL;
			// Done
			return;
		}

		// Move to pulling position
		xdir = fMove + fWalk * BoundBy<int32_t>(iTargetX-GetX(),-10,+10) / 10;

		// Phase by XDir
		iPhaseAdvance=0;
		if (xdir<0) { iPhaseAdvance=-fixtoi(xdir*10); SetDir(DIR_Left);  }
		if (xdir>0) { iPhaseAdvance=+fixtoi(xdir*10); SetDir(DIR_Right); }
		// No YDir
		ydir=0;
		Mobile=1;

		break;
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	case DFA_LIFT:
		// Valid check
		if (!Action.Target) { SetAction(0); return; }
		// Target lifting force
		lftspeed=itofix(2); tydir=0;
		switch (Action.ComDir)
		{
		case COMD_Up:   tydir=-lftspeed; break;
		case COMD_Stop: tydir=-GravAccel; break;
		case COMD_Down: tydir=+lftspeed; break;
		}
		// Lift object
		if (!Action.Target->Lift(tydir,C4REAL100(50)))
			{ SetAction(0); return; }
		// Check LiftTop
		if (Def->LiftTop)
			if (Action.Target->GetY()<=(GetY()+Def->LiftTop))
				if (Action.ComDir==COMD_Up)
					Call(PSF_LiftTop);
		// General
		DoGravity(this);
		break;
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	case DFA_FLOAT:
		// ComDir changes xdir/ydir
		switch (Action.ComDir)
		{
		case COMD_Up:       ydir-=accel; break;
		case COMD_UpRight:  ydir-=accel;  xdir+=accel; break;
		case COMD_Right:                  xdir+=accel; break;
		case COMD_DownRight:ydir+=accel;  xdir+=accel; break;
		case COMD_Down:     ydir+=accel; break;
		case COMD_DownLeft: ydir+=accel;  xdir-=accel; break;
		case COMD_Left:                   xdir-=accel; break;
		case COMD_UpLeft:   ydir-=accel;  xdir-=accel; break;
		case COMD_Stop:
			if (xdir<0) xdir+=decel;
			if (xdir>0) xdir-=decel;
			if ((xdir>-decel) && (xdir<+decel)) xdir=0;
			if (ydir<0) ydir+=decel;
			if (ydir>0) ydir-=decel;
			if ((ydir>-decel) && (ydir<+decel)) ydir=0;
			break;
		}
		// xdir/ydir bounds, don't apply if COMD_None
		if (Action.ComDir != COMD_None)
		{
			if (ydir<-limit) ydir=-limit; if (ydir>+limit) ydir=+limit;
			if (xdir>+limit) xdir=+limit; if (xdir<-limit) xdir=-limit;
		}

		Mobile=1;
		break;
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
		// ATTACH: Force position to target object
		//   own vertex index is determined by high-order byte of action data
		//   target vertex index is determined by low-order byte of action data
	case DFA_ATTACH:

		// No target
		if (!Action.Target)
		{
			if (Status)
			{
				SetAction(0);
				Call(PSF_AttachTargetLost);
			}
			return;
		}

		// Target incomplete and no incomplete activity
		if (!(Action.Target->OCF & OCF_FullCon))
			if (!Action.Target->Def->IncompleteActivity)
				{ SetAction(0); return; }

		// Force containment
		if (Action.Target->Contained!=Contained)
		{
			if (Action.Target->Contained)
				Enter(Action.Target->Contained);
			else
				Exit(GetX(),GetY(),r);
		}

		// Move position (so objects on solidmask move)
		MovePosition(Action.Target->fix_x + Action.Target->Shape.VtxX[Action.Data&255]
		              -Shape.VtxX[Action.Data>>8] - fix_x,
		              Action.Target->fix_y + Action.Target->Shape.VtxY[Action.Data&255]
		              -Shape.VtxY[Action.Data>>8] - fix_y);
		// must zero motion...
		xdir=ydir=0;

		break;
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	case DFA_CONNECT:

		bool fBroke;
		fBroke=false;
		int32_t iConnectX, iConnectY;

		// Line destruction check: Target missing or incomplete
		if (!Action.Target || (Action.Target->Con<FullCon)) fBroke=true;
		if (!Action.Target2 || (Action.Target2->Con<FullCon)) fBroke=true;
		if (fBroke)
		{
			Call(PSF_LineBreak,&C4AulParSet(C4VBool(true)));
			AssignRemoval();
			return;
		}

		// Movement by Target
		if (Action.Target)
		{
			// Connect to attach vertex
			C4Value lineAttachV;
			Action.Target->GetProperty(P_LineAttach, &lineAttachV);
			C4ValueArray *lineAttach = lineAttachV.getArray();
			iConnectX = Action.Target->GetX();
			iConnectY = Action.Target->GetY();
			if (lineAttach)
			{
				iConnectX += lineAttach->GetItem(0).getInt();
				iConnectY += lineAttach->GetItem(1).getInt();
			}
			if ((iConnectX!=Shape.VtxX[0]) || (iConnectY!=Shape.VtxY[0]))
			{
				// Regular wrapping line
				if (Def->LineIntersect == 0)
					if (!Shape.LineConnect(iConnectX,iConnectY,0,+1,
					                       Shape.VtxX[0],Shape.VtxY[0])) fBroke=true;
				// No-intersection line
				if (Def->LineIntersect == 1)
					{ Shape.VtxX[0]=iConnectX; Shape.VtxY[0]=iConnectY; }
			}
		}
		// Movement by Target2
		if (Action.Target2)
		{
			// Connect to attach vertex
			C4Value lineAttachV;
			Action.Target2->GetProperty(P_LineAttach, &lineAttachV);
			C4ValueArray *lineAttach = lineAttachV.getArray();
			iConnectX = Action.Target2->GetX();
			iConnectY = Action.Target2->GetY();
			if (lineAttach)
			{
				iConnectX += lineAttach->GetItem(0).getInt();
				iConnectY += lineAttach->GetItem(1).getInt();
			}
			if ((iConnectX!=Shape.VtxX[Shape.VtxNum-1]) || (iConnectY!=Shape.VtxY[Shape.VtxNum-1]))
			{
				// Regular wrapping line
				if (Def->LineIntersect == 0)
					if (!Shape.LineConnect(iConnectX,iConnectY,Shape.VtxNum-1,-1,
					                       Shape.VtxX[Shape.VtxNum-1],Shape.VtxY[Shape.VtxNum-1])) fBroke=true;
				// No-intersection line
				if (Def->LineIntersect == 1)
					{ Shape.VtxX[Shape.VtxNum-1]=iConnectX; Shape.VtxY[Shape.VtxNum-1]=iConnectY; }
			}
		}

		// Line fBroke
		if (fBroke)
		{
			Call(PSF_LineBreak,0);
			AssignRemoval();
			return;
		}

		// Reduce line segments
		if (!::Game.iTick35)
			ReduceLineSegments(Shape, !::Game.iTick2);

		break;
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	default:
		// Attach
		if (Action.t_attach)
		{
			xdir = ydir = 0;
			Mobile = 1;
		}
		// Free gravity
		else
			DoGravity(this);
		break;
		// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
	}

	// Phase Advance (zero delay means no phase advance)
	if (pActionDef->GetPropertyInt(P_Delay))
	{
		Action.PhaseDelay+=iPhaseAdvance;
		if (Action.PhaseDelay >= pActionDef->GetPropertyInt(P_Delay))
		{
			// Advance Phase
			Action.PhaseDelay=0;
			Action.Phase += pActionDef->GetPropertyInt(P_Step);
			// Phase call
			if (pActionDef->GetPropertyStr(P_PhaseCall))
			{
				Call(pActionDef->GetPropertyStr(P_PhaseCall)->GetCStr());
			}
			// Phase end
			if (Action.Phase>=pActionDef->GetPropertyInt(P_Length))
			{
				C4String *next_action = pActionDef->GetPropertyStr(P_NextAction);
				// Keep current action if there is no NextAction
				if (!next_action)
					Action.Phase = 0;
				// set new action if it's not Hold
				else if (next_action == &Strings.P[P_Hold])
				{
					Action.Phase = pActionDef->GetPropertyInt(P_Length)-1;
					Action.PhaseDelay = pActionDef->GetPropertyInt(P_Delay)-1;
				}
				else
				{
					// Set new action
					SetActionByName(next_action, NULL, NULL, SAC_StartCall | SAC_EndCall);
				}
			}
		}
	}

	return;
}


bool C4Object::SetOwner(int32_t iOwner)
{
	C4Player *pPlr;
	// Check valid owner
	if (!(ValidPlr(iOwner) || iOwner == NO_OWNER)) return false;
	// always set color, even if no owner-change is done
	if (iOwner != NO_OWNER)
		if (GetGraphics()->IsColorByOwner())
		{
			Color=::Players.Get(iOwner)->ColorDw;
			UpdateFace(false);
		}
	// no change?
	if (Owner == iOwner) return true;
	// remove old owner view
	if (ValidPlr(Owner))
	{
		pPlr = ::Players.Get(Owner);
		while (pPlr->FoWViewObjs.Remove(this)) {}
	}
	else
		for (pPlr = ::Players.First; pPlr; pPlr = pPlr->Next)
			while (pPlr->FoWViewObjs.Remove(this)) {}
	// set new owner
	int32_t iOldOwner=Owner;
	Owner=iOwner;
	if (Owner != NO_OWNER)
		// add to plr view
		PlrFoWActualize();
	// this automatically updates controller
	Controller = Owner;
	// script callback
	Call(PSF_OnOwnerChanged, &C4AulParSet(C4VInt(Owner), C4VInt(iOldOwner)));
	// done
	return true;
}


bool C4Object::SetPlrViewRange(int32_t iToRange)
{
	// set new range
	PlrViewRange = iToRange;
	// resort into player's FoW-repeller-list
	PlrFoWActualize();
	// success
	return true;
}


void C4Object::PlrFoWActualize()
{
	C4Player *pPlr;
	// single owner?
	if (ValidPlr(Owner))
	{
		// single player's FoW-list
		pPlr = ::Players.Get(Owner);
		while (pPlr->FoWViewObjs.Remove(this)) {}
		if (PlrViewRange) pPlr->FoWViewObjs.Add(this, C4ObjectList::stNone);
	}
	// no owner?
	else
	{
		// all players!
		for (pPlr = ::Players.First; pPlr; pPlr = pPlr->Next)
		{
			while (pPlr->FoWViewObjs.Remove(this)) {}
			if (PlrViewRange) pPlr->FoWViewObjs.Add(this, C4ObjectList::stNone);
		}
	}
}

void C4Object::SetAudibilityAt(C4TargetFacet &cgo, int32_t iX, int32_t iY)
{
	// target pos (parallax)
	float offX, offY, newzoom;
	GetDrawPosition(cgo, iX, iY, cgo.Zoom, offX, offY, newzoom);
	Audible = Max<int>(Audible, BoundBy(100 - 100 * Distance(cgo.X + cgo.Wdt / 2, cgo.Y + cgo.Hgt / 2, offX, offY) / 700, 0, 100));
	AudiblePan = BoundBy<int>(200 * (offX - cgo.X - (cgo.Wdt / 2)) / cgo.Wdt, -100, 100);
}

bool C4Object::IsVisible(int32_t iForPlr, bool fAsOverlay)
{
	bool fDraw;
	C4Value vis;
	if (!GetProperty(P_Visibility, &vis))
		return true;

	int32_t Visibility;
	C4ValueArray *parameters = vis.getArray();
	if (parameters && parameters->GetSize())
	{
		Visibility = parameters->GetItem(0).getInt();
	}
	else
	{
		Visibility = vis.getInt();
	}
	// check layer
	if (Layer && Layer != this && !fAsOverlay)
	{
		fDraw = Layer->IsVisible(iForPlr, false);
		if (Layer->GetPropertyInt(P_Visibility) & VIS_LayerToggle) fDraw = !fDraw;
		if (!fDraw) return false;
	}
	// no flags set?
	if (!Visibility) return true;
	// check overlay
	if (Visibility & VIS_OverlayOnly)
	{
		if (!fAsOverlay) return false;
		if (Visibility == VIS_OverlayOnly) return true;
	}
	// check visibility
	fDraw=false;
	if (Visibility & VIS_Owner) fDraw = fDraw || (iForPlr==Owner);
	if (iForPlr!=NO_OWNER)
	{
		// check all
		if (Visibility & VIS_Allies)  fDraw = fDraw || (iForPlr!=Owner && !Hostile(iForPlr, Owner));
		if (Visibility & VIS_Enemies) fDraw = fDraw || (iForPlr!=Owner && Hostile(iForPlr, Owner));
		if (parameters)
		{
			if (Visibility & VIS_Select)  fDraw = fDraw || parameters->GetItem(1+iForPlr).getBool();
		}
	}
	else fDraw = fDraw || (Visibility & VIS_God);
	return fDraw;
}

bool C4Object::IsInLiquidCheck()
{
	return GBackLiquid(GetX(),GetY()+Def->Float*Con/FullCon-1);
}

void C4Object::SetRotation(int32_t nr)
{
	while (nr<0) nr+=360; nr%=360;
	// remove solid mask
	if (pSolidMaskData) pSolidMaskData->Remove(false);
	// set rotation
	r=nr;
	fix_r=itofix(nr);
	// Update face
	UpdateFace(true);
}

void C4Object::PrepareDrawing()
{
	// color modulation
	if (ColorMod != 0xffffffff || (BlitMode & (C4GFXBLIT_MOD2 | C4GFXBLIT_CLRSFC_MOD2))) pDraw->ActivateBlitModulation(ColorMod);
	// other blit modes
	pDraw->SetBlitMode(BlitMode);
}

void C4Object::FinishedDrawing()
{
	// color modulation
	pDraw->DeactivateBlitModulation();
	// extra blitting flags
	pDraw->ResetBlitMode();
}

void C4Object::DrawSolidMask(C4TargetFacet &cgo)
{
	// mask must exist
	if (!pSolidMaskData) return;
	// draw it
	pSolidMaskData->Draw(cgo);
}

void C4Object::UpdateSolidMask(bool fRestoreAttachedObjects)
{
	// solidmask doesn't make sense with non-existant objects
	// (the solidmask has already been destroyed in AssignRemoval -
	//  do not reset it!)
	if (!Status) return;
	// Determine necessity, update cSolidMask, put or remove mask
	// Mask if enabled, fullcon, not contained
	if (SolidMask.Wdt > 0 && Con >= FullCon && !Contained)
	{
		// Recheck and put mask
		if (!pSolidMaskData)
		{
			pSolidMaskData = new C4SolidMask(this);
		}
		else
			pSolidMaskData->Remove(false);
		pSolidMaskData->Put(true, NULL, fRestoreAttachedObjects);
	}
	// Otherwise, remove and destroy mask
	else if (pSolidMaskData)
	{
		delete pSolidMaskData; pSolidMaskData = NULL;
	}
}

bool C4Object::Collect(C4Object *pObj)
{
	// Object enter container
	bool fRejectCollect;
	if (!pObj->Enter(this, true, false, &fRejectCollect))
		return false;
	// Cancel attach (hacky)
	ObjectComCancelAttach(pObj);
	// Container Collection call
	Call(PSF_Collection,&C4AulParSet(C4VObj(pObj)));
	// Object Hit call
	if (pObj->Status && pObj->OCF & OCF_HitSpeed1) pObj->Call(PSF_Hit);
	if (pObj->Status && pObj->OCF & OCF_HitSpeed2) pObj->Call(PSF_Hit2);
	if (pObj->Status && pObj->OCF & OCF_HitSpeed3) pObj->Call(PSF_Hit3);
	// post-copy the motion of the new container
	if (pObj->Contained == this) pObj->CopyMotion(this);
	// done, success
	return true;
}

bool C4Object::GrabInfo(C4Object *pFrom)
{
	// safety
	if (!pFrom) return false; if (!Status || !pFrom->Status) return false;
	// even more safety (own info: success)
	if (pFrom == this) return true;
	// only if other object has info
	if (!pFrom->Info) return false;
	// clear own info object
	if (Info)
	{
		Info->Retire();
		ClearInfo (Info);
	}
	// remove objects from any owning crews
	::Players.ClearPointers(pFrom);
	::Players.ClearPointers(this);
	// set info
	Info = pFrom->Info; pFrom->ClearInfo (pFrom->Info);
	// set name
	SetName(Info->Name);
	// retire from old crew
	Info->Retire();
	// if alive, recruit to new crew
	if (Alive) Info->Recruit();
	// make new crew member
	C4Player *pPlr = ::Players.Get(Owner);
	if (pPlr) pPlr->MakeCrewMember(this);
	// done, success
	return true;
}

bool C4Object::ShiftContents(bool fShiftBack, bool fDoCalls)
{
	// get current object
	C4Object *c_obj = Contents.GetObject();
	if (!c_obj) return false;
	// get next/previous
	C4ObjectLink *pLnk = fShiftBack ? (Contents.Last) : (Contents.First->Next);
	for (;;)
	{
		// end reached without success
		if (!pLnk) return false;
		// check object
		C4Object *pObj = pLnk->Obj;
		if (pObj->Status)
			if (!c_obj->CanConcatPictureWith(pObj))
			{
				// object different: shift to this
				DirectComContents(pObj, !!fDoCalls);
				return true;
			}
		// next/prev item
		pLnk = fShiftBack ? (pLnk->Prev) : (pLnk->Next);
	}
	// not reached
}

void C4Object::DirectComContents(C4Object *pTarget, bool fDoCalls)
{
	// safety
	if (!pTarget || !pTarget->Status || pTarget->Contained != this) return;
	// Desired object already at front?
	if (Contents.GetObject() == pTarget) return;
	// select object via script?
	if (fDoCalls)
		if (Call("~ControlContents", &C4AulParSet(C4VPropList(pTarget))))
			return;
	// default action
	if (!(Contents.ShiftContents(pTarget))) return;
	// Selection sound
	if (fDoCalls) if (!Contents.GetObject()->Call("~Selection", &C4AulParSet(C4VObj(this)))) StartSoundEffect("Grab",false,100,this);
	// update menu with the new item in "put" entry
	if (Menu && Menu->IsActive() && Menu->IsContextMenu())
	{
		Menu->Refill();
	}
	// Done
	return;
}

void C4Object::GetParallaxity(int32_t *parX, int32_t *parY)
{
	assert(parX); assert(parY);
	*parX = 100; *parY = 100;
	if (Category & C4D_Foreground)
	{
		*parX = 0; *parY = 0;
		return;
	}
	if (!(Category & C4D_Parallax)) return;
	C4Value parV; GetProperty(P_Parallaxity, &parV);
	C4ValueArray *par = parV.getArray();
	if (!par) return;
	*parX = par->GetItem(0).getInt();
	*parY = par->GetItem(1).getInt();
}

bool C4Object::GetDragImage(C4Object **drag_object, C4ID *drag_id)
{
	// drag is possible if MouseDragImage is assigned
	C4Value parV; GetProperty(P_MouseDragImage, &parV);
	if (!parV) return false;
	// determine drag object/id
	C4Object *obj=NULL; C4ID id;
	if (parV.CheckConversion(C4V_Object)) obj = parV.getObj();
	else if (parV.CheckConversion(C4V_Def)) id = parV.getC4ID();
	if (drag_object) *drag_object = obj;
	if (drag_id) *drag_id = id;
	// drag possible, even w./o image
	return true;
}

bool C4Object::DoSelect()
{
	// selection allowed?
	if (CrewDisabled) return false;
	// do callback
	Call(PSF_CrewSelection, &C4AulParSet(C4VBool(false)));
	// done
	return true;
}

void C4Object::UnSelect()
{
	// do callback
	Call(PSF_CrewSelection, &C4AulParSet(C4VBool(true)));
}

bool C4Object::GetDrawPosition(const C4TargetFacet & cgo,
	float & resultx, float & resulty, float & resultzoom)
{
	return GetDrawPosition(cgo, fixtof(fix_x), fixtof(fix_y), cgo.Zoom, resultx, resulty, resultzoom);
}

bool C4Object::GetDrawPosition(const C4TargetFacet & cgo, float objx, float objy, float zoom, float & resultx, float & resulty, float & resultzoom)
{
	// for HUD
	if(Category & C4D_Foreground)
	{
		resultzoom = zoom;

		if(fix_x < 0)
			resultx = cgo.X + objx + cgo.Wdt;
		else
			resultx = cgo.X + objx;

		if(fix_y < 0)
			resulty = cgo.Y + objy + cgo.Hgt;
		else
			resulty = cgo.Y + objy;

		return true;
	}

	// zoom with parallaxity
	int iParX, iParY;
	GetParallaxity(&iParX, &iParY);
	float targetx = cgo.TargetX; float targety = cgo.TargetY;
	float parx = iParX / 100.0f; float pary = iParY / 100.0f;
	float par = parx; // and pary?

	// Step 1: project to landscape coordinates
	resultzoom = 1.0 / (1.0 - (par - par/zoom));
	// it would be par / (1.0 - (par - par/zoom)) if objects would get smaller farther away
	if (resultzoom <= 0 || resultzoom > 100) // FIXME: optimize treshhold
		return false;

	float rx = ((1 - parx) * targetx) * resultzoom + objx / (parx + zoom - parx * zoom);
	float ry = ((1 - pary) * targety) * resultzoom + objy / (pary + zoom - pary * zoom);

	// Step 2: convert to screen coordinates
	if(parx == 0 && fix_x < 0)
		resultx = cgo.X + (objx + cgo.Wdt) * zoom / resultzoom;
	else
		resultx = cgo.X + (rx - targetx) * zoom / resultzoom;

	if(pary == 0 && fix_y < 0)
		resulty = cgo.Y + (objy + cgo.Hgt) * zoom / resultzoom;
	else
		resulty = cgo.Y + (ry - targety) * zoom / resultzoom;

	return true;
}

void C4Object::GetViewPosPar(float &riX, float &riY, float tx, float ty, const C4Facet &fctViewport)
{
	int iParX, iParY;
	GetParallaxity(&iParX, &iParY);
	// get drawing pos, then subtract original target pos to get drawing pos on landscape
	if (!iParX && GetX()<0)
		// HUD element at right viewport pos
		riX=fixtof(fix_x)+tx+fctViewport.Wdt;
	else
		// regular parallaxity
		riX=fixtof(fix_x)-(tx*(iParX-100)/100);
	if (!iParY && GetY()<0)
		// HUD element at bottom viewport pos
		riY=fixtof(fix_y)+ty+fctViewport.Hgt;
	else
		// regular parallaxity
		riY=fixtof(fix_y)-(ty*(iParY-100)/100);
}

bool C4Object::PutAwayUnusedObject(C4Object *pToMakeRoomForObject)
{
	// get unused object
	C4Object *pUnusedObject;
	C4AulFunc *pFnObj2Drop = GetFunc(PSF_GetObject2Drop);
	if (pFnObj2Drop)
		pUnusedObject = pFnObj2Drop->Exec(this, pToMakeRoomForObject ? &C4AulParSet(C4VObj(pToMakeRoomForObject)) : NULL).getObj();
	else
	{
		// is there any unused object to put away?
		if (!Contents.Last) return false;
		// defaultly, it's the last object in the list
		// (contents list cannot have invalid status-objects)
		pUnusedObject = Contents.Last->Obj;
	}
	// no object to put away? fail
	if (!pUnusedObject) return false;
	// grabbing something?
	bool fPushing = (GetProcedure()==DFA_PUSH);
	if (fPushing)
		// try to put it in there
		if (ObjectComPut(this, Action.Target, pUnusedObject))
			return true;
	// in container? put in there
	if (Contained)
	{
		// try to put it in directly
		// note that this works too, if an object is grabbed inside the container
		if (ObjectComPut(this, Contained, pUnusedObject))
			return true;
		// now putting didn't work - drop it outside
		AddCommand(C4CMD_Drop, pUnusedObject);
		AddCommand(C4CMD_Exit);
		return true;
	}
	else
		// if uncontained, simply try to drop it
		// if this doesn't work, it won't ever
		return !!ObjectComDrop(this, pUnusedObject);
}

bool C4Object::SetGraphics(const char *szGraphicsName, C4Def *pSourceDef)
{
	// safety
	if (!Status) return false;
	// default def
	if (!pSourceDef) pSourceDef = Def;
	// get graphics
	C4DefGraphics *pGrp = pSourceDef->Graphics.Get(szGraphicsName);
	if (!pGrp) return false;
	// no change? (no updates need to be done, then)
	//if (pGraphics == pGrp) return true; // that's not exactly true because the graphics itself might have changed, for example on def reload
	// set new graphics
	pGraphics = pGrp;
	// update Color, SolidMask, etc.
	UpdateGraphics(true);
	// success
	return true;
}

bool C4Object::SetGraphics(C4DefGraphics *pNewGfx, bool fTemp)
{
	// safety
	if (!pNewGfx) return false;
	// set it and update related stuff
	pGraphics = pNewGfx;
	UpdateGraphics(true, fTemp);
	return true;
}

C4GraphicsOverlay *C4Object::GetGraphicsOverlay(int32_t iForID, bool fCreate)
{
	// search in list until ID is found or passed
	C4GraphicsOverlay *pOverlay = pGfxOverlay, *pPrevOverlay = NULL;
	while (pOverlay && pOverlay->GetID() < iForID) { pPrevOverlay = pOverlay; pOverlay = pOverlay->GetNext(); }
	// exact match found?
	if (pOverlay && pOverlay->GetID() == iForID) return pOverlay;
	// ID has been passed: Create new if desired
	if (!fCreate) return NULL;
	C4GraphicsOverlay *pNewOverlay = new C4GraphicsOverlay();
	pNewOverlay->SetID(iForID);
	pNewOverlay->SetNext(pOverlay);
	if (pPrevOverlay) pPrevOverlay->SetNext(pNewOverlay); else pGfxOverlay = pNewOverlay;
	// return newly created overlay
	return pNewOverlay;
}

bool C4Object::RemoveGraphicsOverlay(int32_t iOverlayID)
{
	// search in list until ID is found or passed
	C4GraphicsOverlay *pOverlay = pGfxOverlay, *pPrevOverlay = NULL;
	while (pOverlay && pOverlay->GetID() < iOverlayID) { pPrevOverlay = pOverlay; pOverlay = pOverlay->GetNext(); }
	// exact match found?
	if (pOverlay && pOverlay->GetID() == iOverlayID)
	{
		// remove it
		if (pPrevOverlay) pPrevOverlay->SetNext(pOverlay->GetNext()); else pGfxOverlay = pOverlay->GetNext();
		pOverlay->SetNext(NULL); // prevents deletion of following overlays
		delete pOverlay;
		// removed
		return true;
	}
	// no match found
	return false;
}

bool C4Object::HasGraphicsOverlayRecursion(const C4Object *pCheckObj) const
{
	C4Object *pGfxOvrlObj;
	if (pGfxOverlay)
		for (C4GraphicsOverlay *pGfxOvrl = pGfxOverlay; pGfxOvrl; pGfxOvrl = pGfxOvrl->GetNext())
			if ((pGfxOvrlObj = pGfxOvrl->GetOverlayObject()))
			{
				if (pGfxOvrlObj == pCheckObj) return true;
				if (pGfxOvrlObj->HasGraphicsOverlayRecursion(pCheckObj)) return true;
			}
	return false;
}

bool C4Object::StatusActivate()
{
	// readd to main list
	::Objects.InactiveObjects.Remove(this);
	Status = C4OS_NORMAL;
	::Objects.Add(this);
	// update some values
	UpdateGraphics(false);
	UpdateFace(true);
	UpdatePos();
	Call(PSF_UpdateTransferZone);
	// done, success
	return true;
}

bool C4Object::StatusDeactivate(bool fClearPointers)
{
	// clear particles
	if (FrontParticles) FrontParticles.Clear();
	if (BackParticles) BackParticles.Clear();
	// put into inactive list
	::Objects.Remove(this);
	Status = C4OS_INACTIVE;
	::Objects.InactiveObjects.Add(this, C4ObjectList::stMain);
	// if desired, clear game pointers
	if (fClearPointers)
	{
		// in this case, the object must also exit any container, and any contained objects must be exited
		ClearContentsAndContained();
		Game.ClearPointers(this);
	}
	else
	{
		// always clear transfer
		Game.TransferZones.ClearPointers(this);
	}
	// done, success
	return true;
}

void C4Object::ClearContentsAndContained(bool fDoCalls)
{
	// exit contents from container
	C4Object *cobj; C4ObjectLink *clnk,*next;
	for (clnk=Contents.First; clnk && (cobj=clnk->Obj); clnk=next)
	{
		next=clnk->Next;
		cobj->Exit(GetX(), GetY(), 0,Fix0,Fix0,Fix0, fDoCalls);
	}
	// remove from container *after* contents have been removed!
	if (Contained) Exit(GetX(), GetY(), 0, Fix0, Fix0, Fix0, fDoCalls);
}

bool C4Object::AdjustWalkRotation(int32_t iRangeX, int32_t iRangeY, int32_t iSpeed)
{
	int32_t iDestAngle;
	// attachment at middle (bottom) vertex?
	if (Shape.iAttachVtx<0 || !Def->Shape.VtxX[Shape.iAttachVtx])
	{
		// evaluate floor around attachment pos
		int32_t iSolidLeft=0, iSolidRight=0;
		// left
		int32_t iXCheck = Shape.iAttachX-iRangeX;
		if (GBackSolid(iXCheck, Shape.iAttachY))
		{
			// up
			while (--iSolidLeft>-iRangeY)
				if (GBackSolid(iXCheck, Shape.iAttachY+iSolidLeft))
					{ ++iSolidLeft; break; }
		}
		else
			// down
			while (++iSolidLeft<iRangeY)
				if (GBackSolid(iXCheck, Shape.iAttachY+iSolidLeft))
					{ --iSolidLeft; break; }
		// right
		iXCheck += 2*iRangeX;
		if (GBackSolid(iXCheck, Shape.iAttachY))
		{
			// up
			while (--iSolidRight>-iRangeY)
				if (GBackSolid(iXCheck, Shape.iAttachY+iSolidRight))
					{ ++iSolidRight; break; }
		}
		else
			// down
			while (++iSolidRight<iRangeY)
				if (GBackSolid(iXCheck, Shape.iAttachY+iSolidRight))
					{ --iSolidRight; break; }
		// calculate destination angle
		// 100% accurate for large values of Pi ;)
		iDestAngle=(iSolidRight-iSolidLeft)*(35/Max<int32_t>(iRangeX, 1));
	}
	else
	{
		// attachment at other than horizontal middle vertex: get your feet to the ground!
		// rotate target to large angle is OK, because rotation will stop once the real
		// bottom vertex hits solid ground
		if (Shape.VtxX[Shape.iAttachVtx] > 0)
			iDestAngle = -50;
		else
			iDestAngle = 50;
	}
	// move to destination angle
	if (Abs(iDestAngle-r)>2)
	{
		rdir = itofix(BoundBy<int32_t>(iDestAngle-r, -15,+15));
		rdir/=(10000/iSpeed);
	}
	else rdir=0;
	// done, success
	return true;
}

void C4Object::UpdateInLiquid()
{
	// InLiquid check
	if (IsInLiquidCheck()) // In Liquid
	{
		if (!InLiquid) // Enter liquid
		{
			if (OCF & OCF_HitSpeed2) if (Mass>3)
					Splash(GetX(),GetY()+1,Min(Shape.Wdt*Shape.Hgt/10,20),this);
			InLiquid=1;
		}
	}
	else // Out of liquid
	{
		if (InLiquid) // Leave liquid
			InLiquid=0;
	}
}

StdStrBuf C4Object::GetInfoString()
{
	StdStrBuf sResult;
	// no info for invalid objects
	if (!Status) return sResult;
	// first part always description
	//sResult.Copy(Def->GetDesc());
	// go through all effects and add their desc
	for (C4Effect *pEff = pEffects; pEff; pEff = pEff->pNext)
	{
		C4Value par[7];
		C4Value vInfo = pEff->DoCall(this, PSFS_FxInfo, par[0], par[1], par[2], par[3], par[4], par[5], par[6]);
		if (!vInfo) continue;
		// debug: warn for wrong return types
		if (vInfo.GetType() != C4V_String)
			DebugLogF("Effect %s(#%d) on object %s (#%d) returned wrong info type %d.", pEff->GetName(), pEff->Number, GetName(), Number, vInfo.GetType());
		// get string val
		C4String *psInfo = vInfo.getStr(); const char *szEffInfo;
		if (psInfo && (szEffInfo = psInfo->GetCStr()))
			if (*szEffInfo)
			{
				// OK; this effect has a desc. Add it!
				if (sResult.getLength()) sResult.AppendChar('|');
				sResult.Append(szEffInfo);
			}
	}
	// done
	return sResult;
}

void C4Object::GrabContents(C4Object *pFrom)
{
	// create a temp list of all objects and transfer it
	// this prevents nasty deadlocks caused by RejectEntrance-scripts
	C4ObjectList tmpList; tmpList.Copy(pFrom->Contents);
	C4ObjectLink *cLnk;
	for (cLnk=tmpList.First; cLnk; cLnk=cLnk->Next)
		if (cLnk->Obj->Status)
			cLnk->Obj->Enter(this);
}

bool C4Object::CanConcatPictureWith(C4Object *pOtherObject)
{
	// check current definition ID
	if (id != pOtherObject->id) return false;
	// def overwrite of stack conditions
	int32_t allow_picture_stack = Def->AllowPictureStack;
	if (!(allow_picture_stack & APS_Color))
	{
		// check color if ColorByOwner (flags)
		if (Color != pOtherObject->Color && Def->ColorByOwner) return false;
		// check modulation
		if (ColorMod != pOtherObject->ColorMod) return false;
		if (BlitMode != pOtherObject->BlitMode) return false;
	}
	if (!(allow_picture_stack & APS_Graphics))
	{
		// check graphics
		if (pGraphics != pOtherObject->pGraphics) return false;
		// check any own picture rect
		if (PictureRect != pOtherObject->PictureRect) return false;
	}
	if (!(allow_picture_stack & APS_Name))
	{
		// check name, so zagabar's sandwiches don't stack
		if (GetName() != pOtherObject->GetName()) return false;
	}
	if (!(allow_picture_stack & APS_Overlay))
	{
		// check overlay graphics
		for (C4GraphicsOverlay *pOwnOverlay = pGfxOverlay; pOwnOverlay; pOwnOverlay = pOwnOverlay->GetNext())
			if (pOwnOverlay->IsPicture())
			{
				C4GraphicsOverlay *pOtherOverlay = pOtherObject->GetGraphicsOverlay(pOwnOverlay->GetID(), false);
				if (!pOtherOverlay || !(*pOtherOverlay == *pOwnOverlay)) return false;
			}
		for (C4GraphicsOverlay *pOtherOverlay = pOtherObject->pGfxOverlay; pOtherOverlay; pOtherOverlay = pOtherOverlay->GetNext())
			if (pOtherOverlay->IsPicture())
				if (!GetGraphicsOverlay(pOtherOverlay->GetID(), false)) return false;
	}
	// concat OK
	return true;
}

void C4Object::UpdateScriptPointers()
{
	if (pEffects)
		pEffects->ReAssignAllCallbackFunctions();
}

StdStrBuf C4Object::GetNeededMatStr()
{
	C4Def* pComponent;
	int32_t cnt, ncnt;
	StdStrBuf NeededMats;

	C4IDList NeededComponents;
	Def->GetComponents(&NeededComponents, NULL);

	C4ID idComponent;

	for (cnt = 0; (idComponent=NeededComponents.GetID(cnt)); cnt ++)
	{
		if (NeededComponents.GetCount(cnt)!=0)
		{
			ncnt = NeededComponents.GetCount(cnt) - Component.GetIDCount(idComponent);
			if (ncnt > 0)
			{
				//if(!NeededMats) NeededMats.Append(", "); what was this for...?
				NeededMats.AppendFormat("|%dx ", ncnt);
				if ((pComponent = C4Id2Def(idComponent)))
					NeededMats.Append(pComponent->GetName());
				else
					NeededMats.Append(idComponent.ToString());
			}
		}
	}

	StdStrBuf result;
	if (!!NeededMats)
		{ result.Format(LoadResStr("IDS_CON_BUILDMATNEED"), GetName()); result.Append(NeededMats.getData()); }
	else
		result.Format(LoadResStr("IDS_CON_BUILDMATNONE"), GetName());

	return result;
}

bool C4Object::IsPlayerObject(int32_t iPlayerNumber)
{
	bool fAnyPlr = (iPlayerNumber == NO_OWNER);
	// if an owner is specified: only owned objects
	if (fAnyPlr && !ValidPlr(Owner)) return false;
	// and crew objects
	if (fAnyPlr || Owner == iPlayerNumber)
	{
		// flags are player objects
		if (id == C4ID::Flag) return true;

		C4Player *pOwner = ::Players.Get(Owner);
		if (pOwner)
		{
			if (pOwner && pOwner->Crew.IsContained(this)) return true;
		}
		else
		{
			// Do not force that the owner exists because the function must work for unjoined players (savegame resume)
			if (Def->CrewMember)
				return true;
		}
	}
	// otherwise, not a player object
	return false;
}

bool C4Object::IsUserPlayerObject()
{
	// must be a player object at all
	if (!IsPlayerObject()) return false;
	// and the owner must not be a script player
	C4Player *pOwner = ::Players.Get(Owner);
	if (!pOwner || pOwner->GetType() != C4PT_User) return false;
	// otherwise, it's a user playeer object
	return true;
}

void C4Object::SetPropertyByS(C4String * k, const C4Value & to)
{
	if (k >= &Strings.P[0] && k < &Strings.P[P_LAST])
	{
		switch(k - &Strings.P[0])
		{
			case P_Plane:
				if (!to.getInt()) throw new C4AulExecError("invalid Plane 0");
				SetPlane(to.getInt());
				return;
		}
	}
	C4PropListNumbered::SetPropertyByS(k, to);
}

void C4Object::ResetProperty(C4String * k)
{
	if (k >= &Strings.P[0] && k < &Strings.P[P_LAST])
	{
		switch(k - &Strings.P[0])
		{
			case P_Plane:
				SetPlane(GetPropertyInt(P_Plane));
				return;
		}
	}
	return C4PropListNumbered::ResetProperty(k);
}

bool C4Object::GetPropertyByS(C4String *k, C4Value *pResult) const
{
	if (k >= &Strings.P[0] && k < &Strings.P[P_LAST])
	{
		switch(k - &Strings.P[0])
		{
			case P_Plane: *pResult = C4VInt(Plane); return true;
		}
	}
	return C4PropListNumbered::GetPropertyByS(k, pResult);
}

C4ValueArray * C4Object::GetProperties() const
{
	C4ValueArray * a = C4PropList::GetProperties();
	int i;
	i = a->GetSize();
	a->SetSize(i + 1);
	(*a)[i++] = C4VString(&::Strings.P[P_Plane]);
	return a;
}
