Skip to content

Commit

Permalink
qtscript: Add new cheat 'jsload' which allows you to load any AI scri…
Browse files Browse the repository at this point in the history
…pt you

want from the 'scripts' directory in your write directory to run on the current
player. New script global 'scriptPath' holds the path to whereever you loaded
the script from, and is used automatically for include files. There is no longer
any need to specify any paths for include files if they are located in the same
directory as your main script.
  • Loading branch information
perim committed Feb 4, 2013
1 parent 986a803 commit ccc540b
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 20 deletions.
1 change: 1 addition & 0 deletions src/cheat.cpp
Expand Up @@ -42,6 +42,7 @@ struct CHEAT_ENTRY
bool Cheated = false;
static CHEAT_ENTRY cheatCodes[] =
{
{"jsload", jsAutogame}, // load an AI script for selectedPlayer
{"jsdebug", jsShowDebug}, // show scripting states
{"clone wars", kf_CloneSelected}, // clone selected units
{"noassert", kf_NoAssert}, // turn off asserts
Expand Down
2 changes: 0 additions & 2 deletions src/init.cpp
Expand Up @@ -271,7 +271,6 @@ bool rebuildSearchPath(searchPathMode mode, bool force, const char *current_map)
#endif // DEBUG
// Remove maps and mods
removeSubdirs( curSearchPath->path, "maps", NULL );
removeSubdirs( curSearchPath->path, "campaigns", NULL );
removeSubdirs( curSearchPath->path, "mods/music", NULL );
removeSubdirs( curSearchPath->path, "mods/global", NULL );
removeSubdirs( curSearchPath->path, "mods/campaign", NULL );
Expand Down Expand Up @@ -327,7 +326,6 @@ bool rebuildSearchPath(searchPathMode mode, bool force, const char *current_map)
// Add global and campaign mods
PHYSFS_addToSearchPath( curSearchPath->path, PHYSFS_APPEND );

addSubdirs( curSearchPath->path, "campaigns", PHYSFS_APPEND, NULL, false );
addSubdirs( curSearchPath->path, "mods/music", PHYSFS_APPEND, NULL, false );
addSubdirs( curSearchPath->path, "mods/global", PHYSFS_APPEND, use_override_mods?override_mods:global_mods, true );
addSubdirs( curSearchPath->path, "mods", PHYSFS_APPEND, use_override_mods?override_mods:global_mods, true );
Expand Down
6 changes: 0 additions & 6 deletions src/multiint.cpp
Expand Up @@ -650,12 +650,6 @@ void readAIs()
sstrcpy(path, basepath);
sstrcat(path, *i);
WzConfig aiconf(path, WzConfig::ReadOnly);
if (aiconf.status() != QSettings::NoError)
{
debug(LOG_ERROR, "Failed to open AI %s", path);
continue;
}

AIDATA ai;
aiconf.beginGroup("AI");
sstrcpy(ai.name, aiconf.value("name", "error").toString().toAscii().constData());
Expand Down
56 changes: 45 additions & 11 deletions src/qtscript.cpp
Expand Up @@ -35,6 +35,7 @@
#include <QtCore/QStringList>
#include <QtCore/QFileInfo>
#include <QtGui/QStandardItemModel>
#include <QtGui/QFileDialog>

#include "lib/framework/wzapp.h"
#include "lib/framework/wzconfig.h"
Expand Down Expand Up @@ -294,16 +295,19 @@ void scriptRemoveObject(BASE_OBJECT *psObj)
}

//-- \subsection{include(file)}
//-- Includes another source code file at this point. This is experimental, and breaks the
//-- lint tool, so use with care.
//-- Includes another source code file at this point. You should generally only specify the filename,
//-- not try to specify its path, here.
static QScriptValue js_include(QScriptContext *context, QScriptEngine *engine)
{
QString path = context->argument(0).toString();
QString basePath = engine->globalObject().property("scriptPath").toString();
QFileInfo basename(context->argument(0).toString());
QString path = basePath + "/" + basename.fileName();
UDWORD size;
char *bytes = NULL;
if (!loadFile(path.toUtf8().constData(), &bytes, &size))
{
debug(LOG_ERROR, "Failed to read include file \"%s\"", path.toUtf8().constData());
debug(LOG_ERROR, "Failed to read include file \"%s\" (path=%s, name=%s)",
path.toUtf8().constData(), basePath.toUtf8().constData(), basename.filePath().toUtf8().constData());
return QScriptValue(false);
}
QString source = QString::fromUtf8(bytes, size);
Expand All @@ -325,6 +329,7 @@ static QScriptValue js_include(QScriptContext *context, QScriptEngine *engine)
line, path.toUtf8().constData(), result.toString().toUtf8().constData());
return QScriptValue(false);
}
debug(LOG_SCRIPT, "Included new script file %s", path.toUtf8().constData());
return QScriptValue(true);
}

Expand Down Expand Up @@ -454,7 +459,7 @@ bool updateScripts()
return true;
}

