Add new function syncRequest(req_id, x, y, obj1, obj2) and new event eventSyncRequest(from, req_id, x, y, obj1, obj2). This unassuming pair allows you to synchronize javascript functions

that would otherwise cause desync in a game, as any sync request that is generated is a tiny bit later triggered as an event simultaneously on all clients (and all scripts!). Great care
must be taken to define only sync requests that can be validated against attempts at cheating. For example, if for some reason a sync request is defined that allows a player to order a
unit to hold position, you must check that the request comes from the player that own this droid. Any unused parameter can be filled with null or zero; the obj parameters can be omitted.
Patch reviewed by Cyp.
master
per 2013-08-10 20:40:06 +02:00
parent c0abe4efe3
commit 569975e6ea
8 changed files with 124 additions and 2 deletions

View File

@ -3630,6 +3630,8 @@ const char *messageTypeToString(unsigned messageType_)
case GAME_GAME_TIME: return "GAME_GAME_TIME";
case GAME_PLAYER_LEFT: return "GAME_PLAYER_LEFT";
case GAME_DROIDDISEMBARK: return "GAME_DROIDDISEMBARK";
case GAME_SYNC_REQUEST: return "GAME_SYNC_REQUEST";
// The following messages are used for debug mode.
case GAME_DEBUG_MODE: return "GAME_DEBUG_MODE";
case GAME_DEBUG_ADD_DROID: return "GAME_DEBUG_ADD_DROID";

View File

@ -104,6 +104,7 @@ enum MESSAGE_TYPES
GAME_GAME_TIME, ///< Game time. Used for synchronising, so that all messages are executed at the same gameTime on all clients.
GAME_PLAYER_LEFT, ///< Player has left or dropped.
GAME_DROIDDISEMBARK, ///< droid disembarked from a Transporter
GAME_SYNC_REQUEST, ///< Game event generated from scripts that is meant to be synced
// The following messages are used for debug mode.
GAME_DEBUG_MODE, ///< Request enable/disable debug mode.
GAME_DEBUG_ADD_DROID, ///< Add droid.

View File

@ -1270,8 +1270,6 @@ void actionUpdateDroid(DROID *psDroid)
if ( psPropStats->propulsionType == PROPULSION_TYPE_LIFT )
{
psDroid->action = DACTION_VTOLATTACK;
//actionAddVtolAttackRun( psDroid );
//actionUpdateVtolAttack( psDroid );
}
else
{

View File

@ -577,6 +577,60 @@ Vector3i cameraToHome(UDWORD player,bool scroll)
return res;
}
static void recvSyncRequest(NETQUEUE queue)
{
int32_t req_id, x, y, obj_id, obj_id2, player_id, player_id2;
BASE_OBJECT *psObj = NULL, *psObj2 = NULL;
NETbeginDecode(queue, GAME_SYNC_REQUEST);
NETint32_t(&req_id);
NETint32_t(&x);
NETint32_t(&y);
NETint32_t(&obj_id);
NETint32_t(&player_id);
NETint32_t(&obj_id2);
NETint32_t(&player_id2);
NETend();
syncDebug("sync request received from%d req_id%d x%u y%u %obj1 %obj2", queue.index, req_id, x, y, obj_id, obj_id2);
if (obj_id)
{
psObj = IdToPointer(obj_id, player_id);
}
if (obj_id2)
{
psObj2 = IdToPointer(obj_id2, player_id2);
}
triggerEventSyncRequest(queue.index, req_id, x, y, psObj, psObj2);
}
static void sendObj(BASE_OBJECT *psObj)
{
if (psObj)
{
int32_t obj_id = psObj->id;
int32_t player = psObj->player;
NETint32_t(&obj_id);
NETint32_t(&player);
}
else
{
int32_t dummy = 0;
NETint32_t(&dummy);
NETint32_t(&dummy);
}
}
void sendSyncRequest(int32_t req_id, int32_t x, int32_t y, BASE_OBJECT *psObj, BASE_OBJECT *psObj2)
{
NETbeginEncode(NETgameQueue(selectedPlayer), GAME_SYNC_REQUEST);
NETint32_t(&req_id);
NETint32_t(&x);
NETint32_t(&y);
sendObj(psObj);
sendObj(psObj2);
NETend();
}
// ////////////////////////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////////////////////////
@ -617,6 +671,9 @@ bool recvMessage(void)
case NET_BEACONMSG: //beacon (blip) message
recvBeacon(queue);
break;
case GAME_SYNC_REQUEST:
recvSyncRequest(queue);
break;
case GAME_DROIDDISEMBARK:
recvDroidDisEmbark(queue); //droid has disembarked from a Transporter
break;
@ -826,6 +883,7 @@ void HandleBadParam(const char *msg, const int from, const int actual)
kickPlayer(actual, buf, KICK_TYPE);
}
}
// ////////////////////////////////////////////////////////////////////////////
// Research Stuff. Nat games only send the result of research procedures.
bool SendResearch(uint8_t player, uint32_t index, bool trigger)

