forked from Mirrors/openclonk
1981 lines
55 KiB
C++
1981 lines
55 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.
|
|
*/
|
|
|
|
/* The command stack controls an object's complex and independent behavior */
|
|
|
|
#include "C4Include.h"
|
|
#include "object/C4Command.h"
|
|
|
|
#include "gui/C4GameMessage.h"
|
|
#include "landscape/C4Landscape.h"
|
|
#include "lib/C4Random.h"
|
|
#include "object/C4Def.h"
|
|
#include "object/C4DefList.h"
|
|
#include "object/C4GameObjects.h"
|
|
#include "object/C4Object.h"
|
|
#include "object/C4ObjectCom.h"
|
|
#include "object/C4ObjectInfo.h"
|
|
#include "object/C4ObjectMenu.h"
|
|
#include "platform/C4SoundSystem.h"
|
|
#include "player/C4Player.h"
|
|
#include "player/C4PlayerList.h"
|
|
|
|
const int32_t MoveToRange=5,LetGoRange1=7,LetGoRange2=30,DigRange=1;
|
|
const int32_t FollowRange=6,PushToRange=10,DigOutPositionRange=15;
|
|
const int32_t PathRange=20,MaxPathRange=1000;
|
|
const int32_t JumpAngle=35,JumpLowAngle=80,JumpAngleRange=10,JumpHighAngle=0;
|
|
const int32_t FlightAngleRange=60;
|
|
const int32_t DigOutDirectRange=130;
|
|
const int32_t LetGoHangleAngle=110;
|
|
|
|
StdEnumAdapt<int32_t>::Entry EnumAdaptCommandEntries[C4CMD_Last - C4CMD_First + 2];
|
|
|
|
const char *CommandName(int32_t iCommand)
|
|
{
|
|
switch (iCommand)
|
|
{
|
|
case C4CMD_None: return "None";
|
|
case C4CMD_Follow: return "Follow";
|
|
case C4CMD_MoveTo: return "MoveTo";
|
|
case C4CMD_Enter: return "Enter";
|
|
case C4CMD_Exit: return "Exit";
|
|
case C4CMD_Grab: return "Grab";
|
|
case C4CMD_Throw: return "Throw";
|
|
case C4CMD_UnGrab: return "UnGrab";
|
|
case C4CMD_Jump: return "Jump";
|
|
case C4CMD_Wait: return "Wait";
|
|
case C4CMD_Get: return "Get";
|
|
case C4CMD_Put: return "Put";
|
|
case C4CMD_Drop: return "Drop";
|
|
case C4CMD_Dig: return "Dig";
|
|
case C4CMD_Activate: return "Activate";
|
|
case C4CMD_PushTo: return "PushTo";
|
|
case C4CMD_Transfer: return "Transfer";
|
|
case C4CMD_Attack: return "Attack";
|
|
case C4CMD_Buy: return "Buy";
|
|
case C4CMD_Sell: return "Sell";
|
|
case C4CMD_Acquire: return "Acquire";
|
|
case C4CMD_Retry: return "Retry";
|
|
case C4CMD_Home: return "Home";
|
|
case C4CMD_Call: return "Call";
|
|
case C4CMD_Take: return "Take";
|
|
case C4CMD_Take2: return "Take2";
|
|
default: return "None";
|
|
}
|
|
}
|
|
|
|
const char* CommandNameID(int32_t iCommand)
|
|
{
|
|
switch (iCommand)
|
|
{
|
|
case C4CMD_None: return "IDS_COMM_NONE";
|
|
case C4CMD_Follow: return "IDS_COMM_FOLLOW";
|
|
case C4CMD_MoveTo: return "IDS_COMM_MOVETO";
|
|
case C4CMD_Enter: return "IDS_COMM_ENTER";
|
|
case C4CMD_Exit: return "IDS_COMM_EXIT";
|
|
case C4CMD_Grab: return "IDS_COMM_GRAB";
|
|
case C4CMD_Throw: return "IDS_COMM_THROW";
|
|
case C4CMD_UnGrab: return "IDS_COMM_UNGRAB";
|
|
case C4CMD_Jump: return "IDS_COMM_JUMP";
|
|
case C4CMD_Wait: return "IDS_COMM_WAIT";
|
|
case C4CMD_Get: return "IDS_COMM_GET";
|
|
case C4CMD_Put: return "IDS_COMM_PUT";
|
|
case C4CMD_Drop: return "IDS_COMM_DROP";
|
|
case C4CMD_Dig: return "IDS_COMM_DIG";
|
|
case C4CMD_Activate: return "IDS_COMM_ACTIVATE";
|
|
case C4CMD_PushTo: return "IDS_COMM_PUSHTO";
|
|
case C4CMD_Transfer: return "IDS_COMM_TRANSFER";
|
|
case C4CMD_Attack: return "IDS_COMM_ATTACK";
|
|
case C4CMD_Buy: return "IDS_COMM_BUY";
|
|
case C4CMD_Sell: return "IDS_COMM_SELL";
|
|
case C4CMD_Acquire: return "IDS_COMM_ACQUIRE";
|
|
case C4CMD_Retry: return "IDS_COMM_RETRY";
|
|
case C4CMD_Home: return "IDS_CON_HOME";
|
|
case C4CMD_Call: return "IDS_COMM_CALL";
|
|
case C4CMD_Take: return "IDS_COMM_TAKE";
|
|
case C4CMD_Take2: return "IDS_COMM_TAKE2";
|
|
default: return "IDS_COMM_NONE";
|
|
}
|
|
}
|
|
|
|
bool InitEnumAdaptCommandEntries()
|
|
{
|
|
for (int32_t i = C4CMD_First; i <= C4CMD_Last; i++)
|
|
{
|
|
EnumAdaptCommandEntries[i - C4CMD_First].Name = CommandName(i);
|
|
EnumAdaptCommandEntries[i - C4CMD_First].Val = i;
|
|
}
|
|
EnumAdaptCommandEntries[C4CMD_Last - C4CMD_First + 1].Name = nullptr;
|
|
return true;
|
|
}
|
|
const bool InitEnumAdaptCommandEntriesDummy = InitEnumAdaptCommandEntries();
|
|
|
|
int32_t CommandByName(const char *szCommand)
|
|
{
|
|
for (int32_t cnt=C4CMD_First; cnt<=C4CMD_Last; cnt++)
|
|
if (SEqual(szCommand,CommandName(cnt)))
|
|
return cnt;
|
|
return C4CMD_None;
|
|
}
|
|
|
|
bool FreeMoveTo(C4Object *cObj)
|
|
{
|
|
// Floating: we accept any move-to target
|
|
if (cObj->GetProcedure()==DFA_FLOAT) return true;
|
|
// Assume we're walking: move-to targets are adjusted
|
|
return false;
|
|
}
|
|
|
|
void AdjustMoveToTarget(int32_t &rX, int32_t &rY, bool fFreeMove, int32_t iShapeHgt)
|
|
{
|
|
// Above solid (always)
|
|
int32_t iY=std::min(rY, ::Landscape.GetHeight());
|
|
while ((iY>=0) && GBackSolid(rX,iY)) iY--;
|
|
if (iY>=0) rY=iY;
|
|
// No-free-move adjustments (i.e. if walking)
|
|
if (!fFreeMove)
|
|
{
|
|
// Drop down to bottom of free space
|
|
if (!GBackSemiSolid(rX,rY))
|
|
{
|
|
for (iY=rY; (iY<::Landscape.GetHeight()) && !GBackSemiSolid(rX,iY+1); iY++) {}
|
|
if (iY<::Landscape.GetHeight()) rY=iY;
|
|
}
|
|
// Vertical shape offset above solid
|
|
if (GBackSolid(rX,rY+1) || GBackSolid(rX,rY+5))
|
|
if (!GBackSemiSolid(rX,rY-iShapeHgt/2))
|
|
rY-=iShapeHgt/2;
|
|
}
|
|
|
|
}
|
|
|
|
bool AdjustSolidOffset(int32_t &rX, int32_t &rY, int32_t iXOff, int32_t iYOff)
|
|
{
|
|
// In solid: fail
|
|
if (GBackSolid(rX,rY)) return false;
|
|
// Y Offset
|
|
int32_t cnt;
|
|
for (cnt=1; cnt<iYOff; cnt++)
|
|
{
|
|
if (GBackSolid(rX,rY+cnt) && !GBackSolid(rX,rY-cnt)) rY--;
|
|
if (GBackSolid(rX,rY-cnt) && !GBackSolid(rX,rY+cnt)) rY++;
|
|
}
|
|
// X Offset
|
|
for (cnt=1; cnt<iXOff; cnt++)
|
|
{
|
|
if (GBackSolid(rX+cnt,rY) && !GBackSolid(rX-cnt,rY)) rX--;
|
|
if (GBackSolid(rX-cnt,rY) && !GBackSolid(rX+cnt,rY)) rX++;
|
|
}
|
|
// Done
|
|
return true;
|
|
}
|
|
|
|
int32_t SolidOnWhichSide(int32_t iX, int32_t iY)
|
|
{
|
|
for (int32_t cx=1; cx<10; cx++)
|
|
for (int32_t cy=0; cy<10; cy++)
|
|
{
|
|
if (GBackSolid(iX-cx,iY-cy) || GBackSolid(iX-cx,iY+cy)) return -1;
|
|
if (GBackSolid(iX+cx,iY-cy) || GBackSolid(iX+cx,iY+cy)) return +1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
C4Command::C4Command()
|
|
{
|
|
Default();
|
|
}
|
|
|
|
C4Command::~C4Command()
|
|
{
|
|
Clear();
|
|
}
|
|
|
|
void C4Command::Default()
|
|
{
|
|
Command=C4CMD_None;
|
|
cObj=nullptr;
|
|
Evaluated=false;
|
|
PathChecked=false;
|
|
Finished=false;
|
|
Tx=C4VNull;
|
|
Ty=0;
|
|
Target=Target2=nullptr;
|
|
Data.Set0();
|
|
UpdateInterval=0;
|
|
Failures=0;
|
|
Retries=0;
|
|
Permit=0;
|
|
Text=nullptr;
|
|
Next=nullptr;
|
|
iExec=0;
|
|
BaseMode=C4CMD_Mode_SilentSub;
|
|
}
|
|
|
|
struct ObjectAddWaypoint
|
|
{
|
|
explicit ObjectAddWaypoint(C4Object *obj) : cObj(obj) {}
|
|
bool operator()(int32_t iX, int32_t iY, C4Object *TransferTarget)
|
|
{
|
|
if (!cObj) return false;
|
|
|
|
// Transfer waypoint
|
|
if (TransferTarget)
|
|
return cObj->AddCommand(C4CMD_Transfer,TransferTarget,iX,iY,0,nullptr,false);
|
|
|
|
// Solid offset
|
|
AdjustSolidOffset(iX,iY,cObj->Shape.Wdt/2,cObj->Shape.Hgt/2);
|
|
|
|
// Standard movement waypoint update interval
|
|
int32_t iUpdate = 25;
|
|
// Waypoints before transfer zones are not updated (enforce move to that waypoint)
|
|
if (cObj->Command && (cObj->Command->Command==C4CMD_Transfer)) iUpdate=0;
|
|
// Add waypoint
|
|
assert(cObj->Command);
|
|
if (!cObj->AddCommand(C4CMD_MoveTo,nullptr,iX,iY,iUpdate,nullptr,false,cObj->Command->Data)) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
C4Object *cObj;
|
|
};
|
|
|
|
void C4Command::MoveTo()
|
|
{
|
|
// Determine move-to range
|
|
int32_t iMoveToRange = MoveToRange;
|
|
if (cObj->Def->MoveToRange > 0) iMoveToRange = cObj->Def->MoveToRange;
|
|
|
|
// Current object position
|
|
int32_t cx,cy; cx=cObj->GetX(); cy=cObj->GetY();
|
|
bool fWaypoint=false;
|
|
if (Next && (Next->Command==C4CMD_MoveTo)) fWaypoint=true;
|
|
|
|
// Contained: exit
|
|
if (cObj->Contained)
|
|
{ cObj->AddCommand(C4CMD_Exit,nullptr,0,0,50); return; }
|
|
|
|
// Check path (crew members or specific only)
|
|
if ((cObj->OCF & OCF_CrewMember) || cObj->Def->Pathfinder)
|
|
if (!PathChecked)
|
|
// Not too far
|
|
if (Distance(cx,cy,Tx._getInt(),Ty)<MaxPathRange)
|
|
// Not too close
|
|
if (!(Inside(cx-Tx._getInt(),-PathRange,+PathRange) && Inside(cy-Ty,-PathRange,+PathRange)))
|
|
{
|
|
// Path not free: find path
|
|
if (!PathFree(cx,cy,Tx._getInt(),Ty))
|
|
{
|
|
Game.PathFinder.EnableTransferZones(!cObj->Def->NoTransferZones);
|
|
Game.PathFinder.SetLevel(cObj->Def->Pathfinder);
|
|
if (!Game.PathFinder.Find( cObj->GetX(),cObj->GetY(),
|
|
Tx._getInt(),Ty,
|
|
ObjectAddWaypoint(cObj)))
|
|
{ /* Path not found: react? */ PathChecked=true; /* recheck delay */ }
|
|
return;
|
|
}
|
|
// Path free: recheck delay
|
|
else
|
|
PathChecked=true;
|
|
}
|
|
// Path recheck
|
|
if (!::Game.iTick35) PathChecked=false;
|
|
|
|
// Pushing grab only or not desired: let go (pulling, too?)
|
|
if (cObj->GetProcedure()==DFA_PUSH)
|
|
if (cObj->Action.Target)
|
|
if (cObj->Action.Target->GetPropertyInt(P_Touchable) == 2 || !(Data.getInt() & C4CMD_MoveTo_PushTarget))
|
|
{
|
|
// Re-evaluate this command because vehicle control might have blocked evaluation
|
|
Evaluated=false;
|
|
cObj->AddCommand(C4CMD_UnGrab,nullptr,0,0,50); return;
|
|
}
|
|
|
|
// Special by procedure
|
|
switch (cObj->GetProcedure())
|
|
{
|
|
// Push/pull
|
|
case DFA_PUSH: case DFA_PULL:
|
|
// Use target object's position if on final waypoint
|
|
if (!fWaypoint)
|
|
if (cObj->Action.Target)
|
|
{ cx=cObj->Action.Target->GetX(); cy=cObj->Action.Target->GetY(); }
|
|
break;
|
|
// dig, bridge: stop
|
|
case DFA_DIG: case DFA_BRIDGE:
|
|
ObjectComStop(cObj);
|
|
break;
|
|
}
|
|
|
|
// Target range
|
|
int32_t iTargetRange = iMoveToRange;
|
|
int32_t iRangeFactorTop=1, iRangeFactorBottom=1, iRangeFactorSide=1;
|
|
|
|
// Crew members/pathfinder specific target range
|
|
if (cObj->OCF & OCF_CrewMember) // || cObj->Def->Pathfinder ? (Sven2)
|
|
{
|
|
// Range by size
|
|
iTargetRange=cObj->Shape.Wdt/2;
|
|
// Easier range if waypoint
|
|
if (fWaypoint)
|
|
if (cObj->GetProcedure()!=DFA_SCALE)
|
|
{ iRangeFactorTop=3; iRangeFactorSide=3; iRangeFactorBottom=2; }
|
|
}
|
|
|
|
// Target reached (success)
|
|
if (Inside(cx-Tx._getInt(),-iRangeFactorSide*iTargetRange,+iRangeFactorSide*iTargetRange)
|
|
&& Inside(cy-Ty,-iRangeFactorBottom*iTargetRange,+iRangeFactorTop*iTargetRange))
|
|
{
|
|
cObj->Action.ComDir=COMD_Stop;
|
|
Finish(true); return;
|
|
}
|
|
|
|
// Idles can't move to
|
|
if (!cObj->GetAction())
|
|
{ Finish(); return; }
|
|
|
|
// Action
|
|
switch (cObj->GetProcedure())
|
|
{
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
case DFA_WALK:
|
|
// Head to target
|
|
if (cx<Tx._getInt()-iTargetRange) cObj->Action.ComDir=COMD_Right;
|
|
if (cx>Tx._getInt()+iTargetRange) cObj->Action.ComDir=COMD_Left;
|
|
// Flight control
|
|
if (FlightControl()) return;
|
|
// Jump control
|
|
if (JumpControl()) return;
|
|
break;
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
case DFA_PUSH: case DFA_PULL:
|
|
// Head to target
|
|
if (cx<Tx._getInt()-iTargetRange) cObj->Action.ComDir=COMD_Right;
|
|
if (cx>Tx._getInt()+iTargetRange) cObj->Action.ComDir=COMD_Left;
|
|
break;
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
case DFA_SCALE:
|
|
// Head to target
|
|
if (cy>Ty+iTargetRange) cObj->Action.ComDir=COMD_Up;
|
|
if (cy<Ty-iTargetRange) cObj->Action.ComDir=COMD_Down;
|
|
// Let-Go Control
|
|
if (cObj->Action.Dir==DIR_Left)
|
|
{
|
|
// Target direction
|
|
if ((Tx._getInt()>cx+LetGoRange1) && (Inside(cy-Ty,-LetGoRange2,+LetGoRange2)))
|
|
{ ObjectComLetGo(cObj,+1); return; }
|
|
// Contact (not if just started)
|
|
if (cObj->Action.Time>2)
|
|
if (cObj->t_contact/* & CNAT_Left*/)
|
|
{ ObjectComLetGo(cObj,+1); return; }
|
|
}
|
|
if (cObj->Action.Dir==DIR_Right)
|
|
{
|
|
// Target direction
|
|
if ((Tx._getInt()<cx-LetGoRange1) && (Inside(cy-Ty,-LetGoRange2,+LetGoRange2)))
|
|
{ ObjectComLetGo(cObj,-1); return; }
|
|
// Contact (not if just started)
|
|
if (cObj->Action.Time>2)
|
|
if (cObj->t_contact/* & CNAT_Right*/)
|
|
{ ObjectComLetGo(cObj,-1); return; }
|
|
}
|
|
break;
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
case DFA_SWIM:
|
|
// Head to target
|
|
if (::Game.iTick2)
|
|
{
|
|
if (cx<Tx._getInt()-iTargetRange) cObj->Action.ComDir=COMD_Right;
|
|
if (cx>Tx._getInt()+iTargetRange) cObj->Action.ComDir=COMD_Left;
|
|
}
|
|
else
|
|
{
|
|
if (cy<Ty) cObj->Action.ComDir=COMD_Down;
|
|
if (cy>Ty) cObj->Action.ComDir=COMD_Up;
|
|
}
|
|
break;
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
case DFA_HANGLE:
|
|
// Head to target
|
|
if (cx<Tx._getInt()-iTargetRange) cObj->Action.ComDir=COMD_Right;
|
|
if (cx>Tx._getInt()+iTargetRange) cObj->Action.ComDir=COMD_Left;
|
|
// Let-Go Control
|
|
if (Abs(Angle(cx,cy,Tx._getInt(),Ty))>LetGoHangleAngle)
|
|
ObjectComLetGo(cObj,0);
|
|
break;
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
case DFA_FLOAT:
|
|
{
|
|
C4Real dx = itofix(Tx._getInt()) - cObj->fix_x, dy = itofix(Ty) - cObj->fix_y;
|
|
// normalize
|
|
C4Real dScale = C4REAL100(cObj->GetAction()->GetPropertyInt(P_Speed)) / std::max(Abs(dx), Abs(dy));
|
|
dx *= dScale; dy *= dScale;
|
|
// difference to momentum
|
|
dx -= cObj->xdir; dy -= cObj->ydir;
|
|
// steer
|
|
if (Abs(dx)+Abs(dy) < C4REAL100(20)) cObj->Action.ComDir = COMD_Stop;
|
|
else if (Abs(dy) * 3 < dx) cObj->Action.ComDir = COMD_Right;
|
|
else if (Abs(dy) * 3 < -dx) cObj->Action.ComDir = COMD_Left;
|
|
else if (Abs(dx) * 3 < dy) cObj->Action.ComDir = COMD_Down;
|
|
else if (Abs(dx) * 3 < -dy) cObj->Action.ComDir = COMD_Up;
|
|
else if (dx > 0 && dy > 0) cObj->Action.ComDir = COMD_DownRight;
|
|
else if (dx < 0 && dy > 0) cObj->Action.ComDir = COMD_DownLeft;
|
|
else if (dx > 0 && dy < 0) cObj->Action.ComDir = COMD_UpRight;
|
|
else cObj->Action.ComDir = COMD_UpLeft;
|
|
}
|
|
break;
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
case DFA_FLIGHT:
|
|
// Try to move into right direction
|
|
if (cx<Tx._getInt()-iTargetRange) cObj->Action.ComDir=COMD_Right;
|
|
if (cx>Tx._getInt()+iTargetRange) cObj->Action.ComDir=COMD_Left;
|
|
// Flight control
|
|
if (FlightControl()) return;
|
|
break;
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
}
|
|
|
|
}
|
|
|
|
void C4Command::Dig()
|
|
{
|
|
// Current object and target coordinates
|
|
int32_t cx,cy,tx,ty;
|
|
cx=cObj->GetX(); cy=cObj->GetY();
|
|
tx=Tx._getInt(); ty=Ty+cObj->Shape.GetY() + 3; // Target coordinates are bottom center
|
|
bool fDigOutMaterial=Data.getBool();
|
|
|
|
// Grabbing: let go
|
|
if (cObj->GetProcedure()==DFA_PUSH)
|
|
{ cObj->AddCommand(C4CMD_UnGrab,nullptr,0,0,50); return; }
|
|
|
|
// If contained: exit
|
|
if (cObj->Contained)
|
|
{ cObj->AddCommand(C4CMD_Exit,nullptr,0,0,50); return; }
|
|
|
|
// Scaling or hangling: let go
|
|
if ((cObj->GetProcedure()==DFA_SCALE) || (cObj->GetProcedure()==DFA_HANGLE))
|
|
ObjectComLetGo(cObj,(cObj->Action.Dir==DIR_Left) ? +1 : -1);
|
|
|
|
// Determine move-to range
|
|
int32_t iMoveToRange = MoveToRange;
|
|
if (cObj->Def->MoveToRange > 0) iMoveToRange = cObj->Def->MoveToRange;
|
|
|
|
// Target reached: success
|
|
if (Inside(cx - tx, -iMoveToRange, +iMoveToRange)
|
|
&& Inside(cy - ty, -iMoveToRange, +iMoveToRange))
|
|
{
|
|
ObjectComStop(cObj);
|
|
Finish(true); return;
|
|
}
|
|
|
|
// Can start digging only from walk
|
|
if (cObj->GetProcedure()!=DFA_DIG)
|
|
if (cObj->GetProcedure()!=DFA_WALK)
|
|
// Continue trying (assume currently in flight)
|
|
return;
|
|
|
|
// Start digging
|
|
if (cObj->GetProcedure()!=DFA_DIG)
|
|
if (!ObjectComDig(cObj))
|
|
{ Finish(); return; }
|
|
|
|
// Dig2Object
|
|
if (fDigOutMaterial) cObj->Action.Data=1;
|
|
|
|
// Head to target
|
|
if (cx<tx-DigRange) cObj->Action.ComDir=COMD_Right;
|
|
if (cx>tx+DigRange) cObj->Action.ComDir=COMD_Left;
|
|
if (cy<ty-DigRange) cObj->Action.ComDir=COMD_Down;
|
|
if (cx<tx-DigRange) if (cy<ty-DigRange) cObj->Action.ComDir=COMD_DownRight;
|
|
if (cx>tx-DigRange) if (cy<ty-DigRange) cObj->Action.ComDir=COMD_DownLeft;
|
|
if (cx<tx-DigRange) if (cy>ty+DigRange) cObj->Action.ComDir=COMD_UpRight;
|
|
if (cx>tx+DigRange) if (cy>ty+DigRange) cObj->Action.ComDir=COMD_UpLeft;
|
|
|
|
}
|
|
|
|
|
|
void C4Command::Follow()
|
|
{
|
|
// No-one to follow
|
|
if (!Target) { Finish(); return; }
|
|
|
|
// Follow containment
|
|
if (cObj->Contained!=Target->Contained)
|
|
{
|
|
// Only crew members can follow containment
|
|
if (!cObj->Def->CrewMember)
|
|
{ Finish(); return; }
|
|
// Exit/enter
|
|
if (cObj->Contained) cObj->AddCommand(C4CMD_Exit,nullptr,0,0,50);
|
|
else cObj->AddCommand(C4CMD_Enter,Target->Contained,0,0,50);
|
|
return;
|
|
}
|
|
|
|
// Follow target grabbing
|
|
if (Target->GetProcedure()==DFA_PUSH)
|
|
{
|
|
// Grabbing same: copy ComDir
|
|
if (cObj->GetProcedure()==DFA_PUSH)
|
|
if (cObj->Action.Target==Target->Action.Target)
|
|
{
|
|
cObj->Action.ComDir=Target->Action.ComDir;
|
|
return;
|
|
}
|
|
// Else, grab target's grab
|
|
cObj->AddCommand(C4CMD_Grab,Target->Action.Target);
|
|
return;
|
|
}
|
|
else if (cObj->GetProcedure()==DFA_PUSH)
|
|
{
|
|
// Follow ungrabbing
|
|
cObj->AddCommand(C4CMD_UnGrab);
|
|
return;
|
|
}
|
|
|
|
// If in following range
|
|
if (Inside<int32_t>(cObj->GetX()-Target->GetX(),-FollowRange,+FollowRange)
|
|
&& Inside<int32_t>(cObj->GetY()-Target->GetY(),-FollowRange,+FollowRange))
|
|
{
|
|
// Copy target's Action.ComDir
|
|
cObj->Action.ComDir=Target->Action.ComDir;
|
|
}
|
|
else // Else, move to target
|
|
{
|
|
cObj->AddCommand(C4CMD_MoveTo,nullptr,Target->GetX(),Target->GetY(),10);
|
|
}
|
|
|
|
}
|
|
|
|
void C4Command::Enter()
|
|
{
|
|
DWORD ocf;
|
|
|
|
// No object to enter or can't enter by def
|
|
if (!Target || cObj->Def->NoPushEnter) { Finish(); return; }
|
|
|
|
// Already in target object
|
|
if (cObj->Contained==Target) { Finish(true); return; }
|
|
|
|
// Digging: stop
|
|
if (cObj->GetProcedure()==DFA_DIG) ObjectComStop(cObj);
|
|
|
|
// Pushing grab only or pushing not desired: let go
|
|
if (cObj->GetProcedure()==DFA_PUSH)
|
|
if (cObj->Action.Target)
|
|
if (cObj->Action.Target->GetPropertyInt(P_Touchable) == 2 || !(Data.getInt() & C4CMD_Enter_PushTarget))
|
|
{ cObj->AddCommand(C4CMD_UnGrab,nullptr,0,0,50); return; }
|
|
|
|
// Pushing target: let go
|
|
if (cObj->GetProcedure()==DFA_PUSH)
|
|
if (cObj->Action.Target==Target)
|
|
{ cObj->AddCommand(C4CMD_UnGrab,nullptr,0,0,50); return; }
|
|
|
|
// Grabbing overrides position for target
|
|
int32_t cx,cy;
|
|
cx=cObj->GetX(); cy=cObj->GetY();
|
|
if (cObj->GetProcedure()==DFA_PUSH)
|
|
if (cObj->Action.Target)
|
|
{ cx=cObj->Action.Target->GetX(); cy=cObj->Action.Target->GetY(); }
|
|
|
|
// If in entrance range
|
|
ocf=OCF_Entrance;
|
|
if (!cObj->Contained && Target->At(cx,cy,ocf) && (ocf & OCF_Entrance))
|
|
{
|
|
// Stop
|
|
cObj->Action.ComDir=COMD_Stop;
|
|
// Pushing: push target into entrance, then stop
|
|
if ((cObj->GetProcedure()==DFA_PUSH) && cObj->Action.Target)
|
|
{
|
|
cObj->Action.Target->SetCommand(C4CMD_Enter,Target);
|
|
Finish(true); return;
|
|
}
|
|
// Else, enter self
|
|
else
|
|
{
|
|
// If entrance open, enter object
|
|
if (Target->EntranceStatus!=0)
|
|
{
|
|
cObj->Enter(Target);
|
|
Finish(true); return;
|
|
}
|
|
else // Else, activate entrance
|
|
Target->ActivateEntrance(cObj->Controller,cObj);
|
|
}
|
|
|
|
}
|
|
else // Else, move to object's entrance
|
|
{
|
|
int32_t ex,ey,ewdt,ehgt;
|
|
if (Target->GetEntranceArea(ex,ey,ewdt,ehgt))
|
|
cObj->AddCommand(C4CMD_MoveTo,nullptr,ex+ewdt/2,ey+ehgt/2,50, nullptr, true, C4VInt((Data.getInt() & C4CMD_Enter_PushTarget) ? C4CMD_MoveTo_PushTarget : 0));
|
|
}
|
|
|
|
}
|
|
|
|
void C4Command::Exit()
|
|
{
|
|
|
|
// Outside: done
|
|
if (!cObj->Contained) { Finish(true); return; }
|
|
|
|
// Entrance open, leave object
|
|
if (cObj->Contained->EntranceStatus)
|
|
{
|
|
// Exit to container's container
|
|
if (cObj->Contained->Contained)
|
|
{ cObj->Enter(cObj->Contained->Contained); Finish(true); return; }
|
|
// Exit to entrance area
|
|
int32_t ex,ey,ewdt,ehgt;
|
|
if (cObj->Contained->OCF & OCF_Entrance)
|
|
if (cObj->Contained->GetEntranceArea(ex,ey,ewdt,ehgt))
|
|
{ cObj->Exit(ex+ewdt/2,ey+ehgt+cObj->Shape.GetY()-1); Finish(true); return; }
|
|
// Exit jump out of collection area
|
|
if (cObj->GetPropertyInt(P_Collectible))
|
|
if (cObj->Contained->Def->Collection.Wdt)
|
|
{
|
|
cObj->Exit(cObj->Contained->GetX(),cObj->Contained->GetY()+cObj->Contained->Def->Collection.y-1);
|
|
ObjectComJump(cObj);
|
|
Finish(true); return;
|
|
}
|
|
// Plain exit
|
|
cObj->Exit(cObj->GetX(),cObj->GetY());
|
|
Finish(true); return;
|
|
}
|
|
|
|
// Entrance closed, activate entrance
|
|
else
|
|
{
|
|
if (!cObj->Contained->ActivateEntrance(cObj->Controller,cObj))
|
|
// Entrance activation failed: fail
|
|
{ Finish(); return; }
|
|
}
|
|
}
|
|
|
|
void C4Command::Grab()
|
|
{
|
|
DWORD ocf;
|
|
// Command fulfilled
|
|
if (cObj->GetProcedure()==DFA_PUSH)
|
|
if (cObj->Action.Target==Target)
|
|
{ Finish(true); return; }
|
|
// Digging: stop
|
|
if (cObj->GetProcedure()==DFA_DIG) ObjectComStop(cObj);
|
|
// Grabbing: let go
|
|
if (cObj->GetProcedure()==DFA_PUSH)
|
|
{ cObj->AddCommand(C4CMD_UnGrab,nullptr,0,0,50); return; }
|
|
// No target
|
|
if (!Target) { Finish(); return; }
|
|
// At target object: grab
|
|
ocf=OCF_All;
|
|
if (!cObj->Contained && Target->At(cObj->GetX(),cObj->GetY(),ocf))
|
|
{
|
|
// Scaling or hangling: let go
|
|
if ((cObj->GetProcedure()==DFA_SCALE) || (cObj->GetProcedure()==DFA_HANGLE))
|
|
ObjectComLetGo(cObj,(cObj->Action.Dir==DIR_Left) ? +1 : -1);
|
|
// Grab
|
|
cObj->Action.ComDir=COMD_Stop;
|
|
ObjectComGrab(cObj,Target);
|
|
}
|
|
// Else, move to object
|
|
else
|
|
{
|
|
cObj->AddCommand(C4CMD_MoveTo,nullptr,Target->GetX()+Tx._getInt(),Target->GetY()+Ty,50);
|
|
}
|
|
}
|
|
|
|
void C4Command::PushTo()
|
|
{
|
|
// Target check
|
|
if (!Target) { Finish(); return; }
|
|
|
|
// Target is target self: fail
|
|
if (Target==Target2) { Finish(); return; }
|
|
|
|
// Command fulfilled
|
|
if (Target2)
|
|
{
|
|
// Object in correct target container: success
|
|
if (Target->Contained==Target2)
|
|
{ Finish(true); return; }
|
|
}
|
|
else
|
|
{
|
|
// Object at target position: success
|
|
if (Inside(Target->GetX()-Tx._getInt(),-PushToRange,+PushToRange))
|
|
if (Inside(Target->GetY()-Ty,-PushToRange,+PushToRange))
|
|
{
|
|
cObj->Action.ComDir=COMD_Stop;
|
|
cObj->AddCommand(C4CMD_UnGrab);
|
|
cObj->AddCommand(C4CMD_Wait,nullptr,0,0,10);
|
|
Finish(true); return;
|
|
}
|
|
}
|
|
|
|
// Digging: stop
|
|
if (cObj->GetProcedure()==DFA_DIG) ObjectComStop(cObj);
|
|
|
|
// Target contained: activate
|
|
if (Target->Contained)
|
|
{ cObj->AddCommand(C4CMD_Activate,Target,0,0,40); return; }
|
|
|
|
// Grab target
|
|
if (!((cObj->GetProcedure()==DFA_PUSH) && (cObj->Action.Target==Target)))
|
|
{ cObj->AddCommand(C4CMD_Grab,Target,0,0,40); return; }
|
|
|
|
// Move to target position / enter target object
|
|
if (Target2)
|
|
{ cObj->AddCommand(C4CMD_Enter,Target2,0,0,40, nullptr, true, C4Value(C4CMD_Enter_PushTarget)); return; }
|
|
else
|
|
{ cObj->AddCommand(C4CMD_MoveTo,nullptr,Tx,Ty,40, nullptr, true, C4Value(C4CMD_MoveTo_PushTarget)); return; }
|
|
|
|
}
|
|
|
|
void C4Command::UnGrab()
|
|
{
|
|
ObjectComUnGrab(cObj);
|
|
cObj->Action.ComDir=COMD_Stop;
|
|
Finish(true);
|
|
}
|
|
|
|
void C4Command::Throw()
|
|
{
|
|
|
|
// Digging: stop
|
|
if (cObj->GetProcedure()==DFA_DIG) ObjectComStop(cObj);
|
|
|
|
// Throw specific object not in contents: get object
|
|
if (Target)
|
|
if (!cObj->Contents.GetLink(Target))
|
|
{
|
|
cObj->AddCommand(C4CMD_Get,Target,0,0,40);
|
|
return;
|
|
}
|
|
|
|
// Determine move-to range
|
|
int32_t iMoveToRange = MoveToRange;
|
|
if (cObj->Def->MoveToRange > 0) iMoveToRange = cObj->Def->MoveToRange;
|
|
|
|
// Target coordinates are not default 0/0: targeted throw
|
|
if ((Tx._getInt()!=0) || (Ty!=0))
|
|
{
|
|
|
|
// Grabbing: let go
|
|
if (cObj->GetProcedure()==DFA_PUSH)
|
|
{ cObj->AddCommand(C4CMD_UnGrab,nullptr,0,0,50); return; }
|
|
|
|
// Preferred throwing direction
|
|
int32_t iDir=+1; if (cObj->GetX() > Tx._getInt()) iDir=-1;
|
|
|
|
// Find throwing position
|
|
int32_t iTx,iTy;
|
|
C4Real pthrow=C4REAL100(cObj->GetPropertyInt(P_ThrowSpeed));
|
|
int32_t iHeight = cObj->Shape.Hgt;
|
|
if (!FindThrowingPosition(Tx._getInt(),Ty,pthrow*iDir,-pthrow,iHeight,iTx,iTy))
|
|
if (!FindThrowingPosition(Tx._getInt(),Ty,pthrow*iDir*-1,-pthrow,iHeight,iTx,iTy))
|
|
// No throwing position: fail
|
|
{ Finish(); return; }
|
|
|
|
// At throwing position: face target and throw
|
|
if (Inside<int32_t>(cObj->GetX() - iTx, -iMoveToRange, +iMoveToRange) && Inside<int32_t>(cObj->GetY()-iTy,-15,+15))
|
|
{
|
|
if (cObj->GetX() < Tx._getInt()) cObj->SetDir(DIR_Right); else cObj->SetDir(DIR_Left);
|
|
cObj->Action.ComDir=COMD_Stop;
|
|
if (ObjectComThrow(cObj,Target))
|
|
Finish(true); // Throw successfull: done, else continue
|
|
return;
|
|
}
|
|
|
|
// Move to target position
|
|
cObj->AddCommand(C4CMD_MoveTo,nullptr,iTx,iTy,20);
|
|
|
|
return;
|
|
}
|
|
|
|
// Contained: put or take
|
|
if (cObj->Contained)
|
|
{
|
|
ObjectComPutTake(cObj,cObj->Contained,Target);
|
|
Finish(true); return;
|
|
}
|
|
|
|
// Pushing: put or take
|
|
if (cObj->GetProcedure()==DFA_PUSH)
|
|
{
|
|
if (cObj->Action.Target)
|
|
ObjectComPutTake(cObj,cObj->Action.Target,Target);
|
|
Finish(true); return;
|
|
}
|
|
|
|
// Outside: Throw
|
|
ObjectComThrow(cObj,Target);
|
|
Finish(true);
|
|
}
|
|
|
|
void C4Command::Take()
|
|
{
|
|
ObjectComTake(cObj);
|
|
Finish(true);
|
|
}
|
|
|
|
void C4Command::Take2()
|
|
{
|
|
ObjectComTake2(cObj);
|
|
Finish(true);
|
|
}
|
|
|
|
void C4Command::Drop()
|
|
{
|
|
|
|
// Digging: stop
|
|
if (cObj->GetProcedure()==DFA_DIG) ObjectComStop(cObj);
|
|
|
|
// Drop specific object not in contents: get object
|
|
if (Target)
|
|
if (!cObj->Contents.GetLink(Target))
|
|
{
|
|
cObj->AddCommand(C4CMD_Get,Target,0,0,40);
|
|
return;
|
|
}
|
|
|
|
// Determine move-to range
|
|
int32_t iMoveToRange = MoveToRange;
|
|
if (cObj->Def->MoveToRange > 0) iMoveToRange = cObj->Def->MoveToRange;
|
|
|
|
// Target coordinates are not default 0/0: targeted drop
|
|
if ((Tx._getInt()!=0) || (Ty!=0))
|
|
{
|
|
// Grabbing: let go
|
|
if (cObj->GetProcedure()==DFA_PUSH)
|
|
{ cObj->AddCommand(C4CMD_UnGrab,nullptr,0,0,50); return; }
|
|
// At target position: drop
|
|
if (Inside<int32_t>(cObj->GetX() - Tx._getInt(), -iMoveToRange, +iMoveToRange) && Inside<int32_t>(cObj->GetY()-Ty,-15,+15))
|
|
{
|
|
cObj->Action.ComDir=COMD_Stop;
|
|
ObjectComDrop(cObj,Target);
|
|
Finish(true);
|
|
return;
|
|
}
|
|
// Move to target position
|
|
cObj->AddCommand(C4CMD_MoveTo,nullptr,Tx._getInt(),Ty,20);
|
|
return;
|
|
}
|
|
|
|
// Contained: put
|
|
if (cObj->Contained)
|
|
{
|
|
ObjectComPutTake(cObj,cObj->Contained,Target);
|
|
Finish(true); return;
|
|
}
|
|
|
|
// Pushing: put
|
|
if (cObj->GetProcedure()==DFA_PUSH)
|
|
{
|
|
if (cObj->Action.Target)
|
|
ObjectComPutTake(cObj,cObj->Action.Target,Target);
|
|
Finish(true); return;
|
|
}
|
|
|
|
// Outside: Drop
|
|
ObjectComDrop(cObj,Target);
|
|
Finish(true);
|
|
}
|
|
|
|
void C4Command::Jump()
|
|
{
|
|
// Already in air?
|
|
if (cObj->GetProcedure()==DFA_FLIGHT)
|
|
{
|
|
// Check whether target position is given
|
|
if (Tx._getInt())
|
|
{
|
|
if (cObj->GetX()<Tx._getInt()) cObj->Action.ComDir=COMD_Right;
|
|
else if (cObj->GetX()>Tx._getInt()) cObj->Action.ComDir=COMD_Left;
|
|
else cObj->Action.ComDir=COMD_Stop;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cObj->Action.ComDir=COMD_Stop;
|
|
// Done
|
|
Finish(true);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void C4Command::Wait()
|
|
{
|
|
// Digging: stop
|
|
if (cObj->GetProcedure()==DFA_DIG) ObjectComStop(cObj);
|
|
}
|
|
|
|
bool C4Command::GetTryEnter()
|
|
{
|
|
// Target contained and container has RejectContents: fail
|
|
if (Target->Contained && !!Target->Contained->Call(PSF_RejectContents)) { Finish(); return false; }
|
|
// FIXME: Drop stuff if full here
|
|
bool fWasContained = !!Target->Contained;
|
|
// Grab target object
|
|
bool fRejectCollect = false;
|
|
bool fSuccess = !!Target->Enter(cObj, true, true, &fRejectCollect);
|
|
// target is void?
|
|
// well...most likely the new container has done something with it
|
|
// so count it as success
|
|
if (!Target) { Finish(true); return true; }
|
|
// collection rejected by target: make room for more contents
|
|
if (fRejectCollect)
|
|
{
|
|
if (cObj->PutAwayUnusedObject(Target)) return false;
|
|
// Can't get due to RejectCollect: fail
|
|
Finish();
|
|
return false;
|
|
}
|
|
// if not successfully entered for any other reason, fail
|
|
if (!fSuccess) { Finish(); return false; }
|
|
// get-callback for getting out of containers
|
|
if (fWasContained) cObj->Call(PSF_Get, &C4AulParSet(Target));
|
|
// entered
|
|
return true;
|
|
}
|
|
|
|
void C4Command::Get()
|
|
{
|
|
|
|
// Data set and target specified: open get menu & done (old style)
|
|
if (((Data.getInt()==1) || (Data.getInt()==2)) && Target)
|
|
{
|
|
cObj->ActivateMenu((Data.getInt()==1) ? C4MN_Get : C4MN_Contents,0,0,0,Target);
|
|
Finish(true); return;
|
|
}
|
|
|
|
// Get target specified by container and type
|
|
if (!Target && Target2 && Data)
|
|
if (!(Target = Target2->Contents.Find(Data.getDef())))
|
|
{ Finish(); return; }
|
|
|
|
// No target: failure
|
|
if (!Target) { Finish(); return; }
|
|
|
|
// Target not carryable: failure
|
|
if (!(Target->OCF & OCF_Carryable))
|
|
{ Finish(); return; }
|
|
|
|
// Target collected
|
|
if (Target->Contained == cObj)
|
|
{
|
|
// Get-count specified: decrease count and continue with next object
|
|
if (Tx._getInt() > 1)
|
|
{ Target = nullptr; Tx--; return; }
|
|
// We're done
|
|
else
|
|
{ cObj->Action.ComDir=COMD_Stop; Finish(true); return; }
|
|
}
|
|
|
|
// Grabbing other than target container: let go
|
|
if (cObj->GetProcedure()==DFA_PUSH)
|
|
if (cObj->Action.Target!=Target->Contained)
|
|
{ cObj->AddCommand(C4CMD_UnGrab,nullptr,0,0,50); return; }
|
|
|
|
// Target in solid: dig out
|
|
if (!Target->Contained && (Target->OCF & OCF_InSolid))
|
|
{
|
|
// Check for closest free position
|
|
int32_t iX=Target->GetX(),iY=Target->GetY();
|
|
// Find all-closest dig-out position
|
|
if (!FindClosestFree(iX,iY,-120,+120,-1,-1))
|
|
// None found
|
|
{ Finish(); return; }
|
|
// Check good-angle left/right dig-out position
|
|
int32_t iX2=Target->GetX(),iY2=Target->GetY();
|
|
if (FindClosestFree(iX2,iY2,-140,+140,-40,+40))
|
|
// Use good-angle position if it's not way worse
|
|
if ( Distance(Target->GetX(),Target->GetY(),iX2,iY2) < 10*Distance(Target->GetX(),Target->GetY(),iX,iY) )
|
|
{ iX=iX2; iY=iY2; }
|
|
// Move to closest free position (if not in dig-direct range)
|
|
if (!Inside(cObj->GetX()-iX,-DigOutPositionRange,+DigOutPositionRange)
|
|
|| !Inside(cObj->GetY()-iY,-DigOutPositionRange,+DigOutPositionRange))
|
|
{ cObj->AddCommand(C4CMD_MoveTo,nullptr,iX,iY,50); return; }
|
|
// DigTo
|
|
cObj->AddCommand(C4CMD_Dig,nullptr,Target->GetX(),Target->GetY()+4,50); return;
|
|
}
|
|
|
|
// Digging: stop
|
|
if (cObj->GetProcedure()==DFA_DIG) ObjectComStop(cObj);
|
|
|
|
// Target contained
|
|
if (Target->Contained)
|
|
{
|
|
// target can't be taken out of containers?
|
|
if (Target->Def->NoGet) return;
|
|
// In same container: grab target
|
|
if (cObj->Contained==Target->Contained)
|
|
{
|
|
GetTryEnter();
|
|
// Done
|
|
return;
|
|
}
|
|
|
|
// Leave own container
|
|
if (cObj->Contained)
|
|
{ cObj->AddCommand(C4CMD_Exit,nullptr,0,0,50); return; }
|
|
|
|
// Target container has grab get: grab target container
|
|
if (Target->Contained->Def->GrabPutGet & C4D_Grab_Get)
|
|
{
|
|
// Grabbing target container
|
|
if ((cObj->GetProcedure()==DFA_PUSH) && (cObj->Action.Target==Target->Contained))
|
|
{
|
|
GetTryEnter();
|
|
// Done
|
|
return;
|
|
}
|
|
// Grab target container
|
|
cObj->AddCommand(C4CMD_Grab,Target->Contained,0,0,50);
|
|
return;
|
|
}
|
|
|
|
// Target container has entrance: enter target container
|
|
if (Target->Contained->OCF & OCF_Entrance)
|
|
{ cObj->AddCommand(C4CMD_Enter,Target->Contained,0,0,50); return; }
|
|
|
|
// Can't get to target due to target container: fail
|
|
Finish();
|
|
return;
|
|
}
|
|
|
|
// Target outside
|
|
|
|
// Leave own container
|
|
if (cObj->Contained) { cObj->AddCommand(C4CMD_Exit,nullptr,0,0,50); return; }
|
|
|
|
// Outside
|
|
if (!cObj->Contained)
|
|
{
|
|
|
|
// Target in collection range
|
|
DWORD ocf = OCF_Normal | OCF_Collection;
|
|
if (cObj->At(Target->GetX(),Target->GetY(),ocf))
|
|
{
|
|
// stop here
|
|
cObj->Action.ComDir=COMD_Stop;
|
|
// try getting the object
|
|
if (GetTryEnter()) return;
|
|
}
|
|
|
|
// Target not in range
|
|
else
|
|
{
|
|
// Target in jumping range above clonk: try side-move jump
|
|
if (Inside<int32_t>(cObj->GetX()-Target->GetX(),-10,+10))
|
|
if (Inside<int32_t>(cObj->GetY()-Target->GetY(),30,50))
|
|
{
|
|
int32_t iSideX=1; if (Random(2)) iSideX=-1;
|
|
iSideX=cObj->GetX()+iSideX*(cObj->GetY()-Target->GetY());
|
|
if (PathFree(iSideX,cObj->GetY(),Target->GetX(),Target->GetY()))
|
|
{
|
|
// Side-move jump
|
|
cObj->AddCommand(C4CMD_Jump,nullptr,Tx._getInt(),Ty);
|
|
// FIXME: Drop stuff if full here
|
|
cObj->AddCommand(C4CMD_MoveTo,nullptr,iSideX,cObj->GetY(),50);
|
|
}
|
|
}
|
|
// Move to target (random offset for difficult pickups)
|
|
// ...avoid offsets into solid which would lead to high above surface locations!
|
|
cObj->AddCommand(C4CMD_MoveTo,nullptr,Target->GetX()+Random(15)-7,Target->GetY(),25,nullptr);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
void C4Command::Activate()
|
|
{
|
|
|
|
// Container specified, but no Target & no type: open activate menu for container
|
|
if (Target2 && !Target && !Data)
|
|
{
|
|
cObj->ActivateMenu(C4MN_Activate,0,0,0,Target2);
|
|
Finish(true);
|
|
return;
|
|
}
|
|
|
|
// Target object specified & outside: success
|
|
if (Target)
|
|
if (!Target->Contained)
|
|
{ Finish(true); return; }
|
|
|
|
// No container specified: determine container by target object
|
|
if (!Target2)
|
|
if (Target)
|
|
Target2=Target->Contained;
|
|
|
|
// No container specified: fail
|
|
if (!Target2) { Finish(); return; }
|
|
|
|
// Digging: stop
|
|
if (cObj->GetProcedure()==DFA_DIG) ObjectComStop(cObj);
|
|
|
|
// In container
|
|
if (cObj->Contained==Target2)
|
|
{
|
|
for (Tx.SetInt(Data ? std::max<int32_t>(Tx._getInt(),1) : 1); Tx._getInt(); --Tx)
|
|
{
|
|
// If not specified get object from target contents by type
|
|
// Find first object requested id that has no command exit yet
|
|
if (!Target)
|
|
for (C4Object *pObj : Target2->Contents)
|
|
if (pObj->Status && (pObj->Def==Data.getDef()))
|
|
if (!pObj->Command || (pObj->Command->Command!=C4CMD_Exit))
|
|
{ Target=pObj; break; }
|
|
// No target
|
|
if (!Target) { Finish(); return; }
|
|
|
|
// Thing in own container (target2)
|
|
if (Target->Contained!=Target2) { Finish(); return; }
|
|
|
|
// Activate object to exit
|
|
Target->Controller = cObj->Controller;
|
|
Target->SetCommand(C4CMD_Exit);
|
|
Target = nullptr;
|
|
}
|
|
|
|
Finish(true); return;
|
|
}
|
|
|
|
// Leave own container
|
|
if (cObj->Contained)
|
|
{ cObj->AddCommand(C4CMD_Exit,nullptr,0,0,50); return; }
|
|
|
|
// Target container has entrance: enter
|
|
if (Target2->OCF & OCF_Entrance)
|
|
{ cObj->AddCommand(C4CMD_Enter,Target2,0,0,50); return; }
|
|
|
|
// Can't get to target due to target container: fail
|
|
Finish();
|
|
|
|
}
|
|
|
|
|
|
void C4Command::Put() // Notice: Put command is currently using Ty as an internal reminder flag for letting go after putting...
|
|
{
|
|
// No target container: failure
|
|
if (!Target) { Finish(); return; }
|
|
|
|
// Thing to put specified by type
|
|
if (!Target2 && Data)
|
|
if (!(Target2 = cObj->Contents.Find(Data.getDef())))
|
|
{ Finish(); return; }
|
|
|
|
// No thing to put specified
|
|
if (!Target2)
|
|
// Assume first contents object
|
|
if (!(Target2 = cObj->Contents.GetObject()))
|
|
// No contents object to put - most likely we did have a target but it was deleted,
|
|
// e.g. by AutoSellContents in a base. New behaviour: if there is nothing to put, we
|
|
// now consider the command succesfully completed.
|
|
{ Finish(true); return; }
|
|
|
|
// Thing is in target
|
|
if (Target2->Contained == Target)
|
|
{
|
|
// Put-count specified: decrease count and continue with next object
|
|
if (Tx._getInt() > 1)
|
|
{ Target2 = nullptr; Tx--; return; }
|
|
// We're done
|
|
else
|
|
{ Finish(true); return; }
|
|
}
|
|
|
|
// Thing to put not in contents: get object
|
|
if (!cObj->Contents.GetLink(Target2))
|
|
{
|
|
// Object is nearby and traveling: wait
|
|
if (!Target2->Contained)
|
|
if (Distance(cObj->GetX(),cObj->GetY(),Target2->GetX(),Target2->GetY())<80)
|
|
if (Target2->OCF & OCF_HitSpeed1)
|
|
return;
|
|
// Go get it
|
|
cObj->AddCommand(C4CMD_Get,Target2,0,0,40); return;
|
|
}
|
|
|
|
// Target is contained: can't do it
|
|
if (Target->Contained)
|
|
{ Finish(); return; }
|
|
|
|
// Digging: stop
|
|
if (cObj->GetProcedure()==DFA_DIG) ObjectComStop(cObj);
|
|
|
|
// Grabbing other than target: let go
|
|
C4Object *pGrabbing=nullptr;
|
|
if (cObj->GetProcedure()==DFA_PUSH)
|
|
pGrabbing = cObj->Action.Target;
|
|
if (pGrabbing && (pGrabbing!=Target))
|
|
{ cObj->AddCommand(C4CMD_UnGrab,nullptr,0,0,50); return; }
|
|
|
|
// Inside target container
|
|
if (cObj->Contained == Target)
|
|
{
|
|
// Try to put
|
|
if (!ObjectComPut(cObj,Target,Target2))
|
|
Finish(); // Putting failed
|
|
return;
|
|
}
|
|
|
|
// Leave own container
|
|
if (cObj->Contained)
|
|
{ cObj->AddCommand(C4CMD_Exit,nullptr,0,0,50); return; }
|
|
|
|
// Target has collection: throw in if not fragile, not grabbing target and throwing position found
|
|
if (Target->OCF & OCF_Collection)
|
|
if (!Target2->Def->Fragile)
|
|
if (pGrabbing!=Target)
|
|
{
|
|
int32_t iTx = Target->GetX() + Target->Def->Collection.x + Target->Def->Collection.Wdt/2;
|
|
int32_t iTy = Target->GetY() + Target->Def->Collection.y + Target->Def->Collection.Hgt/2;
|
|
C4Real pthrow=C4REAL100(cObj->GetPropertyInt(P_ThrowSpeed));
|
|
int32_t iHeight = cObj->Shape.Hgt;
|
|
int32_t iPosX,iPosY;
|
|
int32_t iObjDist = Distance(cObj->GetX(),cObj->GetY(),Target->GetX(),Target->GetY());
|
|
if ( (FindThrowingPosition(iTx,iTy,pthrow,-pthrow,iHeight,iPosX,iPosY) && (Distance(iPosX,iPosY,cObj->GetX(),cObj->GetY()) < iObjDist))
|
|
|| (FindThrowingPosition(iTx,iTy,pthrow*-1,-pthrow,iHeight,iPosX,iPosY) && (Distance(iPosX,iPosY,cObj->GetX(),cObj->GetY()) < iObjDist)) )
|
|
{
|
|
// Throw
|
|
cObj->AddCommand(C4CMD_Throw,Target2,iTx,iTy,5);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Target has C4D_Grab_Put: grab target and put
|
|
if (Target->Def->GrabPutGet & C4D_Grab_Put)
|
|
{
|
|
// Grabbing target container
|
|
if (pGrabbing==Target)
|
|
{
|
|
// Try to put
|
|
if (!ObjectComPut(cObj,Target,Target2))
|
|
// Putting failed
|
|
{ Finish(); return; }
|
|
// Let go (if we grabbed the target because of this command)
|
|
if (Ty) cObj->AddCommand(C4CMD_UnGrab,nullptr,0,0);
|
|
return;
|
|
}
|
|
// Grab target and let go after putting
|
|
cObj->AddCommand(C4CMD_Grab,Target,0,0,50);
|
|
Ty = 1;
|
|
return;
|
|
}
|
|
|
|
// Target can be entered: enter target
|
|
if (Target->OCF & OCF_Entrance)
|
|
{ cObj->AddCommand(C4CMD_Enter,Target,0,0,50); return; }
|
|
|
|
}
|
|
|
|
void C4Command::ClearPointers(C4Object *pObj)
|
|
{
|
|
if (cObj==pObj) cObj=nullptr;
|
|
if (Target==pObj) Target=nullptr;
|
|
if (Target2==pObj) Target2=nullptr;
|
|
}
|
|
|
|
void C4Command::Execute()
|
|
{
|
|
|
|
// Finished?!
|
|
if (Finished) return;
|
|
|
|
// Parent object safety
|
|
if (!cObj) { Finish(); return; }
|
|
|
|
// Delegated command failed
|
|
if (Failures)
|
|
{
|
|
// Retry
|
|
if (Retries>0)
|
|
{ Failures=0; Retries--; cObj->AddCommand(C4CMD_Retry,nullptr,0,0,10); return; }
|
|
// Too many failures
|
|
else
|
|
{ Finish(); return; }
|
|
}
|
|
|
|
// Command update
|
|
if (UpdateInterval>0)
|
|
{
|
|
UpdateInterval--;
|
|
if (UpdateInterval==0)
|
|
{
|
|
Finish(true); return;
|
|
}
|
|
}
|
|
|
|
// Initial command evaluation
|
|
if (InitEvaluation()) return;
|
|
|
|
// from now on, we are executing this command... and nobody
|
|
// should dare deleting us
|
|
iExec = 1;
|
|
|
|
// Execute
|
|
switch (Command)
|
|
{
|
|
case C4CMD_Follow: Follow(); break;
|
|
case C4CMD_MoveTo: MoveTo(); break;
|
|
case C4CMD_Enter: Enter(); break;
|
|
case C4CMD_Exit: Exit(); break;
|
|
case C4CMD_Grab: Grab(); break;
|
|
case C4CMD_UnGrab: UnGrab(); break;
|
|
case C4CMD_Throw: Throw(); break;
|
|
case C4CMD_Jump: Jump(); break;
|
|
case C4CMD_Wait: Wait(); break;
|
|
case C4CMD_Get: Get(); break;
|
|
case C4CMD_Put: Put(); break;
|
|
case C4CMD_Drop: Drop(); break;
|
|
case C4CMD_Dig: Dig(); break;
|
|
case C4CMD_Activate: Activate(); break;
|
|
case C4CMD_PushTo: PushTo(); break;
|
|
case C4CMD_Transfer: Transfer(); break;
|
|
case C4CMD_Attack: Attack(); break;
|
|
case C4CMD_Buy: Buy(); break;
|
|
case C4CMD_Sell: Sell(); break;
|
|
case C4CMD_Acquire: Acquire(); break;
|
|
case C4CMD_Retry: Retry(); break;
|
|
case C4CMD_Home: Home(); break;
|
|
case C4CMD_Call: Call(); break;
|
|
case C4CMD_Take: Take(); break; // carlo
|
|
case C4CMD_Take2: Take2(); break; // carlo
|
|
default: Finish(); break;
|
|
}
|
|
|
|
/* // Remember this command might have already been deleted through calls
|
|
// made during execution. You must not do anything here... */
|
|
|
|
// check: command must be deleted?
|
|
if (iExec > 1)
|
|
delete this;
|
|
else
|
|
iExec = 0;
|
|
|
|
}
|
|
|
|
void C4Command::Finish(bool fSuccess, const char *szFailMessage)
|
|
{
|
|
// Mark finished
|
|
Finished=true;
|
|
// Failed
|
|
if (!fSuccess)
|
|
Fail(szFailMessage);
|
|
else
|
|
{
|
|
// successful commands might gain EXP
|
|
int32_t iExpGain = GetExpGain();
|
|
if (iExpGain && cObj) if (cObj->Info)
|
|
for (int32_t i=iExpGain; i; --i)
|
|
if (!(++cObj->Info->ControlCount%5))
|
|
cObj->DoExperience(1);
|
|
}
|
|
}
|
|
|
|
bool C4Command::InitEvaluation()
|
|
{
|
|
// Already evaluated
|
|
if (Evaluated) return false;
|
|
// Set evaluation flag
|
|
Evaluated=true;
|
|
// Evaluate
|
|
switch (Command)
|
|
{
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
case C4CMD_MoveTo:
|
|
{
|
|
// Target coordinates by Target
|
|
if (Target) { Tx+=Target->GetX(); Ty+=Target->GetY(); Target=nullptr; }
|
|
// Adjust coordinates
|
|
int32_t iTx = Tx._getInt();
|
|
if (~Data.getInt() & C4CMD_MoveTo_NoPosAdjust) AdjustMoveToTarget(iTx,Ty,FreeMoveTo(cObj),cObj->Shape.Hgt);
|
|
Tx.SetInt(iTx);
|
|
return true;
|
|
}
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
case C4CMD_PushTo:
|
|
{
|
|
// Adjust coordinates
|
|
int32_t iTx = Tx._getInt();
|
|
AdjustMoveToTarget(iTx,Ty,FreeMoveTo(cObj),cObj->Shape.Hgt);
|
|
Tx.SetInt(iTx);
|
|
return true;
|
|
}
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
case C4CMD_Exit:
|
|
// Cancel attach
|
|
ObjectComCancelAttach(cObj);
|
|
return true;
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
case C4CMD_Jump:
|
|
{
|
|
if (Tx._getInt())
|
|
{
|
|
if (Tx._getInt()<cObj->GetX()) cObj->SetDir(DIR_Left);
|
|
if (Tx._getInt()>cObj->GetX()) cObj->SetDir(DIR_Right);
|
|
}
|
|
ObjectComJump(cObj);
|
|
return true;
|
|
}
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
case C4CMD_Wait:
|
|
// Update interval by Data
|
|
if (!!Data) UpdateInterval=Data.getInt();
|
|
// Else update interval by Tx
|
|
else if (Tx._getInt()) UpdateInterval=Tx._getInt();
|
|
return true;
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
case C4CMD_Acquire:
|
|
// update default search range
|
|
if (!Tx._getInt()) Tx.SetInt(500);
|
|
if (!Ty) Ty=250;
|
|
return true;
|
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
}
|
|
// Need not be evaluated
|
|
return false;
|
|
}
|
|
|
|
void C4Command::Clear()
|
|
{
|
|
Command=C4CMD_None;
|
|
cObj=nullptr;
|
|
Evaluated=false;
|
|
PathChecked=false;
|
|
Tx=C4VNull;
|
|
Ty=0;
|
|
Target=Target2=nullptr;
|
|
UpdateInterval=0;
|
|
if (Text) Text->DecRef();
|
|
Text=nullptr;
|
|
BaseMode=C4CMD_Mode_SilentSub;
|
|
}
|
|
|
|
bool C4Command::FlightControl() // Called by DFA_WALK, DFA_FLIGHT
|
|
{
|
|
// Crew members or pathfinder objects only
|
|
if (!((cObj->OCF & OCF_CrewMember) || cObj->Def->Pathfinder)) return false;
|
|
|
|
// Not while in a disabled action
|
|
C4PropList* pActionDef = cObj->GetAction();
|
|
if (pActionDef)
|
|
{
|
|
if (pActionDef->GetPropertyInt(P_ObjectDisabled)) return false;
|
|
}
|
|
|
|
// Target angle
|
|
int32_t cx=cObj->GetX(),cy=cObj->GetY();
|
|
int32_t iAngle = Angle(cx,cy,Tx._getInt(),Ty); while (iAngle>180) iAngle-=360;
|
|
|
|
// Target in flight angle (sector +/- from straight up), beyond minimum distance, and top free
|
|
if (Inside(iAngle, -FlightAngleRange, +FlightAngleRange)
|
|
|| Inside(iAngle, -FlightAngleRange, +FlightAngleRange))
|
|
if (Distance(cx,cy,Tx._getInt(),Ty) > 30)
|
|
{
|
|
int32_t iTopFree;
|
|
for (iTopFree=0; (iTopFree<50) && !GBackSolid(cx,cy+cObj->Shape.GetY()-iTopFree); ++iTopFree) {}
|
|
if (iTopFree>=15)
|
|
{
|
|
// Take off
|
|
return cObj->SetActionByName("Fly"); // This is a little primitive... we should have a ObjectActionFly or maybe a command for this...
|
|
}
|
|
}
|
|
|
|
// No flight control
|
|
return false;
|
|
}
|
|
|
|
bool C4Command::JumpControl() // Called by DFA_WALK
|
|
{
|
|
|
|
// Crew members or pathfinder objects only
|
|
if (! ((cObj->OCF & OCF_CrewMember) || cObj->Def->Pathfinder) ) return false;
|
|
|
|
// Target angle
|
|
int32_t cx=cObj->GetX(),cy=cObj->GetY();
|
|
int32_t iAngle = Angle(cx,cy,Tx._getInt(),Ty); while (iAngle>180) iAngle-=360;
|
|
|
|
// Diagonal free jump (if in angle range, minimum distance, and top free)
|
|
if (Inside(iAngle-JumpAngle,-JumpAngleRange,+JumpAngleRange)
|
|
|| Inside(iAngle+JumpAngle,-JumpAngleRange,+JumpAngleRange))
|
|
if (PathFree(cx,cy,Tx._getInt(),Ty))
|
|
if (Distance(cx,cy,Tx._getInt(),Ty)>30)
|
|
{
|
|
int32_t iTopFree;
|
|
for (iTopFree=0; (iTopFree<50) && !GBackSolid(cx,cy+cObj->Shape.GetY()-iTopFree); ++iTopFree) {}
|
|
if (iTopFree>=15)
|
|
{
|
|
cObj->AddCommand(C4CMD_Jump,nullptr,Tx,Ty); return true;
|
|
}
|
|
}
|
|
|
|
// High angle side move - jump (2x range)
|
|
if (Inside<int32_t>(iAngle-JumpHighAngle,-3*JumpAngleRange,+3*JumpAngleRange))
|
|
// Vertical range
|
|
if (Inside<int32_t>(cy-Ty,10,40))
|
|
{
|
|
int32_t iSide=SolidOnWhichSide(Tx._getInt(),Ty); // take jump height of side move position into consideration...!
|
|
int32_t iDist=5*Abs(cy-Ty)/6;
|
|
int32_t iSideX=cx-iDist*iSide,iSideY=cy; AdjustMoveToTarget(iSideX,iSideY,false,0);
|
|
// Side move target in range
|
|
if (Inside<int32_t>(iSideY-cy,-20,+20))
|
|
{
|
|
// Path free from side move target to jump target
|
|
if (PathFree(iSideX,iSideY,Tx._getInt(),Ty))
|
|
{
|
|
cObj->AddCommand(C4CMD_Jump,nullptr,Tx,Ty);
|
|
cObj->AddCommand(C4CMD_MoveTo,nullptr,iSideX,iSideY,50);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Low side contact jump
|
|
// Only jump before almost running off a cliff
|
|
int32_t iLowSideRange=5;
|
|
if (!(GBackDensity(cx,cy+cObj->Shape.Hgt/2) >= cObj->Shape.ContactDensity))
|
|
{
|
|
if (cObj->t_contact & CNAT_Right)
|
|
if (Inside(iAngle-JumpLowAngle,-iLowSideRange*JumpAngleRange,+iLowSideRange*JumpAngleRange))
|
|
{
|
|
cObj->AddCommand(C4CMD_Jump,nullptr,Tx,Ty); return true;
|
|
}
|
|
if (cObj->t_contact & CNAT_Left)
|
|
if (Inside(iAngle+JumpLowAngle,-iLowSideRange*JumpAngleRange,+iLowSideRange*JumpAngleRange))
|
|
{
|
|
cObj->AddCommand(C4CMD_Jump,nullptr,Tx,Ty); return true;
|
|
}
|
|
}
|
|
|
|
// No jump control
|
|
return false;
|
|
}
|
|
|
|
void C4Command::Transfer()
|
|
{
|
|
|
|
// No target: failure
|
|
if (!Target) { Finish(); return; }
|
|
|
|
// Find transfer zone
|
|
C4TransferZone *pZone;
|
|
int32_t iEntryX,iEntryY;
|
|
if (!(pZone=Game.TransferZones.Find(Target))) { Finish(); return; }
|
|
|
|
// Not at or in transfer zone: move to entry point
|
|
if (!Inside<int32_t>(cObj->GetX()-pZone->X,-5,pZone->Wdt-1+5))
|
|
{
|
|
if (!pZone->GetEntryPoint(iEntryX,iEntryY,cObj->GetX(),cObj->GetY())) { Finish(); return; }
|
|
cObj->AddCommand(C4CMD_MoveTo,nullptr,iEntryX,iEntryY,25);
|
|
return;
|
|
}
|
|
|
|
// Call target transfer script
|
|
if (!::Game.iTick5)
|
|
{
|
|
if (!Target->Call(PSF_ControlTransfer, &C4AulParSet(cObj, Tx, Ty)).getBool())
|
|
// Transfer not handled by target: done
|
|
{ Finish(true); return; }
|
|
}
|
|
|
|
}
|
|
|
|
void C4Command::Attack()
|
|
{
|
|
|
|
// No target: failure
|
|
if (!Target) { Finish(); return; }
|
|
|
|
// Target is crew member
|
|
if (Target->OCF & OCF_CrewMember)
|
|
{
|
|
|
|
// Throw projectile at target
|
|
for (C4Object *pProjectile : cObj->Contents)
|
|
if (pProjectile->Def->Projectile)
|
|
{
|
|
// Add throw command
|
|
cObj->AddCommand(C4CMD_Throw,pProjectile,Target->GetX(),Target->GetY(),2);
|
|
return;
|
|
}
|
|
|
|
// Follow containment
|
|
if (cObj->Contained!=Target->Contained)
|
|
{
|
|
// Exit
|
|
if (cObj->Contained) cObj->AddCommand(C4CMD_Exit,nullptr,0,0,10);
|
|
// Enter
|
|
else cObj->AddCommand(C4CMD_Enter,Target->Contained,0,0,10);
|
|
return;
|
|
}
|
|
|
|
// Move to target
|
|
cObj->AddCommand(C4CMD_MoveTo,nullptr,Target->GetX(),Target->GetY(),10);
|
|
return;
|
|
|
|
}
|
|
|
|
// For now, attack crew members only
|
|
else
|
|
{
|
|
// Success, target might be no crew member due to that is has been killed
|
|
Finish(true);
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
void C4Command::Buy()
|
|
{
|
|
Finish();
|
|
}
|
|
|
|
void C4Command::Sell()
|
|
{
|
|
Finish();
|
|
}
|
|
|
|
void C4Command::Acquire()
|
|
{
|
|
|
|
// No type to acquire specified: fail
|
|
if (!Data) { Finish(); return; }
|
|
|
|
// Target material in inventory: done
|
|
if (cObj->Contents.Find(Data.getDef()))
|
|
{ Finish(true); return; }
|
|
|
|
// script overload
|
|
int32_t scriptresult = cObj->Call(PSF_ControlCommandAcquire, &C4AulParSet(Target, Tx, Ty, Target2, Data)).getInt ();
|
|
|
|
// script call might have deleted object
|
|
if (!cObj->Status) return;
|
|
if (1 == scriptresult) return;
|
|
if (2 == scriptresult)
|
|
{ Finish(true); return; }
|
|
if (3 == scriptresult)
|
|
{ Finish(); return; }
|
|
|
|
// Find available material
|
|
C4Object *pMaterial=nullptr;
|
|
// Next closest
|
|
while ((pMaterial = Game.FindObject(Data.getDef(),cObj->GetX(),cObj->GetY(),-1,-1,OCF_Available,pMaterial)))
|
|
// Object is not in container to be ignored
|
|
if (!Target2 || pMaterial->Contained!=Target2)
|
|
// Object is near enough
|
|
if (Inside(cObj->GetX()-pMaterial->GetX(),-Tx._getInt(),+Tx._getInt()))
|
|
if (Inside(cObj->GetY()-pMaterial->GetY(),-Ty,+Ty))
|
|
// Must be complete
|
|
if (pMaterial->OCF & OCF_FullCon)
|
|
// Doesn't burn
|
|
if (!pMaterial->GetOnFire())
|
|
// We found one
|
|
break;
|
|
|
|
// Available material found: get material
|
|
if (pMaterial)
|
|
{ cObj->AddCommand(C4CMD_Get,pMaterial,0,0,40); return; }
|
|
|
|
// No available material found: buy material
|
|
// This command will fail immediately if buying at bases is not possible
|
|
// - but the command should be created anyway because it might be overloaded
|
|
cObj->AddCommand(C4CMD_Buy,nullptr,0,0,100,nullptr,true,Data,false,0,nullptr,C4CMD_Mode_Sub);
|
|
|
|
}
|
|
|
|
void C4Command::Fail(const char *szFailMessage)
|
|
{
|
|
// Check for base command (next unfinished)
|
|
C4Command *pBase;
|
|
for (pBase=Next; pBase; pBase=pBase->Next) if (!pBase->Finished) break;
|
|
|
|
bool ExecFail = false;
|
|
switch (BaseMode)
|
|
{
|
|
// silent subcommand
|
|
case C4CMD_Mode_SilentSub:
|
|
{
|
|
// Just count failures in base command
|
|
if (pBase) { pBase->Failures++; return; }
|
|
else ExecFail = true;
|
|
break;
|
|
}
|
|
// verbose subcommand
|
|
case C4CMD_Mode_Sub:
|
|
{
|
|
// Count failures in base command
|
|
if (pBase) { pBase->Failures++; }
|
|
// Execute fail message, if base will fail
|
|
if (!pBase || !pBase->Retries) ExecFail = true;
|
|
break;
|
|
}
|
|
// base command
|
|
case C4CMD_Mode_Base:
|
|
{
|
|
// Just execute fail notice
|
|
ExecFail = true;
|
|
break;
|
|
}
|
|
// silent base command: do nothing
|
|
}
|
|
|
|
char szCommandName[24 + 1];
|
|
char szObjectName[C4MaxName + 1];
|
|
StdStrBuf str;
|
|
|
|
if (ExecFail && cObj && (cObj->OCF & OCF_CrewMember))
|
|
{
|
|
// Fail message
|
|
if (szFailMessage)
|
|
str = szFailMessage;
|
|
C4Object * l_Obj = cObj;
|
|
switch (Command)
|
|
{
|
|
case C4CMD_Call:
|
|
{
|
|
// Call fail-function in target object (no message if non-zero)
|
|
int32_t l_Command = Command;
|
|
if (CallFailed()) return;
|
|
// Fail-function not available or returned zero: standard message
|
|
SCopy(LoadResStr(CommandNameID(l_Command)),szCommandName);
|
|
str.Format(LoadResStr("IDS_CON_FAILURE"),szCommandName);
|
|
break;
|
|
}
|
|
case C4CMD_Exit:
|
|
// No message
|
|
break;
|
|
case C4CMD_Dig:
|
|
// No message
|
|
break;
|
|
case C4CMD_Acquire:
|
|
// Already has a fail message
|
|
if (szFailMessage) break;
|
|
// Fail message with name of target type
|
|
SCopy(LoadResStr(CommandNameID(Command)), szCommandName);
|
|
C4Def *pDef; pDef = Data.getDef();
|
|
SCopy(pDef ? pDef->GetName() : LoadResStr("IDS_OBJ_UNKNOWN"), szObjectName);
|
|
str.Format(LoadResStr("IDS_CON_FAILUREOF"), szCommandName, szObjectName);
|
|
break;
|
|
default:
|
|
// Already has a fail message
|
|
if (szFailMessage) break;
|
|
// Standard no-can-do message
|
|
SCopy(LoadResStr(CommandNameID(Command)), szCommandName);
|
|
str.Format(LoadResStr("IDS_CON_FAILURE"), szCommandName);
|
|
break;
|
|
}
|
|
if (l_Obj) if (l_Obj->Status && !l_Obj->Def->SilentCommands)
|
|
{
|
|
// Message (if not empty)
|
|
if (!!str)
|
|
{
|
|
::Messages.Append(C4GM_Target, str.getData(), l_Obj, NO_OWNER, 0, 0, C4RGB(0xff, 0xff, 0xff), true);
|
|
}
|
|
// Stop Clonk
|
|
l_Obj->Action.ComDir = COMD_Stop;
|
|
// Clonk-specific fail action/sound
|
|
C4AulParSet pars(C4VString(CommandName(Command)), C4VObj(Target), Tx, C4VInt(Ty), C4VObj(Target2), Data);
|
|
l_Obj->Call(PSF_CommandFailure, &pars);
|
|
}
|
|
}
|
|
}
|
|
|
|
void C4Command::Retry()
|
|
{
|
|
|
|
}
|
|
|
|
void C4Command::Home()
|
|
{
|
|
Finish();
|
|
}
|
|
|
|
void C4Command::Set(int32_t iCommand, C4Object *pObj, C4Object *pTarget, C4Value nTx, int32_t iTy,
|
|
C4Object *pTarget2, C4Value iData, int32_t iUpdateInterval,
|
|
bool fEvaluated, int32_t iRetries, C4String * szText, int32_t iBaseMode)
|
|
{
|
|
// Reset
|
|
Clear(); Default();
|
|
// Set
|
|
Command=iCommand;
|
|
cObj=pObj;
|
|
Target=pTarget;
|
|
Tx=nTx; Ty=iTy;
|
|
if (Command != C4CMD_Call && !Tx.CheckConversion(C4V_Int))
|
|
Tx.SetInt(0);
|
|
Target2=pTarget2;
|
|
Data=iData;
|
|
UpdateInterval=iUpdateInterval;
|
|
Evaluated=fEvaluated;
|
|
Retries=iRetries;
|
|
Text = szText;
|
|
if (Text) Text->IncRef();
|
|
BaseMode=iBaseMode;
|
|
}
|
|
|
|
void C4Command::Call()
|
|
{
|
|
// No function name: fail
|
|
if (!Text || !Text->GetCStr() || !Text->GetCStr()[0]) { Finish(); return; }
|
|
// No target object: fail
|
|
if (!Target) { Finish(); return; }
|
|
// Done: success
|
|
Finish(true);
|
|
// Object call FIXME:use c4string-api
|
|
Target->Call(Text->GetCStr(),&C4AulParSet(cObj, Tx, Ty, Target2));
|
|
// Extreme caution notice: the script call might do just about anything
|
|
// including clearing all commands (including this) i.e. through a call
|
|
// to SetCommand. Thus, we must not do anything in this command anymore
|
|
// after the script call (even the Finish has to be called before).
|
|
// The Finish call being misled to the freshly created Build command (by
|
|
// chance, the this-pointer was simply crap at the time) was reason for
|
|
// the latest sync losses in 4.62.
|
|
}
|
|
|
|
void C4Command::CompileFunc(StdCompiler *pComp, C4ValueNumbers * numbers)
|
|
{
|
|
// Version
|
|
int32_t iVersion = 0;
|
|
if (pComp->Separator(StdCompiler::SEP_DOLLAR))
|
|
{
|
|
iVersion = 1;
|
|
pComp->Value(mkIntPackAdapt(iVersion));
|
|
pComp->Separator(StdCompiler::SEP_SEP);
|
|
}
|
|
else
|
|
pComp->NoSeparator();
|
|
// Command name
|
|
pComp->Value(mkEnumAdaptT<uint8_t>(Command, EnumAdaptCommandEntries));
|
|
pComp->Separator(StdCompiler::SEP_SEP);
|
|
// Target X/Y
|
|
pComp->Value(mkParAdapt(Tx, numbers)); pComp->Separator(StdCompiler::SEP_SEP);
|
|
pComp->Value(mkIntPackAdapt(Ty)); pComp->Separator(StdCompiler::SEP_SEP);
|
|
// Target
|
|
pComp->Value(Target); pComp->Separator(StdCompiler::SEP_SEP);
|
|
pComp->Value(Target2); pComp->Separator(StdCompiler::SEP_SEP);
|
|
// Data
|
|
pComp->Value(mkParAdapt(Data, numbers)); pComp->Separator(StdCompiler::SEP_SEP);
|
|
// Update interval
|
|
pComp->Value(mkIntPackAdapt(UpdateInterval)); pComp->Separator(StdCompiler::SEP_SEP);
|
|
// Flags
|
|
pComp->Value(mkIntPackAdapt(Evaluated)); pComp->Separator(StdCompiler::SEP_SEP);
|
|
pComp->Value(mkIntPackAdapt(PathChecked)); pComp->Separator(StdCompiler::SEP_SEP);
|
|
pComp->Value(mkIntPackAdapt(Finished)); pComp->Separator(StdCompiler::SEP_SEP);
|
|
// Retries
|
|
pComp->Value(mkIntPackAdapt(Failures)); pComp->Separator(StdCompiler::SEP_SEP);
|
|
pComp->Value(mkIntPackAdapt(Retries)); pComp->Separator(StdCompiler::SEP_SEP);
|
|
pComp->Value(mkIntPackAdapt(Permit)); pComp->Separator(StdCompiler::SEP_SEP);
|
|
// Base mode
|
|
if (iVersion > 0)
|
|
{
|
|
pComp->Value(mkIntPackAdapt(BaseMode)); pComp->Separator(StdCompiler::SEP_SEP);
|
|
}
|
|
// Text
|
|
StdStrBuf TextBuf;
|
|
if (pComp->isSerializer())
|
|
{
|
|
if (Text)
|
|
TextBuf.Ref(Text->GetData());
|
|
else
|
|
TextBuf.Ref("0");
|
|
}
|
|
pComp->Value(mkParAdapt(TextBuf, StdCompiler::RCT_All));
|
|
if (pComp->isDeserializer())
|
|
{
|
|
if (Text)
|
|
Text->DecRef();
|
|
if (TextBuf == "0")
|
|
{ Text = nullptr; }
|
|
else
|
|
{ Text = Strings.RegString(TextBuf); Text->IncRef(); }
|
|
}
|
|
}
|
|
|
|
void C4Command::Denumerate(C4ValueNumbers * numbers)
|
|
{
|
|
Target.DenumeratePointers();
|
|
Target2.DenumeratePointers();
|
|
Tx.Denumerate(numbers);
|
|
}
|
|
|
|
int32_t C4Command::CallFailed()
|
|
{
|
|
// No function name or no target object: cannot call fail-function
|
|
if (!Text || !Text->GetCStr() || !Text->GetCStr()[0] || !Target) return 0;
|
|
// Compose fail-function name
|
|
char szFunctionFailed[1024+1]; sprintf(szFunctionFailed,"~%sFailed",Text->GetCStr());
|
|
// Call failed-function
|
|
return Target->Call(szFunctionFailed,&C4AulParSet(cObj, Tx, Ty, Target2))._getInt();
|
|
// Extreme caution notice: the script call might do just about anything
|
|
// including clearing all commands (including this) i.e. through a call
|
|
// to SetCommand. Thus, we must not do anything in this command anymore
|
|
// after the script call.
|
|
}
|
|
|
|
int32_t C4Command::GetExpGain()
|
|
{
|
|
// return exp gained by this command
|
|
switch (Command)
|
|
{
|
|
// internal
|
|
case C4CMD_Wait:
|
|
case C4CMD_Transfer:
|
|
case C4CMD_Retry:
|
|
case C4CMD_Call:
|
|
return 0;
|
|
|
|
// regular move commands
|
|
case C4CMD_Follow:
|
|
case C4CMD_MoveTo:
|
|
case C4CMD_Enter:
|
|
case C4CMD_Exit:
|
|
return 1;
|
|
|
|
// simple activities
|
|
case C4CMD_Grab:
|
|
case C4CMD_UnGrab:
|
|
case C4CMD_Throw:
|
|
case C4CMD_Jump:
|
|
case C4CMD_Get:
|
|
case C4CMD_Put:
|
|
case C4CMD_Drop:
|
|
case C4CMD_Activate:
|
|
case C4CMD_PushTo:
|
|
case C4CMD_Dig:
|
|
case C4CMD_Buy:
|
|
case C4CMD_Sell:
|
|
case C4CMD_Take:
|
|
case C4CMD_Take2:
|
|
return 1;
|
|
|
|
// not that simple
|
|
case C4CMD_Acquire:
|
|
case C4CMD_Home:
|
|
return 2;
|
|
|
|
// victory!
|
|
case C4CMD_Attack:
|
|
return 15;
|
|
}
|
|
// unknown command
|
|
return 0;
|
|
}
|