Initial test of qtscript

master
Per Inge Mathisen 2011-01-23 22:30:57 +01:00
parent d39112244b
commit c1f1c3e31d
12 changed files with 503 additions and 20 deletions

View File

@ -71,7 +71,7 @@ PKG_PROG_PKG_CONFIG
AC_PROG_VERSION_CHECK([pkg-config], [0.9])
# Check for Qt
PKG_CHECK_MODULES(QT4, QtCore QtGui QtOpenGL QtNetwork,,[:])
PKG_CHECK_MODULES(QT4, QtCore QtGui QtOpenGL QtNetwork QtScript,,[:])
if test "$pkg_failed" = "yes" ; then
AC_MSG_ERROR([Qt not found - required!])
fi

View File

@ -2,3 +2,4 @@
name = "Semperfi"
vlo = semperfi.vlo
slo = semperfi.slo
js = semperfi.js

View File

@ -0,0 +1,12 @@
function tick()
{
debug("Tick, tock");
}
function eventGameInit()
{
var p = scavengerPlayer();
console("hey, i'm ze console! scav is " + p);
debug("TEST scav is " + p);
setGlobalTimer(tick, 100);
}

View File

@ -125,6 +125,8 @@ noinst_HEADERS = \
power.h \
projectiledef.h \
projectile.h \
qtscript.h \
qtscriptfuncs.h \
radar.h \
random.h \
raycast.h \
@ -240,6 +242,8 @@ warzone2100_SOURCES = \
pointtree.cpp \
power.cpp \
projectile.cpp \
qtscript.cpp \
qtscriptfuncs.cpp \
radar.cpp \
random.cpp \
raycast.cpp \

View File

@ -93,6 +93,7 @@
#include "wrappers.h"
#include "terrain.h"
#include "ingameop.h"
#include "qtscript.h"
static void initMiscVars(void);
@ -797,7 +798,12 @@ BOOL stageOneInitialise(void)
return false;
}
if (!scrTabInitialise()) // Initialise the script system
if (!scrTabInitialise()) // Initialise the old wzscript system
{
return false;
}
if (!initScripts()) // Initialise the new javascript system
{
return false;
}
@ -874,6 +880,11 @@ BOOL stageOneShutDown(void)
return false;
}
if (!shutdownScripts())
{
return false;
}
debug(LOG_TEXTURE, "=== stageOneShutDown ===");
pie_TexShutDown();
// Use mod_multiplay as the default (campaign might have set it to mod_singleplayer)
@ -1126,6 +1137,7 @@ BOOL stageThreeInitialise(void)
if (getLevelLoadType() != GTYPE_SAVE_MIDMISSION)
{
eventFireCallbackTrigger((TRIGGER_TYPE)CALL_GAMEINIT);
triggerEvent(TRIGGER_GAME_INIT);
}
return true;

View File