View File

@ -217,4 +217,6 @@ extern void resetReadyStatus (bool bSendOptions);
STRUCTURE *findResearchingFacilityByResearchIndex(unsigned player, unsigned index);
void sendSyncRequest(int32_t req_id, int32_t x, int32_t y, BASE_OBJECT *psObj, BASE_OBJECT *psObj2);
#endif // __INCLUDED_SRC_MULTIPLAY_H__

View File

@ -1357,3 +1357,30 @@ bool triggerEventDesignCreated(DROID_TEMPLATE *psTemplate)
}
return true;
}
//__ \subsection{eventSyncRequest(req_id, x, y, obj_id, obj_id2)}
//__ An event that is called from a script and synchronized with all other scripts and hosts
//__ to prevent desync from happening. Sync requests must be carefully validated to prevent
//__ cheating!
bool triggerEventSyncRequest(int from, int req_id, int x, int y, BASE_OBJECT *psObj, BASE_OBJECT *psObj2)
{
for (int i = 0; i < scripts.size(); ++i)
{
QScriptEngine *engine = scripts.at(i);
QScriptValueList args;
args += QScriptValue(from);
args += QScriptValue(req_id);
args += QScriptValue(x);
args += QScriptValue(y);
if (psObj)
{
args += convMax(psObj, engine);
}
if (psObj2)
{
args += convMax(psObj2, engine);
}
callFunction(engine, "eventSyncRequest", args);
}
return true;
}

View File

@ -115,5 +115,6 @@ bool triggerEventArea(QString label, DROID *psDroid);
bool triggerEventSelected();
bool triggerEventPlayerLeft(int id);
bool triggerEventDesignCreated(DROID_TEMPLATE *psTemplate);
bool triggerEventSyncRequest(int from, int req_id, int x, int y, BASE_OBJECT *psObj, BASE_OBJECT *psObj2);
#endif

View File

@ -3817,6 +3817,38 @@ static QScriptValue js_syncRandom(QScriptContext *context, QScriptEngine *)
return QScriptValue(gameRand(limit));
}
//-- \subsection{syncRequest(req_id, x, y[, obj[, obj2]])}
//-- Generate a synchronized event request that is sent over the network to all clients and executed simultaneously.
//-- Must be caught in an eventSyncRequest() function. All sync requests must be validated when received, and always
//-- take care only to define sync requests that can be validated against cheating.
static QScriptValue js_syncRequest(QScriptContext *context, QScriptEngine *)
{
int32_t req_id = context->argument(0).toInt32();
int32_t x = world_coord(context->argument(1).toInt32());
int32_t y = world_coord(context->argument(2).toInt32());
BASE_OBJECT *psObj = NULL, *psObj2 = NULL;
if (context->argumentCount() > 3)
{
QScriptValue objVal = context->argument(3);
int oid = objVal.property("id").toInt32();
int oplayer = objVal.property("player").toInt32();
OBJECT_TYPE otype = (OBJECT_TYPE)objVal.property("type").toInt32();
SCRIPT_ASSERT(context, psObj, "No such object id %d belonging to player %d", oid, oplayer);
psObj = IdToObject(otype, oid, oplayer);
}
if (context->argumentCount() > 4)
{
QScriptValue objVal = context->argument(4);
int oid = objVal.property("id").toInt32();
int oplayer = objVal.property("player").toInt32();
OBJECT_TYPE otype = (OBJECT_TYPE)objVal.property("type").toInt32();
SCRIPT_ASSERT(context, psObj, "No such object id %d belonging to player %d", oid, oplayer);
psObj2 = IdToObject(otype, oid, oplayer);
}
sendSyncRequest(req_id, x, y, psObj, psObj2);
return QScriptValue();
}
// ----------------------------------------------------------------------------------------
// Register functions with scripting system
@ -4417,6 +4449,7 @@ bool registerFunctions(QScriptEngine *engine, QString scriptName)
engine->globalObject().setProperty("resetArea", engine->newFunction(js_resetArea));
engine->globalObject().setProperty("addSpotter", engine->newFunction(js_addSpotter));
engine->globalObject().setProperty("removeSpotter", engine->newFunction(js_removeSpotter));
engine->globalObject().setProperty("syncRequest", engine->newFunction(js_syncRequest));
// horrible hacks follow -- do not rely on these being present!
engine->globalObject().setProperty("hackNetOff", engine->newFunction(js_hackNetOff));