/* * 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 "script/C4Aul.h" #include "script/C4AulDefFunc.h" #include "object/C4Command.h" #include "object/C4DefList.h" #include "graphics/C4Draw.h" #include "gui/C4GameMessage.h" #include "graphics/C4GraphicsResource.h" #include "landscape/C4Material.h" #include "landscape/C4Particles.h" #include "object/C4MeshAnimation.h" #include "object/C4ObjectCom.h" #include "object/C4ObjectInfo.h" #include "object/C4ObjectMenu.h" #include "player/C4Player.h" #include "player/C4PlayerList.h" #include "lib/C4Random.h" #include "player/C4RankSystem.h" #include "control/C4Teams.h" #include "lib/StdMeshMath.h" #include "object/C4MeshDenumerator.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,NULL); } 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 = NULL; 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 iEngType, Nillable 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 iDmgType, Nillable 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=1; } 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=1; } 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=1; } 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=NULL; 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=NULL; 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=NULL; 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 NULL; } 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) { 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 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(iIndex,Obj->Shape.VtxNum-1); switch (static_cast(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 = 1; } // set vertices at end of buffer iIndex += C4D_VertexCpyPos; } // range check if (!Inside(iIndex,0,C4D_MaxVertex-1)) return false; // set desired value switch (static_cast(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 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(sep - s,256)); caption[std::min(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, "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); char* pFound = const_cast(SSearch(dummy, "%d")); if (pFound != 0) *(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 = NULL; C4Object* pGfxObj = NULL; 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, NULL, 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("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, NULL, 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,NULL,infocaption,pDef ? pDef->id : C4ID::None,command2,fOwnValue,iValue,fIsSelectable); else if(pGfx) Obj->Menu->Add(caption,pGfx,command,iCount,NULL,infocaption,pDef ? pDef->id : C4ID::None,command2,fOwnValue,iValue,fIsSelectable); else Obj->Menu->Add(caption,fctSymbol,command,iCount,NULL,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 NULL; } 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 iCount) { // default amount parameter if (iCount.IsNil()) iCount = 1; // create objects C4Object *pNewObj = NULL; 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 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 NULL; // 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 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 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 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 = NULL; 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 = NULL; pOverlay->SetAsObject(pOverlayObject, dwBlitMode); break; case C4GraphicsOverlay::MODE_ExtraGraphics: pOverlay->SetAsExtraGraphics(pGrp, dwBlitMode); break; case C4GraphicsOverlay::MODE_Rank: if (pOverlayObject && !pOverlayObject->Status) pOverlayObject = NULL; pOverlay->SetAsRank(dwBlitMode, pOverlayObject); break; case C4GraphicsOverlay::MODE_ObjectPicture: if (pOverlayObject && !pOverlayObject->Status) pOverlayObject = NULL; pOverlay->SetAsObjectPicture(pOverlayObject, dwBlitMode); break; default: DebugLog("SetGraphics: Invalid overlay mode"); pOverlay->SetAsBase(NULL, 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==NULL 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(iCols, 0, 50), Clamp(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=NULL; 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; iShape.VtxNum; ++i) iResult |= Obj->Shape.GetVertexContact(i, dwCheck, Obj->GetX(), Obj->GetY()); return iResult; } // vertex specified: check it if (!Inside(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 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 FnPlayAnimation(C4Object *Obj, C4String *szAnimation, int iSlot, C4ValueArray* PositionProvider, Nillable WeightProvider, Nillable iSibling, Nillable 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 = NULL; 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 FnTransformBone(C4Object *Obj, C4String *szBoneName, C4ValueArray* Transformation, int iSlot, C4ValueArray* WeightProvider, Nillable iSibling, Nillable 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 = NULL; 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 iAnimationNumber, Nillable 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 FnGetRootAnimation(C4Object *Obj, int iSlot, Nillable 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 FnGetAnimationList(C4PropList* _this, Nillable 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 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 FnGetAnimationLength(C4Object *Obj, C4String *szAnimation, Nillable 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 FnGetAnimationName(C4Object *Obj, Nillable iAnimationNumber, Nillable 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 FnGetAnimationPosition(C4Object *Obj, Nillable iAnimationNumber, Nillable 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 FnGetAnimationWeight(C4Object *Obj, Nillable iAnimationNumber, Nillable 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 iAnimationNumber, C4ValueArray* PositionProvider, Nillable 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 iAnimationNumber, C4ValueArray* Transformation, Nillable 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 iAnimationNumber, C4ValueArray* WeightProvider, Nillable 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 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 szParentBone, Nillable 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 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 FnGetDefWidth(C4PropList * _this) { if (!_this) return C4Void(); C4Def *def = _this->GetDef(); if (!def) return C4Void(); return def->Shape.Wdt; } static Nillable 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_AttractLightning" ,C4V_Int, OCF_AttractLightning}, { "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 }, { NULL, 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 }