openclonk/src/object/C4ObjectScript.cpp

2730 lines
83 KiB
C++

/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 1998-2000, Matthes Bender
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
* Copyright (c) 2009-2016, The OpenClonk Team and contributors
*
* Distributed under the terms of the ISC license; see accompanying file
* "COPYING" for details.
*
* "Clonk" is a registered trademark of Matthes Bender, used with permission.
* See accompanying file "TRADEMARK" for details.
*
* To redistribute this file separately, substitute the full license texts
* for the above references.
*/
#include "C4Include.h"
#include "C4ForbidLibraryCompilation.h"
#include "control/C4Teams.h"
#include "graphics/C4Draw.h"
#include "graphics/C4GraphicsResource.h"
#include "gui/C4GameMessage.h"
#include "landscape/C4Material.h"
#include "landscape/C4Particles.h"
#include "lib/C4Random.h"
#include "lib/StdMeshMath.h"
#include "object/C4Command.h"
#include "object/C4DefList.h"
#include "object/C4MeshAnimation.h"
#include "object/C4MeshDenumerator.h"
#include "object/C4ObjectCom.h"
#include "object/C4ObjectInfo.h"
#include "object/C4ObjectMenu.h"
#include "player/C4Player.h"
#include "player/C4PlayerList.h"
#include "player/C4RankSystem.h"
#include "script/C4Aul.h"
#include "script/C4AulDefFunc.h"
bool C4ValueToMatrix(C4Value& value, StdMeshMatrix* matrix)
{
const C4ValueArray* array = value.getArray();
if (!array) return false;
return C4ValueToMatrix(*array, matrix);
}
bool C4ValueToMatrix(const C4ValueArray& array, StdMeshMatrix* matrix)
{
if (array.GetSize() != 12) return false;
StdMeshMatrix& trans = *matrix;
trans(0,0) = array[0].getInt()/1000.0f;
trans(0,1) = array[1].getInt()/1000.0f;
trans(0,2) = array[2].getInt()/1000.0f;
trans(0,3) = array[3].getInt()/1000.0f;
trans(1,0) = array[4].getInt()/1000.0f;
trans(1,1) = array[5].getInt()/1000.0f;
trans(1,2) = array[6].getInt()/1000.0f;
trans(1,3) = array[7].getInt()/1000.0f;
trans(2,0) = array[8].getInt()/1000.0f;
trans(2,1) = array[9].getInt()/1000.0f;
trans(2,2) = array[10].getInt()/1000.0f;
trans(2,3) = array[11].getInt()/1000.0f;
return true;
}
static bool FnChangeDef(C4Object *Obj, C4ID to_id)
{
return !!Obj->ChangeDef(to_id);
}
static void FnSetSolidMask(C4Object *Obj, long iX, long iY, long iWdt, long iHgt, long iTX, long iTY)
{
Obj->SetSolidMask(iX,iY,iWdt,iHgt,iTX,iTY);
}
static void FnSetHalfVehicleSolidMask(C4Object *Obj, bool set)
{
Obj->SetHalfVehicleSolidMask(set);
}
static void FnDeathAnnounce(C4Object *Obj)
{
const long MaxDeathMsg=7;
if (Game.C4S.Head.Film)
return;
// Check if crew member has an own death message
if (Obj->Info && *(Obj->Info->DeathMessage))
{
GameMsgObject(Obj->Info->DeathMessage, Obj);
}
else
{
char idDeathMsg[128+1]; sprintf(idDeathMsg, "IDS_OBJ_DEATH%d", 1 + UnsyncedRandom(MaxDeathMsg));
GameMsgObject(FormatString(LoadResStr(idDeathMsg), Obj->GetName()).getData(), Obj);
}
}
static bool FnGrabContents(C4Object *Obj, C4Object *from)
{
if (!from) return false;
if (Obj == from) return false;
Obj->GrabContents(from);
return true;
}
static bool FnPunch(C4Object *Obj, C4Object *target, long punch)
{
if (!target) return false;
return !!ObjectComPunch(Obj,target,punch);
}
static bool FnKill(C4PropList * _this, C4Object *pObj, bool fForced)
{
if (!pObj) pObj=Object(_this);
if (!pObj) return false;
if (!pObj->GetAlive()) return false;
// Trace kills by player-owned objects
// Do not trace for NO_OWNER, because that would include e.g. the Suicide-rule
if (Object(_this) && ValidPlr(Object(_this)->Controller)) pObj->UpdatLastEnergyLossCause(Object(_this)->Controller);
// Do the kill
pObj->AssignDeath(!!fForced);
return true;
}
static void FnFling(C4Object *Obj, long iXDir, long iYDir, long iPrec, bool fAddSpeed)
{
if (!iPrec) iPrec=1;
Obj->Fling(itofix(iXDir, iPrec),itofix(iYDir, iPrec),fAddSpeed);
// unstick from ground, because Fling command may be issued in an Action-callback,
// where attach-values have already been determined for that frame
Obj->Action.t_attach=0;
}
static bool FnJump(C4Object *Obj)
{
return !!ObjectComJump(Obj);
}
static bool FnEnter(C4Object *Obj, C4Object *pTarget)
{
return !!Obj->Enter(pTarget,true,true,nullptr);
}
static bool FnExit(C4Object *Obj, long tx, long ty, long tr, long txdir, long tydir, long trdir)
{
tx+=Obj->GetX();
ty+=Obj->GetY();
ObjectComCancelAttach(Obj);
return !!Obj->Exit(tx,
ty+Obj->Shape.y,
tr,
itofix(txdir),itofix(tydir),
itofix(trdir) / 10);
}
static bool FnCollect(C4Object *Obj, C4Object *pItem, bool ignoreOCF)
{
// local call / safety
if (!pItem) return false;
// check OCF of collector (MaxCarry)
if ((Obj->OCF & OCF_Collection) || ignoreOCF)
// collect
return !!Obj->Collect(pItem);
// failure
return false;
}
static void FnRemoveObject(C4Object *Obj, bool fEjectContents)
{
Obj->AssignRemoval(fEjectContents);
}
static void FnSetPosition(C4Object *Obj, long iX, long iY, bool fCheckBounds, long iPrec)
{
if (!iPrec) iPrec = 1;
C4Real i_x = itofix(iX, iPrec), i_y = itofix(iY, iPrec);
if (fCheckBounds)
{
// BoundsCheck takes ref to C4Real and not to long
Obj->BoundsCheck(i_x, i_y);
}
Obj->ForcePosition(i_x, i_y);
// update liquid
Obj->UpdateInLiquid();
}
static void FnDoCon(C4Object *Obj, long iChange, long iPrec, bool bGrowFromCenter)
{
if (!iPrec) iPrec = 100;
Obj->DoCon(FullCon*iChange / iPrec, bGrowFromCenter);
}
static long FnGetCon(C4Object *Obj, long iPrec)
{
if (!iPrec) iPrec = 100;
return iPrec*Obj->GetCon()/FullCon;
}
static bool FnSetName(C4PropList * _this, C4String *pNewName, bool fSetInInfo, bool fMakeValidIfExists)
{
if (!Object(_this))
{
if (!_this)
throw NeedNonGlobalContext("SetName");
else if (fSetInInfo)
return false;
// Definition name
_this->SetName(FnStringPar(pNewName));
return true;
}
else
{
// Object name
if (fSetInInfo)
{
// setting name in info
C4ObjectInfo *pInfo = Object(_this)->Info;
if (!pInfo) return false;
const char *szName = pNewName->GetCStr();
// empty names are bad; e.g., could cause problems in savegames
if (!szName || !*szName) return false;
// name must not be too long
if (std::strlen(szName) > C4MaxName) return false;
// any change at all?
if (SEqual(szName, pInfo->Name)) return true;
// make sure names in info list aren't duplicated
// querying owner info list here isn't 100% accurate, as infos might have been stolen by other players
// however, there is no good way to track the original list ATM
C4ObjectInfoList *pInfoList = nullptr;
C4Player *pOwner = ::Players.Get(Object(_this)->Owner);
if (pOwner) pInfoList = &pOwner->CrewInfoList;
char NameBuf[C4MaxName+1];
if (pInfoList) if (pInfoList->NameExists(szName))
{
if (!fMakeValidIfExists) return false;
SCopy(szName, NameBuf, C4MaxName);
pInfoList->MakeValidName(NameBuf);
szName = NameBuf;
}
SCopy(szName, pInfo->Name, C4MaxName);
Object(_this)->SetName(); // make sure object uses info name
Object(_this)->Call(PSF_NameChange,&C4AulParSet(true));
}
else
{
if (!pNewName) Object(_this)->SetName();
else Object(_this)->SetName(pNewName->GetCStr());
Object(_this)->Call(PSF_NameChange,&C4AulParSet(false));
}
}
return true;
}
static C4Value FnSetCrewExtraData(C4Object *Obj, C4String * DataName, const C4Value & Data)
{
const char *strDataName = FnStringPar(DataName);
// valid crew with info? (for great nullpointer prevention)
if (!Obj->Info) return C4Value();
// do not allow data type C4V_Array or C4V_C4Object
if (Data.GetType() != C4V_Nil &&
Data.GetType() != C4V_Int &&
Data.GetType() != C4V_Bool &&
Data.GetType() != C4V_String) return C4VNull;
// get pointer on info...
C4ObjectInfo *pInfo = Obj->Info;
// no name list created yet?
if (!pInfo->ExtraData.pNames)
// create name list
pInfo->ExtraData.CreateTempNameList();
// data name already exists?
long ival;
if ((ival = pInfo->ExtraData.pNames->GetItemNr(strDataName)) != -1)
pInfo->ExtraData[ival] = Data;
else
{
// add name
pInfo->ExtraData.pNames->AddName(strDataName);
// get val id & set
if ((ival = pInfo->ExtraData.pNames->GetItemNr(strDataName)) == -1) return C4Value();
pInfo->ExtraData[ival] = Data;
}
// ok, return the value that has been set
return Data;
}
static C4Value FnGetCrewExtraData(C4Object *Obj, C4String * DataName)
{
const char *strDataName = FnStringPar(DataName);
// valid crew with info?
if (!Obj->Info) return C4Value();
// get pointer on info...
C4ObjectInfo *pInfo = Obj->Info;
// no name list?
if (!pInfo->ExtraData.pNames) return C4Value();
long ival;
if ((ival = pInfo->ExtraData.pNames->GetItemNr(strDataName)) == -1) return C4Value();
// return data
return pInfo->ExtraData[ival];
}
static void FnDoEnergy(C4Object *Obj, long iChange, bool fExact, Nillable<long> iEngType, Nillable<long> iCausedBy)
{
if (iEngType.IsNil()) iEngType = C4FxCall_EngScript;
if (iCausedBy.IsNil())
iCausedBy = NO_OWNER;
Obj->DoEnergy(iChange, fExact, iEngType, iCausedBy);
}
static void FnDoBreath(C4Object *Obj, long iChange)
{
Obj->DoBreath(iChange);
}
static void FnDoDamage(C4Object *Obj, long iChange, Nillable<long> iDmgType, Nillable<long> iCausedBy)
{
if (iDmgType.IsNil()) iDmgType = C4FxCall_DmgScript;
if (iCausedBy.IsNil())
iCausedBy = NO_OWNER;
Obj->DoDamage(iChange, iCausedBy, iDmgType);
}
static void FnSetEntrance(C4Object *Obj, bool e_status)
{
Obj->EntranceStatus = e_status;
}
static void FnSetXDir(C4Object *Obj, long nxdir, long iPrec)
{
// precision (default 10.0)
if (!iPrec) iPrec=10;
// update xdir
Obj->xdir=itofix(nxdir, iPrec);
Obj->Mobile=true;
}
static void FnSetRDir(C4Object *Obj, long nrdir, long iPrec)
{
// precision (default 10.0)
if (!iPrec) iPrec=10;
// update rdir
Obj->rdir=itofix(nrdir, iPrec);
Obj->Mobile=true;
}
static void FnSetYDir(C4Object *Obj, long nydir, long iPrec)
{
// precision (default 10.0)
if (!iPrec) iPrec=10;
// update ydir
Obj->ydir=itofix(nydir, iPrec);
Obj->Mobile=true;
}
static void FnSetR(C4Object *Obj, long nr)
{
Obj->SetRotation(nr);
}
static bool FnSetAction(C4Object *Obj, C4String *szAction,
C4Object *pTarget, C4Object *pTarget2, bool fDirect)
{
if (!szAction) return false;
return !!Obj->SetActionByName(FnStringPar(szAction),pTarget,pTarget2,
C4Object::SAC_StartCall | C4Object::SAC_AbortCall,!!fDirect);
}
static bool FnSetBridgeActionData(C4Object *Obj, long iBridgeLength, bool fMoveClonk, bool fWall, long iBridgeMaterial)
{
if (!Obj->Status) return false;
C4PropList* pActionDef = Obj->GetAction();
// action must be BRIDGE
if (!pActionDef) return false;
if (pActionDef->GetPropertyP(P_Procedure) != DFA_BRIDGE) return false;
// set data
Obj->Action.SetBridgeData(iBridgeLength, fMoveClonk, fWall, iBridgeMaterial);
return true;
}
static bool FnSetActionData(C4Object *Obj, long iData)
{
if (!Obj->Status) return false;
C4PropList* pActionDef = Obj->GetAction();
// bridge: Convert from old style
if (pActionDef && (pActionDef->GetPropertyP(P_Procedure) == DFA_BRIDGE))
return FnSetBridgeActionData(Obj, 0, false, false, iData);
// attach: check for valid vertex indices
if (pActionDef && (pActionDef->GetPropertyP(P_Procedure) == DFA_ATTACH))
if (((iData&255) >= C4D_MaxVertex) || ((iData>>8) >= C4D_MaxVertex))
return false;
// set data
Obj->Action.Data = iData;
return true;
}
static void FnSetComDir(C4Object *Obj, long ncomdir)
{
Obj->Action.ComDir=ncomdir;
}
static void FnSetDir(C4Object *Obj, long ndir)
{
Obj->SetDir(ndir);
}
static void FnSetCategory(C4Object *Obj, long iCategory)
{
Obj->SetCategory(iCategory);
}
static void FnSetAlive(C4Object *Obj, bool nalv)
{
Obj->SetAlive(nalv);
}
static bool FnSetOwner(C4Object *Obj, long iOwner)
{
// Set owner
return !!Obj->SetOwner(iOwner);
}
static bool FnSetPhase(C4Object *Obj, long iVal)
{
return !!Obj->SetPhase(iVal);
}
static bool FnExecuteCommand(C4Object *Obj)
{
return !!Obj->ExecuteCommand();
}
static bool FnSetCommand(C4Object *Obj, C4String * szCommand, C4Object * pTarget,
const C4Value & Tx, int iTy, C4Object * pTarget2,
const C4Value & Data, int iRetries)
{
// Command
if (!szCommand) return false;
long iCommand = CommandByName(FnStringPar(szCommand));
if (!iCommand) { Obj->ClearCommands(); return false; }
// Special: convert iData to szText
C4String *szText=nullptr;
if (iCommand==C4CMD_Call)
szText=Data.getStr();
// FIXME: throw if Tx isn't int
// Set
Obj->SetCommand(iCommand,pTarget,Tx,iTy,pTarget2,false,Data,iRetries,szText);
// Success
return true;
}
static bool FnAddCommand(C4Object *Obj, C4String * szCommand, C4Object * pTarget,
const C4Value & Tx, int iTy, C4Object * pTarget2,
int iUpdateInterval, const C4Value & Data, int iRetries, int iBaseMode)
{
// Command
if (!szCommand) return false;
long iCommand = CommandByName(FnStringPar(szCommand));
if (!iCommand) return false;
// Special: convert iData to szText
C4String *szText=nullptr;
if (iCommand==C4CMD_Call)
szText=Data.getStr();
// Add
return Obj->AddCommand(iCommand,pTarget,Tx,iTy,iUpdateInterval,pTarget2,true,Data,false,iRetries,szText,iBaseMode);
}
static bool FnAppendCommand(C4Object *Obj, C4String * szCommand, C4Object * pTarget,
const C4Value & Tx, int iTy, C4Object * pTarget2,
int iUpdateInterval, const C4Value & Data, int iRetries, int iBaseMode)
{
// Command
if (!szCommand) return false;
long iCommand = CommandByName(FnStringPar(szCommand));
if (!iCommand) return false;
// Special: convert iData to szText
C4String *szText=nullptr;
if (iCommand==C4CMD_Call)
szText=Data.getStr();
// Add
return Obj->AddCommand(iCommand,pTarget,Tx,iTy,iUpdateInterval,pTarget2,true,Data,true,iRetries,szText,iBaseMode);
}
static C4Value FnGetCommand(C4Object *Obj, int iElement, int iCommandNum)
{
C4Command * Command = Obj->Command;
// Move through list to Command iCommandNum
while (Command && iCommandNum--) Command = Command->Next;
// Object has no command or iCommandNum was to high or < 0
if (!Command) return C4VNull;
// Return command element
switch (iElement)
{
case 0: // Name
return C4VString(CommandName(Command->Command));
case 1: // Target
return C4VObj(Command->Target);
case 2: // Tx
return Command->Tx;
case 3: // Ty
return C4VInt(Command->Ty);
case 4: // Target2
return C4VObj(Command->Target2);
case 5: // Data
return Command->Command == C4CMD_Call ? C4VString(Command->Text) : Command->Data;
}
// Undefined element
return C4VNull;
}
static bool FnFinishCommand(C4Object *Obj, bool fSuccess, long iCommandNum)
{
C4Command * Command = Obj->Command;
// Move through list to Command iCommandNum
while (Command && iCommandNum--) Command = Command->Next;
// Object has no command or iCommandNum was to high or < 0
if (!Command) return false;
if (!fSuccess) ++(Command->Failures);
else Command->Finished = true;
return true;
}
static C4String *FnGetAction(C4Object *Obj)
{
C4PropList* pActionDef = Obj->GetAction();
if (!pActionDef) return String("Idle");
return String(pActionDef->GetName());
}
static C4Object *FnGetActionTarget(C4Object *Obj, long target_index)
{
if (target_index==0) return Obj->Action.Target;
if (target_index==1) return Obj->Action.Target2;
return nullptr;
}
static void FnSetActionTargets(C4Object *Obj, C4Object *pTarget1, C4Object *pTarget2)
{
// set targets
Obj->Action.Target=pTarget1;
Obj->Action.Target2=pTarget2;
}
static long FnGetDir(C4Object *Obj)
{
return Obj->Action.Dir;
}
static bool FnGetEntrance(C4Object *Obj)
{
return Obj->EntranceStatus;
}
static long FnGetPhase(C4Object *Obj)
{
return Obj->Action.Phase;
}
static long FnGetEnergy(C4Object *Obj, bool fExact)
{
if (fExact)
{
return Obj->Energy;
}
else
{
return 100*Obj->Energy/C4MaxPhysical;
}
}
static long FnGetBreath(C4Object *Obj)
{
return Obj->Breath;
}
static long FnGetMass(C4PropList * _this)
{
if (!Object(_this))
if (!_this || !_this->GetDef())
throw NeedNonGlobalContext("GetMass");
else
return _this->GetDef()->Mass;
else
return Object(_this)->Mass;
}
static long FnGetRDir(C4Object *Obj, long iPrec)
{
if (!iPrec) iPrec = 10;
return fixtoi(Obj->rdir, iPrec);
}
static long FnGetXDir(C4Object *Obj, long iPrec)
{
if (!iPrec) iPrec = 10;
return fixtoi(Obj->xdir, iPrec);
}
static long FnGetYDir(C4Object *Obj, long iPrec)
{
if (!iPrec) iPrec = 10;
return fixtoi(Obj->ydir, iPrec);
}
static long FnGetR(C4Object *Obj)
{
// Adjust range
long iR = Obj->GetR();
while (iR > 180) iR -= 360;
while (iR < -180) iR += 360;
return iR;
}
static long FnGetComDir(C4Object *Obj)
{
return Obj->Action.ComDir;
}
static long FnGetVertexNum(C4Object *Obj)
{
return Obj->Shape.VtxNum;
}
enum VertexDataIndex
{
VTX_X,
VTX_Y,
VTX_CNAT,
VTX_Friction
};
enum VertexUpdateMode
{
VTX_SetNonpermanent,
VTX_SetPermanent,
VTX_SetPermanentUpd
};
static Nillable<long> FnGetVertex(C4Object *Obj, long iIndex, long iValueToGet)
{
if (Obj->Shape.VtxNum<1) return C4Void();
if (iIndex < 0 || iIndex >= Obj->Shape.VtxNum) return C4Void();
iIndex=std::min<long>(iIndex,Obj->Shape.VtxNum-1);
switch (static_cast<VertexDataIndex>(iValueToGet))
{
case VTX_X: return Obj->Shape.VtxX[iIndex]; break;
case VTX_Y: return Obj->Shape.VtxY[iIndex]; break;
case VTX_CNAT: return Obj->Shape.VtxCNAT[iIndex]; break;
case VTX_Friction: return Obj->Shape.VtxFriction[iIndex]; break;
default:
DebugLog(FormatString("GetVertex: Unknown vertex attribute: %ld", iValueToGet).getData());
return C4Void();
break;
}
// impossible mayhem!
assert(!"FnGetVertex: unreachable code reached");
return C4Void();
}
static bool FnSetVertex(C4Object *Obj, long iIndex, long iValueToSet, long iValue, long iOwnVertexMode)
{
// own vertex mode?
if (iOwnVertexMode)
{
// enter own custom vertex mode if not already set
if (!Obj->fOwnVertices)
{
Obj->Shape.CreateOwnOriginalCopy(Obj->Def->Shape);
Obj->fOwnVertices = true;
}
// set vertices at end of buffer
iIndex += C4D_VertexCpyPos;
}
// range check
if (!Inside<long>(iIndex,0,C4D_MaxVertex-1)) return false;
// set desired value
switch (static_cast<VertexDataIndex>(iValueToSet))
{
case VTX_X: Obj->Shape.VtxX[iIndex]=iValue; break;
case VTX_Y: Obj->Shape.VtxY[iIndex]=iValue; break;
case VTX_CNAT: Obj->Shape.VtxCNAT[iIndex]=iValue; break;
case VTX_Friction: Obj->Shape.VtxFriction[iIndex]=iValue; break;
default:
DebugLogF("SetVertex: Unknown vertex attribute: %ld", iValueToSet);
return false;
break;
}
// vertex update desired?
if (iOwnVertexMode==VTX_SetPermanentUpd) Obj->UpdateShape(true);
return true;
}
static bool FnAddVertex(C4Object *Obj, long iX, long iY)
{
return !!Obj->Shape.AddVertex(iX,iY);
}
static bool FnInsertVertex(C4Object *Obj, long iIndex, long iX, long iY)
{
return !!Obj->Shape.InsertVertex(iIndex,iX,iY);
}
static bool FnRemoveVertex(C4Object *Obj, long iIndex)
{
return !!Obj->Shape.RemoveVertex(iIndex);
}
static void FnSetContactDensity(C4Object *Obj, long iDensity)
{
Obj->Shape.ContactDensity = iDensity;
}
static bool FnGetAlive(C4Object *Obj)
{
return Obj->GetAlive();
}
static long FnGetOwner(C4Object *Obj)
{
return Obj->Owner;
}
static long FnGetController(C4Object *Obj)
{
return Obj->Controller;
}
static bool FnSetController(C4Object *Obj, long iNewController)
{
// validate player
if (iNewController != NO_OWNER && !ValidPlr(iNewController)) return false;
// Set controller
Obj->Controller = iNewController;
return true;
}
static long FnGetKiller(C4Object *Obj)
{
return Obj->LastEnergyLossCausePlayer;
}
static bool FnSetKiller(C4Object *Obj, long iNewKiller)
{
// validate player
if (iNewKiller != NO_OWNER && !ValidPlr(iNewKiller)) return false;
// set killer as last energy loss cause
Obj->LastEnergyLossCausePlayer = iNewKiller;
return true;
}
static long FnGetCategory(C4PropList * _this)
{
if (!Object(_this))
if (!_this || !_this->GetDef())
throw NeedNonGlobalContext("GetCategory");
else
return _this->GetDef()->Category;
else
return Object(_this)->Category;
}
static long FnGetOCF(C4Object *Obj)
{
return Obj->OCF;
}
static long FnGetDamage(C4Object *Obj)
{
return Obj->Damage;
}
static long FnGetValue(C4PropList * _this, C4Object *pInBase, long iForPlayer)
{
if (!Object(_this))
if (!_this || !_this->GetDef())
throw NeedNonGlobalContext("GetValue");
else
return _this->GetDef()->GetValue(pInBase, iForPlayer);
else
return Object(_this)->GetValue(pInBase, iForPlayer);
}
static long FnGetRank(C4Object *Obj)
{
if (!Obj->Info) return 0;
return Obj->Info->Rank;
}
static long FnGetActTime(C4Object *Obj)
{
return Obj->Action.Time;
}
static C4PropList* FnGetID(C4Object *Obj)
{
// return id of object
return Obj->GetPrototype();
}
static Nillable<C4Def*> FnGetMenu(C4Object *Obj)
{
if (Obj->Menu && Obj->Menu->IsActive())
return C4Id2Def(C4ID(Obj->Menu->GetIdentification()));
return C4Void();
}
static bool FnCreateMenu(C4Object *Obj, C4Def *pDef, C4Object *pCommandObj,
long iExtra, C4String *szCaption, long iExtraData,
long iStyle, bool fPermanent, C4ID idMenuID)
{
if (pCommandObj)
// object menu: Validate object
if (!pCommandObj->Status) return false;
// else scenario script callback: No command object OK
// Create symbol
C4FacetSurface fctSymbol;
fctSymbol.Create(C4SymbolSize,C4SymbolSize);
if (pDef) pDef->Draw(fctSymbol);
// Clear any old menu, init new menu
if (!Obj->CloseMenu(false)) return false;
if (!Obj->Menu) Obj->Menu = new C4ObjectMenu; else Obj->Menu->ClearItems();
Obj->Menu->Init(fctSymbol,FnStringPar(szCaption),pCommandObj,iExtra,iExtraData,(idMenuID ? idMenuID : pDef ? pDef->id : C4ID::None).GetHandle(),iStyle,true);
// Set permanent
Obj->Menu->SetPermanent(fPermanent);
return true;
}
const int C4MN_Add_ImgRank = 1,
C4MN_Add_ImgIndexed = 2,
C4MN_Add_ImgObjRank = 3,
C4MN_Add_ImgObject = 4,
C4MN_Add_ImgTextSpec = 5,
C4MN_Add_ImgColor = 6,
C4MN_Add_ImgPropListSpec = 7,
C4MN_Add_MaxImage = 127, // mask for param which decides what to draw as the menu symbol
C4MN_Add_PassValue = 128,
C4MN_Add_ForceCount = 256,
C4MN_Add_ForceNoDesc = 512;
#ifndef _MSC_VER
#define _snprintf snprintf
#endif
static bool FnAddMenuItem(C4Object *Obj, C4String * szCaption, C4String * szCommand, C4Def * pDef, int iCount, const C4Value & Parameter, C4String * szInfoCaption, int iExtra, const C4Value & XPar, const C4Value & XPar2)
{
if (!Obj->Menu) return false;
char caption[256+1];
char parameter[256+1];
char dummy[256+1];
char command[512+1];
char command2[512+1];
char infocaption[C4MaxTitle+1];
// get needed symbol size
int iSymbolSize = Obj->Menu->GetSymbolSize();
// Compose caption with def name
if (szCaption)
{
const char * s = FnStringPar(szCaption);
const char * sep = strstr(s, "%s");
if (sep && pDef)
{
strncpy(caption, s, std::min<intptr_t>(sep - s,256));
caption[std::min<intptr_t>(sep - s,256)] = 0;
strncat(caption, pDef->GetName(), 256);
strncat(caption, sep + 2, 256);
}
else
{
strncpy(caption, s, 256);
caption[256] = 0;
}
}
else
caption[0] = 0;
// create string to include type-information in command
switch (Parameter.GetType())
{
case C4V_Int:
sprintf(parameter, "%d", Parameter.getInt());
break;
case C4V_Bool:
SCopy(Parameter.getBool() ? "true" : "false", parameter);
break;
case C4V_PropList:
if (Parameter.getPropList()->GetObject())
sprintf(parameter, "Object(%d)", Parameter.getPropList()->GetObject()->Number);
else if (Parameter.getPropList()->GetDef())
sprintf(parameter, R"(C4Id("%s"))", Parameter.getPropList()->GetDef()->id.ToString());
else
throw C4AulExecError("proplist as parameter to AddMenuItem");
break;
case C4V_String:
// note this breaks if there is '"' in the string.
parameter[0] = '"';
SCopy(Parameter.getStr()->GetCStr(), parameter + 1, sizeof(command)-3);
SAppendChar('"', command);
break;
case C4V_Nil:
SCopy("nil", parameter);
break;
case C4V_Array:
// Arrays were never allowed, so tell the scripter
throw C4AulExecError("array as parameter to AddMenuItem");
default:
return false;
}
// own value
bool fOwnValue = false; long iValue=0;
if (iExtra & C4MN_Add_PassValue)
{
fOwnValue = true;
iValue = XPar2.getInt();
}
// New Style: native script command
size_t i = 0;
for (; i < SLen(FnStringPar(szCommand)); i++)
if (!IsIdentifier(FnStringPar(szCommand)[i]))
break;
if (i < SLen(FnStringPar(szCommand)))
{
// Search for "%d" an replace it by "%s" for insertion of formatted parameter
SCopy(FnStringPar(szCommand), dummy, 256);
auto* pFound = const_cast<char*>(SSearch(dummy, "%d"));
if (pFound != nullptr)
*(pFound - 1) = 's';
// Compose left-click command
sprintf(command, dummy, parameter, 0);
// Compose right-click command
sprintf(command2, dummy, parameter, 1);
}
// Old style: function name with id and parameter
else
{
const char *szScriptCom = FnStringPar(szCommand);
if (szScriptCom && *szScriptCom)
{
if (iExtra & C4MN_Add_PassValue)
{
// with value
sprintf(command,"%s(%s,%s,0,%ld)",szScriptCom,pDef ? pDef->id.ToString() : "nil",parameter,iValue);
sprintf(command2,"%s(%s,%s,1,%ld)",szScriptCom,pDef ? pDef->id.ToString() : "nil",parameter,iValue);
}
else
{
// without value
sprintf(command,"%s(%s,%s)",szScriptCom,pDef ? pDef->id.ToString() : "nil",parameter);
sprintf(command2,"%s(%s,%s,1)",szScriptCom,pDef ? pDef->id.ToString() : "nil",parameter);
}
}
else
{
// no command
*command = *command2 = '\0';
}
}
// Info caption
SCopy(FnStringPar(szInfoCaption),infocaption,C4MaxTitle);
// Create symbol
C4FacetSurface fctSymbol;
C4DefGraphics* pGfx = nullptr;
C4Object* pGfxObj = nullptr;
switch (iExtra & C4MN_Add_MaxImage)
{
case C4MN_Add_ImgRank:
{
// symbol by rank
C4Facet *pfctRankSym = &::GraphicsResource.fctRank;
int32_t iRankSymNum = ::GraphicsResource.iNumRanks;
if (pDef && pDef->pRankSymbols)
{
pfctRankSym = pDef->pRankSymbols;
iRankSymNum = pDef->iNumRankSymbols;
}
C4RankSystem::DrawRankSymbol(&fctSymbol, iCount, pfctRankSym, iRankSymNum, true);
iCount=0;
break;
}
case C4MN_Add_ImgIndexed:
// draw indexed facet
fctSymbol.Create(iSymbolSize,iSymbolSize);
if (pDef)
pDef->Draw(fctSymbol, false, 0, nullptr, XPar.getInt());
break;
case C4MN_Add_ImgObjRank:
{
// draw current gfx of XPar_C4V including rank
if (!XPar.CheckConversion(C4V_Object)) return false;
C4Object *pGfxObj = XPar.getObj();
if (pGfxObj && pGfxObj->Status)
{
// create graphics
// get rank gfx
C4Facet *pRankRes=&::GraphicsResource.fctRank;
long iRankCnt=::GraphicsResource.iNumRanks;
C4Def *pDef=pGfxObj->Def;
if (pDef->pRankSymbols)
{
pRankRes=pDef->pRankSymbols;
iRankCnt=pDef->iNumRankSymbols;
}
// context menu
C4Facet fctRank;
if (Obj->Menu->IsContextMenu())
{
// context menu entry: left object gfx
long C4MN_SymbolSize = Obj->Menu->GetItemHeight();
fctSymbol.Create(C4MN_SymbolSize * 2,C4MN_SymbolSize);
fctSymbol.Wdt = C4MN_SymbolSize;
pGfxObj->Def->Draw(fctSymbol, false, pGfxObj->Color, pGfxObj);
// right of it the rank
fctRank = fctSymbol;
fctRank.X = C4MN_SymbolSize;
fctSymbol.Wdt *= 2;
}
else
{
// regular menu: draw object picture
fctSymbol.Create(iSymbolSize,iSymbolSize);
pGfxObj->Def->Draw(fctSymbol, false, pGfxObj->Color, pGfxObj);
// rank at top-right corner
fctRank = fctSymbol;
fctRank.X = fctRank.Wdt - pRankRes->Wdt;
fctRank.Wdt = pRankRes->Wdt;
fctRank.Hgt = pRankRes->Hgt;
}
// draw rank
if (pGfxObj->Info)
{
C4Facet fctBackup = (const C4Facet &) fctSymbol;
fctSymbol.Set(fctRank);
C4RankSystem::DrawRankSymbol(&fctSymbol, pGfxObj->Info->Rank, pRankRes, iRankCnt, true);
fctSymbol.Set(fctBackup);
}
}
}
break;
case C4MN_Add_ImgObject:
{
// draw object picture
if (!XPar.CheckConversion(C4V_Object))
throw C4AulExecError(FormatString(R"(call to "%s" parameter %d: got "%s", but expected "%s"!)",
"AddMenuItem", 8, XPar.GetTypeName(), GetC4VName(C4V_Object)
).getData());
pGfxObj = XPar.getObj();
}
break;
case C4MN_Add_ImgTextSpec:
{
C4Def* pDef = C4Id2Def(C4ID(std::string(caption)));
if(pDef)
{
pGfx = &pDef->Graphics;
}
else
{
fctSymbol.Create(iSymbolSize,iSymbolSize);
uint32_t dwClr = XPar.getInt();
if (!szCaption || !Game.DrawTextSpecImage(fctSymbol, caption, nullptr, dwClr ? dwClr : 0xff))
return false;
}
*caption = '\0';
}
break;
case C4MN_Add_ImgPropListSpec:
{
C4PropList *gfx_proplist = XPar.getPropList();
fctSymbol.Create(iSymbolSize,iSymbolSize);
if (!Game.DrawPropListSpecImage(fctSymbol, gfx_proplist))
return false;
}
break;
case C4MN_Add_ImgColor:
// draw colored def facet
fctSymbol.Create(iSymbolSize,iSymbolSize);
if (pDef)
pDef->Draw(fctSymbol, false, XPar.getInt());
break;
default:
// default: by def, if it is not specifically NONE
if (pDef)
{
fctSymbol.Create(iSymbolSize,iSymbolSize);
pDef->Draw(fctSymbol);
}
else
{
// otherwise: Clear symbol!
}
break;
}
// Convert default zero count to no count
if (iCount==0 && !(iExtra & C4MN_Add_ForceCount)) iCount=C4MN_Item_NoCount;
// menuitems without commands are never selectable
bool fIsSelectable = !!*command;
// Add menu item
if(pGfxObj)
Obj->Menu->Add(caption,pGfxObj,command,iCount,nullptr,infocaption,pDef ? pDef->id : C4ID::None,command2,fOwnValue,iValue,fIsSelectable);
else if(pGfx)
Obj->Menu->Add(caption,pGfx,command,iCount,nullptr,infocaption,pDef ? pDef->id : C4ID::None,command2,fOwnValue,iValue,fIsSelectable);
else
Obj->Menu->Add(caption,fctSymbol,command,iCount,nullptr,infocaption,pDef ? pDef->id : C4ID::None,command2,fOwnValue,iValue,fIsSelectable);
return true;
}
static bool FnSelectMenuItem(C4Object *Obj, long iItem)
{
if (!Obj->Menu) return false;
return !!Obj->Menu->SetSelection(iItem, false, true);
}
static bool FnSetMenuDecoration(C4Object *Obj, C4ID idNewDeco)
{
if (!Obj->Menu) return false;
C4GUI::FrameDecoration *pNewDeco = new C4GUI::FrameDecoration();
if (!pNewDeco->SetByDef(idNewDeco))
{
delete pNewDeco;
return false;
}
Obj->Menu->SetFrameDeco(pNewDeco);
return true;
}
static bool FnSetMenuTextProgress(C4Object *Obj, long iNewProgress)
{
if (!Obj->Menu) return false;
return Obj->Menu->SetTextProgress(iNewProgress, false);
}
// Check / Status
static C4Object *FnContained(C4Object *Obj)
{
return Obj->Contained;
}
static C4Object *FnContents(C4Object *Obj, long index)
{
// Special: objects attaching to another object
// cannot be accessed by FnContents
C4Object *cobj;
while ((cobj=Obj->Contents.GetObject(index++)))
if (cobj->GetProcedure()!=DFA_ATTACH) return cobj;
return nullptr;
}
static bool FnShiftContents(C4Object *Obj, bool fShiftBack, C4Def * idTarget, bool fDoCalls)
{
// regular shift
if (!idTarget) return !!Obj->ShiftContents(fShiftBack, fDoCalls);
// check if ID is present within target
C4Object *pNewFront = Obj->Contents.Find(idTarget);
if (!pNewFront) return false;
// select it
Obj->DirectComContents(pNewFront, fDoCalls);
// done, success
return true;
}
static C4Object *FnScrollContents(C4Object *Obj)
{
C4Object *pMove = Obj->Contents.GetObject();
if (pMove)
{
Obj->Contents.Remove(pMove);
Obj->Contents.Add(pMove,C4ObjectList::stNone);
}
return Obj->Contents.GetObject();
}
static long FnContentsCount(C4Object *Obj, C4ID id)
{
return Obj->Contents.ObjectCount(id);
}
static C4Object *FnFindContents(C4Object *Obj, C4Def * c_id)
{
return Obj->Contents.Find(c_id);
}
static C4Object *FnFindOtherContents(C4Object *Obj, C4ID c_id)
{
return Obj->Contents.FindOther(c_id);
}
static bool FnActIdle(C4Object *Obj)
{
return !Obj->GetAction();
}
static bool FnStuck(C4Object *Obj, long off_x, long off_y)
{
return !!Obj->Shape.CheckContact(Obj->GetX()+off_x,Obj->GetY()+off_y);
}
static bool FnInLiquid(C4Object *Obj)
{
return Obj->InLiquid;
}
static bool FnOnFire(C4Object *Obj)
{
if (Obj->GetOnFire()) return true;
// check for effect
if (!Obj->pEffects) return false;
return !!Obj->pEffects->Get(C4Fx_AnyFire);
}
static C4Object *FnCreateContents(C4Object *Obj, C4PropList * PropList, Nillable<long> iCount)
{
// default amount parameter
if (iCount.IsNil()) iCount = 1;
// create objects
C4Object *pNewObj = nullptr;
while (iCount-- > 0) pNewObj = Obj->CreateContents(PropList);
// controller will automatically be set upon entrance
// return last created
return pNewObj;
}
static bool FnMakeCrewMember(C4Object *Obj, long iPlayer)
{
if (!ValidPlr(iPlayer)) return false;
return !!::Players.Get(iPlayer)->MakeCrewMember(Obj);
}
static bool FnGrabObjectInfo(C4Object *Obj, C4Object *pFrom)
{
// local call, safety
if (!pFrom) return false;
// grab info
return !!Obj->GrabInfo(pFrom);
}
static bool FnSetCrewStatus(C4Object *Obj, long iPlr, bool fInCrew)
{
// validate player
C4Player *pPlr = ::Players.Get(iPlr);
if (!pPlr) return false;
// set crew status
return !!pPlr->SetObjectCrewStatus(Obj, fInCrew);
}
static long FnSetTransferZone(C4Object *Obj, long iX, long iY, long iWdt, long iHgt)
{
iX+=Obj->GetX(); iY+=Obj->GetY();
return Game.TransferZones.Set(iX,iY,iWdt,iHgt,Obj);
}
static long FnObjectDistance(C4PropList * _this, C4Object *pObj2, C4Object *pObj)
{
if (!pObj) pObj=Object(_this);
if (!pObj || !pObj2) return 0;
return Distance(pObj->GetX(),pObj->GetY(),pObj2->GetX(),pObj2->GetY());
}
static long FnObjectNumber(C4Object *Obj)
{
return Obj->Number;
// See FnObject
}
static long FnShowInfo(C4Object *Obj, C4Object *pObj)
{
if (!pObj) pObj=Obj;
if (!pObj) return false;
return Obj->ActivateMenu(C4MN_Info,0,0,0,pObj);
}
static void FnSetMass(C4Object *Obj, long iValue)
{
Obj->OwnMass=iValue-Obj->Def->Mass;
Obj->UpdateMass();
}
static long FnGetColor(C4Object *Obj)
{
return Obj->Color;
}
static void FnSetColor(C4Object *Obj, long iValue)
{
Obj->Color=iValue;
Obj->UpdateGraphics(false);
Obj->UpdateFace(false);
}
static void FnSetLightRange(C4Object *Obj, long iRange, Nillable<long> iFadeoutRange)
{
if (iFadeoutRange.IsNil())
{
if(iRange == 0)
iFadeoutRange = 0;
else
iFadeoutRange = C4FOW_DefLightFadeoutRangeX;
}
// set range
Obj->SetLightRange(iRange, iFadeoutRange);
}
static long FnGetLightColor(C4Object *Obj)
{
// get it
return Obj->GetLightColor();
}
static void FnSetLightColor(C4Object *Obj, long iValue)
{
Obj->SetLightColor(iValue);
}
static void FnSetPicture(C4Object *Obj, long iX, long iY, long iWdt, long iHgt)
{
// set new picture rect
Obj->PictureRect.Set(iX, iY, iWdt, iHgt);
}
static C4String *FnGetProcedure(C4Object *Obj)
{
// no action?
C4PropList* pActionDef = Obj->GetAction();
if (!pActionDef) return nullptr;
// get proc
return pActionDef->GetPropertyStr(P_Procedure);
}
static bool FnCheckVisibility(C4Object *Obj, int plr) {
return Obj->IsVisible(plr, false);
}
static bool FnSetClrModulation(C4Object *Obj, Nillable<long> dwClr, long iOverlayID)
{
uint32_t clr = 0xffffffff;
if (!dwClr.IsNil()) clr = dwClr;
// overlay?
if (iOverlayID)
{
C4GraphicsOverlay *pOverlay = Obj->GetGraphicsOverlay(iOverlayID, false);
if (!pOverlay)
{
DebugLogF("SetClrModulation: Overlay %d not defined for object %d (%s)", (int) iOverlayID, (int) Obj->Number, Obj->GetName());
return false;
}
pOverlay->SetClrModulation(clr);
// C4GraphicsOverlay Does not have an StdMeshInstance (yet), no need to
// update faceordering
}
else
{
// set it
Obj->ColorMod=clr;
if (Obj->pMeshInstance)
Obj->pMeshInstance->SetFaceOrderingForClrModulation(clr);
}
// success
return true;
}
static Nillable<long> FnGetClrModulation(C4Object *Obj, long iOverlayID)
{
// overlay?
if (iOverlayID)
{
C4GraphicsOverlay *pOverlay = Obj->GetGraphicsOverlay(iOverlayID, false);
if (!pOverlay)
{
DebugLogF("GetClrModulation: Overlay %d not defined for object %d (%s)", (int) iOverlayID, (int) Obj->Number, Obj->GetName());
return C4Void();
}
return pOverlay->GetClrModulation();
}
else
// get it
return Obj->ColorMod;
}
static bool FnCloseMenu(C4Object *Obj)
{
return !!Obj->CloseMenu(true);
}
static Nillable<long> FnGetMenuSelection(C4Object *Obj)
{
if (!Obj->Menu || !Obj->Menu->IsActive()) return C4Void();
return Obj->Menu->GetSelection();
}
static bool FnCanConcatPictureWith(C4Object *Obj, C4Object *pObj)
{
// safety
if (!Obj->Status || !pObj) return false;
// Call the function in the object
return Obj->CanConcatPictureWith(pObj);
}
static bool FnSetGraphics(C4Object *Obj, C4String *pGfxName, C4Def *pSrcDef, long iOverlayID, long iOverlayMode, C4String *pAction, long dwBlitMode, C4Object *pOverlayObject)
{
// safety
if (!Obj->Status) return false;
// setting overlay?
if (iOverlayID)
{
// any overlays must be positive for now
if (iOverlayID<0) { Log("SetGraphics: Background overlays not implemented!"); return false; }
// deleting overlay?
C4DefGraphics *pGrp = nullptr;
if (iOverlayMode == C4GraphicsOverlay::MODE_Object || iOverlayMode == C4GraphicsOverlay::MODE_Rank || iOverlayMode == C4GraphicsOverlay::MODE_ObjectPicture)
{
if (!pOverlayObject) return Obj->RemoveGraphicsOverlay(iOverlayID);
}
else
{
if (!pSrcDef) return Obj->RemoveGraphicsOverlay(iOverlayID);
pGrp = pSrcDef->Graphics.Get(FnStringPar(pGfxName));
if (!pGrp) return false;
}
// adding/setting
C4GraphicsOverlay *pOverlay = Obj->GetGraphicsOverlay(iOverlayID, true);
switch (iOverlayMode)
{
case C4GraphicsOverlay::MODE_Base:
pOverlay->SetAsBase(pGrp, dwBlitMode);
break;
case C4GraphicsOverlay::MODE_Action:
pOverlay->SetAsAction(pGrp, FnStringPar(pAction), dwBlitMode);
break;
case C4GraphicsOverlay::MODE_IngamePicture:
pOverlay->SetAsIngamePicture(pGrp, dwBlitMode);
break;
case C4GraphicsOverlay::MODE_Picture:
pOverlay->SetAsPicture(pGrp, dwBlitMode);
break;
case C4GraphicsOverlay::MODE_Object:
if (pOverlayObject && !pOverlayObject->Status) pOverlayObject = nullptr;
pOverlay->SetAsObject(pOverlayObject, dwBlitMode);
break;
case C4GraphicsOverlay::MODE_ExtraGraphics:
pOverlay->SetAsExtraGraphics(pGrp, dwBlitMode);
break;
case C4GraphicsOverlay::MODE_Rank:
if (pOverlayObject && !pOverlayObject->Status) pOverlayObject = nullptr;
pOverlay->SetAsRank(dwBlitMode, pOverlayObject);
break;
case C4GraphicsOverlay::MODE_ObjectPicture:
if (pOverlayObject && !pOverlayObject->Status) pOverlayObject = nullptr;
pOverlay->SetAsObjectPicture(pOverlayObject, dwBlitMode);
break;
default:
DebugLog("SetGraphics: Invalid overlay mode");
pOverlay->SetAsBase(nullptr, 0); // make invalid, so it will be removed
break;
}
// remove if invalid
if (!pOverlay->IsValid(Obj))
{
Obj->RemoveGraphicsOverlay(iOverlayID);
return false;
}
// Okay, valid overlay set!
return true;
}
// no overlay: Base graphics
// set graphics - pSrcDef==nullptr defaults to pObj->pDef
return Obj->SetGraphics(FnStringPar(pGfxName), pSrcDef);
}
static long FnGetDefBottom(C4PropList * _this)
{
if (!_this || !_this->GetDef())
throw NeedNonGlobalContext("GetDefBottom");
C4Object *obj = Object(_this);
C4Def *def = _this->GetDef();
assert(!obj || obj->Def == def);
if (obj)
return obj->GetY() + obj->Shape.GetBottom();
else if (def)
return def->Shape.GetBottom();
else
return 0;
}
static bool FnSetMenuSize(C4Object *Obj, long iCols, long iRows)
{
// get menu
C4Menu *pMnu=Obj->Menu;
if (!pMnu || !pMnu->IsActive()) return false;
pMnu->SetSize(Clamp<long>(iCols, 0, 50), Clamp<long>(iRows, 0, 50));
return true;
}
static bool FnGetCrewEnabled(C4Object *Obj)
{
// return status
return !Obj->CrewDisabled;
}
static void FnSetCrewEnabled(C4Object *Obj, bool fEnabled)
{
bool change = (Obj->CrewDisabled == fEnabled) ? true : false;
// set status
Obj->CrewDisabled=!fEnabled;
// deselect
if (!fEnabled)
{
C4Player *pOwner;
if ((pOwner=::Players.Get(Obj->Owner)))
{
// if viewed player cursor gets deactivated and no new cursor is found, follow the old in target mode
bool fWasCursorMode = (pOwner->ViewMode == C4PVM_Cursor);
if (pOwner->Cursor==Obj)
pOwner->AdjustCursorCommand();
if (!pOwner->ViewCursor && !pOwner->Cursor && fWasCursorMode)
pOwner->SetViewMode(C4PVM_Target, Obj);
}
}
// call to crew
if (change)
{
if (fEnabled)
Obj->Call(PSF_CrewEnabled);
else
Obj->Call(PSF_CrewDisabled);
}
}
static void FnDoCrewExp(C4Object *Obj, long iChange)
{
// do exp
Obj->DoExperience(iChange);
}
static bool FnClearMenuItems(C4Object *Obj)
{
// check menu
if (!Obj->Menu) return false;
// clear the items
Obj->Menu->ClearItems();
// success
return true;
}
static C4Object *FnGetObjectLayer(C4Object *Obj)
{
// get layer object
return Obj->Layer;
}
static void FnSetObjectLayer(C4Object *Obj, C4Object *pNewLayer)
{
// set layer object
Obj->Layer = pNewLayer;
// set for all contents as well
for (C4Object* contentObj : Obj->Contents)
if (contentObj && contentObj->Status)
contentObj->Layer = pNewLayer;
}
static void FnSetShape(C4Object *Obj, long iX, long iY, long iWdt, long iHgt)
{
// update shape
Obj->Shape.x = iX;
Obj->Shape.y = iY;
Obj->Shape.Wdt = iWdt;
Obj->Shape.Hgt = iHgt;
// section list needs refresh
Obj->UpdatePos();
}
static bool FnSetObjDrawTransform(C4Object *Obj, long iA, long iB, long iC, long iD, long iE, long iF, long iOverlayID)
{
C4DrawTransform *pTransform;
// overlay?
if (iOverlayID)
{
// set overlay transform
C4GraphicsOverlay *pOverlay = Obj->GetGraphicsOverlay(iOverlayID, false);
if (!pOverlay) return false;
pTransform = pOverlay->GetTransform();
}
else
{
// set base transform
pTransform = Obj->pDrawTransform;
// del transform?
if (!iB && !iC && !iD && !iF && iA==iE && (!iA || iA==1000))
{
// identity/0 and no transform defined: nothing to do
if (!pTransform) return true;
// transform has no flipdir?
if (pTransform->FlipDir == 1)
{
// kill identity-transform, then
delete pTransform;
Obj->pDrawTransform=nullptr;
return true;
}
// flipdir must remain: set identity
pTransform->Set(1,0,0,0,1,0,0,0,1);
return true;
}
// create draw transform if not already present
if (!pTransform) pTransform = Obj->pDrawTransform = new C4DrawTransform();
}
// assign values
pTransform->Set((float) iA/1000, (float) iB/1000, (float) iC/1000, (float) iD/1000, (float) iE/1000, (float) iF/1000, 0, 0, 1);
// done, success
return true;
}
static bool FnSetObjDrawTransform2(C4Object *Obj, long iA, long iB, long iC, long iD, long iE, long iF, long iG, long iH, long iI, long iOverlayID)
{
// local call / safety
C4Object * pObj = Obj;
C4DrawTransform *pTransform;
// overlay?
if (iOverlayID)
{
// set overlay transform
C4GraphicsOverlay *pOverlay = pObj->GetGraphicsOverlay(iOverlayID, false);
if (!pOverlay) return false;
pTransform = pOverlay->GetTransform();
}
else
{
// set base transform
pTransform = pObj->pDrawTransform;
// create draw transform if not already present
if (!pTransform) pTransform = pObj->pDrawTransform = new C4DrawTransform(1);
}
// assign values
#define L2F(l) ((float)l/1000)
C4BltTransform matrix;
matrix.Set(L2F(iA), L2F(iB), L2F(iC), L2F(iD), L2F(iE), L2F(iF), L2F(iG), L2F(iH), L2F(iI));
*pTransform *= matrix;
#undef L2F
// done, success
return true;
}
static bool FnSetObjectStatus(C4Object *Obj, long iNewStatus, bool fClearPointers)
{
// local call / safety
if (!Obj->Status) return false;
// no change
if (Obj->Status == iNewStatus) return true;
// set new status
switch (iNewStatus)
{
case C4OS_NORMAL:
return Obj->StatusActivate();
case C4OS_INACTIVE:
return Obj->StatusDeactivate(fClearPointers);
default:
return false; // status unknown
}
}
static long FnGetObjectStatus(C4Object *Obj)
{
return Obj->Status;
}
static bool FnAdjustWalkRotation(C4Object *Obj, long iRangeX, long iRangeY, long iSpeed)
{
// must be rotateable and attached to solid ground
if (!Obj->Def->Rotateable || ~Obj->Action.t_attach&CNAT_Bottom || Obj->Shape.AttachMat == MNone)
return false;
// adjust rotation
return Obj->AdjustWalkRotation(iRangeX, iRangeY, iSpeed);
}
static long FnGetContact(C4Object *Obj, long iVertex, long dwCheck)
{
// vertex not specified: check all
if (iVertex == -1)
{
long iResult = 0;
for (int i=0; i<Obj->Shape.VtxNum; ++i)
iResult |= Obj->Shape.GetVertexContact(i, dwCheck, Obj->GetX(), Obj->GetY());
return iResult;
}
// vertex specified: check it
if (!Inside<long>(iVertex, 0, Obj->Shape.VtxNum-1)) return 0;
return Obj->Shape.GetVertexContact(iVertex, dwCheck, Obj->GetX(), Obj->GetY());
}
static long FnSetObjectBlitMode(C4Object *Obj, long dwNewBlitMode, long iOverlayID)
{
// overlay?
if (iOverlayID)
{
C4GraphicsOverlay *pOverlay = Obj->GetGraphicsOverlay(iOverlayID, false);
if (!pOverlay)
{
DebugLogF("SetObjectBlitMode: Overlay %d not defined for object %d (%s)", (int) iOverlayID, (int) Obj->Number, Obj->GetName());
return false;
}
pOverlay->SetBlitMode(dwNewBlitMode);
return true;
}
// get prev blit mode
DWORD dwPrevMode = Obj->BlitMode;
// iNewBlitMode = 0: reset to definition default
if (!dwNewBlitMode)
Obj->BlitMode = Obj->Def->BlitMode;
else
// otherwise, set the desired value
// also ensure that the custom flag is set
Obj->BlitMode = dwNewBlitMode | C4GFXBLIT_CUSTOM;
// return previous value
return dwPrevMode;
}
static Nillable<long> FnGetObjectBlitMode(C4Object *Obj, long iOverlayID)
{
// overlay?
if (iOverlayID)
{
C4GraphicsOverlay *pOverlay = Obj->GetGraphicsOverlay(iOverlayID, false);
if (!pOverlay)
{
DebugLogF("SetObjectBlitMode: Overlay %d not defined for object %d (%s)", (int) iOverlayID, (int) Obj->Number, Obj->GetName());
return C4Void();
}
return pOverlay->GetBlitMode();
}
// get blitting mode
return Obj->BlitMode;
}
static long FnGetUnusedOverlayID(C4Object *Obj, long iBaseIndex)
{
// safety
if (!iBaseIndex) return 0;
// find search first unused index from there on
int iSearchDir = (iBaseIndex < 0) ? -1 : 1;
while (Obj->GetGraphicsOverlay(iBaseIndex, false)) iBaseIndex += iSearchDir;
return iBaseIndex;
}
static Nillable<int> FnPlayAnimation(C4Object *Obj, C4String *szAnimation, int iSlot, C4ValueArray* PositionProvider, Nillable<C4ValueArray*> WeightProvider, Nillable<int> iSibling, Nillable<int> iAttachNumber)
{
if (!Obj) return C4Void();
if (!Obj->pMeshInstance) return C4Void();
if (iSlot == 0) return C4Void(); // Reserved for ActMap animations
if (!PositionProvider) return C4Void();
// If no weight provider is passed, this animation should be played exclusively.
bool stop_previous_animations = WeightProvider.IsNil();
// Exclusive mode cannot work with a sibling
if (!iSibling.IsNil() && stop_previous_animations) return C4Void();
StdMeshInstance* Instance = Obj->pMeshInstance;
if (!iAttachNumber.IsNil())
{
const StdMeshInstance::AttachedMesh* Attached = Instance->GetAttachedMeshByNumber(iAttachNumber);
// OwnChild is set if an object's instance is attached. In that case the animation should be set directly on that object.
if (!Attached || !Attached->OwnChild) return C4Void();
Instance = Attached->Child;
}
StdMeshInstance::AnimationNode* s_node = nullptr;
if (!iSibling.IsNil())
{
s_node = Instance->GetAnimationNodeByNumber(iSibling);
if (!s_node || s_node->GetSlot() != iSlot) return C4Void();
}
const StdMeshAnimation* animation = Instance->GetMesh().GetSkeleton().GetAnimationByName(szAnimation->GetData());
if (!animation) return C4Void();
StdMeshInstance::ValueProvider* p_provider = CreateValueProviderFromArray(Obj, *PositionProvider, animation);
StdMeshInstance::ValueProvider* w_provider;
if (stop_previous_animations)
{
w_provider = new C4ValueProviderConst(Fix1);
}
else
{
w_provider = CreateValueProviderFromArray(Obj, *WeightProvider);
}
if (!p_provider || !w_provider)
{
delete p_provider;
delete w_provider;
return C4Void();
}
StdMeshInstance::AnimationNode* n_node = Instance->PlayAnimation(*animation, iSlot, s_node, p_provider, w_provider, stop_previous_animations);
if (!n_node) return C4Void();
return n_node->GetNumber();
}
static Nillable<int> FnTransformBone(C4Object *Obj, C4String *szBoneName, C4ValueArray* Transformation, int iSlot, C4ValueArray* WeightProvider, Nillable<int> iSibling, Nillable<int> iAttachNumber)
{
if (!Obj) return C4Void();
if (!Obj->pMeshInstance) return C4Void();
if (iSlot == 0) return C4Void(); // Reserved for ActMap animations
if (!Transformation) return C4Void();
if (!WeightProvider) return C4Void();
StdMeshInstance* Instance = Obj->pMeshInstance;
if (!iAttachNumber.IsNil())
{
const StdMeshInstance::AttachedMesh* Attached = Instance->GetAttachedMeshByNumber(iAttachNumber);
// OwnChild is set if an object's instance is attached. In that case the animation should be set directly on that object.
if (!Attached || !Attached->OwnChild) return C4Void();
Instance = Attached->Child;
}
StdMeshInstance::AnimationNode* s_node = nullptr;
if (!iSibling.IsNil())
{
s_node = Instance->GetAnimationNodeByNumber(iSibling);
if (!s_node || s_node->GetSlot() != iSlot) return C4Void();
}
const StdMeshBone* bone = Instance->GetMesh().GetSkeleton().GetBoneByName(szBoneName->GetData());
if(!bone) return C4Void();
StdMeshInstance::ValueProvider* w_provider = CreateValueProviderFromArray(Obj, *WeightProvider);
if (!w_provider) return C4Void();
StdMeshMatrix matrix;
if (!C4ValueToMatrix(*Transformation, &matrix))
throw C4AulExecError("TransformBone: Transformation is not a valid 3x4 matrix");
// For bone transformations we cannot use general matrix transformations, but we use decomposed
// translate, scale and rotation components (represented by the StdMeshTransformation class). This
// is less generic since it does not support skewing.
// Still, in the script API we want to expose a matrix parameter so that the convenient Trans_*
// functions can be used. We decompose the passed matrix at this point. If the matrix indeed has
// skewing components, the results will probably look strange since the decomposition would yield
// bogus values, however I don't think that's a practical use case. In the worst case we could add
// a check here and return nil if the matrix cannot be decomposed.
StdMeshTransformation trans = matrix.Decompose();
StdMeshInstance::AnimationNode* n_node = Instance->PlayAnimation(bone, trans, iSlot, s_node, w_provider, false);
if (!n_node) return C4Void();
return n_node->GetNumber();
}
static bool FnStopAnimation(C4Object *Obj, Nillable<int> iAnimationNumber, Nillable<int> iAttachNumber)
{
if (!Obj) return false;
if (!Obj->pMeshInstance) return false;
if (iAnimationNumber.IsNil()) return false; // distinguish nil from 0
StdMeshInstance* Instance = Obj->pMeshInstance;
if (!iAttachNumber.IsNil())
{
const StdMeshInstance::AttachedMesh* Attached = Instance->GetAttachedMeshByNumber(iAttachNumber);
// OwnChild is set if an object's instance is attached. In that case the animation should be set directly on that object.
if (!Attached || !Attached->OwnChild) return false;
Instance = Attached->Child;
}
StdMeshInstance::AnimationNode* node = Instance->GetAnimationNodeByNumber(iAnimationNumber);
// slot 0 is reserved for ActMap animations
if (!node || node->GetSlot() == 0) return false;
Instance->StopAnimation(node);
return true;
}
static Nillable<int> FnGetRootAnimation(C4Object *Obj, int iSlot, Nillable<int> iAttachNumber)
{
if (!Obj) return C4Void();
if (!Obj->pMeshInstance) return C4Void();
StdMeshInstance* Instance = Obj->pMeshInstance;
if (!iAttachNumber.IsNil())
{
const StdMeshInstance::AttachedMesh* Attached = Instance->GetAttachedMeshByNumber(iAttachNumber);
// OwnChild is set if an object's instance is attached. In that case the animation should be set directly on that object.
if (!Attached || !Attached->OwnChild) return C4Void();
Instance = Attached->Child;
}
StdMeshInstance::AnimationNode* node = Instance->GetRootAnimationForSlot(iSlot);
if (!node) return C4Void();
return node->GetNumber();
}
static Nillable<C4ValueArray*> FnGetAnimationList(C4PropList* _this, Nillable<int> iAttachNumber)
{
C4Object *Obj = Object(_this);
const StdMeshSkeleton* skeleton;
if (!Obj)
{
if (!_this || !_this->GetDef()) throw NeedNonGlobalContext("GetAnimationList");
C4Def *def = _this->GetDef();
if (!def->Graphics.IsMesh()) return C4Void();
skeleton = &def->Graphics.Mesh->GetSkeleton();
}
else
{
if (!Obj) return C4Void();
if (!Obj->pMeshInstance) return C4Void();
StdMeshInstance* Instance = Obj->pMeshInstance;
if (!iAttachNumber.IsNil())
{
const StdMeshInstance::AttachedMesh* Attached = Instance->GetAttachedMeshByNumber(iAttachNumber);
// OwnChild is set if an object's instance is attached. In that case the animation list should be obtained directly on that object.
if (!Attached || !Attached->OwnChild) return C4Void();
Instance = Attached->Child;
}
skeleton = &Instance->GetMesh().GetSkeleton();
}
const std::vector<const StdMeshAnimation*> animations = skeleton->GetAnimations();
C4ValueArray* retval = new C4ValueArray(animations.size());
for(unsigned int i = 0; i < animations.size(); ++i)
(*retval)[i] = C4VString(animations[i]->Name);
return retval;
}
static Nillable<int> FnGetAnimationLength(C4Object *Obj, C4String *szAnimation, Nillable<int> iAttachNumber)
{
if (!Obj) return C4Void();
if (!Obj->pMeshInstance) return C4Void();
StdMeshInstance* Instance = Obj->pMeshInstance;
if (!iAttachNumber.IsNil())
{
const StdMeshInstance::AttachedMesh* Attached = Instance->GetAttachedMeshByNumber(iAttachNumber);
// OwnChild is set if an object's instance is attached. In that case the animation should be set directly on that object.
if (!Attached || !Attached->OwnChild) return C4Void();
Instance = Attached->Child;
}
const StdMeshAnimation* animation = Instance->GetMesh().GetSkeleton().GetAnimationByName(szAnimation->GetData());
if (!animation) return C4Void();
return fixtoi(ftofix(animation->Length), 1000); // sync critical!
}
static Nillable<C4String*> FnGetAnimationName(C4Object *Obj, Nillable<int> iAnimationNumber, Nillable<int> iAttachNumber)
{
if (!Obj) return C4Void();
if (!Obj->pMeshInstance) return C4Void();
if (iAnimationNumber.IsNil()) return C4Void(); // distinguish nil from 0
StdMeshInstance* Instance = Obj->pMeshInstance;
if (!iAttachNumber.IsNil())
{
const StdMeshInstance::AttachedMesh* Attached = Instance->GetAttachedMeshByNumber(iAttachNumber);
// OwnChild is set if an object's instance is attached. In that case the animation should be set directly on that object.
if (!Attached || !Attached->OwnChild) return C4Void();
Instance = Attached->Child;
}
StdMeshInstance::AnimationNode* node = Instance->GetAnimationNodeByNumber(iAnimationNumber);
if (!node || node->GetType() != StdMeshInstance::AnimationNode::LeafNode) return C4Void();
return String(node->GetAnimation()->Name.getData());
}
static Nillable<int> FnGetAnimationPosition(C4Object *Obj, Nillable<int> iAnimationNumber, Nillable<int> iAttachNumber)
{
if (!Obj) return C4Void();
if (!Obj->pMeshInstance) return C4Void();
if (iAnimationNumber.IsNil()) return C4Void(); // distinguish nil from 0
StdMeshInstance* Instance = Obj->pMeshInstance;
if (!iAttachNumber.IsNil())
{
const StdMeshInstance::AttachedMesh* Attached = Instance->GetAttachedMeshByNumber(iAttachNumber);
// OwnChild is set if an object's instance is attached. In that case the animation should be set directly on that object.
if (!Attached || !Attached->OwnChild) return C4Void();
Instance = Attached->Child;
}
StdMeshInstance::AnimationNode* node = Instance->GetAnimationNodeByNumber(iAnimationNumber);
if (!node || node->GetType() != StdMeshInstance::AnimationNode::LeafNode) return C4Void();
return fixtoi(node->GetPosition(), 1000);
}
static Nillable<int> FnGetAnimationWeight(C4Object *Obj, Nillable<int> iAnimationNumber, Nillable<int> iAttachNumber)
{
if (!Obj) return C4Void();
if (!Obj->pMeshInstance) return C4Void();
if (iAnimationNumber.IsNil()) return C4Void(); // distinguish nil from 0
StdMeshInstance* Instance = Obj->pMeshInstance;
if (!iAttachNumber.IsNil())
{
const StdMeshInstance::AttachedMesh* Attached = Instance->GetAttachedMeshByNumber(iAttachNumber);
// OwnChild is set if an object's instance is attached. In that case the animation should be set directly on that object.
if (!Attached || !Attached->OwnChild) return C4Void();
Instance = Attached->Child;
}
StdMeshInstance::AnimationNode* node = Instance->GetAnimationNodeByNumber(iAnimationNumber);
if (!node || node->GetType() != StdMeshInstance::AnimationNode::LinearInterpolationNode) return C4Void();
return fixtoi(node->GetWeight(), 1000);
}
static bool FnSetAnimationPosition(C4Object *Obj, Nillable<int> iAnimationNumber, C4ValueArray* PositionProvider, Nillable<int> iAttachNumber)
{
if (!Obj) return false;
if (!Obj->pMeshInstance) return false;
if (iAnimationNumber.IsNil()) return false; // distinguish nil from 0
StdMeshInstance* Instance = Obj->pMeshInstance;
if (!iAttachNumber.IsNil())
{
const StdMeshInstance::AttachedMesh* Attached = Instance->GetAttachedMeshByNumber(iAttachNumber);
// OwnChild is set if an object's instance is attached. In that case the animation should be set directly on that object.
if (!Attached || !Attached->OwnChild) return false;
Instance = Attached->Child;
}
StdMeshInstance::AnimationNode* node = Instance->GetAnimationNodeByNumber(iAnimationNumber);
// slot 0 is reserved for ActMap animations
if (!node || node->GetSlot() == 0 || node->GetType() != StdMeshInstance::AnimationNode::LeafNode) return false;
StdMeshInstance::ValueProvider* p_provider = CreateValueProviderFromArray(Obj, *PositionProvider);
if (!p_provider) return false;
Instance->SetAnimationPosition(node, p_provider);
return true;
}
static bool FnSetAnimationBoneTransform(C4Object *Obj, Nillable<int> iAnimationNumber, C4ValueArray* Transformation, Nillable<int> iAttachNumber)
{
if (!Obj) return false;
if (!Obj->pMeshInstance) return false;
if (iAnimationNumber.IsNil()) return false; // distinguish nil from 0
StdMeshInstance* Instance = Obj->pMeshInstance;
if (!iAttachNumber.IsNil())
{
const StdMeshInstance::AttachedMesh* Attached = Instance->GetAttachedMeshByNumber(iAttachNumber);
// OwnChild is set if an object's instance is attached. In that case the animation should be set directly on that object.
if (!Attached || !Attached->OwnChild) return false;
Instance = Attached->Child;
}
StdMeshInstance::AnimationNode* node = Instance->GetAnimationNodeByNumber(iAnimationNumber);
// slot 0 is reserved for ActMap animations
if (!node || node->GetSlot() == 0 || node->GetType() != StdMeshInstance::AnimationNode::CustomNode) return false;
StdMeshMatrix matrix;
if (!C4ValueToMatrix(*Transformation, &matrix))
throw C4AulExecError("TransformBone: Transformation is not a valid 3x4 matrix");
// Here the same remark applies as in FnTransformBone
StdMeshTransformation trans = matrix.Decompose();
Instance->SetAnimationBoneTransform(node, trans);
return true;
}
static bool FnSetAnimationWeight(C4Object *Obj, Nillable<int> iAnimationNumber, C4ValueArray* WeightProvider, Nillable<int> iAttachNumber)
{
if (!Obj) return false;
if (!Obj->pMeshInstance) return false;
if (iAnimationNumber.IsNil()) return false; // distinguish nil from 0
StdMeshInstance* Instance = Obj->pMeshInstance;
if (!iAttachNumber.IsNil())
{
const StdMeshInstance::AttachedMesh* Attached = Instance->GetAttachedMeshByNumber(iAttachNumber);
// OwnChild is set if an object's instance is attached. In that case the animation should be set directly on that object.
if (!Attached || !Attached->OwnChild) return false;
Instance = Attached->Child;
}
StdMeshInstance::AnimationNode* node = Instance->GetAnimationNodeByNumber(iAnimationNumber);
// slot 0 is reserved for ActMap animations
if (!node || node->GetSlot() == 0 || node->GetType() != StdMeshInstance::AnimationNode::LinearInterpolationNode) return false;
StdMeshInstance::ValueProvider* w_provider = CreateValueProviderFromArray(Obj, *WeightProvider);
if (!w_provider) return false;
Instance->SetAnimationWeight(node, w_provider);
return true;
}
static Nillable<int> FnAttachMesh(C4Object *Obj, C4PropList* Mesh, C4String * szParentBone, C4String * szChildBone, C4ValueArray * Transformation, int Flags, int AttachNumber)
{
if (!Obj->pMeshInstance) return C4Void();
if (!Mesh) return C4Void();
StdMeshMatrix trans = StdMeshMatrix::Identity();
if (Transformation)
if (!C4ValueToMatrix(*Transformation, &trans))
throw C4AulExecError("AttachMesh: Transformation is not a valid 3x4 matrix");
StdMeshInstance::AttachedMesh* attach;
C4Object* pObj = Mesh->GetObject();
if (pObj)
{
if (!pObj->pMeshInstance) return C4Void();
attach = Obj->pMeshInstance->AttachMesh(*pObj->pMeshInstance, new C4MeshDenumerator(pObj), szParentBone->GetData(), szChildBone->GetData(), trans, Flags, false, AttachNumber);
}
else
{
C4Def* pDef = Mesh->GetDef();
if (!pDef) return C4Void();
if (pDef->Graphics.Type != C4DefGraphics::TYPE_Mesh) return C4Void();
attach = Obj->pMeshInstance->AttachMesh(*pDef->Graphics.Mesh, new C4MeshDenumerator(pDef), szParentBone->GetData(), szChildBone->GetData(), trans, Flags, AttachNumber);
if(attach) attach->Child->SetFaceOrderingForClrModulation(Obj->ColorMod);
}
if (!attach) return C4Void();
return attach->Number;
}
static bool FnDetachMesh(C4Object *Obj, long iAttachNumber)
{
if (!Obj || !Obj->pMeshInstance) return false;
return Obj->pMeshInstance->DetachMesh(iAttachNumber);
}
static bool FnSetAttachBones(C4Object *Obj, long iAttachNumber, Nillable<C4String*> szParentBone, Nillable<C4String*> szChildBone)
{
if (!Obj || !Obj->pMeshInstance) return false;
StdMeshInstance::AttachedMesh* attach = Obj->pMeshInstance->GetAttachedMeshByNumber(iAttachNumber);
if (!attach) return false;
if (!szParentBone.IsNil())
{
C4String* ParentBone = szParentBone;
if (!attach->SetParentBone(ParentBone->GetData())) return false;
}
if (!szChildBone.IsNil())
{
C4String* ChildBone = szChildBone;
if (!attach->SetChildBone(ChildBone->GetData())) return false;
}
return true;
}
static bool FnSetAttachTransform(C4Object *Obj, long iAttachNumber, C4ValueArray* Transformation)
{
if (!Obj || !Obj->pMeshInstance) return false;
if (!Transformation) return false;
StdMeshInstance::AttachedMesh* attach = Obj->pMeshInstance->GetAttachedMeshByNumber(iAttachNumber);
if (!attach) return false;
StdMeshMatrix trans;
if (!C4ValueToMatrix(*Transformation, &trans))
throw C4AulExecError("SetAttachTransform: Transformation is not a valid 3x4 matrix");
attach->SetAttachTransformation(trans);
return true;
}
static Nillable<C4String*> FnGetMeshMaterial(C4PropList * _this, int iSubMesh)
{
// Called in object or definition context?
C4Object *Obj = Object(_this);
if (!Obj)
{
if (!_this || !_this->GetDef()) throw NeedNonGlobalContext("GetMeshMaterial");
// Called in definition context: Get definition default mesh material
C4Def *def = _this->GetDef();
if (!def->Graphics.IsMesh()) return C4Void();
if (iSubMesh < 0 || (unsigned int)iSubMesh >= def->Graphics.Mesh->GetNumSubMeshes()) return C4Void();
const StdSubMesh &submesh = def->Graphics.Mesh->GetSubMesh(iSubMesh);
return String(submesh.GetMaterial().Name.getData());
}
else
{
// Called in object context: Get material of mesh instance
if (!Obj->pMeshInstance) return C4Void();
if (iSubMesh < 0 || (unsigned int)iSubMesh >= Obj->pMeshInstance->GetNumSubMeshes()) return C4Void();
StdSubMeshInstance& submesh = Obj->pMeshInstance->GetSubMesh(iSubMesh);
return String(submesh.GetMaterial().Name.getData());
}
}
static bool FnSetMeshMaterial(C4Object *Obj, C4String* Material, int iSubMesh)
{
if (!Obj || !Obj->pMeshInstance) return false;
if (iSubMesh < 0 || (unsigned int)iSubMesh >= Obj->pMeshInstance->GetNumSubMeshes()) return false;
if (!Material) return false;
const StdMeshMaterial* material = ::MeshMaterialManager.GetMaterial(Material->GetData().getData());
if (!material) return false;
Obj->pMeshInstance->SetMaterial(iSubMesh, *material);
return true;
}
static bool FnCreateParticleAtBone(C4Object* Obj, C4String* szName, C4String* szBoneName, C4ValueArray* Pos, C4ValueArray* Dir, C4Value lifetime, C4PropList *properties, int amount)
{
// safety
if(!Obj || !Obj->Status) return false;
// Get bone
if(!Obj->pMeshInstance) return false;
const StdMesh& mesh = Obj->pMeshInstance->GetMesh();
const StdMeshBone* bone = mesh.GetSkeleton().GetBoneByName(szBoneName->GetData());
if(!bone) return false;
// get particle
C4ParticleDef *pDef=::Particles.definitions.GetDef(FnStringPar(szName));
if (!pDef) return false;
#ifndef USE_CONSOLE
// Get transform
Obj->pMeshInstance->UpdateBoneTransforms();
const StdMeshMatrix transform = Obj->pMeshInstance->GetBoneTransform(bone->Index) * StdMeshMatrix::Transform(bone->Transformation);
// Get offset and direction
StdMeshVector x, dir;
if(Pos)
{
if(Pos->GetSize() != 3)
throw C4AulExecError("CreateParticleAtBone: Pos is not a three-vector");
x.x = (*Pos).GetItem(0).getInt();
x.y = (*Pos).GetItem(1).getInt();
x.z = (*Pos).GetItem(2).getInt();
}
else { x.x = x.y = x.z = 0.0f; }
if(Dir)
{
if(Dir->GetSize() != 3)
throw C4AulExecError("CreateParticleAtBone: Dir is not a three-vector");
dir.x = (*Dir).GetItem(0).getInt() / 10.0f;
dir.y = (*Dir).GetItem(1).getInt() / 10.0f;
dir.z = (*Dir).GetItem(2).getInt() / 10.0f;
}
else { dir.x = dir.y = dir.z = 0.0f; }
// Apply the bone transformation to them, to go from bone coordinates
// to mesh coordinates.
// This is a good example why we should have different types for
// position vectors and displacement vectors. TODO.
StdMeshVector transformed_x = transform * x;
transformed_x.x += transform(0,3);
transformed_x.y += transform(1,3);
transformed_x.z += transform(2,3);
x = transformed_x;
dir = transform * dir;
// Apply MeshTransformation in the mesh reference frame
C4Value value;
Obj->GetProperty(P_MeshTransformation, &value);
StdMeshMatrix MeshTransform;
if (!C4ValueToMatrix(value, &MeshTransform))
MeshTransform = StdMeshMatrix::Identity();
x = MeshTransform * x;
dir = MeshTransform * dir;
x.x += MeshTransform(0,3);
x.y += MeshTransform(1,3);
x.z += MeshTransform(2,3);
// Now go to world coordinates -- this code is copied from and needs to
// stay in sync with C4DrawGL::PerformMesh, so the particles are
// created at the correct position.
// TODO: This should be moved into a common function.
const StdMeshBox& box = mesh.GetBoundingBox();
StdMeshVector v1, v2;
v1.x = box.x1; v1.y = box.y1; v1.z = box.z1;
v2.x = box.x2; v2.y = box.y2; v2.z = box.z2;
const float tx = fixtof(Obj->fix_x) + Obj->Def->Shape.GetX();
const float ty = fixtof(Obj->fix_y) + Obj->Def->Shape.GetY();
const float twdt = Obj->Def->Shape.Wdt;
const float thgt = Obj->Def->Shape.Hgt;
const float rx = -std::min(v1.x,v2.x) / fabs(v2.x - v1.x);
const float ry = -std::min(v1.y,v2.y) / fabs(v2.y - v1.y);
const float dx = tx + rx*twdt;
const float dy = ty + ry*thgt;
x.x += dx;
x.y += dy;
// This was added in the block before and could also just be removed from tx/ty.
// However, the block would no longer be equal to where it came from.
x.x -= fixtof(Obj->fix_x);
x.y -= fixtof(Obj->fix_y);
// Finally, apply DrawTransform to the world coordinates,
// and incorporate object rotation into the transformation
C4DrawTransform draw_transform;
if(Obj->pDrawTransform)
{
draw_transform.SetTransformAt(*Obj->pDrawTransform, fixtof(Obj->fix_x), fixtof(Obj->fix_y));
draw_transform.Rotate(fixtof(Obj->fix_r), 0.0f, 0.0f);
}
else
{
draw_transform.SetRotate(fixtof(Obj->fix_r), 0.0f, 0.0f);
}
StdMeshMatrix DrawTransform;
DrawTransform(0, 0) = draw_transform.mat[0];
DrawTransform(0, 1) = draw_transform.mat[1];
DrawTransform(0, 2) = 0.0f;
DrawTransform(0, 3) = draw_transform.mat[2];
DrawTransform(1, 0) = draw_transform.mat[3];
DrawTransform(1, 1) = draw_transform.mat[4];
DrawTransform(1, 2) = 0.0f;
DrawTransform(1, 3) = draw_transform.mat[5];
DrawTransform(2, 0) = 0.0f;
DrawTransform(2, 1) = 0.0f;
DrawTransform(2, 2) = 1.0f;
DrawTransform(2, 3) = 0.0f;
x = DrawTransform * x;
dir = DrawTransform * dir;
x.x += DrawTransform(0,3);
x.y += DrawTransform(1,3);
x.z += DrawTransform(2,3);
// construct data
C4ParticleValueProvider valueX, valueY, valueSpeedX, valueSpeedY, valueLifetime;
valueX.Set(x.x);
valueY.Set(x.y);
valueSpeedX.Set(dir.x);
valueSpeedY.Set(dir.y);
valueLifetime.Set(lifetime);
// cast
if (amount < 1) amount = 1;
::Particles.Create(pDef, valueX, valueY, valueSpeedX, valueSpeedY, valueLifetime, properties, amount, Obj);
#endif
// success, even if not created
return true;
}
static Nillable<long> FnGetDefWidth(C4PropList * _this)
{
if (!_this) return C4Void();
C4Def *def = _this->GetDef();
if (!def) return C4Void();
return def->Shape.Wdt;
}
static Nillable<long> FnGetDefHeight(C4PropList * _this)
{
if (!_this) return C4Void();
C4Def *def = _this->GetDef();
if (!def) return C4Void();
return def->Shape.Hgt;
}
//=========================== C4Script Function Map ===================================
C4ScriptConstDef C4ScriptObjectConstMap[]=
{
{ "C4D_None" ,C4V_Int, C4D_None},
{ "C4D_All" ,C4V_Int, C4D_All},
{ "C4D_StaticBack" ,C4V_Int, C4D_StaticBack},
{ "C4D_Structure" ,C4V_Int, C4D_Structure},
{ "C4D_Vehicle" ,C4V_Int, C4D_Vehicle},
{ "C4D_Living" ,C4V_Int, C4D_Living},
{ "C4D_Object" ,C4V_Int, C4D_Object},
{ "C4D_Goal" ,C4V_Int, C4D_Goal},
{ "C4D_Environment" ,C4V_Int, C4D_Environment},
{ "C4D_Rule" ,C4V_Int, C4D_Rule},
{ "C4D_Background" ,C4V_Int, C4D_Background},
{ "C4D_Parallax" ,C4V_Int, C4D_Parallax},
{ "C4D_MouseSelect" ,C4V_Int, C4D_MouseSelect},
{ "C4D_Foreground" ,C4V_Int, C4D_Foreground},
{ "C4D_MouseIgnore" ,C4V_Int, C4D_MouseIgnore},
{ "C4D_IgnoreFoW" ,C4V_Int, C4D_IgnoreFoW},
{ "C4D_GrabGet" ,C4V_Int, C4D_Grab_Get},
{ "C4D_GrabPut" ,C4V_Int, C4D_Grab_Put},
{ "C4D_Border_Sides" ,C4V_Int, C4D_Border_Sides},
{ "C4D_Border_Top" ,C4V_Int, C4D_Border_Top},
{ "C4D_Border_Bottom" ,C4V_Int, C4D_Border_Bottom},
{ "C4D_Border_Layer" ,C4V_Int, C4D_Border_Layer},
{ "COMD_None" ,C4V_Int, COMD_None},
{ "COMD_Stop" ,C4V_Int, COMD_Stop},
{ "COMD_Up" ,C4V_Int, COMD_Up},
{ "COMD_UpRight" ,C4V_Int, COMD_UpRight},
{ "COMD_Right" ,C4V_Int, COMD_Right},
{ "COMD_DownRight" ,C4V_Int, COMD_DownRight},
{ "COMD_Down" ,C4V_Int, COMD_Down},
{ "COMD_DownLeft" ,C4V_Int, COMD_DownLeft},
{ "COMD_Left" ,C4V_Int, COMD_Left},
{ "COMD_UpLeft" ,C4V_Int, COMD_UpLeft},
{ "DIR_Left" ,C4V_Int, DIR_Left},
{ "DIR_Right" ,C4V_Int, DIR_Right},
{ "OCF_Construct" ,C4V_Int, OCF_Construct},
{ "OCF_Grab" ,C4V_Int, OCF_Grab},
{ "OCF_Collectible" ,C4V_Int, OCF_Carryable},
{ "OCF_OnFire" ,C4V_Int, OCF_OnFire},
{ "OCF_HitSpeed1" ,C4V_Int, OCF_HitSpeed1},
{ "OCF_Fullcon" ,C4V_Int, OCF_FullCon},
{ "OCF_Inflammable" ,C4V_Int, OCF_Inflammable},
{ "OCF_Rotate" ,C4V_Int, OCF_Rotate},
{ "OCF_Exclusive" ,C4V_Int, OCF_Exclusive},
{ "OCF_Entrance" ,C4V_Int, OCF_Entrance},
{ "OCF_HitSpeed2" ,C4V_Int, OCF_HitSpeed2},
{ "OCF_HitSpeed3" ,C4V_Int, OCF_HitSpeed3},
{ "OCF_Collection" ,C4V_Int, OCF_Collection},
{ "OCF_HitSpeed4" ,C4V_Int, OCF_HitSpeed4},
{ "OCF_NotContained" ,C4V_Int, OCF_NotContained},
{ "OCF_CrewMember" ,C4V_Int, OCF_CrewMember},
{ "OCF_InLiquid" ,C4V_Int, OCF_InLiquid},
{ "OCF_InSolid" ,C4V_Int, OCF_InSolid},
{ "OCF_InFree" ,C4V_Int, OCF_InFree},
{ "OCF_Available" ,C4V_Int, OCF_Available},
{ "OCF_Container" ,C4V_Int, OCF_Container},
{ "OCF_Alive" ,C4V_Int, (int) OCF_Alive},
{ "VIS_All" ,C4V_Int, VIS_All},
{ "VIS_None" ,C4V_Int, VIS_None},
{ "VIS_Owner" ,C4V_Int, VIS_Owner},
{ "VIS_Allies" ,C4V_Int, VIS_Allies},
{ "VIS_Enemies" ,C4V_Int, VIS_Enemies},
{ "VIS_Select" ,C4V_Int, VIS_Select},
{ "VIS_God" ,C4V_Int, VIS_God},
{ "VIS_LayerToggle" ,C4V_Int, VIS_LayerToggle},
{ "VIS_OverlayOnly" ,C4V_Int, VIS_OverlayOnly},
{ "VIS_Editor" ,C4V_Int, VIS_Editor},
{ "C4MN_Style_Normal" ,C4V_Int, C4MN_Style_Normal},
{ "C4MN_Style_Context" ,C4V_Int, C4MN_Style_Context},
{ "C4MN_Style_Info" ,C4V_Int, C4MN_Style_Info},
{ "C4MN_Style_Dialog" ,C4V_Int, C4MN_Style_Dialog},
{ "C4MN_Style_EqualItemHeight",C4V_Int, C4MN_Style_EqualItemHeight},
{ "C4MN_Extra_None" ,C4V_Int, C4MN_Extra_None},
{ "C4MN_Extra_Value" ,C4V_Int, C4MN_Extra_Value},
{ "C4MN_Extra_Info" ,C4V_Int, C4MN_Extra_Info},
{ "C4MN_Add_ImgRank" ,C4V_Int, C4MN_Add_ImgRank},
{ "C4MN_Add_ImgIndexed" ,C4V_Int, C4MN_Add_ImgIndexed},
{ "C4MN_Add_ImgObjRank" ,C4V_Int, C4MN_Add_ImgObjRank},
{ "C4MN_Add_ImgObject" ,C4V_Int, C4MN_Add_ImgObject},
{ "C4MN_Add_ImgTextSpec" ,C4V_Int, C4MN_Add_ImgTextSpec},
{ "C4MN_Add_ImgPropListSpec",C4V_Int, C4MN_Add_ImgPropListSpec},
{ "C4MN_Add_ImgColor" ,C4V_Int, C4MN_Add_ImgColor},
{ "C4MN_Add_PassValue" ,C4V_Int, C4MN_Add_PassValue},
{ "C4MN_Add_ForceCount" ,C4V_Int, C4MN_Add_ForceCount},
{ "C4MN_Add_ForceNoDesc" ,C4V_Int, C4MN_Add_ForceNoDesc},
{ "GFXOV_MODE_None" ,C4V_Int, C4GraphicsOverlay::MODE_None }, // gfx overlay modes
{ "GFXOV_MODE_Base" ,C4V_Int, C4GraphicsOverlay::MODE_Base }, //
{ "GFXOV_MODE_Action" ,C4V_Int, C4GraphicsOverlay::MODE_Action }, //
{ "GFXOV_MODE_Picture" ,C4V_Int, C4GraphicsOverlay::MODE_Picture }, //
{ "GFXOV_MODE_IngamePicture" ,C4V_Int, C4GraphicsOverlay::MODE_IngamePicture }, //
{ "GFXOV_MODE_Object" ,C4V_Int, C4GraphicsOverlay::MODE_Object }, //
{ "GFXOV_MODE_ExtraGraphics" ,C4V_Int, C4GraphicsOverlay::MODE_ExtraGraphics }, //
{ "GFXOV_MODE_Rank" ,C4V_Int, C4GraphicsOverlay::MODE_Rank}, //
{ "GFXOV_MODE_ObjectPicture" ,C4V_Int, C4GraphicsOverlay::MODE_ObjectPicture}, //
{ "GFX_Overlay" ,C4V_Int, 1}, // default overlay index
{ "GFXOV_Clothing" ,C4V_Int, 1000}, // overlay indices for clothes on Clonks, etc.
{ "GFXOV_Tools" ,C4V_Int, 2000}, // overlay indices for tools, weapons, etc.
{ "GFXOV_ProcessTarget" ,C4V_Int, 3000}, // overlay indices for objects processed by a Clonk
{ "GFXOV_Misc" ,C4V_Int, 5000}, // overlay indices for other stuff
{ "GFXOV_UI" ,C4V_Int, 6000}, // overlay indices for user interface
{ "GFX_BLIT_Additive" ,C4V_Int, C4GFXBLIT_ADDITIVE}, // blit modes
{ "GFX_BLIT_Mod2" ,C4V_Int, C4GFXBLIT_MOD2}, //
{ "GFX_BLIT_ClrSfc_OwnClr" ,C4V_Int, C4GFXBLIT_CLRSFC_OWNCLR}, //
{ "GFX_BLIT_ClrSfc_Mod2" ,C4V_Int, C4GFXBLIT_CLRSFC_MOD2}, //
{ "GFX_BLIT_Wireframe" ,C4V_Int, C4GFXBLIT_WIREFRAME}, //
{ "GFX_BLIT_Custom" ,C4V_Int, C4GFXBLIT_CUSTOM}, //
{ "GFX_BLIT_Parent" ,C4V_Int, C4GFXBLIT_PARENT}, //
// contact attachment
{ "CNAT_None" ,C4V_Int, CNAT_None },
{ "CNAT_Left" ,C4V_Int, CNAT_Left },
{ "CNAT_Right" ,C4V_Int, CNAT_Right },
{ "CNAT_Top" ,C4V_Int, CNAT_Top },
{ "CNAT_Bottom" ,C4V_Int, CNAT_Bottom },
{ "CNAT_Center" ,C4V_Int, CNAT_Center },
{ "CNAT_MultiAttach" ,C4V_Int, CNAT_MultiAttach },
{ "CNAT_NoCollision" ,C4V_Int, CNAT_NoCollision },
{ "CNAT_PhaseHalfVehicle" ,C4V_Int, CNAT_PhaseHalfVehicle },
// vertex data
{ "VTX_X" ,C4V_Int, VTX_X },
{ "VTX_Y" ,C4V_Int, VTX_Y },
{ "VTX_CNAT" ,C4V_Int, VTX_CNAT },
{ "VTX_Friction" ,C4V_Int, VTX_Friction },
// vertex set mode
{ "VTX_SetPermanent" ,C4V_Int, VTX_SetPermanent },
{ "VTX_SetPermanentUpd" ,C4V_Int, VTX_SetPermanentUpd },
{ "C4OS_DELETED" ,C4V_Int, C4OS_DELETED },
{ "C4OS_NORMAL" ,C4V_Int, C4OS_NORMAL },
{ "C4OS_INACTIVE" ,C4V_Int, C4OS_INACTIVE },
{ "C4CMD_Base" ,C4V_Int, C4CMD_Mode_Base },
{ "C4CMD_SilentBase" ,C4V_Int, C4CMD_Mode_SilentBase },
{ "C4CMD_Sub" ,C4V_Int, C4CMD_Mode_Sub },
{ "C4CMD_SilentSub" ,C4V_Int, C4CMD_Mode_SilentSub },
{ "C4CMD_MoveTo_NoPosAdjust" ,C4V_Int, C4CMD_MoveTo_NoPosAdjust },
{ "C4CMD_MoveTo_PushTarget" ,C4V_Int, C4CMD_MoveTo_PushTarget },
{ "C4CMD_Enter_PushTarget" ,C4V_Int, C4CMD_Enter_PushTarget },
{ "C4AVP_Const" ,C4V_Int, C4AVP_Const },
{ "C4AVP_Linear" ,C4V_Int, C4AVP_Linear },
{ "C4AVP_X" ,C4V_Int, C4AVP_X },
{ "C4AVP_Y" ,C4V_Int, C4AVP_Y },
{ "C4AVP_R" ,C4V_Int, C4AVP_R },
{ "C4AVP_AbsX" ,C4V_Int, C4AVP_AbsX },
{ "C4AVP_AbsY" ,C4V_Int, C4AVP_AbsY },
{ "C4AVP_Dist" ,C4V_Int, C4AVP_Dist },
{ "C4AVP_XDir" ,C4V_Int, C4AVP_XDir },
{ "C4AVP_YDir" ,C4V_Int, C4AVP_YDir },
{ "C4AVP_RDir" ,C4V_Int, C4AVP_RDir },
{ "C4AVP_AbsRDir" ,C4V_Int, C4AVP_AbsRDir },
{ "C4AVP_CosR" ,C4V_Int, C4AVP_CosR },
{ "C4AVP_SinR" ,C4V_Int, C4AVP_SinR },
{ "C4AVP_CosV" ,C4V_Int, C4AVP_CosV },
{ "C4AVP_SinV" ,C4V_Int, C4AVP_SinV },
{ "C4AVP_Action" ,C4V_Int, C4AVP_Action },
{ "ANIM_Loop" ,C4V_Int, ANIM_Loop },
{ "ANIM_Hold" ,C4V_Int, ANIM_Hold },
{ "ANIM_Remove" ,C4V_Int, ANIM_Remove },
{ "AM_None" ,C4V_Int, StdMeshInstance::AM_None },
{ "AM_DrawBefore" ,C4V_Int, StdMeshInstance::AM_DrawBefore },
{ "AM_MatchSkeleton" ,C4V_Int, StdMeshInstance::AM_MatchSkeleton },
{ nullptr, C4V_Nil, 0}
};
void InitObjectFunctionMap(C4AulScriptEngine *pEngine)
{
// add all def constants (all Int)
for (C4ScriptConstDef *pCDef = &C4ScriptObjectConstMap[0]; pCDef->Identifier; pCDef++)
{
assert(pCDef->ValType == C4V_Int); // only int supported currently
pEngine->RegisterGlobalConstant(pCDef->Identifier, C4VInt(pCDef->Data));
}
C4PropListStatic * p = pEngine->GetPropList();
#define F(f) ::AddFunc(p, #f, Fn##f)
F(DoCon);
F(GetCon);
F(DoDamage);
F(DoEnergy);
F(DoBreath);
F(GetEnergy);
F(OnFire);
F(Stuck);
F(InLiquid);
F(SetAction);
F(SetActionData);
F(SetBridgeActionData);
F(GetAction);
F(GetActTime);
F(GetOwner);
F(GetMass);
F(GetBreath);
F(GetMenu);
F(GetVertexNum);
F(GetVertex);
F(SetVertex);
F(AddVertex);
F(InsertVertex);
F(RemoveVertex);
::AddFunc(p, "SetContactDensity", FnSetContactDensity, false);
F(GetController);
F(SetController);
F(SetName);
F(GetKiller);
F(SetKiller);
F(GetPhase);
F(SetPhase);
F(GetCategory);
F(GetOCF);
F(SetAlive);
F(GetAlive);
F(GetDamage);
F(SetComDir);
F(GetComDir);
F(SetDir);
F(GetDir);
F(SetEntrance);
F(GetEntrance);
F(SetCategory);
F(FinishCommand);
F(ActIdle);
F(SetRDir);
F(GetRDir);
F(GetXDir);
F(GetYDir);
F(GetR);
F(SetXDir);
F(SetYDir);
F(SetR);
F(SetOwner);
F(MakeCrewMember);
F(GrabObjectInfo);
F(CreateContents);
F(ShiftContents);
F(GetID);
F(Contents);
F(ScrollContents);
F(Contained);
F(ContentsCount);
::AddFunc(p, "FindContents", FnFindContents, false);
::AddFunc(p, "FindOtherContents", FnFindOtherContents, false);
F(RemoveObject);
F(GetActionTarget);
F(SetActionTargets);
::AddFunc(p, "SetCrewStatus", FnSetCrewStatus, false);
F(SetPosition);
F(CreateMenu);
F(AddMenuItem);
F(SelectMenuItem);
F(SetMenuDecoration);
F(SetMenuTextProgress);
F(ObjectDistance);
F(GetValue);
F(GetRank);
F(SetTransferZone);
F(SetMass);
F(GetColor);
F(SetColor);
F(SetLightRange);
F(GetLightColor);
F(SetLightColor);
F(SetPicture);
F(GetProcedure);
F(CanConcatPictureWith);
F(SetGraphics);
F(ObjectNumber);
F(ShowInfo);
F(CheckVisibility);
F(SetClrModulation);
F(GetClrModulation);
F(CloseMenu);
F(GetMenuSelection);
F(GetDefBottom);
F(SetMenuSize);
F(GetCrewEnabled);
F(SetCrewEnabled);
F(DoCrewExp);
F(ClearMenuItems);
F(GetObjectLayer);
F(SetObjectLayer);
F(SetShape);
F(SetObjDrawTransform);
::AddFunc(p, "SetObjDrawTransform2", FnSetObjDrawTransform2, false);
::AddFunc(p, "SetObjectStatus", FnSetObjectStatus, false);
::AddFunc(p, "GetObjectStatus", FnGetObjectStatus, false);
::AddFunc(p, "AdjustWalkRotation", FnAdjustWalkRotation, false);
F(GetContact);
F(SetObjectBlitMode);
F(GetObjectBlitMode);
::AddFunc(p, "GetUnusedOverlayID", FnGetUnusedOverlayID, false);
F(ExecuteCommand);
F(PlayAnimation);
F(TransformBone);
F(StopAnimation);
F(GetRootAnimation);
F(GetAnimationList);
F(GetAnimationLength);
F(GetAnimationName);
F(GetAnimationPosition);
F(GetAnimationWeight);
F(SetAnimationPosition);
F(SetAnimationBoneTransform);
F(SetAnimationWeight);
F(AttachMesh);
F(DetachMesh);
F(SetAttachBones);
F(SetAttachTransform);
F(GetMeshMaterial);
F(SetMeshMaterial);
F(CreateParticleAtBone);
F(ChangeDef);
F(GrabContents);
F(Punch);
F(Kill);
F(Fling);
::AddFunc(p, "Jump", FnJump, false);
F(Enter);
F(DeathAnnounce);
F(SetSolidMask);
F(SetHalfVehicleSolidMask);
F(Exit);
F(Collect);
F(SetCommand);
F(AddCommand);
F(AppendCommand);
F(GetCommand);
F(SetCrewExtraData);
F(GetCrewExtraData);
F(GetDefWidth);
F(GetDefHeight);
#undef F
}