774 lines
22 KiB
C++
774 lines
22 KiB
C++
/*
|
|
This file is part of Warzone 2100.
|
|
Copyright (C) 1999-2004 Eidos Interactive
|
|
Copyright (C) 2005-2013 Warzone 2100 Project
|
|
|
|
Warzone 2100 is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Warzone 2100 is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Warzone 2100; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*/
|
|
/*
|
|
* Multibot.c
|
|
*
|
|
* Alex Lee , 97/98 Pumpkin Studios, Bath
|
|
* Multiplay stuff relevant to droids only.
|
|
*/
|
|
#include "lib/framework/frame.h"
|
|
|
|
#include "droid.h" // for droid sending and ordering.
|
|
#include "droiddef.h"
|
|
#include "keymap.h"
|
|
#include "stats.h"
|
|
#include "move.h" // for ordering droids
|
|
#include "objmem.h"
|
|
#include "power.h" // for powercalculated
|
|
#include "order.h"
|
|
#include "map.h"
|
|
#include "group.h"
|
|
#include "lib/netplay/netplay.h" // the netplay library.
|
|
#include "multiplay.h" // warzone net stuff.
|
|
#include "multijoin.h"
|
|
#include "cmddroid.h" // command droids
|
|
#include "action.h"
|
|
#include "console.h"
|
|
#include "mapgrid.h"
|
|
#include "multirecv.h"
|
|
#include "transporter.h"
|
|
|
|
#include <vector>
|
|
#include <algorithm>
|
|
|
|
|
|
enum SubType
|
|
{
|
|
ObjOrder, LocOrder, SecondaryOrder
|
|
};
|
|
|
|
struct QueuedDroidInfo
|
|
{
|
|
/// Sorts by order, then finally by droid id, to group multiple droids with the same order.
|
|
bool operator <(QueuedDroidInfo const &z) const
|
|
{
|
|
int orComp = orderCompare(z);
|
|
if (orComp != 0) return orComp < 0;
|
|
return droidId < z.droidId;
|
|
}
|
|
/// Returns 0 if order is the same, non-zero otherwise.
|
|
int orderCompare(QueuedDroidInfo const &z) const
|
|
{
|
|
if (player != z.player) return player < z.player ? -1 : 1;
|
|
if (subType != z.subType) return subType < z.subType ? -1 : 1;
|
|
switch (subType)
|
|
{
|
|
case ObjOrder:
|
|
case LocOrder:
|
|
if (order != z.order) return order < z.order ? -1 : 1;
|
|
if (subType == ObjOrder)
|
|
{
|
|
if (destId != z.destId) return destId < z.destId ? -1 : 1;
|
|
if (destType != z.destType) return destType < z.destType ? -1 : 1;
|
|
}
|
|
else
|
|
{
|
|
if (pos.x != z.pos.x) return pos.x < z.pos.x ? -1 : 1;
|
|
if (pos.y != z.pos.y) return pos.y < z.pos.y ? -1 : 1;
|
|
}
|
|
if (order == DORDER_BUILD || order == DORDER_LINEBUILD)
|
|
{
|
|
if (structRef != z.structRef) return structRef < z.structRef ? -1 : 1;
|
|
if (direction != z.direction) return direction < z.direction ? -1 : 1;
|
|
}
|
|
if (order == DORDER_LINEBUILD)
|
|
{
|
|
if (pos2.x != z.pos2.x) return pos2.x < z.pos2.x ? -1 : 1;
|
|
if (pos2.y != z.pos2.y) return pos2.y < z.pos2.y ? -1 : 1;
|
|
}
|
|
if (order == DORDER_BUILDMODULE)
|
|
{
|
|
if (index != z.index) return index < z.index ? -1 : 1;
|
|
}
|
|
if (add != z.add) return add < z.add ? -1 : 1;
|
|
break;
|
|
case SecondaryOrder:
|
|
if (secOrder != z.secOrder) return secOrder < z.secOrder ? -1 : 1;
|
|
if (secState != z.secState) return secState < z.secState ? -1 : 1;
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
uint8_t player;
|
|
uint32_t droidId;
|
|
SubType subType;
|
|
// subType == ObjOrder || subType == LocOrder
|
|
DROID_ORDER order;
|
|
uint32_t destId; // if (subType == ObjOrder)
|
|
OBJECT_TYPE destType; // if (subType == ObjOrder)
|
|
Vector2i pos; // if (subType == LocOrder)
|
|
uint32_t y; // if (subType == LocOrder)
|
|
uint32_t structRef; // if (order == DORDER_BUILD || order == DORDER_LINEBUILD)
|
|
uint16_t direction; // if (order == DORDER_BUILD || order == DORDER_LINEBUILD)
|
|
uint32_t index; // if (order == DORDER_BUILDMODULE)
|
|
Vector2i pos2; // if (order == DORDER_LINEBUILD)
|
|
bool add;
|
|
// subType == SecondaryOrder
|
|
SECONDARY_ORDER secOrder;
|
|
SECONDARY_STATE secState;
|
|
};
|
|
|
|
static std::vector<QueuedDroidInfo> queuedOrders;
|
|
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// Local Prototypes
|
|
|
|
static BASE_OBJECT *processDroidTarget(OBJECT_TYPE desttype, uint32_t destid);
|
|
static BASE_OBJECT TargetMissing_(OBJ_NUM_TYPES, 0, 0); // This memory is never referenced.
|
|
static BASE_OBJECT *const TargetMissing = &TargetMissing_; // Error return value for processDroidTarget.
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// Command Droids.
|
|
|
|
// sod em.
|
|
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// Secondary Orders.
|
|
|
|
// Send
|
|
bool sendDroidSecondary(const DROID* psDroid, SECONDARY_ORDER sec, SECONDARY_STATE state)
|
|
{
|
|
if (!bMultiMessages)
|
|
return true;
|
|
|
|
QueuedDroidInfo info;
|
|
memset(&info, 0x00, sizeof(info)); // Suppress uninitialised warnings. (The uninitialised values in the queue would be ignored when reading the queue.)
|
|
|
|
info.player = psDroid->player;
|
|
info.droidId = psDroid->id;
|
|
info.subType = SecondaryOrder;
|
|
info.secOrder = sec;
|
|
info.secState = state;
|
|
|
|
// Send later, grouped by order, so multiple droids with the same order can be encoded to much less data.
|
|
queuedOrders.push_back(info);
|
|
|
|
return true;
|
|
}
|
|
|
|
/** Broadcast that droid is being unloaded from a transporter
|
|
*
|
|
* \sa recvDroidDisEmbark()
|
|
*/
|
|
bool sendDroidDisembark(DROID const *psTransporter, DROID const *psDroid)
|
|
{
|
|
if (!bMultiMessages)
|
|
return true;
|
|
|
|
NETbeginEncode(NETgameQueue(selectedPlayer), GAME_DROIDDISEMBARK);
|
|
{
|
|
uint32_t player = psTransporter->player;
|
|
uint32_t droidId = psDroid->id;
|
|
uint32_t transportId = psTransporter->id;
|
|
|
|
NETuint32_t(&player);
|
|
NETuint32_t(&droidId);
|
|
NETuint32_t(&transportId);
|
|
}
|
|
return NETend();
|
|
}
|
|
|
|
/** Receive info about a droid that is being unloaded from a transporter
|
|
*/
|
|
bool recvDroidDisEmbark(NETQUEUE queue)
|
|
{
|
|
DROID *psFoundDroid = NULL, *psTransporterDroid = NULL;
|
|
DROID *psCheckDroid = NULL;
|
|
|
|
NETbeginDecode(queue, GAME_DROIDDISEMBARK);
|
|
{
|
|
uint32_t player;
|
|
uint32_t droidID;
|
|
uint32_t transporterID;
|
|
|
|
NETuint32_t(&player);
|
|
NETuint32_t(&droidID);
|
|
NETuint32_t(&transporterID);
|
|
|
|
NETend();
|
|
|
|
// find the transporter first
|
|
psTransporterDroid = IdToDroid(transporterID, player);
|
|
if (!psTransporterDroid)
|
|
{
|
|
// Possible it already died? (sync error?)
|
|
debug(LOG_WARNING, "player's %d transport droid %d wasn't found?", player, transporterID);
|
|
return false;
|
|
}
|
|
if (!canGiveOrdersFor(queue.index, psTransporterDroid->player))
|
|
{
|
|
return false;
|
|
}
|
|
// we need to find the droid *in* the transporter
|
|
psCheckDroid = psTransporterDroid ->psGroup->psList;
|
|
while (psCheckDroid)
|
|
{
|
|
// is this the one we want?
|
|
if( psCheckDroid->id == droidID)
|
|
{
|
|
psFoundDroid = psCheckDroid;
|
|
break;
|
|
}
|
|
// not found, so check next one in *group*
|
|
psCheckDroid = psCheckDroid->psGrpNext;
|
|
}
|
|
// don't continue if we couldn't find it.
|
|
if (!psFoundDroid)
|
|
{
|
|
// I don't think this could ever be possible...but
|
|
debug(LOG_ERROR, "Couldn't find droid %d to disembark from player %d's transporter?", droidID, player);
|
|
return false;
|
|
}
|
|
|
|
transporterRemoveDroid(psTransporterDroid, psFoundDroid, ModeImmediate);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// Droids
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// Send a new Droid to the other players
|
|
bool SendDroid(DROID_TEMPLATE* pTemplate, uint32_t x, uint32_t y, uint8_t player, uint32_t id, const INITIAL_DROID_ORDERS *initialOrdersP)
|
|
{
|
|
if (!bMultiMessages)
|
|
return true;
|
|
|
|
ASSERT(x != 0 && y != 0, "SendDroid: Invalid droid coordinates");
|
|
ASSERT( player < MAX_PLAYERS, "invalid player %u", player);
|
|
|
|
// Dont send other droids during campaign setup
|
|
if (ingame.localJoiningInProgress)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Only send the droid if we are responsible
|
|
if (!myResponsibility(player))
|
|
{
|
|
// Don't build if we are not responsible
|
|
return false;
|
|
}
|
|
|
|
debug(LOG_SYNC, "Droid sent with id of %u", id);
|
|
NETbeginEncode(NETgameQueue(selectedPlayer), GAME_DEBUG_ADD_DROID);
|
|
{
|
|
Position pos(x, y, 0);
|
|
bool haveInitialOrders = initialOrdersP != NULL;
|
|
int32_t droidType = pTemplate->droidType;
|
|
|
|
NETuint8_t(&player);
|
|
NETuint32_t(&id);
|
|
NETPosition(&pos);
|
|
NETqstring(pTemplate->name);
|
|
NETint32_t(&droidType);
|
|
NETuint8_t(&pTemplate->asParts[COMP_BODY]);
|
|
NETuint8_t(&pTemplate->asParts[COMP_BRAIN]);
|
|
NETuint8_t(&pTemplate->asParts[COMP_PROPULSION]);
|
|
NETuint8_t(&pTemplate->asParts[COMP_REPAIRUNIT]);
|
|
NETuint8_t(&pTemplate->asParts[COMP_ECM]);
|
|
NETuint8_t(&pTemplate->asParts[COMP_SENSOR]);
|
|
NETuint8_t(&pTemplate->asParts[COMP_CONSTRUCT]);
|
|
NETint8_t(&pTemplate->numWeaps);
|
|
for (int i = 0; i < pTemplate->numWeaps; i++)
|
|
{
|
|
NETuint8_t(&pTemplate->asWeaps[i]);
|
|
}
|
|
NETbool(&haveInitialOrders);
|
|
if (haveInitialOrders)
|
|
{
|
|
INITIAL_DROID_ORDERS initialOrders = *initialOrdersP;
|
|
NETuint32_t(&initialOrders.secondaryOrder);
|
|
NETint32_t(&initialOrders.moveToX);
|
|
NETint32_t(&initialOrders.moveToY);
|
|
NETuint32_t(&initialOrders.factoryId); // For making scripts happy.
|
|
}
|
|
}
|
|
debug(LOG_LIFE, "===> sending Droid from %u id of %u ",player,id);
|
|
return NETend();
|
|
}
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// receive droid creation information from other players
|
|
bool recvDroid(NETQUEUE queue)
|
|
{
|
|
DROID_TEMPLATE t, *pT = &t;
|
|
DROID* psDroid;
|
|
uint8_t player;
|
|
uint32_t id;
|
|
Position pos;
|
|
bool haveInitialOrders;
|
|
INITIAL_DROID_ORDERS initialOrders;
|
|
|
|
NETbeginDecode(queue, GAME_DEBUG_ADD_DROID);
|
|
{
|
|
int32_t droidType;
|
|
|
|
NETuint8_t(&player);
|
|
NETuint32_t(&id);
|
|
NETPosition(&pos);
|
|
NETqstring(pT->name);
|
|
pT->id = pT->name;
|
|
NETint32_t(&droidType);
|
|
NETuint8_t(&pT->asParts[COMP_BODY]);
|
|
NETuint8_t(&pT->asParts[COMP_BRAIN]);
|
|
NETuint8_t(&pT->asParts[COMP_PROPULSION]);
|
|
NETuint8_t(&pT->asParts[COMP_REPAIRUNIT]);
|
|
NETuint8_t(&pT->asParts[COMP_ECM]);
|
|
NETuint8_t(&pT->asParts[COMP_SENSOR]);
|
|
NETuint8_t(&pT->asParts[COMP_CONSTRUCT]);
|
|
NETint8_t(&pT->numWeaps);
|
|
for (int i = 0; i < pT->numWeaps; i++)
|
|
{
|
|
NETuint8_t(&pT->asWeaps[i]);
|
|
}
|
|
NETbool(&haveInitialOrders);
|
|
if (haveInitialOrders)
|
|
{
|
|
NETuint32_t(&initialOrders.secondaryOrder);
|
|
NETint32_t(&initialOrders.moveToX);
|
|
NETint32_t(&initialOrders.moveToY);
|
|
NETuint32_t(&initialOrders.factoryId); // For making scripts happy.
|
|
}
|
|
pT->droidType = (DROID_TYPE)droidType;
|
|
}
|
|
NETend();
|
|
|
|
if (!getDebugMappingStatus() && bMultiPlayer)
|
|
{
|
|
debug(LOG_WARNING, "Failed to add droid for player %u.", NetPlay.players[queue.index].position);
|
|
return false;
|
|
}
|
|
|
|
ASSERT_OR_RETURN(false, player < MAX_PLAYERS, "invalid player %u", player);
|
|
|
|
debug(LOG_LIFE, "<=== getting Droid from %u id of %u ",player,id);
|
|
if ((pos.x == 0 && pos.y == 0) || pos.x > world_coord(mapWidth) || pos.y > world_coord(mapHeight))
|
|
{
|
|
debug(LOG_ERROR, "Received bad droid position (%d, %d) from %d about p%d (%s)", (int)pos.x, (int)pos.y,
|
|
queue.index, player, isHumanPlayer(player) ? "Human" : "AI");
|
|
return false;
|
|
}
|
|
|
|
// Create that droid on this machine.
|
|
psDroid = reallyBuildDroid(pT, pos, player, false);
|
|
|
|
// If we were able to build the droid set it up
|
|
if (psDroid)
|
|
{
|
|
psDroid->id = id;
|
|
addDroid(psDroid, apsDroidLists);
|
|
|
|
if (haveInitialOrders)
|
|
{
|
|
psDroid->secondaryOrder = initialOrders.secondaryOrder;
|
|
psDroid->secondaryOrderPending = psDroid->secondaryOrder;
|
|
orderDroidLoc(psDroid, DORDER_MOVE, initialOrders.moveToX, initialOrders.moveToY, ModeImmediate);
|
|
cbNewDroid(IdToStruct(initialOrders.factoryId, ANYPLAYER), psDroid);
|
|
}
|
|
|
|
syncDebugDroid(psDroid, '+');
|
|
}
|
|
else
|
|
{
|
|
debug(LOG_ERROR, "Packet from %d cannot create droid for p%d (%s)!", queue.index,
|
|
player, isHumanPlayer(player) ? "Human" : "AI");
|
|
#ifdef DEBUG
|
|
CONPRINTF(ConsoleString, (ConsoleString, "MULTIPLAYER: Couldn't build a remote droid, relying on checking to resync"));
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/// Does not read/write info->droidId!
|
|
static void NETQueuedDroidInfo(QueuedDroidInfo *info)
|
|
{
|
|
NETuint8_t(&info->player);
|
|
NETenum(&info->subType);
|
|
switch (info->subType)
|
|
{
|
|
case ObjOrder:
|
|
case LocOrder:
|
|
NETenum(&info->order);
|
|
if (info->subType == ObjOrder)
|
|
{
|
|
NETuint32_t(&info->destId);
|
|
NETenum(&info->destType);
|
|
}
|
|
else
|
|
{
|
|
NETauto(&info->pos);
|
|
}
|
|
if (info->order == DORDER_BUILD || info->order == DORDER_LINEBUILD)
|
|
{
|
|
NETuint32_t(&info->structRef);
|
|
NETuint16_t(&info->direction);
|
|
}
|
|
if (info->order == DORDER_LINEBUILD)
|
|
{
|
|
NETauto(&info->pos2);
|
|
}
|
|
if (info->order == DORDER_BUILDMODULE)
|
|
{
|
|
NETauto(&info->index);
|
|
}
|
|
NETbool(&info->add);
|
|
break;
|
|
case SecondaryOrder:
|
|
NETenum(&info->secOrder);
|
|
NETenum(&info->secState);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Actually send the droid info.
|
|
void sendQueuedDroidInfo()
|
|
{
|
|
// Sort queued orders, to group the same order to multiple droids.
|
|
std::sort(queuedOrders.begin(), queuedOrders.end());
|
|
|
|
std::vector<QueuedDroidInfo>::iterator eqBegin, eqEnd;
|
|
for (eqBegin = queuedOrders.begin(); eqBegin != queuedOrders.end(); eqBegin = eqEnd)
|
|
{
|
|
// Find end of range of orders which differ only by the droid ID.
|
|
for (eqEnd = eqBegin + 1; eqEnd != queuedOrders.end() && eqEnd->orderCompare(*eqBegin) == 0; ++eqEnd)
|
|
{}
|
|
|
|
NETbeginEncode(NETgameQueue(selectedPlayer), GAME_DROIDINFO);
|
|
NETQueuedDroidInfo(&*eqBegin);
|
|
|
|
uint32_t num = eqEnd - eqBegin;
|
|
NETuint32_t(&num);
|
|
|
|
uint32_t prevDroidId = 0;
|
|
for (unsigned n = 0; n < num; ++n)
|
|
{
|
|
uint32_t droidId = (eqBegin + n)->droidId;
|
|
|
|
// Encode deltas between droid IDs, since the deltas are smaller than the actual droid IDs, and will encode to less bytes on average.
|
|
uint32_t deltaDroidId = droidId - prevDroidId;
|
|
NETuint32_t(&deltaDroidId);
|
|
|
|
prevDroidId = droidId;
|
|
}
|
|
NETend();
|
|
}
|
|
|
|
// Sent the orders. Don't send them again.
|
|
queuedOrders.clear();
|
|
}
|
|
|
|
DROID_ORDER_DATA infoToOrderData(QueuedDroidInfo const &info, STRUCTURE_STATS const *psStats)
|
|
{
|
|
DROID_ORDER_DATA sOrder;
|
|
sOrder.type = info.order;
|
|
sOrder.pos = info.pos;
|
|
sOrder.pos2 = info.pos2;
|
|
sOrder.direction = info.direction;
|
|
sOrder.index = info.index;
|
|
sOrder.psObj = processDroidTarget(info.destType, info.destId);
|
|
sOrder.psStats = const_cast<STRUCTURE_STATS *>(psStats);
|
|
|
|
return sOrder;
|
|
}
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// Droid update information
|
|
void sendDroidInfo(DROID *psDroid, DroidOrder const &order, bool add)
|
|
{
|
|
if (!myResponsibility(psDroid->player))
|
|
{
|
|
return;
|
|
}
|
|
|
|
QueuedDroidInfo info;
|
|
memset(&info, 0x00, sizeof(info)); // Suppress uninitialised warnings. (The uninitialised values in the queue would be ignored when reading the queue.)
|
|
|
|
info.player = psDroid->player;
|
|
info.droidId = psDroid->id;
|
|
info.subType = order.psObj != NULL? ObjOrder : LocOrder;
|
|
info.order = order.type;
|
|
if (info.subType == ObjOrder)
|
|
{
|
|
info.destId = order.psObj->id;
|
|
info.destType = order.psObj->type;
|
|
}
|
|
else
|
|
{
|
|
info.pos = order.pos;
|
|
}
|
|
if (order.type == DORDER_BUILD || order.type == DORDER_LINEBUILD)
|
|
{
|
|
info.structRef = order.psStats->ref;
|
|
info.direction = order.direction;
|
|
if (!isConstructionDroid(psDroid))
|
|
{
|
|
return; // No point ordering things to build if they can't build anything.
|
|
}
|
|
}
|
|
if (order.type == DORDER_LINEBUILD)
|
|
{
|
|
info.pos2 = order.pos2;
|
|
}
|
|
if (order.type == DORDER_BUILDMODULE)
|
|
{
|
|
info.index = order.index;
|
|
}
|
|
|
|
info.add = add;
|
|
|
|
// Send later, grouped by order, so multiple droids with the same order can be encoded to much less data.
|
|
queuedOrders.push_back(info);
|
|
|
|
// Update pending orders, so the UI knows it happened.
|
|
DROID_ORDER_DATA sOrder = infoToOrderData(info, order.psStats);
|
|
if (!add)
|
|
{
|
|
psDroid->listPendingBegin = psDroid->asOrderList.size();
|
|
}
|
|
orderDroidAddPending(psDroid, &sOrder);
|
|
}
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// receive droid information form other players.
|
|
bool recvDroidInfo(NETQUEUE queue)
|
|
{
|
|
NETbeginDecode(queue, GAME_DROIDINFO);
|
|
{
|
|
QueuedDroidInfo info;
|
|
memset(&info, 0x00, sizeof(info));
|
|
NETQueuedDroidInfo(&info);
|
|
|
|
STRUCTURE_STATS *psStats = NULL;
|
|
if (info.subType == LocOrder && (info.order == DORDER_BUILD || info.order == DORDER_LINEBUILD))
|
|
{
|
|
// Find structure target
|
|
for (unsigned typeIndex = 0; typeIndex < numStructureStats; typeIndex++)
|
|
{
|
|
if (asStructureStats[typeIndex].ref == info.structRef)
|
|
{
|
|
psStats = asStructureStats + typeIndex;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (info.subType)
|
|
{
|
|
case ObjOrder: syncDebug("Order=%s,%d(%d)", getDroidOrderName(info.order), info.destId, info.destType); break;
|
|
case LocOrder: syncDebug("Order=%s,(%d,%d)", getDroidOrderName(info.order), info.pos.x, info.pos.y); break;
|
|
case SecondaryOrder: syncDebug("SecondaryOrder=%d,%08X", (int)info.secOrder, (int)info.secState); break;
|
|
}
|
|
|
|
DROID_ORDER_DATA sOrder = infoToOrderData(info, psStats);
|
|
|
|
uint32_t num = 0;
|
|
NETuint32_t(&num);
|
|
|
|
for (unsigned n = 0; n < num; ++n)
|
|
{
|
|
// Get the next droid ID which is being given this order.
|
|
uint32_t deltaDroidId = 0;
|
|
NETuint32_t(&deltaDroidId);
|
|
info.droidId += deltaDroidId;
|
|
|
|
DROID *psDroid = IdToDroid(info.droidId, info.player);
|
|
if (!psDroid)
|
|
{
|
|
debug(LOG_NEVER, "Packet from %d refers to non-existent droid %u, [%s : p%d]",
|
|
queue.index, info.droidId, isHumanPlayer(info.player) ? "Human" : "AI", info.player);
|
|
syncDebug("Droid %d missing", info.droidId);
|
|
continue; // Can't find the droid, so skip this droid.
|
|
}
|
|
if (!canGiveOrdersFor(queue.index, psDroid->player))
|
|
{
|
|
debug(LOG_WARNING, "Droid order for wrong player.");
|
|
syncDebug("Wrong player.");
|
|
continue;
|
|
}
|
|
|
|
CHECK_DROID(psDroid);
|
|
|
|
syncDebugDroid(psDroid, '<');
|
|
|
|
switch (info.subType)
|
|
{
|
|
case ObjOrder:
|
|
case LocOrder:
|
|
/*
|
|
* If the current order not is a command order and we are not a
|
|
* commander yet are in the commander group remove us from it.
|
|
*/
|
|
if (hasCommander(psDroid))
|
|
{
|
|
psDroid->psGroup->remove(psDroid);
|
|
}
|
|
|
|
if (sOrder.psObj != TargetMissing) // Only do order if the target didn't die.
|
|
{
|
|
if (!info.add)
|
|
{
|
|
orderDroidListEraseRange(psDroid, 0, psDroid->listSize + 1); // Clear all non-pending orders, plus the first pending order (which is probably the order we just received).
|
|
orderDroidBase(psDroid, &sOrder); // Execute the order immediately (even if in the middle of another order.
|
|
}
|
|
else
|
|
{
|
|
orderDroidAdd(psDroid, &sOrder); // Add the order to the (non-pending) list. Will probably overwrite the corresponding pending order, assuming all pending orders were written to the list.
|
|
}
|
|
}
|
|
break;
|
|
case SecondaryOrder:
|
|
// Set the droids secondary order
|
|
turnOffMultiMsg(true);
|
|
secondarySetState(psDroid, info.secOrder, info.secState);
|
|
turnOffMultiMsg(false);
|
|
break;
|
|
}
|
|
|
|
syncDebugDroid(psDroid, '>');
|
|
|
|
CHECK_DROID(psDroid);
|
|
}
|
|
}
|
|
NETend();
|
|
|
|
return true;
|
|
}
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// process droid order
|
|
static BASE_OBJECT *processDroidTarget(OBJECT_TYPE desttype, uint32_t destid)
|
|
{
|
|
// Target is a location
|
|
if (destid == 0 && desttype == 0)
|
|
{
|
|
return NULL;
|
|
}
|
|
// Target is an object
|
|
else
|
|
{
|
|
BASE_OBJECT *psObj = NULL;
|
|
|
|
switch (desttype)
|
|
{
|
|
case OBJ_DROID:
|
|
psObj = IdToDroid(destid, ANYPLAYER);
|
|
break;
|
|
case OBJ_STRUCTURE:
|
|
psObj = IdToStruct(destid, ANYPLAYER);
|
|
break;
|
|
case OBJ_FEATURE:
|
|
psObj = IdToFeature(destid, ANYPLAYER);
|
|
break;
|
|
|
|
// We should not get this!
|
|
case OBJ_PROJECTILE:
|
|
debug(LOG_ERROR, "ProcessDroidOrder: order specified destination as a bullet. what am i to do??");
|
|
break;
|
|
default:
|
|
debug(LOG_ERROR, "ProcessDroidOrder: unknown object type");
|
|
break;
|
|
}
|
|
|
|
// If we did not find anything, return
|
|
if (!psObj) // failed to find it;
|
|
{
|
|
syncDebug("Target missing");
|
|
return TargetMissing; // Can't return NULL, since then the order would still be attempted.
|
|
}
|
|
|
|
return psObj;
|
|
}
|
|
}
|
|
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// Inform other players that a droid has been destroyed
|
|
bool SendDestroyDroid(const DROID* psDroid)
|
|
{
|
|
if (!bMultiMessages)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
NETbeginEncode(NETgameQueue(selectedPlayer), GAME_DEBUG_REMOVE_DROID);
|
|
{
|
|
uint32_t id = psDroid->id;
|
|
|
|
// Send the droid's ID
|
|
debug(LOG_DEATH, "Requested all players to destroy droid %u", (unsigned int)id);
|
|
NETuint32_t(&id);
|
|
}
|
|
return NETend();
|
|
}
|
|
|
|
// ////////////////////////////////////////////////////////////////////////////
|
|
// Accept a droid which was destroyed on another machine
|
|
bool recvDestroyDroid(NETQUEUE queue)
|
|
{
|
|
DROID* psDroid;
|
|
|
|
NETbeginDecode(queue, GAME_DEBUG_REMOVE_DROID);
|
|
{
|
|
uint32_t id;
|
|
|
|
// Retrieve the droid
|
|
NETuint32_t(&id);
|
|
psDroid = IdToDroid(id, ANYPLAYER);
|
|
if (!psDroid)
|
|
{
|
|
debug(LOG_DEATH, "droid %d on request from player %d can't be found? Must be dead already?",
|
|
id, queue.index );
|
|
return false;
|
|
}
|
|
}
|
|
NETend();
|
|
|
|
if (!getDebugMappingStatus() && bMultiPlayer)
|
|
{
|
|
debug(LOG_WARNING, "Failed to remove droid for player %u.", NetPlay.players[queue.index].position);
|
|
return false;
|
|
}
|
|
|
|
// If the droid has not died on our machine yet, destroy it
|
|
if(!psDroid->died)
|
|
{
|
|
turnOffMultiMsg(true);
|
|
debug(LOG_DEATH, "Killing droid %d on request from player %d - huh?", psDroid->id, queue.index);
|
|
destroyDroid(psDroid, gameTime - deltaGameTime + 1); // deltaGameTime is actually 0 here, since we're between updates. However, the value of gameTime - deltaGameTime + 1 will not change when we start the next tick.
|
|
turnOffMultiMsg(false);
|
|
}
|
|
else
|
|
{
|
|
debug(LOG_DEATH, "droid %d is confirmed dead by player %d.", psDroid->id, queue.index);
|
|
}
|
|
|
|
return true;
|
|
}
|