@ -81,6 +81,7 @@
#include "game.h"
#include "warzoneconfig.h"
#include "modding.h"
#include "qtscript.h"
#include "multiplay.h"
#include "multiint.h"
@ -217,6 +218,7 @@ struct AIDATA
char name[MAX_LEN_AI_NAME];
char slo[MAX_LEN_AI_NAME];
char vlo[MAX_LEN_AI_NAME];
char js[MAX_LEN_AI_NAME];
char tip[255];
int assigned; ///< How many AIs have we assigned of this type
};
@ -289,16 +291,21 @@ void loadMultiScripts()
for (int i = 0; i < game.maxPlayers; i++)
{
// The i == selectedPlayer hack is to enable autogames
if (bMultiPlayer && game.type == SKIRMISH && (!NetPlay.players[i].allocated || i == selectedPlayer) && NetPlay.players[i].ai >= 0)
if (bMultiPlayer && game.type == SKIRMISH && (!NetPlay.players[i].allocated || i == selectedPlayer)
&& NetPlay.players[i].ai >= 0 && myResponsibility(i))
{
resLoadFile("SCRIPT", aidata[NetPlay.players[i].ai].slo);
resLoadFile("SCRIPTVAL", aidata[NetPlay.players[i].ai].vlo);
if (aidata[NetPlay.players[i].ai].js[0] != '\0')
{
loadPlayerScript(aidata[NetPlay.players[i].ai].js, i, NetPlay.players[i].difficulty);
}
}
}
// Load scavengers
resForceBaseDir("multiplay/script/");
if (game.scavengers)
if (game.scavengers && myResponsibility(scavengerPlayer()))
{
resLoadFile("SCRIPT", "scavfact.slo");
resLoadFile("SCRIPTVAL", "scavfact.vlo");
@ -544,6 +551,7 @@ void readAIs()
sstrcpy(ai.name, inifile_get(inif, "name", "error"));
sstrcpy(ai.slo, inifile_get(inif, "slo", "error"));
sstrcpy(ai.vlo, inifile_get(inif, "vlo", "error"));
sstrcpy(ai.js, inifile_get(inif, "js", ""));
sstrcpy(ai.tip, inifile_get(inif, "tip", "Click to choose this AI"));
if (strcmp(ai.name, "Nexus") == 0)
{

View File

@ -453,7 +453,7 @@ BASE_OBJECT *IdToPointer(UDWORD id,UDWORD player)
// ////////////////////////////////////////////////////////////////////////////
// return a players name.
const char* getPlayerName(unsigned int player)
const char* getPlayerName(int player)
{
ASSERT_OR_RETURN(NULL, player < MAX_PLAYERS , "Wrong player index: %u", player);
@ -474,18 +474,18 @@ const char* getPlayerName(unsigned int player)
return NetPlay.players[player].name;
}
BOOL setPlayerName(UDWORD player, const char *sName)
BOOL setPlayerName(int player, const char *sName)
{
ASSERT_OR_RETURN(false, player < MAX_PLAYERS, "Player index (%u) out of range", player);
ASSERT_OR_RETURN(false, player < MAX_PLAYERS && player >= 0, "Player index (%u) out of range", player);
sstrcpy(playerName[player], sName);
return true;
}
// ////////////////////////////////////////////////////////////////////////////
// to determine human/computer players and responsibilities of each..
BOOL isHumanPlayer(UDWORD player)
BOOL isHumanPlayer(int player)
{
if (player >= MAX_PLAYERS)
if (player >= MAX_PLAYERS || player < 0)
{
return false; // obvious, really
}
@ -493,7 +493,7 @@ BOOL isHumanPlayer(UDWORD player)
}
// returns player responsible for 'player'
UDWORD whosResponsible(UDWORD player)
int whosResponsible(int player)
{
if (isHumanPlayer(player))
{
@ -510,13 +510,13 @@ UDWORD whosResponsible(UDWORD player)
}
//returns true if selected player is responsible for 'player'
BOOL myResponsibility(UDWORD player)
BOOL myResponsibility(int player)
{
return whosResponsible(player) == selectedPlayer;
}
//returns true if 'player' is responsible for 'playerinquestion'
BOOL responsibleFor(UDWORD player, UDWORD playerinquestion)
BOOL responsibleFor(int player, int playerinquestion)
{
return whosResponsible(playerinquestion) == player;
}
@ -2050,7 +2050,7 @@ static BOOL recvBeacon(NETQUEUE queue)
return addBeaconBlip(locX, locY, receiver, sender, beaconReceiveMsg[sender]);
}
const char* getPlayerColourName(unsigned int player)
const char* getPlayerColourName(int player)
{
static const char* playerColors[] =
{

View File

@ -153,13 +153,13 @@ extern WZ_DECL_WARN_UNUSED_RESULT BOOL IdToDroid(UDWORD id, UDWORD player, DRO
extern WZ_DECL_WARN_UNUSED_RESULT FEATURE *IdToFeature(UDWORD id,UDWORD player);
extern WZ_DECL_WARN_UNUSED_RESULT DROID_TEMPLATE *IdToTemplate(UDWORD tempId,UDWORD player);
extern const char* getPlayerName(unsigned int player);
extern BOOL setPlayerName (UDWORD player, const char *sName);
extern const char* getPlayerColourName(unsigned int player);
extern BOOL isHumanPlayer (UDWORD player); //to tell if the player is a computer or not.
extern BOOL myResponsibility (UDWORD player);
extern BOOL responsibleFor (UDWORD player, UDWORD playerinquestion);
extern UDWORD whosResponsible (UDWORD player);
extern const char* getPlayerName(int player);
extern BOOL setPlayerName(int player, const char *sName);
extern const char* getPlayerColourName(int player);
extern BOOL isHumanPlayer(int player); //to tell if the player is a computer or not.
extern BOOL myResponsibility(int player);
extern BOOL responsibleFor(int player, int playerinquestion);
extern int whosResponsible(int player);
int scavengerSlot(); // Returns the player number that scavengers would have if they were enabled.
int scavengerPlayer(); // Returns the player number that the scavengers have, or -1 if disabled.
extern Vector3i cameraToHome (UDWORD player,BOOL scroll);

231
src/qtscript.cpp Normal file
View File

@ -0,0 +1,231 @@
/*
This file is part of Warzone 2100.
Copyright (C) 2011 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
*/
/**
* @file qtscript.cpp
*
* New scripting system
*/
#include "qtscript.h"
#include <QtCore/QTextStream>
#include <QtScript/QScriptEngine>
#include <QtScript/QScriptValue>
#include <QtScript/QScriptSyntaxCheckResult>
#include <QtCore/QList>
#include <QtCore/QString>
#include "lib/framework/file.h"
#include "lib/gamelib/gtime.h"
#include "qtscriptfuncs.h"
struct timerNode
{
QString function; // maybe we should save this as a QScriptValue instead? might be faster
QScriptEngine *engine;
QString baseobj;
int frameTime;
int ms;
int player;
timerNode(QScriptEngine *caller, QString val, int plr, int frame) : function(val) { player = plr; ms = frame; frameTime = frame + gameTime; engine = caller; }
// implement operator less TODO
};
/// List of timer events for scripts. Before running them, we sort the list then run as many as we have time for.
/// In this way, we implement load balancing of events and keep frame rates tidy for users. Since scripts run on the
/// host, we do not need to worry about each peer simulating the world differently.
QList<timerNode> timers;
/// Scripting engine (what others call the scripting context, but QtScript's nomenclature is different).
QList<QScriptEngine *> scripts;
static bool callFunction(QScriptEngine *engine, QString funcName, QScriptValueList args)
{
QScriptValue value = engine->globalObject().property(funcName);
if (value.isValid() && value.isFunction())
{
QScriptValue result = value.call(QScriptValue(), args);
if (engine->hasUncaughtException())
{
// TODO, get filename to output here somehow
int line = engine->uncaughtExceptionLineNumber();
debug(LOG_ERROR, "Uncaught exception calling event %s at line %d: %s",
funcName.toAscii().constData(), line, result.toString().toAscii().constData());
return false;
}
}
return true;
}
static QScriptValue js_removeTimer(QScriptContext *context, QScriptEngine *engine)
{
QString function = context->argument(0).toString();
int i;
for (i = 0; i < timers.size(); ++i)
{
timerNode node = timers.at(i);
if (node.function == function)
{
timers.removeAt(i);
break;
}
}
if (i == timers.size())
{
// Friendly warning
debug(LOG_ERROR, "Did not find timer %s to remove", function.toAscii().constData());
}
return QScriptValue();
}
/// Special scripting function that registers a non-specific timer event
static QScriptValue js_setGlobalTimer(QScriptContext *context, QScriptEngine *engine)
{
QScriptValue function = context->argument(0);
QScriptValue ms = context->argument(1);
int player = engine->globalObject().property("me").toInt32();
timerNode node(engine, function.toString(), player, ms.toInt32() + gameTime);
timers.push_back(node);
return QScriptValue();
}
/// Special scripting function that registers a object-specific timer event
static QScriptValue js_setObjectTimer(QScriptContext *context, QScriptEngine *engine)
{
QScriptValue function = context->argument(0);
QScriptValue ms = context->argument(1);
QScriptValue obj = context->argument(2);
int player = engine->globalObject().property("me").toInt32();
timerNode node(engine, function.toString(), player, ms.toInt32());
node.baseobj = obj.toString();
timers.push_back(node);
return QScriptValue();
}
bool initScripts()
{
return true;
}
bool shutdownScripts()
{
timers.clear();
while (!scripts.isEmpty())
{
delete scripts.takeFirst();
}
return true;
}
bool updateScripts()
{
// Check for timers, and run them if applicable.
// TODO - load balancing
for (int i = 0; i < timers.size(); ++i)
{
timerNode node = timers.at(i);
if (node.frameTime <= gameTime)
{
node.frameTime = node.ms + gameTime; // update for next invokation
callFunction(node.engine, node.function, QScriptValueList());
}
}
return true;
}
bool loadPlayerScript(const char *filename, int player, int difficulty)
{
QString path(QString("multiplay/skirmish/") + filename);
ASSERT_OR_RETURN(false, player < MAX_PLAYERS, "Player index %d out of bounds", player);
QScriptEngine *engine = new QScriptEngine();
UDWORD size;
char *bytes;
if (!loadFile(path.toAscii().constData(), &bytes, &size))
{
debug(LOG_ERROR, "Failed to read script file \"%s\"", path.toAscii().constData());
return false;
}
QString source = QString::fromAscii(bytes, size);
QScriptSyntaxCheckResult syntax = QScriptEngine::checkSyntax(source);
if (syntax.state() != QScriptSyntaxCheckResult::Valid)
{
debug(LOG_ERROR, "Syntax error in %s line %d: %s", filename, syntax.errorLineNumber(), syntax.errorMessage().toAscii().constData());
free(bytes);
return false;
}
QScriptValue result = engine->evaluate(source, filename);
if (engine->hasUncaughtException())
{
int line = engine->uncaughtExceptionLineNumber();
debug(LOG_ERROR, "Uncaught exception at line %d, file %s: %s", line, filename, result.toString().toAscii().constData());
free(bytes);
return false;
}
engine->globalObject().setProperty("setGlobalTimer", engine->newFunction(js_setGlobalTimer));
engine->globalObject().setProperty("setObjectTimer", engine->newFunction(js_setObjectTimer));
engine->globalObject().setProperty("removeTimer", engine->newFunction(js_removeTimer));
engine->globalObject().setProperty("me", player, QScriptValue::ReadOnly | QScriptValue::Undeletable);
engine->globalObject().setProperty("difficulty", difficulty, QScriptValue::ReadOnly | QScriptValue::Undeletable);
registerFunctions(engine);
scripts.push_back(engine);
free(bytes);
return true;
}
bool loadGlobalScript(const char *filename)
{
return loadPlayerScript(filename, 0, 0);
}
bool loadScriptStates(const char *filename)
{
Q_UNUSED(filename);
return true;
}
bool saveScriptStates(const char *filename)
{
Q_UNUSED(filename);
return true;
}
/// Update scripting state associated with the given object
bool updateObject(BASE_OBJECT *psObj)
{
Q_UNUSED(psObj);
return true;
}
/// For generic, parameter-less triggers
bool triggerEvent(SCRIPT_TRIGGER_TYPE trigger)
{
for (int i = 0; i < scripts.size(); ++i)
{
QScriptEngine *engine = scripts.at(i);
switch (trigger)
{
case TRIGGER_GAME_INIT:
callFunction(engine, "eventGameInit", QScriptValueList());
break;
}
}
return true;
}

59
src/qtscript.h Normal file
View File

@ -0,0 +1,59 @@
/*
This file is part of Warzone 2100.
Copyright (C) 2011 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
*/
#ifndef __INCLUDED_QTSCRIPT_H__
#define __INCLUDED_QTSCRIPT_H__
#include "lib/framework/frame.h"
#include "basedef.h"
enum SCRIPT_TRIGGER_TYPE
{
TRIGGER_GAME_INIT
};
/// Initialize script system
bool initScripts();
/// Shutdown script system
bool shutdownScripts();
/// Run this each logical frame to update frame-dependent script states
bool updateScripts();
// Load and evaluate the given script, kept in memory
bool loadGlobalScript(const char *filename);
bool loadPlayerScript(const char *filename, int player, int difficulty);
// Set/write variables in the script's global context, run after loading script,
// but before triggering any events.
bool loadScriptStates(const char *filename);
bool saveScriptStates(const char *filename);
/// Update scripting state associated with the given object
bool updateObject(BASE_OBJECT *psObj);
/// For generic, parameter-less triggers, using an enum to avoid declaring a ton of parameter-less functions
bool triggerEvent(SCRIPT_TRIGGER_TYPE trigger);
// For each trigger with function parameters, a function to trigger it here
// bool triggerEventReachedLocation(ORDER order, DROID *psDroid);
// ...
#endif

121
src/qtscriptfuncs.cpp Normal file
View File

@ -0,0 +1,121 @@
/*
This file is part of Warzone 2100.
Copyright (C) 2011 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
*/
/**
* @file qtscriptfuncs.cpp
*
* New scripting system -- script functions
*/
#include "qtscriptfuncs.h"
#include <QtScript/QScriptValue>
#include "console.h"
#include "map.h"
// All script functions should be prefixed with "js_" then followed by same name as in script.
// is this really useful?
static QScriptValue js_debug(QScriptContext *context, QScriptEngine *engine)
{
QString result;
for (int i = 0; i < context->argumentCount(); ++i)
{
if (i != 0)
{
result.append(QLatin1String(" "));
}
QString s = context->argument(i).toString();
if (context->state() == QScriptContext::ExceptionState)
{
break;
}
result.append(s);
}
qWarning(result.toAscii().constData());
return QScriptValue();
}
static QScriptValue js_scavengerPlayer(QScriptContext *context, QScriptEngine *engine)
{
Q_UNUSED(context);
Q_UNUSED(engine);
return QScriptValue(scavengerPlayer());
}
// TODO, should cover scrShowConsoleText, scrAddConsoleText, scrTagConsoleText and scrConsole
static QScriptValue js_console(QScriptContext *context, QScriptEngine *engine)
{
int player = engine->globalObject().property("me").toInt32();
if (player == selectedPlayer)
{
QString result;
for (int i = 0; i < context->argumentCount(); ++i)
{
if (i != 0)
{
result.append(QLatin1String(" "));
}
QString s = context->argument(i).toString();
if (context->state() == QScriptContext::ExceptionState)
{
break;
}
result.append(s);
}
//permitNewConsoleMessages(true);
//setConsolePermanence(true,true);
addConsoleMessage(result.toAscii().constData(), CENTRE_JUSTIFY, SYSTEM_MESSAGE);
//permitNewConsoleMessages(false);
}
return QScriptValue();
}
#if 0
static QScriptValue js_getDerrick(QScriptContext *context, QScriptEngine *engine)
{
BASE_OBJECT *psObj = NULL;
QScriptValue param = context->argument(0);
int i = param.toInt32();
if (i < (int)derricks.size())
{
const int x = derricks[i].x;
const int y = derricks[i].y;
MAPTILE *psTile = worldTile(x, y);
if (psTile)
{
psObj = psTile->psObject;
}
}
return QScriptEngine::toScriptValue(psObj);
}
#endif
// ----------------------------------------------------------------------------------------
bool registerFunctions(QScriptEngine *engine)
{
// Register functions to the script engine here
//engine->globalObject().setProperty("getDerrick", engine->newFunction(js_getDerrick));
engine->globalObject().setProperty("debug", engine->newFunction(js_debug));
engine->globalObject().setProperty("console", engine->newFunction(js_console));
engine->globalObject().setProperty("scavengerPlayer", engine->newFunction(js_scavengerPlayer));
return true;
}

35
src/qtscriptfuncs.h Normal file
View File

@ -0,0 +1,35 @@
/*
This file is part of Warzone 2100.
Copyright (C) 2011 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
*/
#ifndef __INCLUDED_QTSCRIPTFUNCS_H__
#define __INCLUDED_QTSCRIPTFUNCS_H__
#include "lib/framework/frame.h"
#include "qtscript.h"
#include <QtScript/QScriptEngine>
// ----------------------------------------------
// Private to scripting module functions below
/// Register functions to engine context
bool registerFunctions(QScriptEngine *engine);
#endif