From f88f363ad66109b611aa9288138f3fb1e3f74069 Mon Sep 17 00:00:00 2001 From: Per Inge Mathisen Date: Sun, 23 Oct 2011 16:41:08 -0400 Subject: [PATCH] Javascript improvements to prepare for AI porting. The two types of repeating timers now unified and functionality of setObjectTimer() implemented as an extra parameter to setGlobalTimer(). Also added a new one-shot timer called queue(). --- data/base/multiplay/script/scavfact.js | 2 +- data/base/multiplay/skirmish/rules.js | 6 +- src/action.cpp | 2 + src/main.cpp | 2 + src/order.cpp | 4 ++ src/qtscript.cpp | 83 ++++++++++++++++++++------ src/qtscript.h | 3 +- src/qtscriptfuncs.cpp | 10 ++++ src/transporter.cpp | 2 + tests/lint.cpp | 20 ++++--- 10 files changed, 103 insertions(+), 31 deletions(-) diff --git a/data/base/multiplay/script/scavfact.js b/data/base/multiplay/script/scavfact.js index c9416b8d6..da329c015 100644 --- a/data/base/multiplay/script/scavfact.js +++ b/data/base/multiplay/script/scavfact.js @@ -82,7 +82,7 @@ function eventGameInit() attackGroup = newGroup(); // allocate a new group groupAddArea(attackGroup, 0, 0, (mapWidth * 128), (mapHeight * 128)); scavtick(); - setGlobalTimer("scavtick", 15000); // start a constant timer function + setTimer("scavtick", 15000); // start a constant timer function } // deal with a droid being built by us diff --git a/data/base/multiplay/skirmish/rules.js b/data/base/multiplay/skirmish/rules.js index 3013665a6..61eac6c87 100644 --- a/data/base/multiplay/skirmish/rules.js +++ b/data/base/multiplay/skirmish/rules.js @@ -4,10 +4,6 @@ // contains the rules for starting and ending a game. // as well as warning messages. // - -// TODO set tech level as a global, and set that up here as well -// TODO use gameTime instead of t second ticker... - // ///////////////////////////////////////////////////////////////// var lastHitTime = 0; @@ -122,7 +118,7 @@ function eventGameInit() } } } - setGlobalTimer("checkEndConditions", 100); + setTimer("checkEndConditions", 100); } // ///////////////////////////////////////////////////////////////// diff --git a/src/action.cpp b/src/action.cpp index 7bef0453c..254e4c771 100644 --- a/src/action.cpp +++ b/src/action.cpp @@ -36,6 +36,7 @@ #include "intdisplay.h" #include "mission.h" #include "projectile.h" +#include "qtscript.h" #include "random.h" #include "research.h" #include "scriptcb.h" @@ -998,6 +999,7 @@ void actionUpdateDroid(DROID *psDroid) { //the script can call startMission for this callback for offworld missions eventFireCallbackTrigger((TRIGGER_TYPE)CALL_START_NEXT_LEVEL); + triggerEvent(TRIGGER_START_LEVEL); } } } diff --git a/src/main.cpp b/src/main.cpp index c38e6e958..323f379b8 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -68,6 +68,7 @@ #include "mission.h" #include "modding.h" #include "multiplay.h" +#include "qtscript.h" #include "research.h" #include "scripttabs.h" #include "seqdisp.h" @@ -821,6 +822,7 @@ static void startGameLoop(void) if (game.type == SKIRMISH) { eventFireCallbackTrigger((TRIGGER_TYPE)CALL_START_NEXT_LEVEL); + triggerEvent(TRIGGER_START_LEVEL); } screen_disableMapPreview(); } diff --git a/src/order.cpp b/src/order.cpp index 4cfd6ceb5..a4b4bf274 100644 --- a/src/order.cpp +++ b/src/order.cpp @@ -42,6 +42,7 @@ #include "intorder.h" #include "orderdef.h" #include "transporter.h" +#include "qtscript.h" #include "group.h" #include "cmddroid.h" #include "lib/script/script.h" @@ -332,6 +333,7 @@ void orderUpdateDroid(DROID *psDroid) { // the script can call startMission for this callback for offworld missions eventFireCallbackTrigger((TRIGGER_TYPE)CALL_START_NEXT_LEVEL); + triggerEvent(TRIGGER_START_LEVEL); } } } @@ -420,6 +422,7 @@ void orderUpdateDroid(DROID *psDroid) { //the script can call startMission for this callback for offworld missions eventFireCallbackTrigger((TRIGGER_TYPE)CALL_START_NEXT_LEVEL); + triggerEvent(TRIGGER_START_LEVEL); /* clear order */ psDroid->order = DORDER_NONE; @@ -1156,6 +1159,7 @@ void orderUpdateDroid(DROID *psDroid) { // the script can call startMission for this callback for offworld missions eventFireCallbackTrigger((TRIGGER_TYPE)CALL_START_NEXT_LEVEL); + triggerEvent(TRIGGER_START_LEVEL); } } } diff --git a/src/qtscript.cpp b/src/qtscript.cpp index b667eb4cd..c149a7ad9 100644 --- a/src/qtscript.cpp +++ b/src/qtscript.cpp @@ -41,16 +41,23 @@ #include "qtscriptfuncs.h" +enum timerType +{ + TIMER_REPEAT, TIMER_ONESHOT_READY, TIMER_ONESHOT_DONE +}; + struct timerNode { QString function; QScriptEngine *engine; - QString baseobj; + int baseobj; int frameTime; int ms; int player; + timerType type; timerNode() {} - timerNode(QScriptEngine *caller, QString val, int plr, int frame) : function(val) { player = plr; ms = frame; frameTime = frame + gameTime; engine = caller; } + timerNode(QScriptEngine *caller, QString val, int plr, int frame) + : function(val), engine(caller), baseobj(-1), frameTime(frame + gameTime), ms(frame), player(plr), type(TIMER_REPEAT) {} bool operator== (const timerNode &t) { return function == t.function && player == t.player; } // implement operator less TODO }; @@ -112,28 +119,41 @@ static QScriptValue js_removeTimer(QScriptContext *context, QScriptEngine *engin return QScriptValue(); } -/// Special scripting function that registers a non-specific timer event. Note: Functions must be passed -/// quoted, otherwise they will be inlined! -static QScriptValue js_setGlobalTimer(QScriptContext *context, QScriptEngine *engine) +/// Special scripting function that registers a timer event. Note: Functions must be passed +/// quoted, otherwise they will be inlined! If a third parameter is passed, this must be an +/// object, which is then passed to the timer. If the object is dead, the timer stops. +static QScriptValue js_setTimer(QScriptContext *context, QScriptEngine *engine) { QString funcName = context->argument(0).toString(); QScriptValue ms = context->argument(1); int player = engine->globalObject().property("me").toInt32(); - timerNode node(engine, funcName, player, ms.toInt32() + gameTime); + timerNode node(engine, funcName, player, ms.toInt32()); + if (context->argumentCount() == 3) + { + QScriptValue obj = context->argument(2); + node.baseobj = obj.property("id").toInt32(); + } + node.type = TIMER_REPEAT; timers.push_back(node); return QScriptValue(); } -/// Special scripting function that registers a object-specific timer event. Note: Functions must be passed -/// quoted, otherwise they will be inlined! -static QScriptValue js_setObjectTimer(QScriptContext *context, QScriptEngine *engine) +/// Special scripting function that queues up a function to call once at a later time frame. +/// Note: Functions must be passed quoted, otherwise they will be inlined! If a third +/// parameter is passed, this must be an object, which is then passed to the timer. If +/// the object is dead, the timer stops. +static QScriptValue js_queue(QScriptContext *context, QScriptEngine *engine) { QString funcName = context->argument(0).toString(); QScriptValue ms = context->argument(1); - QScriptValue obj = context->argument(2); int player = engine->globalObject().property("me").toInt32(); timerNode node(engine, funcName, player, ms.toInt32()); - node.baseobj = obj.toString(); + if (context->argumentCount() == 3) + { + QScriptValue obj = context->argument(2); + node.baseobj = obj.property("id").toInt32(); + } + node.type = TIMER_ONESHOT_READY; timers.push_back(node); return QScriptValue(); } @@ -195,10 +215,24 @@ bool updateScripts() engine->globalObject().setProperty("gameTime", gameTime, QScriptValue::ReadOnly | QScriptValue::Undeletable); } + // Weed out dead timers + for (int i = 0; i < timers.count(); ) + { + const timerNode node = timers.at(i); + if (node.type == TIMER_ONESHOT_DONE || (node.baseobj > NOT_CURRENT_LIST && !IdToPointer(node.baseobj, node.player))) + { + timers.removeAt(i); + } + else + { + i++; + } + } // Check for timers, and run them if applicable. // TODO - load balancing QList runlist; // make a new list here, since we might trample all over the timer list during execution QList::iterator iter; + // Weed out dead timers for (iter = timers.begin(); iter != timers.end(); iter++) { if (iter->frameTime <= gameTime) @@ -206,10 +240,20 @@ bool updateScripts() iter->frameTime = iter->ms + gameTime; // update for next invokation runlist.append(*iter); } + else if (iter->type == TIMER_ONESHOT_READY) + { + iter->type = TIMER_ONESHOT_DONE; + runlist.append(*iter); + } } for (iter = runlist.begin(); iter != runlist.end(); iter++) { - callFunction(iter->engine, iter->function, QScriptValueList()); + QScriptValueList args; + if (iter->baseobj > NOT_CURRENT_LIST) + { + args += convObj(IdToPointer(iter->baseobj, iter->player), iter->engine); + } + callFunction(iter->engine, iter->function, args); } return true; } @@ -241,8 +285,8 @@ bool loadPlayerScript(QString path, int player, int difficulty) ASSERT_OR_RETURN(false, !engine->hasUncaughtException(), "Uncaught exception at line %d, file %s: %s", engine->uncaughtExceptionLineNumber(), path.toAscii().constData(), result.toString().toAscii().constData()); // Special functions - engine->globalObject().setProperty("setGlobalTimer", engine->newFunction(js_setGlobalTimer)); - engine->globalObject().setProperty("setObjectTimer", engine->newFunction(js_setObjectTimer)); + engine->globalObject().setProperty("setTimer", engine->newFunction(js_setTimer)); + engine->globalObject().setProperty("queue", engine->newFunction(js_queue)); engine->globalObject().setProperty("removeTimer", engine->newFunction(js_removeTimer)); engine->globalObject().setProperty("include", engine->newFunction(js_include)); @@ -300,11 +344,12 @@ bool saveScriptStates(const char *filename) ini.beginGroup(QString("triggers_") + QString::number(i)); ini.setValue("player", node.player); ini.setValue("function", node.function.toUtf8().constData()); - if (!node.baseobj.isEmpty()) + if (!node.baseobj >= NOT_CURRENT_LIST) { - ini.setValue("object", node.baseobj.toUtf8().constData()); + ini.setValue("object", QVariant(node.baseobj)); } ini.setValue("frame", node.frameTime); + ini.setValue("type", (int)node.type); ini.setValue("ms", node.ms); ini.endGroup(); } @@ -343,7 +388,8 @@ bool loadScriptStates(const char *filename) debug(LOG_SAVE, "Registering trigger %d for player %d", i, node.player); node.engine = findEngineForPlayer(node.player); node.function = ini.value("function").toString(); - node.baseobj = ini.value("baseobj", QString()).toString(); + node.baseobj = ini.value("baseobj", -1).toInt(); + node.type = (timerType)ini.value("type", TIMER_REPEAT).toInt(); timers.push_back(node); ini.endGroup(); } @@ -379,6 +425,9 @@ bool triggerEvent(SCRIPT_TRIGGER_TYPE trigger) case TRIGGER_GAME_INIT: callFunction(engine, "eventGameInit", QScriptValueList()); break; + case TRIGGER_START_LEVEL: + callFunction(engine, "eventStartLevel", QScriptValueList()); + break; } } return true; diff --git a/src/qtscript.h b/src/qtscript.h index 7a49f928d..653ebbf09 100644 --- a/src/qtscript.h +++ b/src/qtscript.h @@ -27,7 +27,8 @@ enum SCRIPT_TRIGGER_TYPE { - TRIGGER_GAME_INIT + TRIGGER_GAME_INIT, + TRIGGER_START_LEVEL }; // ---------------------------------------------- diff --git a/src/qtscriptfuncs.cpp b/src/qtscriptfuncs.cpp index 17f82a765..55fa6df4a 100644 --- a/src/qtscriptfuncs.cpp +++ b/src/qtscriptfuncs.cpp @@ -59,6 +59,7 @@ QScriptValue convStructure(STRUCTURE *psStruct, QScriptEngine *engine) value.setProperty("y", psStruct->pos.y, QScriptValue::ReadOnly); value.setProperty("z", psStruct->pos.z, QScriptValue::ReadOnly); value.setProperty("player", psStruct->player, QScriptValue::ReadOnly); + value.setProperty("selected", psStruct->selected, QScriptValue::ReadOnly); return value; } @@ -71,6 +72,7 @@ QScriptValue convDroid(DROID *psDroid, QScriptEngine *engine) value.setProperty("y", psDroid->pos.y, QScriptValue::ReadOnly); value.setProperty("z", psDroid->pos.z, QScriptValue::ReadOnly); value.setProperty("player", psDroid->player, QScriptValue::ReadOnly); + value.setProperty("selected", psDroid->selected, QScriptValue::ReadOnly); return value; } @@ -83,6 +85,7 @@ QScriptValue convObj(BASE_OBJECT *psObj, QScriptEngine *engine) value.setProperty("y", psObj->pos.y, QScriptValue::ReadOnly); value.setProperty("z", psObj->pos.z, QScriptValue::ReadOnly); value.setProperty("player", psObj->player, QScriptValue::ReadOnly); + value.setProperty("selected", psObj->selected, QScriptValue::ReadOnly); return value; } @@ -654,6 +657,12 @@ static QScriptValue js_translate(QScriptContext *context, QScriptEngine *engine) return QScriptValue(gettext(context->argument(0).toString().toUtf8().constData())); } +static QScriptValue js_playerPower(QScriptContext *context, QScriptEngine *engine) +{ + int player = context->argument(0).toInt32(); + return QScriptValue(getPower(player)); +} + // ---------------------------------------------------------------------------------------- // Register functions with scripting system @@ -677,6 +686,7 @@ bool registerFunctions(QScriptEngine *engine) engine->globalObject().setProperty("groupAddDroid", engine->newFunction(js_groupAddDroid)); engine->globalObject().setProperty("groupSize", engine->newFunction(js_groupSize)); engine->globalObject().setProperty("orderDroidLoc", engine->newFunction(js_orderDroidLoc)); + engine->globalObject().setProperty("playerPower", engine->newFunction(js_playerPower)); // Functions that operate on the current player only engine->globalObject().setProperty("centreView", engine->newFunction(js_centreView)); diff --git a/src/transporter.cpp b/src/transporter.cpp index 0bc5b3948..1d2a428a5 100644 --- a/src/transporter.cpp +++ b/src/transporter.cpp @@ -41,6 +41,7 @@ #include "mission.h" #include "objects.h" #include "display.h" +#include "qtscript.h" #include "lib/script/script.h" #include "scripttabs.h" #include "order.h" @@ -1638,6 +1639,7 @@ bool updateTransporter(DROID *psTransporter) //the script can call startMission for this callback for offworld missions eventFireCallbackTrigger((TRIGGER_TYPE)CALL_START_NEXT_LEVEL); + triggerEvent(TRIGGER_START_LEVEL); // clear order psTransporter->order = DORDER_NONE; diff --git a/tests/lint.cpp b/tests/lint.cpp index 822bfede9..314cb6e3c 100644 --- a/tests/lint.cpp +++ b/tests/lint.cpp @@ -111,6 +111,7 @@ QScriptValue convStructure(QScriptEngine *engine) value.setProperty("y", 11, QScriptValue::ReadOnly); value.setProperty("z", 0, QScriptValue::ReadOnly); value.setProperty("player", 1, QScriptValue::ReadOnly); + value.setProperty("selected", 0, QScriptValue::ReadOnly); return value; } @@ -122,6 +123,7 @@ QScriptValue convDroid(QScriptEngine *engine) value.setProperty("y", 11, QScriptValue::ReadOnly); value.setProperty("z", 0, QScriptValue::ReadOnly); value.setProperty("player", 1, QScriptValue::ReadOnly); + value.setProperty("selected", 0, QScriptValue::ReadOnly); return value; } @@ -133,6 +135,7 @@ QScriptValue convObj(QScriptEngine *engine) value.setProperty("y", 11, QScriptValue::ReadOnly); value.setProperty("z", 0, QScriptValue::ReadOnly); value.setProperty("player", 1, QScriptValue::ReadOnly); + value.setProperty("selected", 0, QScriptValue::ReadOnly); return value; } @@ -442,13 +445,15 @@ static QScriptValue js_removeTimer(QScriptContext *context, QScriptEngine *engin /// Special scripting function that registers a non-specific timer event. Note: Functions must be passed /// quoted, otherwise they will be inlined! -static QScriptValue js_setGlobalTimer(QScriptContext *context, QScriptEngine *engine) +static QScriptValue js_setTimer(QScriptContext *context, QScriptEngine *engine) { - ARG_COUNT_EXACT(2); + ARG_COUNT_VAR(2, 3); ARG_STRING(0); ARG_NUMBER(1); + if (context->argumentCount() == 3) ARG_OBJ(2); QString funcName = context->argument(0).toString(); // TODO - check that a function by that name exists + // TODO - object argument int player = engine->globalObject().property("me").toInt32(); timerNode node(engine, funcName, player); timers.push_back(node); @@ -457,14 +462,15 @@ static QScriptValue js_setGlobalTimer(QScriptContext *context, QScriptEngine *en /// Special scripting function that registers a object-specific timer event. Note: Functions must be passed /// quoted, otherwise they will be inlined! -static QScriptValue js_setObjectTimer(QScriptContext *context, QScriptEngine *engine) +static QScriptValue js_queue(QScriptContext *context, QScriptEngine *engine) { - ARG_COUNT_EXACT(2); + ARG_COUNT_VAR(2, 3); ARG_STRING(0); ARG_NUMBER(1); - ARG_OBJ(2); + if (context->argumentCount() == 3) ARG_OBJ(2); QString funcName = context->argument(0).toString(); // TODO - check that a function by that name exists + // TODO - object argument int player = engine->globalObject().property("me").toInt32(); timerNode node(engine, funcName, player); timers.push_back(node); @@ -536,8 +542,8 @@ bool testPlayerScript(QString path, int player, int difficulty) return false; } // Special functions - engine->globalObject().setProperty("setGlobalTimer", engine->newFunction(js_setGlobalTimer)); - engine->globalObject().setProperty("setObjectTimer", engine->newFunction(js_setObjectTimer)); + engine->globalObject().setProperty("setTimer", engine->newFunction(js_setTimer)); + engine->globalObject().setProperty("queue", engine->newFunction(js_queue)); engine->globalObject().setProperty("removeTimer", engine->newFunction(js_removeTimer)); engine->globalObject().setProperty("include", engine->newFunction(js_include));