bool loadPlayerScript(QString path, int player, int difficulty)
QScriptEngine *loadPlayerScript(QString path, int player, int difficulty)
{
ASSERT_OR_RETURN(false, player < MAX_PLAYERS, "Player index %d out of bounds", player);
QScriptEngine *engine = new QScriptEngine();
Expand All @@ -463,12 +468,12 @@ bool loadPlayerScript(QString path, int player, int difficulty)
if (!loadFile(path.toUtf8().constData(), &bytes, &size))
{
debug(LOG_ERROR, "Failed to read script file \"%s\"", path.toUtf8().constData());
return false;
return NULL;
}
QString source = QString::fromUtf8(bytes, size);
free(bytes);
QScriptSyntaxCheckResult syntax = QScriptEngine::checkSyntax(source);
ASSERT_OR_RETURN(false, syntax.state() == QScriptSyntaxCheckResult::Valid, "Syntax error in %s line %d: %s",
ASSERT_OR_RETURN(NULL, syntax.state() == QScriptSyntaxCheckResult::Valid, "Syntax error in %s line %d: %s",
path.toUtf8().constData(), syntax.errorLineNumber(), syntax.errorMessage().toUtf8().constData());
// Special functions
engine->globalObject().setProperty("setTimer", engine->newFunction(js_setTimer));
Expand Down Expand Up @@ -525,25 +530,31 @@ bool loadPlayerScript(QString path, int player, int difficulty)
it.next();
internalNamespace.insert(it.name(), 1);
}

// We need to always save the 'me' special variable.
//== \item[me] The player the script is currently running as.
engine->globalObject().setProperty("me", player, QScriptValue::ReadOnly | QScriptValue::Undeletable);
QScriptValue result = engine->evaluate(source, path);
ASSERT_OR_RETURN(false, !engine->hasUncaughtException(), "Uncaught exception at line %d, file %s: %s",
engine->uncaughtExceptionLineNumber(), path.toUtf8().constData(), result.toString().toUtf8().constData());

// We also need to save the special 'scriptName' variable.
//== \item[scriptName] Base name of the script that is running.
engine->globalObject().setProperty("scriptName", basename.baseName(), QScriptValue::ReadOnly | QScriptValue::Undeletable);

// We also need to save the special 'scriptPath' variable.
//== \item[scriptPath] Base path of the script that is running.
engine->globalObject().setProperty("scriptPath", basename.path(), QScriptValue::ReadOnly | QScriptValue::Undeletable);

QScriptValue result = engine->evaluate(source, path);
ASSERT_OR_RETURN(false, !engine->hasUncaughtException(), "Uncaught exception at line %d, file %s: %s",
engine->uncaughtExceptionLineNumber(), path.toUtf8().constData(), result.toString().toUtf8().constData());

// Register script
scripts.push_back(engine);

MONITOR *monitor = new MONITOR;
monitors.insert(engine, monitor);

debug(LOG_SAVE, "Created script engine %d for player %d from %s", scripts.size() - 1, player, path.toUtf8().constData());
return true;
return engine;
}

bool loadGlobalScript(QString path)
Expand Down Expand Up @@ -746,6 +757,29 @@ bool jsEvaluate(QScriptEngine *engine, const QString &text)
return true;
}

void jsAutogame()
{
QString srcPath(PHYSFS_getWriteDir());
srcPath += PHYSFS_getDirSeparator();
srcPath += "scripts";
QString path = QFileDialog::getOpenFileName(NULL, "Choose AI script to load", srcPath, "Javascript files (*.js)");
QFileInfo basename(path);
if (path.isEmpty())
{
console("No file specified");
return;
}
QScriptEngine *engine = loadPlayerScript("scripts/" + basename.fileName(), selectedPlayer, DIFFICULTY_MEDIUM);
if (!engine)
{
console("Failed to load selected AI! Check your logs to see why.");
return;
}
console("Loaded the %s AI script for current player!", path.toUtf8().constData());
callFunction(engine, "eventGameInit", QScriptValueList());
callFunction(engine, "eventStartLevel", QScriptValueList());
}

void jsShowDebug()
{
// Add globals
Expand Down
5 changes: 4 additions & 1 deletion src/qtscript.h
Expand Up @@ -62,7 +62,7 @@ bool updateScripts();

// Load and evaluate the given script, kept in memory
bool loadGlobalScript(QString path);
bool loadPlayerScript(QString path, int player, int difficulty);
QScriptEngine *loadPlayerScript(QString path, int player, int difficulty);

// Set/write variables in the script's global context, run after loading script,
// but before triggering any events.
Expand All @@ -81,6 +81,9 @@ void scriptRemoveObject(BASE_OBJECT *psObj);
/// Open debug GUI
void jsShowDebug();

/// Choose autogame AI
void jsAutogame();

/// Run-time code from user
bool jsEvaluate(QScriptEngine *engine, const QString &text);

Expand Down

0 comments on commit ccc540b

Please sign in to comment.