forked from Mirrors/openclonk
2391 lines
72 KiB
C++
2391 lines
72 KiB
C++
/*
|
|
* OpenClonk, http://www.openclonk.org
|
|
*
|
|
* Copyright (c) 1998-2000, 2003-2005, 2008 Matthes Bender
|
|
* Copyright (c) 2001-2008 Sven Eberhardt
|
|
* Copyright (c) 2001 Michael Käser
|
|
* Copyright (c) 2002, 2004, 2006 Peter Wortmann
|
|
* Copyright (c) 2004-2006, 2008-2009 Günther Brammer
|
|
* Copyright (c) 2009 Nicolas Hake
|
|
* Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de
|
|
*
|
|
* Portions might be copyrighted by other authors who have contributed
|
|
* to OpenClonk.
|
|
*
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
* See isc_license.txt for full license and disclaimer.
|
|
*
|
|
* "Clonk" is a registered trademark of Matthes Bender.
|
|
* See clonk_trademark_license.txt for full license.
|
|
*/
|
|
|
|
/* The command stack controls an object's complex and independent behavior */
|
|
|
|
#include <C4Include.h>
|
|
#include <C4Command.h>
|
|
|
|
#ifndef BIG_C4INCLUDE
|
|
#include <C4Object.h>
|
|
#include <C4ObjectCom.h>
|
|
#include <C4ObjectInfo.h>
|
|
#include <C4Random.h>
|
|
#include <C4GameMessage.h>
|
|
#include <C4ObjectMenu.h>
|
|
#include <C4Player.h>
|
|
#include <C4Landscape.h>
|
|
#include <C4Game.h>
|
|
#include <C4PlayerList.h>
|
|
#include <C4GameObjects.h>
|
|
#endif
|
|
|
|
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)
|
|
{
|
|
static const char *szCommandName[] = {
|
|
"None","Follow","MoveTo","Enter","Exit","Grab","Build","Throw","Chop",
|
|
"UnGrab","Jump","Wait","Get","Put","Drop","Dig","Activate","PushTo",
|
|
"Construct","Transfer","Attack","Context","Buy","Sell","Acquire",
|
|
"Energy","Retry","Home","Call","Take","Take2" };
|
|
|
|
if (!Inside<int32_t>(iCommand,C4CMD_First,C4CMD_Last)) return "None";
|
|
|
|
return szCommandName[iCommand];
|
|
}
|
|
|
|
const char* CommandNameID(int32_t iCommand)
|
|
{
|
|
static const char* dwCommandNameID[] = {
|
|
"IDS_COMM_NONE","IDS_COMM_FOLLOW","IDS_COMM_MOVETO","IDS_COMM_ENTER",
|
|
"IDS_COMM_EXIT","IDS_COMM_GRAB","IDS_COMM_BUILD","IDS_COMM_THROW","IDS_COMM_CHOP",
|
|
"IDS_COMM_UNGRAB","IDS_COMM_JUMP","IDS_COMM_WAIT","IDS_COMM_GET","IDS_COMM_PUT",
|
|
"IDS_COMM_DROP","IDS_COMM_DIG","IDS_COMM_ACTIVATE","IDS_COMM_PUSHTO",
|
|
"IDS_COMM_CONSTRUCT","IDS_COMM_TRANSFER","IDS_COMM_ATTACK","IDS_COMM_CONTEXT",
|
|
"IDS_COMM_BUY","IDS_COMM_SELL","IDS_COMM_ACQUIRE","IDS_COMM_ENERGY","IDS_COMM_RETRY",
|
|
"IDS_CON_HOME","IDS_COMM_CALL","IDS_COMM_TAKE","IDS_COMM_TAKE2" };
|
|
|
|
if (!Inside<int32_t>(iCommand, C4CMD_First, C4CMD_Last)) return "IDS_COMM_NONE";
|
|
|
|
return dwCommandNameID[iCommand];
|
|
}
|
|
|
|
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 = NULL;
|
|
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;
|
|
}
|
|
|
|
void AdjustMoveToTarget(int32_t &rX, int32_t &rY, BOOL fFreeMove, int32_t iShapeHgt)
|
|
{
|
|
// Above solid (always)
|
|
int32_t iY;
|
|
for (iY=rY; (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<GBackHgt) && !GBackSemiSolid(rX,iY+1); iY++) {}
|
|
if (iY<GBackHgt) 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 FreeMoveTo(C4Object *cObj)
|
|
{
|
|
// Floating: we accept any move-to target
|
|
if (cObj->GetProcedure()==DFA_FLOAT) return TRUE;
|
|
// Can fly: we accept any move-to target
|
|
if (cObj->GetPhysical()->CanFly) return TRUE;
|
|
// Assume we're walking: move-to targets are adjusted
|
|
return FALSE;
|
|
}
|
|
|
|
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=NULL;
|
|
Evaluated=FALSE;
|
|
PathChecked=FALSE;
|
|
Finished=FALSE;
|
|
Tx=C4VNull;
|
|
Ty=0;
|
|
Target=Target2=NULL;
|
|
Data=0;
|
|
UpdateInterval=0;
|
|
Failures=0;
|
|
Retries=0;
|
|
Permit=0;
|
|
Text=NULL;
|
|
Next=NULL;
|
|
iExec=0;
|
|
BaseMode=C4CMD_Mode_SilentSub;
|
|
}
|
|
|
|
static bool ObjectAddWaypoint(int32_t iX, int32_t iY, intptr_t iTransferTarget, intptr_t ipObject)
|
|
{
|
|
C4Object *cObj = (C4Object*) ipObject; if (!cObj) return FALSE;
|
|
|
|
// Transfer waypoint
|
|
if (iTransferTarget)
|
|
return cObj->AddCommand(C4CMD_Transfer,(C4Object*)iTransferTarget,iX,iY,0,NULL,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
|
|
//AddCommand(iCommand,pTarget,iTx,iTy,iUpdateInterval,pTarget2,fInitEvaluation,iData,fAppend,iRetries,szText,iBaseMode)
|
|
assert(cObj->Command);
|
|
if (!cObj->AddCommand(C4CMD_MoveTo,NULL,iX,iY,25,NULL,FALSE,cObj->Command->Data)) return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
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,NULL,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,
|
|
(intptr_t)cObj)) // intptr for 64bit?
|
|
{ /* 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->Def->Grab == 2 || !(Data & C4CMD_MoveTo_PushTarget))
|
|
{
|
|
// Re-evaluate this command because vehicle control might have blocked evaluation
|
|
Evaluated=FALSE;
|
|
cObj->AddCommand(C4CMD_UnGrab,NULL,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;
|
|
// Chop, build, dig, bridge: stop
|
|
case DFA_CHOP: case DFA_BUILD: 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/5;
|
|
// 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->Action.Act<=ActIdle)
|
|
{ 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:
|
|
{
|
|
FIXED dx = itofix(Tx._getInt()) - cObj->fix_x, dy = itofix(Ty) - cObj->fix_y;
|
|
// normalize
|
|
FIXED dScale = FIXED100(cObj->GetPhysical()->Float) / Max(Abs(dx), Abs(dy));
|
|
dx *= dScale; dy *= dScale;
|
|
// difference to momentum
|
|
dx -= cObj->xdir; dy -= cObj->ydir;
|
|
// steer
|
|
if(Abs(dx)+Abs(dy) < FIXED100(20)) cObj->Action.ComDir = COMD_None;
|
|
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:
|
|
// 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;
|
|
|
|
// Grabbing: let go
|
|
if (cObj->GetProcedure()==DFA_PUSH)
|
|
{ cObj->AddCommand(C4CMD_UnGrab,NULL,0,0,50); return; }
|
|
|
|
// If contained: exit
|
|
if (cObj->Contained)
|
|
{ cObj->AddCommand(C4CMD_Exit,NULL,0,0,50); return; }
|
|
|
|
// Building or chopping: stop
|
|
if ((cObj->GetProcedure()==DFA_CHOP) || (cObj->GetProcedure()==DFA_BUILD))
|
|
ObjectComStop(cObj);
|
|
|
|
// 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()
|
|
{
|
|
|
|
// If crew member, only selected objects can follow
|
|
if (cObj->Def->CrewMember)
|
|
// Finish successfully to avoid fail message
|
|
if (!cObj->Select && cObj->Owner != NO_OWNER) { Finish(TRUE); return; }
|
|
|
|
// 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,NULL,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,NULL,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; }
|
|
|
|
// Building or chopping: stop
|
|
if ((cObj->GetProcedure()==DFA_CHOP) || (cObj->GetProcedure()==DFA_BUILD))
|
|
ObjectComStop(cObj);
|
|
|
|
// 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->Def->Grab == 2 || !(Data & C4CMD_Enter_PushTarget))
|
|
{ cObj->AddCommand(C4CMD_UnGrab,NULL,0,0,50); return; }
|
|
|
|
// Pushing target: let go
|
|
if (cObj->GetProcedure()==DFA_PUSH)
|
|
if (cObj->Action.Target==Target)
|
|
{ cObj->AddCommand(C4CMD_UnGrab,NULL,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,NULL,ex+ewdt/2,ey+ehgt/2,50, NULL, true, (Data & C4CMD_Enter_PushTarget) ? C4CMD_MoveTo_PushTarget : 0);
|
|
}
|
|
|
|
}
|
|
|
|
void C4Command::Exit()
|
|
{
|
|
|
|
// Outside: done
|
|
if (!cObj->Contained) { Finish(TRUE); return; }
|
|
|
|
// Building: stop
|
|
if (cObj->GetProcedure()==DFA_BUILD)
|
|
ObjectComStop(cObj);
|
|
|
|
// 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->Def->Carryable)
|
|
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; }
|
|
// Building or chopping: stop
|
|
if ((cObj->GetProcedure()==DFA_CHOP) || (cObj->GetProcedure()==DFA_BUILD))
|
|
ObjectComStop(cObj);
|
|
// Digging: stop
|
|
if (cObj->GetProcedure()==DFA_DIG) ObjectComStop(cObj);
|
|
// Grabbing: let go
|
|
if (cObj->GetProcedure()==DFA_PUSH)
|
|
{ cObj->AddCommand(C4CMD_UnGrab,NULL,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,NULL,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,NULL,0,0,10);
|
|
Finish(TRUE); return;
|
|
}
|
|
}
|
|
|
|
// Building or chopping: stop
|
|
if ((cObj->GetProcedure()==DFA_CHOP) || (cObj->GetProcedure()==DFA_BUILD))
|
|
ObjectComStop(cObj);
|
|
|
|
// 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, NULL, true, C4CMD_Enter_PushTarget); return; }
|
|
else
|
|
{ cObj->AddCommand(C4CMD_MoveTo,NULL,Tx,Ty,40, NULL, true, C4CMD_MoveTo_PushTarget); return; }
|
|
|
|
}
|
|
|
|
void C4Command::Chop()
|
|
{
|
|
DWORD ocf;
|
|
// No target: fail
|
|
if (!Target) { Finish(); return; }
|
|
// Can not chop: fail
|
|
if (!cObj->GetPhysical()->CanChop)
|
|
{ Finish(); return; }
|
|
// Target not chopable: done (assume was successfully chopped)
|
|
if (!(Target->OCF & OCF_Chop))
|
|
{ Finish(TRUE); return; }
|
|
// Chopping target: wait
|
|
if ((cObj->GetProcedure()==DFA_CHOP) && (cObj->Action.Target==Target))
|
|
return;
|
|
// Grabbing: let go
|
|
if (cObj->GetProcedure()==DFA_PUSH)
|
|
{ cObj->AddCommand(C4CMD_UnGrab,NULL,0,0,50); return; }
|
|
// Building, digging or chopping other: stop
|
|
if ((cObj->GetProcedure()==DFA_DIG) || (cObj->GetProcedure()==DFA_CHOP) || (cObj->GetProcedure()==DFA_BUILD))
|
|
{ ObjectComStop(cObj); return; }
|
|
// At target object, in correct shopping position
|
|
ocf=OCF_All;
|
|
if (!cObj->Contained && Target->At(cObj->GetX(),cObj->GetY(),ocf) && Inside<int32_t>(Abs(cObj->GetX() - Target->GetX()), 4, 9))
|
|
{
|
|
cObj->Action.ComDir=COMD_Stop;
|
|
ObjectComChop(cObj,Target);
|
|
}
|
|
// Else, move to object
|
|
else
|
|
{
|
|
cObj->AddCommand(C4CMD_MoveTo, NULL, (cObj->GetX() > Target->GetX()) ? Target->GetX()+6 : Target->GetX()-6, Target->GetY(), 50);
|
|
// Too close? Move away first.
|
|
if (Abs(cObj->GetX() - Target->GetX()) < 5)
|
|
cObj->AddCommand(C4CMD_MoveTo, NULL, (cObj->GetX() > Target->GetX()) ? Target->GetX()+15 : Target->GetX()-15, Target->GetY(), 50);
|
|
}
|
|
}
|
|
|
|
void C4Command::Build()
|
|
{
|
|
DWORD ocf;
|
|
// No target: cancel
|
|
if (!Target)
|
|
{ Finish(); return; }
|
|
// Lost the ability to build? Fail.
|
|
if (cObj->GetPhysical() && !cObj->GetPhysical()->CanConstruct)
|
|
{
|
|
Finish(FALSE, FormatString(LoadResStr("IDS_TEXT_CANTBUILD"), cObj->GetName()).getData());
|
|
return;
|
|
}
|
|
// Target complete: Command fulfilled
|
|
if (Target->GetCon()>=FullCon)
|
|
{
|
|
// Activate internal vehicles
|
|
if (Target->Contained && (Target->Category & C4D_Vehicle))
|
|
cObj->AddCommand(C4CMD_Activate,Target);
|
|
// Energy supply (if necessary and nobody else is doing so already)
|
|
if (Game.Rules & C4RULE_StructuresNeedEnergy)
|
|
if (Target->Def->LineConnect & C4D_Power_Input)
|
|
if (!Game.FindObjectByCommand(C4CMD_Energy,Target))
|
|
{
|
|
// if another Clonk is also building this structure and carries a linekit already, that Clonk should rather perform the energy command
|
|
C4Object *pOtherBuilder = NULL;
|
|
if (!cObj->Contents.Find(C4ID_Linekit))
|
|
{
|
|
while (pOtherBuilder = Game.FindObjectByCommand(C4CMD_Build,Target, C4VNull,0, NULL, pOtherBuilder))
|
|
if (pOtherBuilder->Contents.Find(C4ID_Linekit))
|
|
break;
|
|
}
|
|
if (!pOtherBuilder)
|
|
cObj->AddCommand(C4CMD_Energy,Target);
|
|
}
|
|
// Done
|
|
cObj->Action.ComDir=COMD_Stop;
|
|
Finish(TRUE); return;
|
|
}
|
|
// Currently working on target: continue
|
|
if (cObj->GetProcedure()==DFA_BUILD)
|
|
if (cObj->Action.Target==Target)
|
|
return;
|
|
// Grabbing: let go
|
|
if (cObj->GetProcedure()==DFA_PUSH)
|
|
{ cObj->AddCommand(C4CMD_UnGrab,NULL,0,0,50); return; }
|
|
// Digging: stop
|
|
if (cObj->GetProcedure()==DFA_DIG) ObjectComStop(cObj);
|
|
// Worker ist structure or static back: internal target build only (old stuff)
|
|
if ((cObj->Category & C4D_Structure) || (cObj->Category & C4D_StaticBack))
|
|
{
|
|
// Target is internal
|
|
if (Target->Contained==cObj)
|
|
{ ObjectComBuild(cObj,Target); return; }
|
|
// Target is not internal: cancel
|
|
Finish(); return;
|
|
}
|
|
// At target check
|
|
ocf=OCF_All;
|
|
if ( (Target->Contained && (cObj->Contained==Target->Contained))
|
|
|| (Target->At(cObj->GetX(),cObj->GetY(),ocf) && (cObj->GetProcedure()==DFA_WALK)) )
|
|
{
|
|
ObjectComStop(cObj);
|
|
ObjectComBuild(cObj,Target);
|
|
return;
|
|
}
|
|
// Else, move to object
|
|
else
|
|
{
|
|
if (Target->Contained) cObj->AddCommand(C4CMD_Enter,Target->Contained,0,0,50);
|
|
else cObj->AddCommand(C4CMD_MoveTo,NULL,Target->GetX(),Target->GetY(),50);
|
|
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,NULL,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;
|
|
FIXED pthrow=ValByPhysical(400, cObj->GetPhysical()->Throw);
|
|
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,NULL,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,NULL,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,NULL,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()
|
|
{
|
|
// Tx not default 0: adjust jump direction
|
|
if (Tx._getInt())
|
|
{
|
|
if (Tx._getInt()<cObj->GetX()) cObj->SetDir(DIR_Left);
|
|
if (Tx._getInt()>cObj->GetX()) cObj->SetDir(DIR_Right);
|
|
}
|
|
// Jump
|
|
ObjectComJump(cObj);
|
|
// Done
|
|
Finish(TRUE);
|
|
}
|
|
|
|
void C4Command::Wait()
|
|
{
|
|
// Digging: stop
|
|
if (cObj->GetProcedure()==DFA_DIG) ObjectComStop(cObj);
|
|
}
|
|
|
|
void C4Command::Context()
|
|
{
|
|
// Not context object specified (in Target2): fail
|
|
if (!Target2) { Finish(); return; }
|
|
// Open context menu for target
|
|
cObj->ActivateMenu(C4MN_Context,0,0,0,Target2);
|
|
if (Tx._getInt()!=0 && Ty!=0)
|
|
if (cObj->Menu)
|
|
{
|
|
cObj->Menu->SetAlignment(C4MN_Align_Free);
|
|
cObj->Menu->SetLocation(Tx._getInt(),Ty);
|
|
}
|
|
// Done
|
|
Finish(TRUE);
|
|
}
|
|
|
|
bool C4Command::GetTryEnter()
|
|
{
|
|
// No minimum con knowledge vehicles/items: fail
|
|
if (Target->Contained && CheckMinimumCon(Target)) { /* fail??! */ return false; }
|
|
// Target contained and container has RejectContents: fail
|
|
if (Target->Contained && !!Target->Contained->Call(PSF_RejectContents)) { Finish(); return false; }
|
|
// Collection limit: drop other object
|
|
// return after drop, so multiple objects may be dropped
|
|
if (cObj->Def->CollectionLimit && (cObj->Contents.ObjectCount()>=cObj->Def->CollectionLimit))
|
|
{
|
|
if (!cObj->PutAwayUnusedObject(Target)) { Finish(); return false; }
|
|
return false;
|
|
}
|
|
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(C4VObj(Target)));
|
|
// entered
|
|
return true;
|
|
}
|
|
|
|
void C4Command::Get()
|
|
{
|
|
|
|
// Data set and target specified: open get menu & done (old style)
|
|
if (((Data==1) || (Data==2)) && Target)
|
|
{
|
|
cObj->ActivateMenu((Data==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)))
|
|
{ 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 = NULL; 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,NULL,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,NULL,iX,iY,50); return; }
|
|
// DigTo
|
|
cObj->AddCommand(C4CMD_Dig,NULL,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,NULL,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,NULL,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,NULL,Tx._getInt(),Ty);
|
|
if (cObj->Def->CollectionLimit && (cObj->Contents.ObjectCount()>=cObj->Def->CollectionLimit))
|
|
cObj->AddCommand(C4CMD_Drop); // Drop object if necessary due to collection limit
|
|
// Need to kill NoCollectDelay after drop...!
|
|
cObj->AddCommand(C4CMD_MoveTo,NULL,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,NULL,Target->GetX()+Random(15)-7,Target->GetY(),25,NULL);
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
bool C4Command::CheckMinimumCon (C4Object *pObj)
|
|
{
|
|
if ((pObj->Category & C4D_Vehicle) || (pObj->Category & C4D_Object))
|
|
if (pObj->Category & C4D_SelectKnowledge)
|
|
if (pObj->GetCon() < FullCon)
|
|
{
|
|
//SoundEffect("Error",0,100,cObj);
|
|
Finish(false, FormatString(LoadResStr("IDS_OBJ_NOCONACTIV"),pObj->GetName()).getData());
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
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 ? 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
|
|
C4Object *pObj; C4ObjectLink *cLnk;
|
|
if (!Target)
|
|
for (cLnk=Target2->Contents.First; cLnk && (pObj=cLnk->Obj); cLnk=cLnk->Next)
|
|
if (pObj->Status && (pObj->Def->id==static_cast<C4ID>(Data)))
|
|
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; }
|
|
|
|
// No minimum con knowledge vehicles/items
|
|
if (CheckMinimumCon(Target)) return;
|
|
|
|
// Activate object to exit
|
|
Target->Controller = cObj->Controller;
|
|
Target->SetCommand(C4CMD_Exit);
|
|
Target = 0;
|
|
}
|
|
|
|
Finish(true); return;
|
|
}
|
|
|
|
// Leave own container
|
|
if (cObj->Contained)
|
|
{ cObj->AddCommand(C4CMD_Exit,NULL,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)))
|
|
{ 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 = NULL; 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=NULL;
|
|
if (cObj->GetProcedure()==DFA_PUSH)
|
|
pGrabbing = cObj->Action.Target;
|
|
if (pGrabbing && (pGrabbing!=Target))
|
|
{ cObj->AddCommand(C4CMD_UnGrab,NULL,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,NULL,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;
|
|
FIXED pthrow=ValByPhysical(400, cObj->GetPhysical()->Throw);
|
|
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,NULL,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=NULL;
|
|
if (Target==pObj) Target=NULL;
|
|
if (Target2==pObj) Target2=NULL;
|
|
}
|
|
|
|
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,NULL,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_Chop: Chop(); break;
|
|
case C4CMD_Build: Build(); 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_Construct: Construct(); break;
|
|
case C4CMD_Transfer: Transfer(); break;
|
|
case C4CMD_Attack: Attack(); break;
|
|
case C4CMD_Context: Context(); break;
|
|
case C4CMD_Buy: Buy(); break;
|
|
case C4CMD_Sell: Sell(); break;
|
|
case C4CMD_Acquire: Acquire(); break;
|
|
case C4CMD_Energy: Energy(); 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=NULL; }
|
|
// Adjust coordinates
|
|
int32_t iTx = Tx._getInt();
|
|
if (~Data & 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_Wait:
|
|
// Update interval by Data
|
|
if (Data) UpdateInterval=Data;
|
|
// 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=NULL;
|
|
Evaluated=FALSE;
|
|
PathChecked=FALSE;
|
|
Tx=C4VNull;
|
|
Ty=0;
|
|
Target=Target2=NULL;
|
|
UpdateInterval=0;
|
|
if (Text) Text->DecRef(); Text=NULL;
|
|
BaseMode=C4CMD_Mode_SilentSub;
|
|
}
|
|
|
|
void C4Command::Construct()
|
|
{
|
|
// Only those who can
|
|
if (cObj->GetPhysical() && !cObj->GetPhysical()->CanConstruct)
|
|
{
|
|
Finish(FALSE, FormatString(LoadResStr("IDS_TEXT_CANTBUILD"), cObj->GetName()).getData());
|
|
return;
|
|
}
|
|
// No target type to construct: open menu & done
|
|
if (!Data)
|
|
{
|
|
cObj->ActivateMenu(C4MN_Construction);
|
|
Finish(TRUE); return;
|
|
}
|
|
|
|
// Determine move-to range
|
|
int32_t iMoveToRange = MoveToRange;
|
|
if (cObj->Def->MoveToRange > 0) iMoveToRange = cObj->Def->MoveToRange;
|
|
|
|
// this is a secondary construct command (i.e., help with construction)?
|
|
if (Target)
|
|
{
|
|
// check if target is building something
|
|
C4Command *pBuildCmd = Target->FindCommand(C4CMD_Build);
|
|
if (pBuildCmd)
|
|
{
|
|
// then help
|
|
Finish(TRUE);
|
|
cObj->AddCommand(C4CMD_Build,pBuildCmd->Target);
|
|
}
|
|
// construct command still present? (might find another stacked command, which doesn't really matter for now...)
|
|
if (!Target->FindCommand(C4CMD_Construct))
|
|
// command aborted (or done?): failed to help; don't issue another construct command, because it is likely to fail anyway
|
|
// (and maybe, it had been finished while this Clonk was still moving to the site)
|
|
{ Finish(FALSE); return; }
|
|
// site not yet placed: move to target, if necessary and known
|
|
if (Tx._getInt() || Ty)
|
|
if (!Inside<int32_t>(cObj->GetX() - Tx._getInt(), -iMoveToRange, +iMoveToRange) || !Inside<int32_t>(cObj->GetY()-Ty,-20,+20))
|
|
{ cObj->AddCommand(C4CMD_MoveTo,NULL,Tx,Ty,50); return; }
|
|
// at target construction site and site not yet placed: wait
|
|
cObj->AddCommand(C4CMD_Wait,NULL,0,0,10);
|
|
return;
|
|
}
|
|
|
|
// No valid target type: fail
|
|
C4Def *pDef; if (!(pDef=C4Id2Def(Data))) { Finish(); return; }
|
|
|
|
// player has knowledge of this construction?
|
|
C4Player *pPlayer = ::Players.Get(cObj->Owner);
|
|
if(pPlayer) if(!pPlayer->Knowledge.GetIDCount(Data, 1)) { Finish(); return; }
|
|
|
|
// Building, chopping, digging: stop
|
|
if ((cObj->GetProcedure()==DFA_CHOP) || (cObj->GetProcedure()==DFA_BUILD) || (cObj->GetProcedure()==DFA_DIG))
|
|
ObjectComStop(cObj);
|
|
|
|
// Pushing: let go
|
|
if (cObj->GetProcedure()==DFA_PUSH)
|
|
if (cObj->Action.Target)
|
|
{ cObj->AddCommand(C4CMD_UnGrab,NULL,0,0,50); return; }
|
|
|
|
// No construction site specified: find one
|
|
if ((Tx._getInt()==0) && (Ty==0))
|
|
{
|
|
Tx.SetInt(cObj->GetX()); Ty=cObj->GetY();
|
|
int32_t iTx = Tx._getInt();
|
|
if (!FindConSiteSpot(iTx,Ty,pDef->Shape.Wdt,pDef->Shape.Hgt,pDef->Category,20))
|
|
// No site found: fail
|
|
{ Finish(); return; }
|
|
Tx.SetInt(iTx);
|
|
}
|
|
|
|
// command has been validated: check for script overload now
|
|
int32_t scriptresult = cObj->Call(PSF_ControlCommandConstruction, &C4AulParSet(C4VObj(Target), Tx, C4VInt(Ty), C4VObj(Target2), C4VID(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; }
|
|
|
|
// Has no construction kit: acquire one
|
|
C4Object *pKit;
|
|
if (!(pKit=cObj->Contents.Find(C4ID_Conkit)))
|
|
{ cObj->AddCommand(C4CMD_Acquire,0,0,0,50,0,TRUE,C4ID_Conkit,FALSE,5,0,C4CMD_Mode_Sub); return; }
|
|
|
|
// Move to construction site
|
|
if (!Inside<int32_t>(cObj->GetX() - Tx._getInt(), -iMoveToRange, +iMoveToRange)
|
|
|| !Inside<int32_t>(cObj->GetY() - Ty, -20, +20))
|
|
{ cObj->AddCommand(C4CMD_MoveTo,NULL,Tx,Ty,50); return; }
|
|
|
|
// Check construction site
|
|
if (!ConstructionCheck(Data,Tx._getInt(),Ty,cObj))
|
|
// Site no good: fail
|
|
{ Finish(); return; }
|
|
|
|
// Create construction
|
|
C4Object *pConstruction = Game.CreateObjectConstruction(Data,NULL,cObj->Owner,Tx._getInt(),Ty,1,TRUE);
|
|
|
|
// Remove conkit
|
|
pKit->AssignRemoval();
|
|
|
|
// Finish, start building
|
|
Finish(TRUE);
|
|
cObj->AddCommand(C4CMD_Build,pConstruction);
|
|
|
|
}
|
|
|
|
BOOL C4Command::FlightControl() // Called by DFA_WALK, DFA_FLIGHT
|
|
{
|
|
// Objects with CanFly physical only
|
|
if (!cObj->GetPhysical()->CanFly) return FALSE;
|
|
// Crew members or pathfinder objects only
|
|
if (!((cObj->OCF & OCF_CrewMember) || cObj->Def->Pathfinder)) return FALSE;
|
|
|
|
// Not while in a disabled action
|
|
if (cObj->Action.Act > ActIdle)
|
|
{
|
|
C4ActionDef *actdef = &(cObj->Def->ActMap[cObj->Action.Act]);
|
|
if (actdef->Disabled) 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)
|
|
{
|
|
//sprintf(OSTR,"Flight take off at %d (%d)",iAngle,Distance(cx,cy,Tx,Ty)); Log(OSTR); GameMsgObject(OSTR,cObj);
|
|
//cObj->AddCommand(C4CMD_Jump,NULL,Tx,Ty); return TRUE;
|
|
|
|
// Take off
|
|
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)
|
|
{
|
|
//sprintf(OSTR,"Diagonal %d (%d)",iAngle,Distance(cx,cy,Tx,Ty)); GameMsgObject(OSTR,cObj);
|
|
cObj->AddCommand(C4CMD_Jump,NULL,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))
|
|
{
|
|
//sprintf(OSTR,"High side move %d (%d,%d)",iAngle,iSideX-cx,iSideY-cy); GameMsgObject(OSTR,cObj);
|
|
cObj->AddCommand(C4CMD_Jump,NULL,Tx,Ty);
|
|
cObj->AddCommand(C4CMD_MoveTo,NULL,iSideX,iSideY,50);
|
|
return TRUE;
|
|
}
|
|
/*else
|
|
{ sprintf(OSTR,"Side move %d/%d path not free",iSideX,iSideY); GameMsgObject(OSTR,cObj); }*/
|
|
}
|
|
/*else
|
|
{ sprintf(OSTR,"Side move %d out of range",iSideX-cx); GameMsgObject(OSTR,cObj); }*/
|
|
}
|
|
/*else
|
|
{ sprintf(OSTR,"No high range %d",cy-Ty); GameMsgObject(OSTR,cObj); }*/
|
|
|
|
// Low side contact jump
|
|
int32_t iLowSideRange=5;
|
|
if (cObj->t_contact & CNAT_Right)
|
|
if (Inside(iAngle-JumpLowAngle,-iLowSideRange*JumpAngleRange,+iLowSideRange*JumpAngleRange))
|
|
{
|
|
//sprintf(OSTR,"Low contact right %d",iAngle); GameMsgObject(OSTR,cObj);
|
|
cObj->AddCommand(C4CMD_Jump,NULL,Tx,Ty); return TRUE;
|
|
}
|
|
if (cObj->t_contact & CNAT_Left)
|
|
if (Inside(iAngle+JumpLowAngle,-iLowSideRange*JumpAngleRange,+iLowSideRange*JumpAngleRange))
|
|
{
|
|
//sprintf(OSTR,"Low contact left %d",iAngle); GameMsgObject(OSTR,cObj);
|
|
cObj->AddCommand(C4CMD_Jump,NULL,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,NULL,iEntryX,iEntryY,25);
|
|
return;
|
|
}
|
|
|
|
// Call target transfer script
|
|
if (!::Game.iTick5)
|
|
{
|
|
|
|
C4AulScriptFunc *f;
|
|
BOOL fHandled = (f = Target->Def->Script.SFn_ControlTransfer) != NULL;
|
|
if (fHandled) fHandled = f->Exec(Target,&C4AulParSet(C4VObj(cObj), Tx, C4VInt(Ty))).getBool();
|
|
|
|
if (!fHandled)
|
|
// 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)
|
|
{
|
|
|
|
C4Object *pProjectile=NULL;
|
|
// Throw projectile at target
|
|
for (C4ObjectLink *pLnk=cObj->Contents.First; pLnk && (pProjectile=pLnk->Obj); pLnk=pLnk->Next)
|
|
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,NULL,0,0,10);
|
|
// Enter
|
|
else cObj->AddCommand(C4CMD_Enter,Target->Contained,0,0,10);
|
|
return;
|
|
}
|
|
|
|
// Move to target
|
|
cObj->AddCommand(C4CMD_MoveTo,NULL,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()
|
|
{
|
|
// Base buying disabled? Fail.
|
|
if (~Game.C4S.Game.Realism.BaseFunctionality & BASEFUNC_Buy) { Finish(); return; }
|
|
// No target (base) object specified: find closest base
|
|
int32_t cnt; C4Object *pBase;
|
|
if (!Target)
|
|
for (cnt=0; pBase=Game.FindFriendlyBase(cObj->Owner,cnt); cnt++)
|
|
if (!Target || Distance(cObj->GetX(),cObj->GetY(),pBase->GetX(),pBase->GetY())<Distance(cObj->GetX(),cObj->GetY(),Target->GetX(),Target->GetY()))
|
|
Target=pBase;
|
|
// No target (base) object: fail
|
|
if (!Target) { Finish(); return; }
|
|
// No type to buy specified: open buy menu for base
|
|
if (!Data)
|
|
{
|
|
cObj->ActivateMenu(C4MN_Buy,0,0,0,Target);
|
|
Finish(TRUE); return;
|
|
}
|
|
// Target object is no base or hostile: fail
|
|
if (!ValidPlr(Target->Base) || Hostile(Target->Base,cObj->Owner))
|
|
{ Finish(); return; }
|
|
// Target material undefined: fail
|
|
C4Def *pDef = C4Id2Def(Data);
|
|
if (!pDef) { Finish(); return; }
|
|
// Material not available for purchase at base: fail
|
|
if (!::Players.Get(Target->Base)->HomeBaseMaterial.GetIDCount(Data))
|
|
{
|
|
Finish(false, FormatString(LoadResStr("IDS_PLR_NOTAVAIL"),pDef->GetName()).getData());
|
|
return;
|
|
}
|
|
// Base owner has not enough funds: fail
|
|
if (::Players.Get(Target->Base)->Wealth < pDef->GetValue(Target, cObj->Owner))
|
|
{ Finish(false, LoadResStr("IDS_PLR_NOWEALTH")); return; }
|
|
// Not within target object: enter
|
|
if (cObj->Contained!=Target)
|
|
{ cObj->AddCommand(C4CMD_Enter,Target,0,0,50); return; }
|
|
// Buy object(s)
|
|
for (Tx.SetInt(Max<int32_t>(Tx._getInt(),1)); Tx._getInt(); Tx--)
|
|
if (!Buy2Base(cObj->Owner,Target,Data))
|
|
// Failed (with ugly message)
|
|
{ Finish(); return; }
|
|
// Done: success
|
|
Finish(TRUE);
|
|
}
|
|
|
|
void C4Command::Sell()
|
|
{
|
|
// Base sale disabled? Fail.
|
|
if (~Game.C4S.Game.Realism.BaseFunctionality & BASEFUNC_Sell) { Finish(); return; }
|
|
// No target (base) object specified: find closest base
|
|
int32_t cnt; C4Object *pBase;
|
|
if (!Target)
|
|
for (cnt=0; pBase=Game.FindBase(cObj->Owner,cnt); cnt++)
|
|
if (!Target || Distance(cObj->GetX(),cObj->GetY(),pBase->GetX(),pBase->GetY())<Distance(cObj->GetX(),cObj->GetY(),Target->GetX(),Target->GetY()))
|
|
Target=pBase;
|
|
// No target (base) object: fail
|
|
if (!Target) { Finish(); return; }
|
|
// No type to sell specified: open sell menu for base
|
|
if (!Data)
|
|
{
|
|
cObj->ActivateMenu(C4MN_Sell,0,0,0,Target);
|
|
Finish(TRUE); return;
|
|
}
|
|
// Target object is no base or hostile: fail
|
|
if (!ValidPlr(Target->Base) || Hostile(Target->Base,cObj->Owner))
|
|
{ Finish(); return; }
|
|
// Not within target object: enter
|
|
if (cObj->Contained!=Target)
|
|
{ cObj->AddCommand(C4CMD_Enter,Target,0,0,50); return; }
|
|
// Sell object(s)
|
|
for (Tx.SetInt(Max<int32_t>(Tx._getInt(),1)); Tx._getInt(); Tx--)
|
|
if (!SellFromBase(cObj->Owner,Target,Data,Target2))
|
|
// Failed
|
|
{ Finish(); return; }
|
|
else
|
|
// preferred sell object can be sold once only :)
|
|
Target2=NULL;
|
|
// Done
|
|
Finish(TRUE);
|
|
}
|
|
|
|
void C4Command::Acquire()
|
|
{
|
|
|
|
// No type to acquire specified: fail
|
|
if (!Data) { Finish(); return; }
|
|
|
|
// Target material in inventory: done
|
|
if (cObj->Contents.Find(Data))
|
|
{ Finish(TRUE); return; }
|
|
|
|
// script overload
|
|
int32_t scriptresult = cObj->Call(PSF_ControlCommandAcquire, &C4AulParSet(C4VObj(Target), Tx, C4VInt(Ty), C4VObj(Target2), C4VID(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=NULL;
|
|
// Next closest
|
|
while (pMaterial = Game.FindObject(Data,cObj->GetX(),cObj->GetY(),-1,-1,OCF_Available,NULL,NULL,NULL,NULL,ANY_OWNER,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))
|
|
// Object is not connected to a pipe (for line construction kits)
|
|
if (!Game.FindObject(C4ID_SourcePipe,0,0,0,0,OCF_All,"Connect",pMaterial))
|
|
if (!Game.FindObject(C4ID_DrainPipe,0,0,0,0,OCF_All,"Connect",pMaterial))
|
|
// 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,NULL,0,0,100,NULL,TRUE,Data,false,0,0,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_Build:
|
|
// Needed components
|
|
if (!Target) break;
|
|
// BuildNeedsMaterial call to builder script...
|
|
if (!!cObj->Call(PSF_BuildNeedsMaterial, &C4AulParSet(
|
|
C4VID(Target->Component.GetID(0)), C4VInt(Target->Component.GetCount(0))))) // WTF? This is passing current components. Not needed ones!
|
|
break; // no message
|
|
if (szFailMessage) break;
|
|
str = Target->GetNeededMatStr(cObj);
|
|
break;
|
|
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:
|
|
case C4CMD_Construct:
|
|
// Already has a fail message
|
|
if (szFailMessage) break;
|
|
// Fail message with name of target type
|
|
SCopy(LoadResStr(CommandNameID(Command)), szCommandName);
|
|
C4Def *pDef; pDef = ::Definitions.ID2Def(Data);
|
|
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, FWhite, TRUE);
|
|
}
|
|
// Fail sound
|
|
StartSoundEffect("CommandFailure*",false,100,l_Obj);
|
|
// Stop Clonk
|
|
l_Obj->Action.ComDir = COMD_Stop;
|
|
}
|
|
}
|
|
}
|
|
|
|
C4Object *CreateLine(C4ID idType, int32_t iOwner, C4Object *pFrom, C4Object *pTo);
|
|
|
|
void C4Command::Energy()
|
|
{
|
|
DWORD ocf=OCF_All;
|
|
// No target: fail
|
|
if (!Target) { Finish(); return; }
|
|
// Target can't be supplied: fail
|
|
if (!(Target->Def->LineConnect & C4D_Power_Input)) { Finish(); return; }
|
|
// Target supplied
|
|
if ( !(Game.Rules & C4RULE_StructuresNeedEnergy)
|
|
|| (Game.FindObject(C4ID_PowerLine,0,0,0,0,OCF_All,"Connect",Target) && !Target->NeedEnergy) )
|
|
{ Finish(TRUE); return; }
|
|
// No energy supply specified: find one
|
|
if (!Target2) Target2=Game.FindObject(0,Target->GetX(),Target->GetY(),-1,-1,OCF_PowerSupply,NULL,NULL,Target);
|
|
// No energy supply: fail
|
|
if (!Target2) { Finish(); return; }
|
|
// Energy supply too far away: fail
|
|
if (Distance(cObj->GetX(),cObj->GetY(),Target2->GetX(),Target2->GetY())>650) { Finish(); return; }
|
|
// Not a valid energy supply: fail
|
|
if (!(Target2->Def->LineConnect & C4D_Power_Output)) { Finish(); return; }
|
|
// No linekit: get one
|
|
C4Object *pKit, *pLine = NULL, *pKitWithLine;
|
|
if (!(pKit=cObj->Contents.Find(C4ID_Linekit)))
|
|
{ cObj->AddCommand(C4CMD_Acquire,NULL,0,0,50,NULL,TRUE,C4ID_Linekit); return; }
|
|
// Find line constructing kit
|
|
for (int32_t cnt=0; pKitWithLine=cObj->Contents.GetObject(cnt); cnt++)
|
|
if ((pKitWithLine->id==C4ID_Linekit) && (pLine=Game.FindObject(C4ID_PowerLine,0,0,0,0,OCF_All,"Connect",pKitWithLine)))
|
|
break;
|
|
// No line constructed yet
|
|
if (!pLine)
|
|
{
|
|
// Move to supply
|
|
if (!Target2->At(cObj->GetX(),cObj->GetY(),ocf))
|
|
{ cObj->AddCommand(C4CMD_MoveTo,Target2,0,0,50); return; }
|
|
// At power supply: connect
|
|
pLine = CreateLine(C4ID_PowerLine,cObj->Owner,Target2,pKit);
|
|
if (!pLine) { Finish(); return; }
|
|
StartSoundEffect("Connect",false,100,cObj);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// A line is already present: Make sure not to override the target
|
|
if (pLine->Action.Target == pKitWithLine)
|
|
Target2 = pLine->Action.Target2;
|
|
else
|
|
Target2 = pLine->Action.Target;
|
|
}
|
|
// Move to target
|
|
if (!Target->At(cObj->GetX(),cObj->GetY(),ocf))
|
|
{ cObj->AddCommand(C4CMD_MoveTo,Target,0,0,50); return; }
|
|
// Connect
|
|
pLine->SetActionByName("Connect",Target2,Target);
|
|
pKitWithLine->AssignRemoval();
|
|
StartSoundEffect("Connect",0,100,cObj);
|
|
// Done
|
|
cObj->Action.ComDir=COMD_Stop;
|
|
// Success
|
|
Finish(TRUE);
|
|
}
|
|
|
|
void C4Command::Retry()
|
|
{
|
|
|
|
}
|
|
|
|
void C4Command::Home()
|
|
{
|
|
// At home base: done
|
|
if (cObj->Contained && (cObj->Contained->Base==cObj->Owner))
|
|
{ Finish(TRUE); return; }
|
|
// No target (base) object specified: find closest base
|
|
int32_t cnt; C4Object *pBase;
|
|
if (!Target)
|
|
for (cnt=0; pBase=Game.FindBase(cObj->Owner,cnt); cnt++)
|
|
if (!Target || Distance(cObj->GetX(),cObj->GetY(),pBase->GetX(),pBase->GetY())<Distance(cObj->GetX(),cObj->GetY(),Target->GetX(),Target->GetY()))
|
|
Target=pBase;
|
|
// No base: fail
|
|
if (!Target) { Finish(); return; }
|
|
// Enter base
|
|
cObj->AddCommand(C4CMD_Enter,Target);
|
|
}
|
|
|
|
void C4Command::Set(int32_t iCommand, C4Object *pObj, C4Object *pTarget, C4Value nTx, int32_t iTy,
|
|
C4Object *pTarget2, int32_t 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;
|
|
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(C4VObj(cObj), Tx, C4VInt(Ty), C4VObj(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)
|
|
{
|
|
// Version
|
|
int32_t iVersion = 0;
|
|
if(pComp->Seperator(StdCompiler::SEP_DOLLAR))
|
|
{
|
|
iVersion = 1;
|
|
pComp->Value(mkIntPackAdapt(iVersion));
|
|
pComp->Seperator(StdCompiler::SEP_SEP);
|
|
}
|
|
else
|
|
pComp->NoSeperator();
|
|
// Command name
|
|
pComp->Value(mkEnumAdaptT<uint8_t>(Command, EnumAdaptCommandEntries));
|
|
pComp->Seperator(StdCompiler::SEP_SEP);
|
|
// Target X/Y
|
|
pComp->Value(Tx); pComp->Seperator(StdCompiler::SEP_SEP);
|
|
pComp->Value(mkIntPackAdapt(Ty)); pComp->Seperator(StdCompiler::SEP_SEP);
|
|
// Target
|
|
pComp->Value(mkIntPackAdapt(reinterpret_cast<int32_t &>(Target))); pComp->Seperator(StdCompiler::SEP_SEP);
|
|
pComp->Value(mkIntPackAdapt(reinterpret_cast<int32_t &>(Target2))); pComp->Seperator(StdCompiler::SEP_SEP);
|
|
// Data
|
|
pComp->Value(mkIntPackAdapt(Data)); pComp->Seperator(StdCompiler::SEP_SEP);
|
|
// Update interval
|
|
pComp->Value(mkIntPackAdapt(UpdateInterval)); pComp->Seperator(StdCompiler::SEP_SEP);
|
|
// Flags
|
|
pComp->Value(mkIntPackAdapt(Evaluated)); pComp->Seperator(StdCompiler::SEP_SEP);
|
|
pComp->Value(mkIntPackAdapt(PathChecked)); pComp->Seperator(StdCompiler::SEP_SEP);
|
|
pComp->Value(mkIntPackAdapt(Finished)); pComp->Seperator(StdCompiler::SEP_SEP);
|
|
// Retries
|
|
pComp->Value(mkIntPackAdapt(Failures)); pComp->Seperator(StdCompiler::SEP_SEP);
|
|
pComp->Value(mkIntPackAdapt(Retries)); pComp->Seperator(StdCompiler::SEP_SEP);
|
|
pComp->Value(mkIntPackAdapt(Permit)); pComp->Seperator(StdCompiler::SEP_SEP);
|
|
// Base mode
|
|
if(iVersion > 0)
|
|
{
|
|
pComp->Value(mkIntPackAdapt(BaseMode)); pComp->Seperator(StdCompiler::SEP_SEP);
|
|
}
|
|
// Text
|
|
StdStrBuf TextBuf;
|
|
if(pComp->isDecompiler())
|
|
{
|
|
if(Text)
|
|
TextBuf.Ref(Text->Data);
|
|
else
|
|
TextBuf.Ref("0");
|
|
}
|
|
pComp->Value(mkParAdapt(TextBuf, StdCompiler::RCT_All));
|
|
if(pComp->isCompiler())
|
|
{
|
|
if(Text)
|
|
Text->DecRef();
|
|
if(TextBuf == "0")
|
|
{ Text = NULL; }
|
|
else
|
|
{ Text = ::ScriptEngine.Strings.RegString(TextBuf); Text->IncRef(); }
|
|
}
|
|
}
|
|
|
|
void C4Command::DenumeratePointers()
|
|
{
|
|
Target = ::Objects.ObjectPointer((long)Target);
|
|
Target2 = ::Objects.ObjectPointer((long)Target2);
|
|
Tx.DenumeratePointer();
|
|
}
|
|
|
|
void C4Command::EnumeratePointers()
|
|
{
|
|
Target = (C4Object*) ::Objects.ObjectNumber(Target);
|
|
Target2 = (C4Object*) ::Objects.ObjectNumber(Target2);
|
|
}
|
|
|
|
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(C4VObj(cObj), Tx, C4VInt(Ty), C4VObj(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_Context:
|
|
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;
|
|
|
|
// advanced activities
|
|
case C4CMD_Chop:
|
|
case C4CMD_Build:
|
|
case C4CMD_Construct:
|
|
case C4CMD_Energy:
|
|
return 5;
|
|
|
|
// victory!
|
|
case C4CMD_Attack:
|
|
return 15;
|
|
}
|
|
// unknown command
|
|
return 0;
|
|
}
|