4746 lines
202 KiB
C++
4746 lines
202 KiB
C++
/*
|
|
This file is part of Warzone 2100.
|
|
Copyright (C) 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
|
|
*/
|
|
/**
|
|
* @file qtscriptfuncs.cpp
|
|
*
|
|
* New scripting system -- script functions
|
|
*/
|
|
|
|
#include "lib/framework/wzapp.h"
|
|
#include "lib/framework/wzconfig.h"
|
|
#include "lib/sound/audio.h"
|
|
#include "lib/netplay/netplay.h"
|
|
#include "qtscriptfuncs.h"
|
|
#include "lib/ivis_opengl/tex.h"
|
|
|
|
#include <QtScript/QScriptValue>
|
|
#include <QtCore/QStringList>
|
|
#include <QtGui/QStandardItemModel>
|
|
|
|
#include "action.h"
|
|
#include "combat.h"
|
|
#include "console.h"
|
|
#include "design.h"
|
|
#include "display3d.h"
|
|
#include "map.h"
|
|
#include "mission.h"
|
|
#include "transporter.h"
|
|
#include "message.h"
|
|
#include "display3d.h"
|
|
#include "intelmap.h"
|
|
#include "hci.h"
|
|
#include "wrappers.h"
|
|
#include "challenge.h"
|
|
#include "research.h"
|
|
#include "multilimit.h"
|
|
#include "multigifts.h"
|
|
#include "template.h"
|
|
#include "lighting.h"
|
|
#include "radar.h"
|
|
#include "random.h"
|
|
#include "frontend.h"
|
|
#include "loop.h"
|
|
#include "scriptextern.h"
|
|
#include "gateway.h"
|
|
#include "mapgrid.h"
|
|
#include "lighting.h"
|
|
#include "atmos.h"
|
|
#include "warcam.h"
|
|
#include "projectile.h"
|
|
|
|
#define FAKE_REF_LASSAT 999
|
|
#define ALL_PLAYERS -1
|
|
#define ALLIES -2
|
|
#define ENEMIES -3
|
|
|
|
#define SCRCB_RES (COMP_NUMCOMPONENTS + 0)
|
|
#define SCRCB_REP (COMP_NUMCOMPONENTS + 1)
|
|
#define SCRCB_POW (COMP_NUMCOMPONENTS + 2)
|
|
#define SCRCB_CON (COMP_NUMCOMPONENTS + 3)
|
|
#define SCRCB_REA (COMP_NUMCOMPONENTS + 4)
|
|
#define SCRCB_ARM (COMP_NUMCOMPONENTS + 5)
|
|
#define SCRCB_HEA (COMP_NUMCOMPONENTS + 6)
|
|
#define SCRCB_ELW (COMP_NUMCOMPONENTS + 7)
|
|
#define SCRCB_HIT (COMP_NUMCOMPONENTS + 8)
|
|
|
|
// TODO, move this stuff into a script common subsystem
|
|
#include "scriptfuncs.h"
|
|
extern bool structDoubleCheck(BASE_STATS *psStat,UDWORD xx,UDWORD yy, SDWORD maxBlockingTiles);
|
|
extern Vector2i positions[MAX_PLAYERS];
|
|
extern std::vector<Vector2i> derricks;
|
|
|
|
// private qtscript bureaucracy
|
|
typedef QMap<BASE_OBJECT *, int> GROUPMAP;
|
|
typedef QMap<QScriptEngine *, GROUPMAP *> ENGINEMAP;
|
|
static ENGINEMAP groups;
|
|
|
|
struct labeltype
|
|
{
|
|
Vector2i p1, p2;
|
|
int id;
|
|
int type;
|
|
int player;
|
|
QList<int> idlist;
|
|
|
|
bool operator==(const labeltype &o) const { return id == o.id && type == o.type && player == o.player; }
|
|
};
|
|
typedef QMap<QString, labeltype> LABELMAP;
|
|
static LABELMAP labels;
|
|
static QStandardItemModel *labelModel = NULL;
|
|
|
|
#define SCRIPT_ASSERT_PLAYER(_context, _player) \
|
|
SCRIPT_ASSERT(_context, _player >= 0 && _player < MAX_PLAYERS, "Invalid player index %d", _player);
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
// Utility functions -- not called directly from scripts
|
|
|
|
static void updateLabelModel()
|
|
{
|
|
if (!labelModel)
|
|
{
|
|
return;
|
|
}
|
|
labelModel->setRowCount(0);
|
|
labelModel->setRowCount(labels.size());
|
|
int nextRow = 0;
|
|
for (LABELMAP::iterator i = labels.begin(); i != labels.end(); i++)
|
|
{
|
|
labeltype &l = (*i);
|
|
labelModel->setItem(nextRow, 0, new QStandardItem(i.key()));
|
|
const char *c = "?";
|
|
switch (l.type)
|
|
{
|
|
case OBJ_DROID: c = "DROID"; break;
|
|
case OBJ_FEATURE: c = "FEATURE"; break;
|
|
case OBJ_STRUCTURE: c = "STRUCTURE"; break;
|
|
case SCRIPT_POSITION: c = "POSITION"; break;
|
|
case SCRIPT_AREA: c = "AREA"; break;
|
|
case SCRIPT_GROUP: c = "GROUP"; break;
|
|
case SCRIPT_PLAYER:
|
|
case SCRIPT_RESEARCH:
|
|
case OBJ_PROJECTILE:
|
|
case OBJ_TARGET:
|
|
case SCRIPT_COUNT: c = "ERROR"; break;
|
|
}
|
|
labelModel->setItem(nextRow, 1, new QStandardItem(QString(c)));
|
|
nextRow++;
|
|
}
|
|
}
|
|
|
|
QStandardItemModel *createLabelModel()
|
|
{
|
|
labelModel = new QStandardItemModel(0, 2);
|
|
labelModel->setHeaderData(0, Qt::Horizontal, QString("Label"));
|
|
labelModel->setHeaderData(1, Qt::Horizontal, QString("Type"));
|
|
updateLabelModel();
|
|
return labelModel;
|
|
}
|
|
|
|
static void clearMarks()
|
|
{
|
|
for (int x = 0; x < mapWidth; x++) // clear old marks
|
|
{
|
|
for (int y = 0; y < mapHeight; y++)
|
|
{
|
|
MAPTILE *psTile = mapTile(x, y);
|
|
psTile->tileInfoBits &= ~BITS_MARKED;
|
|
}
|
|
}
|
|
}
|
|
|
|
void showLabel(const QString &key)
|
|
{
|
|
if (!labels.contains(key))
|
|
{
|
|
debug(LOG_ERROR, "label %s not found", key.toUtf8().constData());
|
|
return;
|
|
}
|
|
labeltype &l = labels[key];
|
|
if (l.type == SCRIPT_AREA || l.type == SCRIPT_POSITION)
|
|
{
|
|
setViewPos(map_coord(l.p1.x), map_coord(l.p1.y), false); // move camera position
|
|
clearMarks();
|
|
int maxx = map_coord(l.p2.x);
|
|
int maxy = map_coord(l.p2.y);
|
|
if (l.type == SCRIPT_POSITION)
|
|
{
|
|
maxx = MIN(mapWidth, maxx + 1);
|
|
maxy = MIN(mapHeight, maxy + 1);
|
|
}
|
|
for (int x = map_coord(l.p1.x); x < maxx; x++) // make new ones
|
|
{
|
|
for (int y = map_coord(l.p1.y); y < maxy; y++)
|
|
{
|
|
MAPTILE *psTile = mapTile(x, y);
|
|
psTile->tileInfoBits |= BITS_MARKED;
|
|
}
|
|
}
|
|
}
|
|
else if (l.type == OBJ_DROID || l.type == OBJ_FEATURE || l.type == OBJ_STRUCTURE)
|
|
{
|
|
clearMarks();
|
|
BASE_OBJECT *psObj = IdToObject((OBJECT_TYPE)l.type, l.id, l.player);
|
|
if (psObj)
|
|
{
|
|
setViewPos(map_coord(psObj->pos.x), map_coord(psObj->pos.y), false); // move camera position
|
|
MAPTILE *psTile = mapTile(map_coord(psObj->pos.x), map_coord(psObj->pos.y));
|
|
psTile->tileInfoBits |= BITS_MARKED;
|
|
}
|
|
}
|
|
else if (l.type == SCRIPT_GROUP)
|
|
{
|
|
bool cameraMoved = false;
|
|
clearMarks();
|
|
for (ENGINEMAP::iterator i = groups.begin(); i != groups.end(); ++i)
|
|
{
|
|
for (GROUPMAP::const_iterator iter = i.value()->constBegin(); iter != i.value()->constEnd(); ++iter)
|
|
{
|
|
if (iter.value() == l.id)
|
|
{
|
|
BASE_OBJECT *psObj = iter.key();
|
|
if (!cameraMoved)
|
|
{
|
|
setViewPos(map_coord(psObj->pos.x), map_coord(psObj->pos.y), false); // move camera position
|
|
cameraMoved = true;
|
|
}
|
|
MAPTILE *psTile = mapTile(map_coord(psObj->pos.x), map_coord(psObj->pos.y));
|
|
psTile->tileInfoBits |= BITS_MARKED;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool areaLabelCheck(DROID *psDroid)
|
|
{
|
|
int x = psDroid->pos.x;
|
|
int y = psDroid->pos.y;
|
|
for (LABELMAP::iterator i = labels.begin(); i != labels.end(); i++)
|
|
{
|
|
labeltype &l = (*i);
|
|
if (l.id == -1 && l.p1.x < x && l.p1.y < y && l.p2.x > x && l.p2.y > y
|
|
&& (l.player == ALL_PLAYERS || l.player == psDroid->player)
|
|
&& l.type == SCRIPT_AREA)
|
|
{
|
|
// We're inside an untriggered area
|
|
l.id = 1; // deactivate it
|
|
triggerEventArea(i.key(), psDroid);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void removeFromGroup(QScriptEngine *engine, GROUPMAP *psMap, BASE_OBJECT *psObj)
|
|
{
|
|
if (psMap->contains(psObj))
|
|
{
|
|
int groupId = psMap->take(psObj); // take and remove item
|
|
QScriptValue groupMembers = engine->globalObject().property("groupSizes");
|
|
const int newValue = groupMembers.property(groupId).toInt32() - 1;
|
|
ASSERT(newValue >= 0, "Bad group count in group %d (was %d)", groupId, newValue + 1);
|
|
groupMembers.setProperty(groupId, newValue, QScriptValue::ReadOnly);
|
|
triggerEventGroupLoss(psObj, groupId, newValue, engine);
|
|
}
|
|
}
|
|
|
|
void groupRemoveObject(BASE_OBJECT *psObj)
|
|
{
|
|
for (ENGINEMAP::iterator i = groups.begin(); i != groups.end(); ++i)
|
|
{
|
|
removeFromGroup(i.key(), i.value(), psObj);
|
|
}
|
|
}
|
|
|
|
static bool groupAddObject(BASE_OBJECT *psObj, int groupId, QScriptEngine *engine)
|
|
{
|
|
ASSERT_OR_RETURN(false, psObj && engine, "Bad parameter");
|
|
GROUPMAP *psMap = groups.value(engine);
|
|
removeFromGroup(engine, psMap, psObj);
|
|
QScriptValue groupMembers = engine->globalObject().property("groupSizes");
|
|
int prev = groupMembers.property(QString::number(groupId)).toInt32();
|
|
groupMembers.setProperty(QString::number(groupId), prev + 1, QScriptValue::ReadOnly);
|
|
psMap->insert(psObj, groupId);
|
|
return true; // inserted
|
|
}
|
|
|
|
//;; \subsection{Research}
|
|
//;; Describes a research item. The following properties are defined:
|
|
//;; \begin{description}
|
|
//;; \item[power] Number of power points needed for starting the research.
|
|
//;; \item[points] Number of resarch points needed to complete the research.
|
|
//;; \item[started] A boolean saying whether or not this research has been started by current player or any of its allies.
|
|
//;; \item[done] A boolean saying whether or not this research has been completed.
|
|
//;; \item[name] A string containing the full name of the research.
|
|
//;; \item[id] A string containing the index name of the research.
|
|
//;; \item[type] The type will always be RESEARCH_DATA.
|
|
//;; \end{description}
|
|
QScriptValue convResearch(RESEARCH *psResearch, QScriptEngine *engine, int player)
|
|
{
|
|
QScriptValue value = engine->newObject();
|
|
value.setProperty("power", (int)psResearch->researchPower);
|
|
value.setProperty("points", (int)psResearch->researchPoints);
|
|
bool started = false;
|
|
for (int i = 0; i < game.maxPlayers; i++)
|
|
{
|
|
if (aiCheckAlliances(player, i) || player == i)
|
|
{
|
|
int bits = asPlayerResList[i][psResearch->index].ResearchStatus;
|
|
started = started || (bits & STARTED_RESEARCH) || (bits & STARTED_RESEARCH_PENDING) || (bits & RESBITS_PENDING_ONLY);
|
|
}
|
|
}
|
|
value.setProperty("started", started); // including whether an ally has started it
|
|
value.setProperty("done", IsResearchCompleted(&asPlayerResList[player][psResearch->index]));
|
|
value.setProperty("fullname", psResearch->name); // temporary
|
|
value.setProperty("name", psResearch->id); // will be changed to contain fullname
|
|
value.setProperty("id", psResearch->id);
|
|
value.setProperty("type", SCRIPT_RESEARCH);
|
|
QScriptValue results = engine->newArray(psResearch->resultStrings.size());
|
|
for (int i = 0; i < psResearch->resultStrings.size(); i++)
|
|
{
|
|
results.setProperty(i, psResearch->resultStrings[i]);
|
|
}
|
|
value.setProperty("results", results);
|
|
return value;
|
|
}
|
|
|
|
//;; \subsection{Structure\label{objects:structure}}
|
|
//;; Describes a structure (building). It inherits all the properties of the base object (see below).
|
|
//;; In addition, the following properties are defined:
|
|
//;; \begin{description}
|
|
//;; \item[status] The completeness status of the structure. It will be one of BEING_BUILT and BUILT.
|
|
//;; \item[type] The type will always be STRUCTURE.
|
|
//;; \item[cost] What it would cost to build this structure. (3.2+ only)
|
|
//;; \item[stattype] The stattype defines the type of structure. It will be one of HQ, FACTORY, POWER_GEN, RESOURCE_EXTRACTOR,
|
|
//;; LASSAT, DEFENSE, WALL, RESEARCH_LAB, REPAIR_FACILITY, CYBORG_FACTORY, VTOL_FACTORY, REARM_PAD, SAT_UPLINK, GATE
|
|
//;; and COMMAND_CONTROL.
|
|
//;; \item[modules] If the stattype is set to one of the factories, POWER_GEN or RESEARCH_LAB, then this property is set to the
|
|
//;; number of module upgrades it has.
|
|
//;; \item[canHitAir] True if the structure has anti-air capabilities. (3.2+ only)
|
|
//;; \item[canHitGround] True if the structure has anti-ground capabilities. (3.2+ only)
|
|
//;; \item[isSensor] True if the structure has sensor ability. (3.2+ only)
|
|
//;; \item[isCB] True if the structure has counter-battery ability. (3.2+ only)
|
|
//;; \item[isRadarDetector] True if the structure has radar detector ability. (3.2+ only)
|
|
//;; \item[range] Maximum range of its weapons. (3.2+ only)
|
|
//;; \item[hasIndirect] One or more of the structure's weapons are indirect. (3.2+ only)
|
|
//;; \end{description}
|
|
QScriptValue convStructure(STRUCTURE *psStruct, QScriptEngine *engine)
|
|
{
|
|
bool aa = false;
|
|
bool ga = false;
|
|
bool indirect = false;
|
|
int range = -1;
|
|
for (int i = 0; i < psStruct->numWeaps; i++)
|
|
{
|
|
if (psStruct->asWeaps[i].nStat)
|
|
{
|
|
WEAPON_STATS *psWeap = &asWeaponStats[psStruct->asWeaps[i].nStat];
|
|
aa = aa || psWeap->surfaceToAir & SHOOT_IN_AIR;
|
|
ga = ga || psWeap->surfaceToAir & SHOOT_ON_GROUND;
|
|
indirect = indirect || psWeap->movementModel == MM_INDIRECT || psWeap->movementModel == MM_HOMINGINDIRECT;
|
|
range = MAX((int)psWeap->upgrade[psStruct->player].maxRange, range);
|
|
}
|
|
}
|
|
QScriptValue value = convObj(psStruct, engine);
|
|
value.setProperty("isCB", structCBSensor(psStruct), QScriptValue::ReadOnly);
|
|
value.setProperty("isSensor", structStandardSensor(psStruct), QScriptValue::ReadOnly);
|
|
value.setProperty("canHitAir", aa, QScriptValue::ReadOnly);
|
|
value.setProperty("canHitGround", ga, QScriptValue::ReadOnly);
|
|
value.setProperty("hasIndirect", indirect, QScriptValue::ReadOnly);
|
|
value.setProperty("isRadarDetector", objRadarDetector(psStruct), QScriptValue::ReadOnly);
|
|
value.setProperty("range", range, QScriptValue::ReadOnly);
|
|
value.setProperty("status", (int)psStruct->status, QScriptValue::ReadOnly);
|
|
value.setProperty("health", 100 * psStruct->body / MAX(1, structureBody(psStruct)), QScriptValue::ReadOnly);
|
|
value.setProperty("cost", psStruct->pStructureType->powerToBuild, QScriptValue::ReadOnly);
|
|
switch (psStruct->pStructureType->type) // don't bleed our source insanities into the scripting world
|
|
{
|
|
case REF_WALL:
|
|
case REF_WALLCORNER:
|
|
case REF_GATE:
|
|
value.setProperty("stattype", (int)REF_WALL, QScriptValue::ReadOnly);
|
|
break;
|
|
case REF_GENERIC:
|
|
case REF_DEFENSE:
|
|
if (isLasSat(psStruct->pStructureType))
|
|
{
|
|
value.setProperty("stattype", (int)FAKE_REF_LASSAT, QScriptValue::ReadOnly);
|
|
break;
|
|
}
|
|
value.setProperty("stattype", (int)REF_DEFENSE, QScriptValue::ReadOnly);
|
|
break;
|
|
default:
|
|
value.setProperty("stattype", (int)psStruct->pStructureType->type, QScriptValue::ReadOnly);
|
|
break;
|
|
}
|
|
if (psStruct->pStructureType->type == REF_FACTORY || psStruct->pStructureType->type == REF_CYBORG_FACTORY
|
|
|| psStruct->pStructureType->type == REF_VTOL_FACTORY
|
|
|| psStruct->pStructureType->type == REF_RESEARCH
|
|
|| psStruct->pStructureType->type == REF_POWER_GEN)
|
|
{
|
|
value.setProperty("modules", psStruct->capacity, QScriptValue::ReadOnly);
|
|
}
|
|
else
|
|
{
|
|
value.setProperty("modules", QScriptValue::NullValue);
|
|
}
|
|
QScriptValue weaponlist = engine->newArray(psStruct->numWeaps);
|
|
for (int j = 0; j < psStruct->numWeaps; j++)
|
|
{
|
|
QScriptValue weapon = engine->newObject();
|
|
const WEAPON_STATS *psStats = asWeaponStats + psStruct->asWeaps[j].nStat;
|
|
weapon.setProperty("fullname", psStats->name, QScriptValue::ReadOnly);
|
|
weapon.setProperty("name", psStats->id, QScriptValue::ReadOnly); // will be changed to contain full name
|
|
weapon.setProperty("id", psStats->id, QScriptValue::ReadOnly);
|
|
weapon.setProperty("lastFired", psStruct->asWeaps[j].lastFired, QScriptValue::ReadOnly);
|
|
weaponlist.setProperty(j, weapon, QScriptValue::ReadOnly);
|
|
}
|
|
value.setProperty("weapons", weaponlist, QScriptValue::ReadOnly);
|
|
return value;
|
|
}
|
|
|
|
//;; \subsection{Feature}
|
|
//;; Describes a feature (a \emph{game object} not owned by any player). It inherits all the properties of the base object (see below).
|
|
//;; In addition, the following properties are defined:
|
|
//;; \begin{description}
|
|
//;; \item[type] It will always be FEATURE.
|
|
//;; \item[stattype] The type of feature. Defined types are OIL_RESOURCE, OIL_DRUM and ARTIFACT.
|
|
//;; \item[damageable] Can this feature be damaged?
|
|
//;; \end{description}
|
|
QScriptValue convFeature(FEATURE *psFeature, QScriptEngine *engine)
|
|
{
|
|
QScriptValue value = convObj(psFeature, engine);
|
|
value.setProperty("health", 100 * psFeature->psStats->body / MAX(1, psFeature->body), QScriptValue::ReadOnly);
|
|
value.setProperty("damageable", psFeature->psStats->damageable, QScriptValue::ReadOnly);
|
|
value.setProperty("stattype", psFeature->psStats->subType, QScriptValue::ReadOnly);
|
|
return value;
|
|
}
|
|
|
|
//;; \subsection{Droid}
|
|
//;; Describes a droid. It inherits all the properties of the base object (see below).
|
|
//;; In addition, the following properties are defined:
|
|
//;; \begin{description}
|
|
//;; \item[type] It will always be DROID.
|
|
//;; \item[order] The current order of the droid. This is its plan. The following orders are defined:
|
|
//;; \begin{description}
|
|
//;; \item[DORDER_ATTACK] Order a droid to attack something.
|
|
//;; \item[DORDER_MOVE] Order a droid to move somewhere.
|
|
//;; \item[DORDER_SCOUT] Order a droid to move somewhere and stop to attack anything on the way.
|
|
//;; \item[DORDER_BUILD] Order a droid to build something.
|
|
//;; \item[DORDER_HELPBUILD] Order a droid to help build something.
|
|
//;; \item[DORDER_LINEBUILD] Order a droid to build something repeatedly in a line.
|
|
//;; \item[DORDER_REPAIR] Order a droid to repair something.
|
|
//;; \item[DORDER_RETREAT] Order a droid to retreat back to HQ.
|
|
//;; \item[DORDER_PATROL] Order a droid to patrol.
|
|
//;; \item[DORDER_DEMOLISH] Order a droid to demolish something.
|
|
//;; \item[DORDER_EMBARK] Order a droid to embark on a transport.
|
|
//;; \item[DORDER_DISEMBARK] Order a transport to disembark its units at the given position.
|
|
//;; \item[DORDER_FIRESUPPORT] Order a droid to fire at whatever the target sensor is targeting. (3.2+ only)
|
|
//;; \item[DORDER_STOP] Order a droid to stop whatever it is doing. (3.2+ only)
|
|
//;; \item[DORDER_RTR] Order a droid to return for repairs. (3.2+ only)
|
|
//;; \item[DORDER_RTB] Order a droid to return to base. (3.2+ only)
|
|
//;; \item[DORDER_HOLD] Order a droid to hold its position. (3.2+ only)
|
|
//;; \item[DORDER_REARM] Order a VTOL droid to rearm. If given a target, will go to specified rearm pad. If not, will go to nearest rearm pad. (3.2+ only)
|
|
//;; \item[DORDER_OBSERVE] Order a droid to keep a target in sensor view. (3.2+ only)
|
|
//;; \item[DORDER_RECOVER] Order a droid to pick up something. (3.2+ only)
|
|
//;; \item[DORDER_RECYCLE] Order a droid to factory for recycling. (3.2+ only)
|
|
//;; \end{description}
|
|
//;; \item[action] The current action of the droid. This is how it intends to carry out its plan. The
|
|
//;; C++ code may change the action frequently as it tries to carry out its order. You never want to set
|
|
//;; the action directly, but it may be interesting to look at what it currently is.
|
|
//;; \item[droidType] The droid's type. The following types are defined:
|
|
//;; \begin{description}
|
|
//;; \item[DROID_CONSTRUCT] Trucks and cyborg constructors.
|
|
//;; \item[DROID_WEAPON] Droids with weapon turrets, except cyborgs.
|
|
//;; \item[DROID_PERSON] Non-cyborg two-legged units, like scavengers.
|
|
//;; \item[DROID_REPAIR] Units with repair turret, including repair cyborgs.
|
|
//;; \item[DROID_SENSOR] Units with sensor turret.
|
|
//;; \item[DROID_ECM] Unit with ECM jammer turret.
|
|
//;; \item[DROID_CYBORG] Cyborgs with weapons.
|
|
//;; \item[DROID_TRANSPORTER] Cyborg transporter.
|
|
//;; \item[DROID_SUPERTRANSPORTER] Droid transporter.
|
|
//;; \item[DROID_COMMAND] Commanders.
|
|
//;; \end{description}
|
|
//;; \item[group] The group this droid is member of. This is a numerical ID. If not a member of any group, will be set to \emph{null}.
|
|
//;; \item[armed] The percentage of weapon capability that is fully armed. Will be \emph{null} for droids other than VTOLs.
|
|
//;; \item[experience] Amount of experience this droid has, based on damage it has dealt to enemies.
|
|
//;; \item[cost] What it would cost to build the droid. (3.2+ only)
|
|
//;; \item[isVTOL] True if the droid is VTOL. (3.2+ only)
|
|
//;; \item[canHitAir] True if the droid has anti-air capabilities. (3.2+ only)
|
|
//;; \item[canHitGround] True if the droid has anti-ground capabilities. (3.2+ only)
|
|
//;; \item[isSensor] True if the droid has sensor ability. (3.2+ only)
|
|
//;; \item[isCB] True if the droid has counter-battery ability. (3.2+ only)
|
|
//;; \item[isRadarDetector] True if the droid has radar detector ability. (3.2+ only)
|
|
//;; \item[hasIndirect] One or more of the droid's weapons are indirect. (3.2+ only)
|
|
//;; \item[range] Maximum range of its weapons. (3.2+ only)
|
|
//;; \item[body] The body component of the droid. (3.2+ only)
|
|
//;; \item[propulsion] The propulsion component of the droid. (3.2+ only)
|
|
//;; \item[weapons] The weapon components of the droid, as an array. Contains 'name', 'id', 'armed' percentage and 'lastFired' properties. (3.2+ only)
|
|
//;; \item[cargoCapacity] Defined for transporters only: Total cargo capacity (number of items that will fit may depend on their size). (3.2+ only)
|
|
//;; \item[cargoSpace] Defined for transporters only: Cargo capacity left. (3.2+ only)
|
|
//;; \item[cargoCount] Defined for transporters only: Number of individual \emph{items} in the cargo hold. (3.2+ only)
|
|
//;; \item[cargoSize] The amount of cargo space the droid will take inside a transport. (3.2+ only)
|
|
//;; \end{description}
|
|
QScriptValue convDroid(DROID *psDroid, QScriptEngine *engine)
|
|
{
|
|
bool aa = false;
|
|
bool ga = false;
|
|
bool indirect = false;
|
|
int range = -1;
|
|
const BODY_STATS *psBodyStats = &asBodyStats[psDroid->asBits[COMP_BODY]];
|
|
|
|
for (int i = 0; i < psDroid->numWeaps; i++)
|
|
{
|
|
if (psDroid->asWeaps[i].nStat)
|
|
{
|
|
WEAPON_STATS *psWeap = &asWeaponStats[psDroid->asWeaps[i].nStat];
|
|
aa = aa || psWeap->surfaceToAir & SHOOT_IN_AIR;
|
|
ga = ga || psWeap->surfaceToAir & SHOOT_ON_GROUND;
|
|
indirect = indirect || psWeap->movementModel == MM_INDIRECT || psWeap->movementModel == MM_HOMINGINDIRECT;
|
|
range = MAX((int)psWeap->upgrade[psDroid->player].maxRange, range);
|
|
}
|
|
}
|
|
DROID_TYPE type = psDroid->droidType;
|
|
QScriptValue value = convObj(psDroid, engine);
|
|
value.setProperty("action", (int)psDroid->action, QScriptValue::ReadOnly);
|
|
if (range >= 0)
|
|
{
|
|
value.setProperty("range", range, QScriptValue::ReadOnly);
|
|
}
|
|
else
|
|
{
|
|
value.setProperty("range", QScriptValue::NullValue);
|
|
}
|
|
value.setProperty("order", (int)psDroid->order.type, QScriptValue::ReadOnly);
|
|
value.setProperty("cost", calcDroidPower(psDroid), QScriptValue::ReadOnly);
|
|
value.setProperty("hasIndirect", indirect, QScriptValue::ReadOnly);
|
|
switch (psDroid->droidType) // hide some engine craziness
|
|
{
|
|
case DROID_CYBORG_CONSTRUCT:
|
|
type = DROID_CONSTRUCT; break;
|
|
case DROID_CYBORG_SUPER:
|
|
type = DROID_CYBORG; break;
|
|
case DROID_DEFAULT:
|
|
type = DROID_WEAPON; break;
|
|
case DROID_CYBORG_REPAIR:
|
|
type = DROID_REPAIR; break;
|
|
default:
|
|
break;
|
|
}
|
|
value.setProperty("bodySize", psBodyStats->size, QScriptValue::ReadOnly);
|
|
if (psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER)
|
|
{
|
|
value.setProperty("cargoCapacity", TRANSPORTER_CAPACITY, QScriptValue::ReadOnly);
|
|
value.setProperty("cargoLeft", calcRemainingCapacity(psDroid), QScriptValue::ReadOnly);
|
|
value.setProperty("cargoCount", psDroid->psGroup->getNumMembers(), QScriptValue::ReadOnly);
|
|
}
|
|
value.setProperty("isRadarDetector", objRadarDetector(psDroid), QScriptValue::ReadOnly);
|
|
value.setProperty("isCB", cbSensorDroid(psDroid), QScriptValue::ReadOnly);
|
|
value.setProperty("isSensor", standardSensorDroid(psDroid), QScriptValue::ReadOnly);
|
|
value.setProperty("canHitAir", aa, QScriptValue::ReadOnly);
|
|
value.setProperty("canHitGround", ga, QScriptValue::ReadOnly);
|
|
value.setProperty("isVTOL", isVtolDroid(psDroid), QScriptValue::ReadOnly);
|
|
value.setProperty("droidType", (int)type, QScriptValue::ReadOnly);
|
|
value.setProperty("experience", (double)psDroid->experience / 65536.0, QScriptValue::ReadOnly);
|
|
value.setProperty("health", 100.0 / (double)psDroid->originalBody * (double)psDroid->body, QScriptValue::ReadOnly);
|
|
value.setProperty("body", asBodyStats[psDroid->asBits[COMP_BODY]].id, QScriptValue::ReadOnly);
|
|
value.setProperty("propulsion", asPropulsionStats[psDroid->asBits[COMP_PROPULSION]].id, QScriptValue::ReadOnly);
|
|
value.setProperty("armed", 0.0, QScriptValue::ReadOnly); // deprecated!
|
|
QScriptValue weaponlist = engine->newArray(psDroid->numWeaps);
|
|
for (int j = 0; j < psDroid->numWeaps; j++)
|
|
{
|
|
int armed = droidReloadBar(psDroid, &psDroid->asWeaps[j], j);
|
|
QScriptValue weapon = engine->newObject();
|
|
const WEAPON_STATS *psStats = asWeaponStats + psDroid->asWeaps[j].nStat;
|
|
weapon.setProperty("fullname", psStats->name, QScriptValue::ReadOnly);
|
|
weapon.setProperty("id", psStats->id, QScriptValue::ReadOnly); // will be changed to full name
|
|
weapon.setProperty("name", psStats->id, QScriptValue::ReadOnly);
|
|
weapon.setProperty("lastFired", psDroid->asWeaps[j].lastFired, QScriptValue::ReadOnly);
|
|
weapon.setProperty("armed", armed, QScriptValue::ReadOnly);
|
|
weaponlist.setProperty(j, weapon, QScriptValue::ReadOnly);
|
|
}
|
|
value.setProperty("weapons", weaponlist, QScriptValue::ReadOnly);
|
|
value.setProperty("cargoSize", transporterSpaceRequired(psDroid), QScriptValue::ReadOnly);
|
|
return value;
|
|
}
|
|
|
|
//;; \subsection{Base Object}
|
|
//;; Describes a basic object. It will always be a droid, structure or feature, but sometimes
|
|
//;; the difference does not matter, and you can treat any of them simply as a basic object.
|
|
//;; The following properties are defined:
|
|
//;; \begin{description}
|
|
//;; \item[type] It will be one of DROID, STRUCTURE or FEATURE.
|
|
//;; \item[id] The unique ID of this object.
|
|
//;; \item[x] X position of the object in tiles.
|
|
//;; \item[y] Y position of the object in tiles.
|
|
//;; \item[z] Z (height) position of the object in tiles.
|
|
//;; \item[player] The player owning this object.
|
|
//;; \item[selected] A boolean saying whether 'selectedPlayer' has selected this object.
|
|
//;; \item[name] A user-friendly name for this object.
|
|
//;; \item[health] Percentage that this object is damaged (where 100% means not damaged at all).
|
|
//;; \item[armour] Amount of armour points that protect against kinetic weapons.
|
|
//;; \item[thermal] Amount of thermal protection that protect against heat based weapons.
|
|
//;; \item[born] The game time at which this object was produced or came into the world. (3.2+ only)
|
|
//;; \end{description}
|
|
QScriptValue convObj(BASE_OBJECT *psObj, QScriptEngine *engine)
|
|
{
|
|
QScriptValue value = engine->newObject();
|
|
ASSERT_OR_RETURN(value, psObj, "No object for conversion");
|
|
value.setProperty("id", psObj->id, QScriptValue::ReadOnly);
|
|
value.setProperty("x", map_coord(psObj->pos.x), QScriptValue::ReadOnly);
|
|
value.setProperty("y", map_coord(psObj->pos.y), QScriptValue::ReadOnly);
|
|
value.setProperty("z", map_coord(psObj->pos.z), QScriptValue::ReadOnly);
|
|
value.setProperty("player", psObj->player, QScriptValue::ReadOnly);
|
|
value.setProperty("armour", objArmour(psObj, WC_KINETIC), QScriptValue::ReadOnly);
|
|
value.setProperty("thermal", objArmour(psObj, WC_HEAT), QScriptValue::ReadOnly);
|
|
value.setProperty("type", psObj->type, QScriptValue::ReadOnly);
|
|
value.setProperty("selected", psObj->selected, QScriptValue::ReadOnly);
|
|
value.setProperty("name", objInfo(psObj), QScriptValue::ReadOnly);
|
|
value.setProperty("born", psObj->born, QScriptValue::ReadOnly);
|
|
GROUPMAP *psMap = groups.value(engine);
|
|
if (psMap->contains(psObj))
|
|
{
|
|
int group = psMap->value(psObj);
|
|
value.setProperty("group", group, QScriptValue::ReadOnly);
|
|
}
|
|
else
|
|
{
|
|
value.setProperty("group", QScriptValue::NullValue);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
//;; \subsection{Template}
|
|
//;; Describes a template type. Templates are droid designs that a player has created. This type is experimental
|
|
//;; and subject to change.
|
|
//;; The following properties are defined:
|
|
//;; \begin{description}
|
|
//;; \item[id] The ID of this object.
|
|
//;; \item[name] Name of the template.
|
|
//;; \item[cost] The power cost of the template if put into production.
|
|
//;; \item[droidType] The type of droid that would be created.
|
|
//;; \item[body] The name of the body type.
|
|
//;; \item[propulsion] The name of the propulsion type.
|
|
//;; \item[brain] The name of the brain type.
|
|
//;; \item[repair] The name of the repair type.
|
|
//;; \item[ecm] The name of the ECM (electronic counter-measure) type.
|
|
//;; \item[construct] The name of the construction type.
|
|
//;; \item[weapons] An array of weapon names attached to this template.
|
|
//;; \end{description}
|
|
QScriptValue convTemplate(DROID_TEMPLATE *psTempl, QScriptEngine *engine)
|
|
{
|
|
QScriptValue value = engine->newObject();
|
|
ASSERT_OR_RETURN(value, psTempl, "No object for conversion");
|
|
value.setProperty("fullname", psTempl->name, QScriptValue::ReadOnly);
|
|
value.setProperty("name", psTempl->id, QScriptValue::ReadOnly);
|
|
value.setProperty("id", psTempl->id, QScriptValue::ReadOnly);
|
|
value.setProperty("points", calcTemplateBuild(psTempl), QScriptValue::ReadOnly);
|
|
value.setProperty("power", calcTemplatePower(psTempl), QScriptValue::ReadOnly); // deprecated, use cost below
|
|
value.setProperty("cost", calcTemplatePower(psTempl), QScriptValue::ReadOnly);
|
|
value.setProperty("droidType", psTempl->droidType, QScriptValue::ReadOnly);
|
|
value.setProperty("body", (asBodyStats + psTempl->asParts[COMP_BODY])->id, QScriptValue::ReadOnly);
|
|
value.setProperty("propulsion", (asPropulsionStats + psTempl->asParts[COMP_PROPULSION])->id, QScriptValue::ReadOnly);
|
|
value.setProperty("brain", (asBrainStats + psTempl->asParts[COMP_BRAIN])->id, QScriptValue::ReadOnly);
|
|
value.setProperty("repair", (asRepairStats + psTempl->asParts[COMP_REPAIRUNIT])->id, QScriptValue::ReadOnly);
|
|
value.setProperty("ecm", (asECMStats + psTempl->asParts[COMP_ECM])->id, QScriptValue::ReadOnly);
|
|
value.setProperty("sensor", (asSensorStats + psTempl->asParts[COMP_SENSOR])->id, QScriptValue::ReadOnly);
|
|
value.setProperty("construct", (asConstructStats + psTempl->asParts[COMP_CONSTRUCT])->id, QScriptValue::ReadOnly);
|
|
QScriptValue weaponlist = engine->newArray(psTempl->numWeaps);
|
|
for (int j = 0; j < psTempl->numWeaps; j++)
|
|
{
|
|
weaponlist.setProperty(j, QScriptValue((asWeaponStats + psTempl->asWeaps[j])->id), QScriptValue::ReadOnly);
|
|
}
|
|
value.setProperty("weapons", weaponlist);
|
|
return value;
|
|
}
|
|
|
|
QScriptValue convMax(BASE_OBJECT *psObj, QScriptEngine *engine)
|
|
{
|
|
if (!psObj)
|
|
{
|
|
return QScriptValue::NullValue;
|
|
}
|
|
switch (psObj->type)
|
|
{
|
|
case OBJ_DROID: return convDroid((DROID *)psObj, engine);
|
|
case OBJ_STRUCTURE: return convStructure((STRUCTURE *)psObj, engine);
|
|
case OBJ_FEATURE: return convFeature((FEATURE *)psObj, engine);
|
|
default: ASSERT(false, "No such supported object type"); return convObj(psObj, engine);
|
|
}
|
|
}
|
|
|
|
BASE_OBJECT *IdToObject(OBJECT_TYPE type, int id, int player)
|
|
{
|
|
switch (type)
|
|
{
|
|
case OBJ_DROID: return IdToDroid(id, player);
|
|
case OBJ_FEATURE: return IdToFeature(id, player);
|
|
case OBJ_STRUCTURE: return IdToStruct(id, player);
|
|
default: return NULL;
|
|
}
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
// Group system
|
|
//
|
|
|
|
bool loadGroup(QScriptEngine *engine, int groupId, int objId)
|
|
{
|
|
BASE_OBJECT *psObj = IdToPointer(objId, ANYPLAYER);
|
|
return groupAddObject(psObj, groupId, engine);
|
|
}
|
|
|
|
bool saveGroups(WzConfig &ini, QScriptEngine *engine)
|
|
{
|
|
// Save group info as a list of group memberships for each droid
|
|
GROUPMAP *psMap = groups.value(engine);
|
|
for (GROUPMAP::const_iterator i = psMap->constBegin(); i != psMap->constEnd(); ++i)
|
|
{
|
|
QStringList value;
|
|
BASE_OBJECT *psObj = i.key();
|
|
if (ini.contains(QString::number(psObj->id)))
|
|
{
|
|
value.push_back(ini.value(QString::number(psObj->id)).toString());
|
|
}
|
|
value.push_back(QString::number(i.value()));
|
|
ini.setValue(QString::number(psObj->id), value);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
// Label system (function defined in qtscript.h header)
|
|
//
|
|
|
|
// Load labels
|
|
bool loadLabels(const char *filename)
|
|
{
|
|
int groupidx = -1;
|
|
|
|
if (!PHYSFS_exists(filename))
|
|
{
|
|
debug(LOG_SAVE, "No %s found -- not adding any labels", filename);
|
|
return false;
|
|
}
|
|
WzConfig ini(filename, WzConfig::ReadOnly);
|
|
labels.clear();
|
|
QStringList list = ini.childGroups();
|
|
debug(LOG_SAVE, "Loading %d labels...", list.size());
|
|
for (int i = 0; i < list.size(); ++i)
|
|
{
|
|
ini.beginGroup(list[i]);
|
|
labeltype p;
|
|
QString label(ini.value("label").toString());
|
|
if (labels.contains(label))
|
|
{
|
|
debug(LOG_ERROR, "Duplicate label found");
|
|
}
|
|
else if (list[i].startsWith("position"))
|
|
{
|
|
p.p1 = ini.vector2i("pos");
|
|
p.p2 = p.p1;
|
|
p.type = SCRIPT_POSITION;
|
|
p.player = ALL_PLAYERS;
|
|
p.id = -1;
|
|
labels.insert(label, p);
|
|
}
|
|
else if (list[i].startsWith("area"))
|
|
{
|
|
p.p1 = ini.vector2i("pos1");
|
|
p.p2 = ini.vector2i("pos2");
|
|
p.type = SCRIPT_AREA;
|
|
p.player = ini.value("player", ALL_PLAYERS).toInt();
|
|
p.id = ini.value("triggered", -1).toInt();
|
|
labels.insert(label, p);
|
|
}
|
|
else if (list[i].startsWith("object"))
|
|
{
|
|
p.id = ini.value("id").toInt();
|
|
p.type = ini.value("type").toInt();
|
|
p.player = ini.value("player").toInt();
|
|
labels.insert(label, p);
|
|
}
|
|
else if (list[i].startsWith("group"))
|
|
{
|
|
p.id = groupidx--;
|
|
p.type = SCRIPT_GROUP;
|
|
p.player = ini.value("player").toInt();
|
|
QStringList list = ini.value("members").toStringList();
|
|
for (QStringList::iterator j = list.begin(); j != list.end(); j++)
|
|
{
|
|
int id = (*j).toInt();
|
|
BASE_OBJECT *psObj = IdToPointer(id, p.player);
|
|
ASSERT(psObj, "Unit %d belonging to player %d not found from label %s",
|
|
id, p.player, list[i].toUtf8().constData());
|
|
p.idlist += id;
|
|
}
|
|
labels.insert(label, p);
|
|
}
|
|
else
|
|
{
|
|
debug(LOG_ERROR, "Misnamed group in %s", filename);
|
|
}
|
|
ini.endGroup();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool writeLabels(const char *filename)
|
|
{
|
|
int c[4]; // make unique, incremental section names
|
|
memset(c, 0, sizeof(c));
|
|
WzConfig ini(filename);
|
|
for (LABELMAP::const_iterator i = labels.constBegin(); i != labels.constEnd(); i++)
|
|
{
|
|
QString key = i.key();
|
|
labeltype l = i.value();
|
|
if (l.type == SCRIPT_POSITION)
|
|
{
|
|
ini.beginGroup("position_" + QString::number(c[0]++));
|
|
ini.setVector2i("pos", l.p1);
|
|
ini.setValue("label", key);
|
|
ini.endGroup();
|
|
}
|
|
else if (l.type == SCRIPT_AREA)
|
|
{
|
|
ini.beginGroup("area_" + QString::number(c[1]++));
|
|
ini.setVector2i("pos1", l.p1);
|
|
ini.setVector2i("pos2", l.p2);
|
|
ini.setValue("label", key);
|
|
ini.setValue("player", l.player);
|
|
ini.setValue("triggered", l.id);
|
|
ini.endGroup();
|
|
}
|
|
else if (l.type == SCRIPT_GROUP)
|
|
{
|
|
ini.beginGroup("group_" + QString::number(c[2]++));
|
|
ini.setValue("player", l.player);
|
|
QStringList list;
|
|
for (int i = 0; i < l.idlist.size(); i++)
|
|
{
|
|
list += QString::number(l.idlist[i]);
|
|
}
|
|
ini.setValue("members", list);
|
|
ini.setValue("label", key);
|
|
ini.endGroup();
|
|
}
|
|
else
|
|
{
|
|
ini.beginGroup("object_" + QString::number(c[3]++));
|
|
ini.setValue("id", l.id);
|
|
ini.setValue("player", l.player);
|
|
ini.setValue("type", l.type);
|
|
ini.setValue("label", key);
|
|
ini.endGroup();
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
// Script functions
|
|
//
|
|
// All script functions should be prefixed with "js_" then followed by same name as in script.
|
|
|
|
//-- \subsection{getWeaponInfo(weapon id)}
|
|
//-- Return information about a particular weapon type. DEPRECATED - query the Stats object instead.
|
|
static QScriptValue js_getWeaponInfo(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QString id = context->argument(0).toString();
|
|
int idx = getCompFromName(COMP_WEAPON, id);
|
|
SCRIPT_ASSERT(context, idx >= 0, "No such weapon: %s", id.toUtf8().constData());
|
|
WEAPON_STATS *psStats = asWeaponStats + idx;
|
|
QScriptValue info = engine->newObject();
|
|
info.setProperty("id", id);
|
|
info.setProperty("name", psStats->name);
|
|
info.setProperty("impactClass", psStats->weaponClass == WC_KINETIC ? "KINETIC" : "HEAT");
|
|
info.setProperty("damage", psStats->base.damage);
|
|
info.setProperty("firePause", psStats->base.firePause);
|
|
info.setProperty("fireOnMove", psStats->fireOnMove);
|
|
return QScriptValue(info);
|
|
}
|
|
|
|
//-- \subsection{resetArea(label[, filter])}
|
|
//-- Reset the trigger on an area. Next time a unit enters the area, it will trigger
|
|
//-- an area event. Optionally add a filter on it in the second parameter, which can
|
|
//-- be a specific player to watch for, or ALL_PLAYERS by default.
|
|
//-- This is a fast operation of O(log n) algorithmic complexity.
|
|
static QScriptValue js_resetArea(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
QString labelName = context->argument(0).toString();
|
|
SCRIPT_ASSERT(context, labels.contains(labelName), "Label %s not found", labelName.toUtf8().constData());
|
|
labeltype &l = labels[labelName];
|
|
l.id = -1; // make active again
|
|
if (context->argumentCount() > 1)
|
|
{
|
|
l.player = context->argument(1).toInt32();
|
|
}
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{enumLabels([filter])}
|
|
//-- Returns a string list of labels that exist for this map. The optional filter
|
|
//-- parameter can be used to only return labels of one specific type. (3.2+ only)
|
|
static QScriptValue js_enumLabels(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QStringList matches;
|
|
if (context->argumentCount() > 0) // filter
|
|
{
|
|
SCRIPT_TYPE type = (SCRIPT_TYPE)context->argument(0).toInt32();
|
|
for (LABELMAP::iterator i = labels.begin(); i != labels.end(); i++)
|
|
{
|
|
labeltype &l = (*i);
|
|
if (l.type == type)
|
|
{
|
|
matches += i.key();
|
|
}
|
|
}
|
|
}
|
|
else // fast path, give all
|
|
{
|
|
matches = labels.keys();
|
|
}
|
|
QScriptValue result = engine->newArray(matches.size());
|
|
for (int i = 0; i < matches.size(); i++)
|
|
{
|
|
result.setProperty(i, QScriptValue(matches[i]), QScriptValue::ReadOnly);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//-- \subsection{addLabel(object, label)}
|
|
//-- Add a label to a game object. If there already is a label by that name, it is overwritten.
|
|
//-- This is a fast operation of O(log n) algorithmic complexity.
|
|
static QScriptValue js_addLabel(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
struct labeltype value;
|
|
QScriptValue qval = context->argument(0);
|
|
value.type = qval.property("type").toInt32();
|
|
value.id = qval.property("id").toInt32();
|
|
value.player = qval.property("player").toInt32();
|
|
value.p1.x = world_coord(qval.property("x").toInt32());
|
|
value.p1.y = world_coord(qval.property("y").toInt32());
|
|
value.p2.x = world_coord(qval.property("x2").toInt32());
|
|
value.p2.y = world_coord(qval.property("y2").toInt32());
|
|
if (value.type == OBJ_DROID || value.type == OBJ_STRUCTURE || value.type == OBJ_FEATURE)
|
|
{
|
|
BASE_OBJECT *psObj = IdToObject((OBJECT_TYPE)value.type, value.id, value.player);
|
|
SCRIPT_ASSERT(context, psObj, "Object id %d not found belonging to player %d", value.id, value.player);
|
|
}
|
|
QString key = context->argument(1).toString();
|
|
labels.insert(key, value);
|
|
updateLabelModel();
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{removeLabel(label)}
|
|
//-- Remove a label from the game. Returns the number of labels removed, which should normally be
|
|
//-- either 1 (label found) or 0 (label not found).
|
|
static QScriptValue js_removeLabel(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QString key = context->argument(0).toString();
|
|
int result = labels.remove(key);
|
|
updateLabelModel();
|
|
return QScriptValue(result);
|
|
}
|
|
|
|
//-- \subsection{getLabel(object)}
|
|
//-- Get a label string belonging to a game object. If the object has multiple labels, only the first
|
|
//-- label found will be returned. If the object has no labels, null is returned.
|
|
//-- This is a relatively slow operation of O(n) algorithmic complexity.
|
|
static QScriptValue js_getLabel(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
struct labeltype value;
|
|
QScriptValue objparam = context->argument(0);
|
|
value.id = objparam.property("id").toInt32();
|
|
value.player = objparam.property("player").toInt32();
|
|
value.type = (OBJECT_TYPE)objparam.property("type").toInt32();
|
|
QString label = labels.key(value, QString());
|
|
if (!label.isEmpty())
|
|
{
|
|
return QScriptValue(label);
|
|
}
|
|
return QScriptValue::NullValue;
|
|
}
|
|
|
|
//-- \subsection{getObject(label | x, y | type, player, id)}
|
|
//-- Fetch something denoted by a label, a map position or its object ID. A label refers to an area,
|
|
//-- a position or a \emph{game object} on the map defined using the map editor and stored
|
|
//-- together with the map. In this case, the only argument is a text label. The function
|
|
//-- returns an object that has a type variable defining what it is (in case this is
|
|
//-- unclear). This type will be one of DROID, STRUCTURE, FEATURE, AREA, GROUP or POSITION.
|
|
//-- The AREA has defined 'x', 'y', 'x2', and 'y2', while POSITION has only defined 'x' and 'y'.
|
|
//-- The GROUP type has defined 'type' and 'id' of the group, which can be passed to enumGroup().
|
|
//-- This is a fast operation of O(log n) algorithmic complexity. If the label is not found, an
|
|
//-- undefined value is returned. If whatever object the label should point at no longer exists,
|
|
//-- a null value is returned.
|
|
//-- You can also fetch a STRUCTURE or FEATURE type game object from a given map position (if any).
|
|
//-- This is a very fast operation of O(1) algorithmic complexity. Droids cannot be fetched in this
|
|
//-- manner, since they do not a unique placement on map tiles. Finally, you can fetch an object using
|
|
//-- its ID, in which case you need to pass its type, owner and unique object ID. This is an
|
|
//-- operation of O(n) algorithmic complexity.
|
|
static QScriptValue js_getObject(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
if (context->argumentCount() == 2) // get at position case
|
|
{
|
|
int x = context->argument(0).toInt32();
|
|
int y = context->argument(1).toInt32();
|
|
SCRIPT_ASSERT(context, tileOnMap(x, y), "Map position (%d, %d) not on the map!", x, y);
|
|
const MAPTILE *psTile = mapTile(x, y);
|
|
return QScriptValue(convMax(psTile->psObject, engine));
|
|
}
|
|
else if (context->argumentCount() == 3) // get by ID case
|
|
{
|
|
OBJECT_TYPE type = (OBJECT_TYPE)context->argument(0).toInt32();
|
|
int player = context->argument(1).toInt32();
|
|
int id = context->argument(2).toInt32();
|
|
SCRIPT_ASSERT_PLAYER(context, player);
|
|
return QScriptValue(convMax(IdToObject(type, id, player), engine));
|
|
}
|
|
// get by label case
|
|
BASE_OBJECT *psObj;
|
|
QString label = context->argument(0).toString();
|
|
QScriptValue ret;
|
|
if (labels.contains(label))
|
|
{
|
|
ret = engine->newObject();
|
|
labeltype p = labels.value(label);
|
|
switch (p.type)
|
|
{
|
|
case SCRIPT_AREA:
|
|
ret.setProperty("x2", map_coord(p.p2.x), QScriptValue::ReadOnly);
|
|
ret.setProperty("y2", map_coord(p.p2.y), QScriptValue::ReadOnly);
|
|
ret.setProperty("type", p.type, QScriptValue::ReadOnly);
|
|
// fall through
|
|
case SCRIPT_POSITION:
|
|
ret.setProperty("x", map_coord(p.p1.x), QScriptValue::ReadOnly);
|
|
ret.setProperty("y", map_coord(p.p1.y), QScriptValue::ReadOnly);
|
|
ret.setProperty("type", p.type, QScriptValue::ReadOnly);
|
|
break;
|
|
case SCRIPT_GROUP:
|
|
ret.setProperty("id", p.id, QScriptValue::ReadOnly);
|
|
ret.setProperty("type", p.type, QScriptValue::ReadOnly);
|
|
break;
|
|
case OBJ_DROID:
|
|
case OBJ_FEATURE:
|
|
case OBJ_STRUCTURE:
|
|
psObj = IdToObject((OBJECT_TYPE)p.type, p.id, p.player);
|
|
return convMax(psObj, engine);
|
|
default:
|
|
ASSERT(false, "Bad object label type found for label %s!", label.toUtf8().constData());
|
|
break;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
//-- \subsection{enumBlips(player)}
|
|
//-- Return an array containing all the non-transient radar blips that the given player
|
|
//-- can see. This includes sensors revealed by radar detectors, as well as ECM jammers.
|
|
//-- It does not include units going out of view.
|
|
static QScriptValue js_enumBlips(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QList<Position> matches;
|
|
int player = context->argument(0).toInt32();
|
|
SCRIPT_ASSERT_PLAYER(context, player);
|
|
for (BASE_OBJECT *psSensor = apsSensorList[0]; psSensor; psSensor = psSensor->psNextFunc)
|
|
{
|
|
if (psSensor->visible[player] > 0 && psSensor->visible[player] < UBYTE_MAX)
|
|
{
|
|
matches.push_back(psSensor->pos);
|
|
}
|
|
}
|
|
QScriptValue result = engine->newArray(matches.size());
|
|
for (int i = 0; i < matches.size(); i++)
|
|
{
|
|
Position p = matches.at(i);
|
|
QScriptValue v = engine->newObject();
|
|
v.setProperty("x", map_coord(p.x), QScriptValue::ReadOnly);
|
|
v.setProperty("y", map_coord(p.y), QScriptValue::ReadOnly);
|
|
v.setProperty("type", SCRIPT_POSITION, QScriptValue::ReadOnly);
|
|
result.setProperty(i, v);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//-- \subsection{enumSelected()}
|
|
//-- Return an array containing all game objects currently selected by the host player.
|
|
QScriptValue js_enumSelected(QScriptContext *, QScriptEngine *engine)
|
|
{
|
|
QList<BASE_OBJECT *> matches;
|
|
for (DROID *psDroid = apsDroidLists[selectedPlayer]; psDroid; psDroid = psDroid->psNext)
|
|
{
|
|
if (psDroid->selected)
|
|
{
|
|
matches.push_back(psDroid);
|
|
}
|
|
}
|
|
for (STRUCTURE *psStruct = apsStructLists[selectedPlayer]; psStruct; psStruct = psStruct->psNext)
|
|
{
|
|
if (psStruct->selected)
|
|
{
|
|
matches.push_back(psStruct);
|
|
}
|
|
}
|
|
// TODO - also add selected delivery points
|
|
QScriptValue result = engine->newArray(matches.size());
|
|
for (int i = 0; i < matches.size(); i++)
|
|
{
|
|
result.setProperty(i, convMax(matches.at(i), engine));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//-- \subsection{enumGateways()}
|
|
//-- Return an array containing all the gateways on the current map. The array contains object with the properties
|
|
//-- x1, y1, x2 and y2.
|
|
static QScriptValue js_enumGateways(QScriptContext *, QScriptEngine *engine)
|
|
{
|
|
QScriptValue result = engine->newArray(gwNumGateways());
|
|
int i = 0;
|
|
for (GATEWAY *psGateway = gwGetGateways(); psGateway; psGateway = psGateway->psNext)
|
|
{
|
|
QScriptValue v = engine->newObject();
|
|
v.setProperty("x1", psGateway->x1, QScriptValue::ReadOnly);
|
|
v.setProperty("y1", psGateway->y1, QScriptValue::ReadOnly);
|
|
v.setProperty("x2", psGateway->x2, QScriptValue::ReadOnly);
|
|
v.setProperty("y2", psGateway->y2, QScriptValue::ReadOnly);
|
|
result.setProperty(i++, v);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//-- \subsection{enumTemplates(player)}
|
|
//-- Return an array containing all the buildable templates for the given player.
|
|
static QScriptValue js_enumTemplates(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
int player = context->argument(0).toInt32();
|
|
int count = 0;
|
|
for (DROID_TEMPLATE *psCurr = apsDroidTemplates[player]; psCurr != NULL; psCurr = psCurr->psNext) count++;
|
|
QScriptValue result = engine->newArray(count);
|
|
count = 0;
|
|
for (DROID_TEMPLATE *psCurr = apsDroidTemplates[player]; psCurr != NULL; psCurr = psCurr->psNext)
|
|
{
|
|
result.setProperty(count, convTemplate(psCurr, engine));
|
|
count++;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//-- \subsection{enumGroup(group)}
|
|
//-- Return an array containing all the members of a given group.
|
|
static QScriptValue js_enumGroup(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
int groupId = context->argument(0).toInt32();
|
|
QList<BASE_OBJECT *> matches;
|
|
GROUPMAP *psMap = groups.value(engine);
|
|
|
|
for (GROUPMAP::const_iterator i = psMap->constBegin(); i != psMap->constEnd(); ++i)
|
|
{
|
|
if (i.value() == groupId)
|
|
{
|
|
matches.push_back(i.key());
|
|
}
|
|
}
|
|
QScriptValue result = engine->newArray(matches.size());
|
|
for (int i = 0; i < matches.size(); i++)
|
|
{
|
|
BASE_OBJECT *psObj = matches.at(i);
|
|
result.setProperty(i, convMax(psObj, engine));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//-- \subsection{newGroup()}
|
|
//-- Allocate a new group. Returns its numerical ID. Deprecated since 3.2 - you should now
|
|
//-- use your own number scheme for groups.
|
|
static QScriptValue js_newGroup(QScriptContext *, QScriptEngine *engine)
|
|
{
|
|
static int i = 1; // group zero reserved
|
|
return QScriptValue(i++);
|
|
}
|
|
|
|
//-- \subsection{activateStructure(structure, [target[, ability]])}
|
|
//-- Activate a special ability on a structure. Currently only works on the lassat.
|
|
//-- The lassat needs a target.
|
|
static QScriptValue js_activateStructure(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
QScriptValue structVal = context->argument(0);
|
|
int id = structVal.property("id").toInt32();
|
|
int player = structVal.property("player").toInt32();
|
|
STRUCTURE *psStruct = IdToStruct(id, player);
|
|
SCRIPT_ASSERT(context, psStruct, "No such structure id %d belonging to player %d", id, player);
|
|
// ... and then do nothing with psStruct yet
|
|
QScriptValue objVal = context->argument(1);
|
|
int oid = objVal.property("id").toInt32();
|
|
int oplayer = objVal.property("player").toInt32();
|
|
OBJECT_TYPE otype = (OBJECT_TYPE)objVal.property("type").toInt32();
|
|
BASE_OBJECT *psObj = IdToObject(otype, oid, oplayer);
|
|
SCRIPT_ASSERT(context, psObj, "No such object id %d belonging to player %d", oid, oplayer);
|
|
orderStructureObj(player, psObj);
|
|
return QScriptValue(true);
|
|
}
|
|
|
|
//-- \subsection{findResearch(research)}
|
|
//-- Return list of research items remaining to be researched for the given research item. (3.2+ only)
|
|
static QScriptValue js_findResearch(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QList<RESEARCH *> list;
|
|
QString resName = context->argument(0).toString();
|
|
int player = engine->globalObject().property("me").toInt32();
|
|
RESEARCH *psTarget = getResearch(resName.toUtf8().constData());
|
|
SCRIPT_ASSERT(context, psTarget, "No such research: %s", resName.toUtf8().constData());
|
|
PLAYER_RESEARCH *plrRes = &asPlayerResList[player][psTarget->index];
|
|
if (IsResearchStartedPending(plrRes) || IsResearchCompleted(plrRes))
|
|
{
|
|
return engine->newArray(0); // return empty array
|
|
}
|
|
debug(LOG_SCRIPT, "Find reqs for %s for player %d", resName.toUtf8().constData(), player);
|
|
// Go down the requirements list for the desired tech
|
|
QList<RESEARCH *> reslist;
|
|
RESEARCH *cur = psTarget;
|
|
while (cur)
|
|
{
|
|
if (!(asPlayerResList[player][cur->index].ResearchStatus & RESEARCHED))
|
|
{
|
|
debug(LOG_SCRIPT, "Added research in %d's %s for %s", player, getID(cur), getID(psTarget));
|
|
list.append(cur);
|
|
}
|
|
RESEARCH *prev = cur;
|
|
cur = NULL;
|
|
if (prev->pPRList.size())
|
|
{
|
|
cur = &asResearch[prev->pPRList[0]]; // get first pre-req
|
|
}
|
|
for (int i = 1; i < prev->pPRList.size(); i++)
|
|
{
|
|
// push any other pre-reqs on the stack
|
|
reslist += &asResearch[prev->pPRList[i]];
|
|
}
|
|
if (!cur && reslist.size())
|
|
{
|
|
cur = reslist.takeFirst(); // retrieve options from the stack
|
|
}
|
|
}
|
|
QScriptValue retval = engine->newArray(list.size());
|
|
for (int i = 0; i < list.size(); i++)
|
|
{
|
|
retval.setProperty(i, convResearch(list[i], engine, player));
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
//-- \subsection{pursueResearch(lab, research)}
|
|
//-- Start researching the first available technology on the way to the given technology.
|
|
//-- First parameter is the structure to research in, which must be a research lab. The
|
|
//-- second parameter is the technology to pursue, as a text string as defined in "research.ini".
|
|
//-- The second parameter may also be an array of such strings. The first technology that has
|
|
//-- not yet been researched in that list will be pursued.
|
|
static QScriptValue js_pursueResearch(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QScriptValue structVal = context->argument(0);
|
|
int id = structVal.property("id").toInt32();
|
|
int player = structVal.property("player").toInt32();
|
|
STRUCTURE *psStruct = IdToStruct(id, player);
|
|
SCRIPT_ASSERT(context, psStruct, "No such structure id %d belonging to player %d", id, player);
|
|
QScriptValue list = context->argument(1);
|
|
RESEARCH *psResearch = NULL; // Dummy initialisation.
|
|
if (list.isArray())
|
|
{
|
|
int length = list.property("length").toInt32();
|
|
int k;
|
|
for (k = 0; k < length; k++)
|
|
{
|
|
QString resName = list.property(k).toString();
|
|
psResearch = getResearch(resName.toUtf8().constData());
|
|
SCRIPT_ASSERT(context, psResearch, "No such research: %s", resName.toUtf8().constData());
|
|
PLAYER_RESEARCH *plrRes = &asPlayerResList[player][psResearch->index];
|
|
if (!IsResearchStartedPending(plrRes) && !IsResearchCompleted(plrRes))
|
|
{
|
|
break; // use this one
|
|
}
|
|
}
|
|
if (k == length)
|
|
{
|
|
debug(LOG_SCRIPT, "Exhausted research list -- doing nothing");
|
|
return QScriptValue(false);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QString resName = list.toString();
|
|
psResearch = getResearch(resName.toUtf8().constData());
|
|
SCRIPT_ASSERT(context, psResearch, "No such research: %s", resName.toUtf8().constData());
|
|
PLAYER_RESEARCH *plrRes = &asPlayerResList[player][psResearch->index];
|
|
if (IsResearchStartedPending(plrRes) || IsResearchCompleted(plrRes))
|
|
{
|
|
debug(LOG_SCRIPT, "%s has already been researched!", resName.toUtf8().constData());
|
|
return QScriptValue(false);
|
|
}
|
|
}
|
|
SCRIPT_ASSERT(context, psStruct->pStructureType->type == REF_RESEARCH, "Not a research lab: %s", objInfo(psStruct));
|
|
RESEARCH_FACILITY *psResLab = (RESEARCH_FACILITY *)psStruct->pFunctionality;
|
|
SCRIPT_ASSERT(context, psResLab->psSubject == NULL, "Research lab not ready");
|
|
// Go down the requirements list for the desired tech
|
|
QList<RESEARCH *> reslist;
|
|
RESEARCH *cur = psResearch;
|
|
int iterations = 0; // Only used to assert we're not stuck in the loop.
|
|
while (cur)
|
|
{
|
|
if (researchAvailable(cur->index, player, ModeQueue))
|
|
{
|
|
bool started = false;
|
|
for (int i = 0; i < game.maxPlayers; i++)
|
|
{
|
|
if (i == player || (aiCheckAlliances(player, i) && alliancesSharedResearch(game.alliance)))
|
|
{
|
|
int bits = asPlayerResList[i][cur->index].ResearchStatus;
|
|
started = started || (bits & STARTED_RESEARCH) || (bits & STARTED_RESEARCH_PENDING)
|
|
|| (bits & RESBITS_PENDING_ONLY) || (bits & RESEARCHED);
|
|
}
|
|
}
|
|
if (!started) // found relevant item on the path?
|
|
{
|
|
sendResearchStatus(psStruct, cur->index, player, true);
|
|
#if defined (DEBUG)
|
|
char sTemp[128];
|
|
sprintf(sTemp, "player:%d starts topic from script: %s", player, getID(cur));
|
|
NETlogEntry(sTemp, SYNC_FLAG, 0);
|
|
#endif
|
|
debug(LOG_SCRIPT, "Started research in %d's %s(%d) of %s", player,
|
|
objInfo(psStruct), psStruct->id, getName(cur));
|
|
return QScriptValue(true);
|
|
}
|
|
}
|
|
RESEARCH *prev = cur;
|
|
cur = NULL;
|
|
if (!prev->pPRList.empty())
|
|
{
|
|
cur = &asResearch[prev->pPRList[0]]; // get first pre-req
|
|
}
|
|
for (int i = 1; i < prev->pPRList.size(); i++)
|
|
{
|
|
// push any other pre-reqs on the stack
|
|
reslist += &asResearch[prev->pPRList[i]];
|
|
}
|
|
if (!cur && !reslist.empty())
|
|
{
|
|
cur = reslist.takeFirst(); // retrieve options from the stack
|
|
}
|
|
ASSERT_OR_RETURN(QScriptValue(false), ++iterations < asResearch.size()*100 || !cur, "Possible cyclic dependencies in prerequisites, possibly of research \"%s\".", getName(cur));
|
|
}
|
|
debug(LOG_SCRIPT, "No research topic found for %s(%d)", objInfo(psStruct), psStruct->id);
|
|
return QScriptValue(false); // none found
|
|
}
|
|
|
|
//-- \subsection{getResearch(research[, player])}
|
|
//-- Fetch information about a given technology item, given by a string that matches
|
|
//-- its definition in "research.ini". If not found, returns null.
|
|
static QScriptValue js_getResearch(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
int player = 0;
|
|
if (context->argumentCount() == 2)
|
|
{
|
|
player = context->argument(1).toInt32();
|
|
}
|
|
else
|
|
{
|
|
player = engine->globalObject().property("me").toInt32();
|
|
}
|
|
QString resName = context->argument(0).toString();
|
|
RESEARCH *psResearch = getResearch(resName.toUtf8().constData());
|
|
if (!psResearch)
|
|
{
|
|
return QScriptValue::NullValue;
|
|
}
|
|
return convResearch(psResearch, engine, player);
|
|
}
|
|
|
|
//-- \subsection{enumResearch()}
|
|
//-- Returns an array of all research objects that are currently and immediately available for research.
|
|
static QScriptValue js_enumResearch(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QList<RESEARCH *> reslist;
|
|
int player = engine->globalObject().property("me").toInt32();
|
|
for (int i = 0; i < asResearch.size(); i++)
|
|
{
|
|
RESEARCH *psResearch = &asResearch[i];
|
|
if (!IsResearchCompleted(&asPlayerResList[player][i]) && researchAvailable(i, player, ModeQueue))
|
|
{
|
|
reslist += psResearch;
|
|
}
|
|
}
|
|
QScriptValue result = engine->newArray(reslist.size());
|
|
for (int i = 0; i < reslist.size(); i++)
|
|
{
|
|
result.setProperty(i, convResearch(reslist[i], engine, player));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//-- \subsection{componentAvailable([component type,] component name)}
|
|
//-- Checks whether a given component is available to the current player. The first argument is
|
|
//-- optional and deprecated.
|
|
static QScriptValue js_componentAvailable(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
int player = engine->globalObject().property("me").toInt32();
|
|
QString id = (context->argumentCount() == 1) ? context->argument(0).toString() : context->argument(1).toString();
|
|
COMPONENT_STATS *psComp = getCompStatsFromName(id);
|
|
SCRIPT_ASSERT(context, psComp, "No such component: %s", id.toUtf8().constData());
|
|
return QScriptValue(apCompLists[player][psComp->compType][psComp->index] == AVAILABLE);
|
|
}
|
|
|
|
//-- \subsection{addFeature(name, x, y)}
|
|
//-- Create and place a feature at the given x, y position. Will cause a desync in multiplayer.
|
|
//-- Returns the created game object on success, null otherwise. (3.2+ only)
|
|
static QScriptValue js_addFeature(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QString featName = context->argument(0).toString();
|
|
int x = context->argument(1).toInt32();
|
|
int y = context->argument(2).toInt32();
|
|
int feature = getFeatureStatFromName(featName.toUtf8().constData());
|
|
FEATURE_STATS *psStats = &asFeatureStats[feature];
|
|
for (FEATURE *psFeat = apsFeatureLists[0]; psFeat; psFeat = psFeat->psNext)
|
|
{
|
|
SCRIPT_ASSERT(context, map_coord(psFeat->pos.x) != x || map_coord(psFeat->pos.y) != y,
|
|
"Building feature on tile already occupied");
|
|
}
|
|
FEATURE *psFeature = buildFeature(psStats, world_coord(x), world_coord(y), false);
|
|
return convFeature(psFeature, engine);
|
|
}
|
|
|
|
static int get_first_available_component(int player, int capacity, const QScriptValue &list, COMPONENT_TYPE type, bool strict)
|
|
{
|
|
if (list.isArray())
|
|
{
|
|
int length = list.property("length").toInt32();
|
|
int k;
|
|
for (k = 0; k < length; k++)
|
|
{
|
|
QString compName = list.property(k).toString();
|
|
int result = getCompFromName(type, compName);
|
|
if (result >= 0 && (apCompLists[player][type][result] == AVAILABLE || !strict)
|
|
&& (type != COMP_BODY || asBodyStats[result].size <= capacity))
|
|
{
|
|
return result; // found one!
|
|
}
|
|
if (result < 0)
|
|
{
|
|
debug(LOG_ERROR, "No such component: %s", compName.toUtf8().constData());
|
|
}
|
|
}
|
|
}
|
|
else if (list.isString())
|
|
{
|
|
int result = getCompFromName(type, list.toString());
|
|
if (result >= 0 && (apCompLists[player][type][result] == AVAILABLE || !strict)
|
|
&& (type != COMP_BODY || asBodyStats[result].size <= capacity))
|
|
{
|
|
return result; // found it!
|
|
}
|
|
if (result < 0)
|
|
{
|
|
debug(LOG_ERROR, "No such component: %s", list.toString().toUtf8().constData());
|
|
}
|
|
}
|
|
return -1; // no available component found in list
|
|
}
|
|
|
|
static DROID_TEMPLATE *makeTemplate(int player, const QString &templName, QScriptContext *context, int paramstart, int capacity, bool strict)
|
|
{
|
|
const int firstTurret = paramstart + 4; // index position of first turret parameter
|
|
DROID_TEMPLATE *psTemplate = new DROID_TEMPLATE;
|
|
int numTurrets = context->argumentCount() - firstTurret; // anything beyond first six parameters, are turrets
|
|
int result;
|
|
|
|
memset(psTemplate->asParts, 0, sizeof(psTemplate->asParts)); // reset to defaults
|
|
memset(psTemplate->asWeaps, 0, sizeof(psTemplate->asWeaps));
|
|
int body = get_first_available_component(player, capacity, context->argument(paramstart), COMP_BODY, strict);
|
|
if (body < 0)
|
|
{
|
|
debug(LOG_SCRIPT, "Wanted to build %s but body types all unavailable",
|
|
templName.toUtf8().constData());
|
|
delete psTemplate;
|
|
return NULL; // no component available
|
|
}
|
|
int prop = get_first_available_component(player, capacity, context->argument(paramstart + 1), COMP_PROPULSION, strict);
|
|
if (prop < 0)
|
|
{
|
|
debug(LOG_SCRIPT, "Wanted to build %s but propulsion types all unavailable",
|
|
templName.toUtf8().constData());
|
|
delete psTemplate;
|
|
return NULL; // no component available
|
|
}
|
|
psTemplate->asParts[COMP_BODY] = body;
|
|
psTemplate->asParts[COMP_PROPULSION] = prop;
|
|
|
|
psTemplate->numWeaps = 0;
|
|
numTurrets = MIN(numTurrets, asBodyStats[body].weaponSlots); // Restrict max no. turrets
|
|
if (asBodyStats[body].droidTypeOverride != DROID_ANY)
|
|
{
|
|
psTemplate->droidType = asBodyStats[body].droidTypeOverride; // set droidType based on body
|
|
}
|
|
// Find first turret component type (assume every component in list is same type)
|
|
QString compName;
|
|
if (context->argument(firstTurret).isArray())
|
|
{
|
|
compName = context->argument(firstTurret).property(0).toString();
|
|
}
|
|
else // must be string
|
|
{
|
|
compName = context->argument(firstTurret).toString();
|
|
}
|
|
COMPONENT_STATS *psComp = getCompStatsFromName(compName);
|
|
if (psComp == NULL)
|
|
{
|
|
debug(LOG_ERROR, "Wanted to build %s but %s does not exist", templName.toUtf8().constData(), compName.toUtf8().constData());
|
|
delete psTemplate;
|
|
return NULL;
|
|
}
|
|
if (psComp->compType == COMP_WEAPON)
|
|
{
|
|
for (int i = 0; i < numTurrets; i++) // may be multi-weapon
|
|
{
|
|
result = get_first_available_component(player, SIZE_NUM, context->argument(firstTurret + i), COMP_WEAPON, strict);
|
|
if (result < 0)
|
|
{
|
|
debug(LOG_SCRIPT, "Wanted to build %s but no weapon available", templName.toUtf8().constData());
|
|
delete psTemplate;
|
|
return NULL;
|
|
}
|
|
psTemplate->asWeaps[i] = result;
|
|
psTemplate->numWeaps++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (psComp->compType == COMP_BRAIN)
|
|
{
|
|
psTemplate->numWeaps = 1; // hack, necessary to pass intValidTemplate
|
|
}
|
|
result = get_first_available_component(player, SIZE_NUM, context->argument(firstTurret), psComp->compType, strict);
|
|
if (result < 0)
|
|
{
|
|
debug(LOG_SCRIPT, "Wanted to build %s but turret unavailable", templName.toUtf8().constData());
|
|
delete psTemplate;
|
|
return NULL;
|
|
}
|
|
psTemplate->asParts[psComp->compType] = result;
|
|
}
|
|
bool valid = intValidTemplate(psTemplate, templName.toUtf8().constData(), true, player);
|
|
if (valid)
|
|
{
|
|
return psTemplate;
|
|
}
|
|
else
|
|
{
|
|
delete psTemplate;
|
|
debug(LOG_ERROR, "Invalid template %s", templName.toUtf8().constData());
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
//-- \subsection{addDroid(player, x, y, name, body, propulsion, reserved, reserved, turrets...)}
|
|
//-- Create and place a droid at the given x, y position as belonging to the given player, built with
|
|
//-- the given components. Currently does not support placing droids in multiplayer, doing so will
|
|
//-- cause a desync. Returns the created droid on success, otherwise returns null. Passing "" for
|
|
//-- reserved parameters is recommended.
|
|
static QScriptValue js_addDroid(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
int player = context->argument(0).toInt32();
|
|
SCRIPT_ASSERT_PLAYER(context, player);
|
|
int x = context->argument(1).toInt32();
|
|
int y = context->argument(2).toInt32();
|
|
QString templName = context->argument(3).toString();
|
|
DROID_TEMPLATE *psTemplate = makeTemplate(player, templName, context, 4, SIZE_NUM, false);
|
|
if (psTemplate)
|
|
{
|
|
bool oldMulti = bMultiMessages;
|
|
bMultiMessages = false; // ugh, fixme
|
|
DROID *psDroid = buildDroid(psTemplate, world_coord(x) + TILE_UNITS / 2, world_coord(y) + TILE_UNITS / 2, player, false, NULL);
|
|
if (psDroid)
|
|
{
|
|
addDroid(psDroid, apsDroidLists);
|
|
debug(LOG_LIFE, "Created droid %s by script for player %d: %u", objInfo(psDroid), player, psDroid->id);
|
|
}
|
|
else
|
|
{
|
|
debug(LOG_ERROR, "Invalid droid %s", templName.toUtf8().constData());
|
|
}
|
|
bMultiMessages = oldMulti; // ugh
|
|
delete psTemplate;
|
|
return psDroid ? QScriptValue(convDroid(psDroid, engine)) : QScriptValue::NullValue;
|
|
}
|
|
return QScriptValue::NullValue;
|
|
}
|
|
|
|
//-- \subsection{makeTemplate(player, name, body, propulsion, reserved, turrets...)}
|
|
//-- Create a template (virtual droid) with the given components. Can be useful for calculating the cost
|
|
//-- of droids before putting them into production, for instance. Will fail and return null if template
|
|
//-- could not possibly be built using current research.
|
|
static QScriptValue js_makeTemplate(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
int player = context->argument(0).toInt32();
|
|
QString templName = context->argument(1).toString();
|
|
DROID_TEMPLATE *psTemplate = makeTemplate(player, templName, context, 2, SIZE_NUM, true);
|
|
if (!psTemplate)
|
|
{
|
|
return QScriptValue::NullValue;
|
|
}
|
|
QScriptValue retval = convTemplate(psTemplate, engine);
|
|
delete psTemplate;
|
|
return QScriptValue(retval);
|
|
}
|
|
|
|
//-- \subsection{buildDroid(factory, name, body, propulsion, reserved, reserved, turrets...)}
|
|
//-- Start factory production of new droid with the given name, body, propulsion and turrets.
|
|
//-- The reserved parameter should be passed \emph{null} for now. The components can be
|
|
//-- passed as ordinary strings, or as a list of strings. If passed as a list, the first available
|
|
//-- component in the list will be used. The second reserved parameter used to be a droid type.
|
|
//-- It is now unused and in 3.2+ should be passed "", while in 3.1 it should be the
|
|
//-- droid type to be built. Returns a boolean that is true if production was started.
|
|
static QScriptValue js_buildDroid(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QScriptValue structVal = context->argument(0);
|
|
int id = structVal.property("id").toInt32();
|
|
int player = structVal.property("player").toInt32();
|
|
STRUCTURE *psStruct = IdToStruct(id, player);
|
|
SCRIPT_ASSERT(context, psStruct, "No such structure id %d belonging to player %d", id, player);
|
|
SCRIPT_ASSERT(context, (psStruct->pStructureType->type == REF_FACTORY || psStruct->pStructureType->type == REF_CYBORG_FACTORY
|
|
|| psStruct->pStructureType->type == REF_VTOL_FACTORY), "Structure %s is not a factory", objInfo(psStruct));
|
|
QString templName = context->argument(1).toString();
|
|
const int capacity = psStruct->capacity; // body size limit
|
|
DROID_TEMPLATE *psTemplate = makeTemplate(player, templName, context, 2, capacity, true);
|
|
if (psTemplate)
|
|
{
|
|
SCRIPT_ASSERT(context, validTemplateForFactory(psTemplate, psStruct, true),
|
|
"Invalid template %s for factory %s",
|
|
getName(psTemplate), getName(psStruct->pStructureType));
|
|
// Delete similar template from existing list before adding this one
|
|
for (int j = 0; j < apsTemplateList.size(); j++)
|
|
{
|
|
DROID_TEMPLATE *t = apsTemplateList[j];
|
|
if (t->name.compare(psTemplate->name) == 0)
|
|
{
|
|
debug(LOG_SCRIPT, "deleting %s for player %d", getName(t), player);
|
|
deleteTemplateFromProduction(t, player, ModeQueue); // duplicate? done below?
|
|
SendDestroyTemplate(t, player);
|
|
break;
|
|
}
|
|
}
|
|
// Add to list
|
|
debug(LOG_SCRIPT, "adding template %s for player %d", getName(psTemplate), player);
|
|
psTemplate->multiPlayerID = generateNewObjectId();
|
|
psTemplate->psNext = apsDroidTemplates[player];
|
|
apsDroidTemplates[player] = psTemplate;
|
|
sendTemplate(player, psTemplate);
|
|
if (!structSetManufacture(psStruct, psTemplate, ModeQueue))
|
|
{
|
|
debug(LOG_ERROR, "Could not produce template %s in %s", getName(psTemplate), objInfo(psStruct));
|
|
return QScriptValue(false);
|
|
}
|
|
}
|
|
return QScriptValue(psTemplate != NULL);
|
|
}
|
|
|
|
//-- \subsection{enumStruct([player[, structure type[, looking player]]])}
|
|
//-- Returns an array of structure objects. If no parameters given, it will
|
|
//-- return all of the structures for the current player. The second parameter
|
|
//-- can be either a string with the name of the structure type as defined in
|
|
//-- "structures.ini", or a stattype as defined in \ref{objects:structure}. The
|
|
//-- third parameter can be used to filter by visibility, the default is not
|
|
//-- to filter.
|
|
static QScriptValue js_enumStruct(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QList<STRUCTURE *> matches;
|
|
int player = -1, looking = -1;
|
|
QString statsName;
|
|
QScriptValue val;
|
|
STRUCTURE_TYPE type = NUM_DIFF_BUILDINGS;
|
|
|
|
switch (context->argumentCount())
|
|
{
|
|
default:
|
|
case 3: looking = context->argument(2).toInt32(); // fall-through
|
|
case 2: val = context->argument(1);
|
|
if (val.isNumber())
|
|
{
|
|
type = (STRUCTURE_TYPE)val.toInt32();
|
|
}
|
|
else
|
|
{
|
|
statsName = val.toString();
|
|
} // fall-through
|
|
case 1: player = context->argument(0).toInt32(); break;
|
|
case 0: player = engine->globalObject().property("me").toInt32();
|
|
}
|
|
|
|
SCRIPT_ASSERT_PLAYER(context, player);
|
|
SCRIPT_ASSERT(context, looking < MAX_PLAYERS && looking >= -1, "Looking player index out of range: %d", looking);
|
|
for (STRUCTURE *psStruct = apsStructLists[player]; psStruct; psStruct = psStruct->psNext)
|
|
{
|
|
if ((looking == -1 || psStruct->visible[looking])
|
|
&& !psStruct->died
|
|
&& (type == NUM_DIFF_BUILDINGS || type == psStruct->pStructureType->type)
|
|
&& (statsName.isEmpty() || statsName.compare(psStruct->pStructureType->id) == 0))
|
|
{
|
|
matches.push_back(psStruct);
|
|
}
|
|
}
|
|
QScriptValue result = engine->newArray(matches.size());
|
|
for (int i = 0; i < matches.size(); i++)
|
|
{
|
|
STRUCTURE *psStruct = matches.at(i);
|
|
result.setProperty(i, convStructure(psStruct, engine));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//-- \subsection{enumStructOffWorld([player[, structure type[, looking player]]])}
|
|
//-- Returns an array of structure objects in your base when on an off-world mission, NULL otherwise.
|
|
//-- If no parameters given, it will return all of the structures for the current player.
|
|
//-- The second parameter can be either a string with the name of the structure type as defined
|
|
//-- in "structures.ini", or a stattype as defined in \ref{objects:structure}.
|
|
//-- The third parameter can be used to filter by visibility, the default is not
|
|
//-- to filter.
|
|
static QScriptValue js_enumStructOffWorld(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QList<STRUCTURE *> matches;
|
|
int player = -1, looking = -1;
|
|
QString statsName;
|
|
QScriptValue val;
|
|
STRUCTURE_TYPE type = NUM_DIFF_BUILDINGS;
|
|
|
|
switch (context->argumentCount())
|
|
{
|
|
default:
|
|
case 3: looking = context->argument(2).toInt32(); // fall-through
|
|
case 2: val = context->argument(1);
|
|
if (val.isNumber())
|
|
{
|
|
type = (STRUCTURE_TYPE)val.toInt32();
|
|
}
|
|
else
|
|
{
|
|
statsName = val.toString();
|
|
} // fall-through
|
|
case 1: player = context->argument(0).toInt32(); break;
|
|
case 0: player = engine->globalObject().property("me").toInt32();
|
|
}
|
|
|
|
SCRIPT_ASSERT(context, player < MAX_PLAYERS && player >= 0, "Target player index out of range: %d", player);
|
|
SCRIPT_ASSERT(context, looking < MAX_PLAYERS && looking >= -1, "Looking player index out of range: %d", looking);
|
|
for (STRUCTURE *psStruct = mission.apsStructLists[player]; psStruct; psStruct = psStruct->psNext)
|
|
{
|
|
if ((looking == -1 || psStruct->visible[looking])
|
|
&& !psStruct->died
|
|
&& (type == NUM_DIFF_BUILDINGS || type == psStruct->pStructureType->type)
|
|
&& (statsName.isEmpty() || statsName.compare(psStruct->pStructureType->id) == 0))
|
|
{
|
|
matches.push_back(psStruct);
|
|
}
|
|
}
|
|
QScriptValue result = engine->newArray(matches.size());
|
|
for (int i = 0; i < matches.size(); i++)
|
|
{
|
|
STRUCTURE *psStruct = matches.at(i);
|
|
result.setProperty(i, convStructure(psStruct, engine));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//-- \subsection{enumFeature(player[, name])}
|
|
//-- Returns an array of all features seen by player of given name, as defined in "features.ini".
|
|
//-- If player is \emph{ALL_PLAYERS}, it will return all features irrespective of visibility to any player. If
|
|
//-- name is empty, it will return any feature.
|
|
static QScriptValue js_enumFeature(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QList<FEATURE *> matches;
|
|
int looking = context->argument(0).toInt32();
|
|
QString statsName;
|
|
if (context->argumentCount() > 1)
|
|
{
|
|
statsName = context->argument(1).toString();
|
|
}
|
|
SCRIPT_ASSERT(context, looking < MAX_PLAYERS && looking >= -1, "Looking player index out of range: %d", looking);
|
|
for (FEATURE *psFeat = apsFeatureLists[0]; psFeat; psFeat = psFeat->psNext)
|
|
{
|
|
if ((looking == -1 || psFeat->visible[looking])
|
|
&& !psFeat->died
|
|
&& (statsName.isEmpty() || statsName.compare(psFeat->psStats->id) == 0))
|
|
{
|
|
matches.push_back(psFeat);
|
|
}
|
|
}
|
|
QScriptValue result = engine->newArray(matches.size());
|
|
for (int i = 0; i < matches.size(); i++)
|
|
{
|
|
FEATURE *psFeat = matches.at(i);
|
|
result.setProperty(i, convFeature(psFeat, engine));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//-- \subsection{enumCargo(transport droid)}
|
|
//-- Returns an array of droid objects inside given transport.
|
|
static QScriptValue js_enumCargo(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QScriptValue droidVal = context->argument(0);
|
|
int id = droidVal.property("id").toInt32();
|
|
int player = droidVal.property("player").toInt32();
|
|
DROID *psDroid = IdToDroid(id, player);
|
|
SCRIPT_ASSERT(context, psDroid, "No such droid id %d belonging to player %d", id, player);
|
|
SCRIPT_ASSERT(context, psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER, "Wrong droid type");
|
|
QScriptValue result = engine->newArray(psDroid->psGroup->getNumMembers());
|
|
int i = 0;
|
|
for (DROID *psCurr = psDroid->psGroup->psList; psCurr; psCurr = psCurr->psGrpNext, i++)
|
|
{
|
|
if (psDroid != psCurr)
|
|
{
|
|
result.setProperty(i, convDroid(psCurr, engine));
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
//-- \subsection{enumDroid([player[, droid type[, looking player]]])}
|
|
//-- Returns an array of droid objects. If no parameters given, it will
|
|
//-- return all of the droids for the current player. The second, optional parameter
|
|
//-- is the name of the droid type. The third parameter can be used to filter by
|
|
//-- visibility - the default is not to filter.
|
|
static QScriptValue js_enumDroid(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QList<DROID *> matches;
|
|
int player = -1, looking = -1;
|
|
DROID_TYPE droidType = DROID_ANY;
|
|
DROID_TYPE droidType2;
|
|
|
|
switch (context->argumentCount())
|
|
{
|
|
default:
|
|
case 3: looking = context->argument(2).toInt32(); // fall-through
|
|
case 2: droidType = (DROID_TYPE)context->argument(1).toInt32(); // fall-through
|
|
case 1: player = context->argument(0).toInt32(); break;
|
|
case 0: player = engine->globalObject().property("me").toInt32();
|
|
}
|
|
switch (droidType) // hide some engine craziness
|
|
{
|
|
case DROID_CONSTRUCT:
|
|
droidType2 = DROID_CYBORG_CONSTRUCT; break;
|
|
case DROID_WEAPON:
|
|
droidType2 = DROID_CYBORG_SUPER; break;
|
|
case DROID_REPAIR:
|
|
droidType2 = DROID_CYBORG_REPAIR; break;
|
|
case DROID_CYBORG:
|
|
droidType2 = DROID_CYBORG_SUPER; break;
|
|
default:
|
|
droidType2 = droidType;
|
|
break;
|
|
}
|
|
SCRIPT_ASSERT_PLAYER(context, player);
|
|
SCRIPT_ASSERT(context, looking < MAX_PLAYERS && looking >= -1, "Looking player index out of range: %d", looking);
|
|
for (DROID *psDroid = apsDroidLists[player]; psDroid; psDroid = psDroid->psNext)
|
|
{
|
|
if ((looking == -1 || psDroid->visible[looking])
|
|
&& !psDroid->died
|
|
&& (droidType == DROID_ANY || droidType == psDroid->droidType || droidType2 == psDroid->droidType))
|
|
{
|
|
matches.push_back(psDroid);
|
|
}
|
|
}
|
|
QScriptValue result = engine->newArray(matches.size());
|
|
for (int i = 0; i < matches.size(); i++)
|
|
{
|
|
DROID *psDroid = matches.at(i);
|
|
result.setProperty(i, convDroid(psDroid, engine));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void dumpScriptLog(const QString &scriptName, int me, const QString &info)
|
|
{
|
|
QString path = PHYSFS_getWriteDir();
|
|
path += "/logs/" + scriptName + "." + QString::number(me) + ".log";
|
|
FILE *fp = fopen(path.toUtf8().constData(), "a");
|
|
if (fp)
|
|
{
|
|
fputs(info.toUtf8().constData(), fp);
|
|
fclose(fp);
|
|
}
|
|
}
|
|
|
|
//-- \subsection{dump(string...)}
|
|
//-- Output text to a debug file.
|
|
static QScriptValue js_dump(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);
|
|
}
|
|
result += "\n";
|
|
|
|
QString scriptName = engine->globalObject().property("scriptName").toString();
|
|
int me = engine->globalObject().property("me").toInt32();
|
|
dumpScriptLog(scriptName, me, result);
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{debug(string...)}
|
|
//-- Output text to the command line.
|
|
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("%s", result.toUtf8().constData());
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{pickStructLocation(droid, structure type, x, y)}
|
|
//-- Pick a location for constructing a certain type of building near some given position.
|
|
//-- Returns an object containing "type" POSITION, and "x" and "y" values, if successful.
|
|
static QScriptValue js_pickStructLocation(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QScriptValue droidVal = context->argument(0);
|
|
const int id = droidVal.property("id").toInt32();
|
|
const int player = droidVal.property("player").toInt32();
|
|
DROID *psDroid = IdToDroid(id, player);
|
|
QString statName = context->argument(1).toString();
|
|
int index = getStructStatFromName(statName.toUtf8().constData());
|
|
SCRIPT_ASSERT(context, index >= 0, "%s not found", statName.toUtf8().constData());
|
|
STRUCTURE_STATS *psStat = &asStructureStats[index];
|
|
const int startX = context->argument(2).toInt32();
|
|
const int startY = context->argument(3).toInt32();
|
|
int numIterations = 30;
|
|
bool found = false;
|
|
int incX, incY, x, y;
|
|
int maxBlockingTiles = 0;
|
|
|
|
SCRIPT_ASSERT(context, psDroid, "No such droid id %d belonging to player %d", id, player);
|
|
SCRIPT_ASSERT(context, psStat, "No such stat found: %s", statName.toUtf8().constData());
|
|
SCRIPT_ASSERT_PLAYER(context, player);
|
|
SCRIPT_ASSERT(context, startX >= 0 && startX < mapWidth && startY >= 0 && startY < mapHeight, "Bad position (%d, %d)", startX, startY);
|
|
|
|
if (context->argumentCount() > 4) // final optional argument
|
|
{
|
|
maxBlockingTiles = context->argument(4).toInt32();
|
|
}
|
|
|
|
x = startX;
|
|
y = startY;
|
|
|
|
Vector2i offset(psStat->baseWidth * (TILE_UNITS / 2), psStat->baseBreadth * (TILE_UNITS / 2));
|
|
|
|
// save a lot of typing... checks whether a position is valid
|
|
#define LOC_OK(_x, _y) (tileOnMap(_x, _y) && \
|
|
(!psDroid || fpathCheck(psDroid->pos, Vector3i(world_coord(_x), world_coord(_y), 0), PROPULSION_TYPE_WHEELED)) \
|
|
&& validLocation(psStat, world_coord(Vector2i(_x, _y)) + offset, 0, player, false) && structDoubleCheck(psStat, _x, _y, maxBlockingTiles))
|
|
|
|
// first try the original location
|
|
if (LOC_OK(startX, startY))
|
|
{
|
|
found = true;
|
|
}
|
|
|
|
// try some locations nearby
|
|
for (incX = 1, incY = 1; incX < numIterations && !found; incX++, incY++)
|
|
{
|
|
y = startY - incY; // top
|
|
for (x = startX - incX; x < startX + incX; x++)
|
|
{
|
|
if (LOC_OK(x, y))
|
|
{
|
|
found = true;
|
|
goto endstructloc;
|
|
}
|
|
}
|
|
x = startX + incX; // right
|
|
for (y = startY - incY; y < startY + incY; y++)
|
|
{
|
|
if (LOC_OK(x, y))
|
|
{
|
|
found = true;
|
|
goto endstructloc;
|
|
}
|
|
}
|
|
y = startY + incY; // bottom
|
|
for (x = startX + incX; x > startX - incX; x--)
|
|
{
|
|
if (LOC_OK(x, y))
|
|
{
|
|
found = true;
|
|
goto endstructloc;
|
|
}
|
|
}
|
|
x = startX - incX; // left
|
|
for (y = startY + incY; y > startY - incY; y--)
|
|
{
|
|
if (LOC_OK(x, y))
|
|
{
|
|
found = true;
|
|
goto endstructloc;
|
|
}
|
|
}
|
|
}
|
|
|
|
endstructloc:
|
|
if (found)
|
|
{
|
|
QScriptValue retval = engine->newObject();
|
|
retval.setProperty("x", x + map_coord(offset.x), QScriptValue::ReadOnly);
|
|
retval.setProperty("y", y + map_coord(offset.y), QScriptValue::ReadOnly);
|
|
retval.setProperty("type", SCRIPT_POSITION, QScriptValue::ReadOnly);
|
|
return retval;
|
|
}
|
|
else
|
|
{
|
|
debug(LOG_SCRIPT, "Did not find valid positioning for %s", getName(psStat));
|
|
}
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{structureIdle(structure)}
|
|
//-- Is given structure idle?
|
|
static QScriptValue js_structureIdle(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
QScriptValue structVal = context->argument(0);
|
|
int id = structVal.property("id").toInt32();
|
|
int player = structVal.property("player").toInt32();
|
|
STRUCTURE *psStruct = IdToStruct(id, player);
|
|
SCRIPT_ASSERT(context, psStruct, "No such structure id %d belonging to player %d", id, player);
|
|
return QScriptValue(structureIdle(psStruct));
|
|
}
|
|
|
|
//-- \subsection{removeStruct(structure)}
|
|
//-- Immediately remove the given structure from the map. Returns a boolean that is true on success.
|
|
//-- No special effects are applied. Deprecated since 3.2.
|
|
static QScriptValue js_removeStruct(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
QScriptValue structVal = context->argument(0);
|
|
int id = structVal.property("id").toInt32();
|
|
int player = structVal.property("player").toInt32();
|
|
STRUCTURE *psStruct = IdToStruct(id, player);
|
|
SCRIPT_ASSERT(context, psStruct, "No such structure id %d belonging to player %d", id, player);
|
|
return QScriptValue(removeStruct(psStruct, true));
|
|
}
|
|
|
|
//-- \subsection{removeObject(game object[, special effects?])}
|
|
//-- Remove the given game object with special effects. Returns a boolean that is true on success.
|
|
//-- A second, optional boolean parameter specifies whether special effects are to be applied. (3.2+ only)
|
|
static QScriptValue js_removeObject(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
QScriptValue qval = context->argument(0);
|
|
int id = qval.property("id").toInt32();
|
|
int player = qval.property("player").toInt32();
|
|
OBJECT_TYPE type = (OBJECT_TYPE)qval.property("type").toInt32();
|
|
BASE_OBJECT *psObj = IdToObject(type, id, player);
|
|
SCRIPT_ASSERT(context, psObj, "Object id %d not found belonging to player %d", id, player);
|
|
bool sfx = false;
|
|
if (context->argumentCount() > 1)
|
|
{
|
|
sfx = context->argument(1).toBool();
|
|
}
|
|
bool retval = false;
|
|
if (sfx)
|
|
{
|
|
switch (psObj->type)
|
|
{
|
|
case OBJ_STRUCTURE: destroyStruct((STRUCTURE *)psObj, gameTime); break;
|
|
case OBJ_DROID: retval = destroyDroid((DROID *)psObj, gameTime); break;
|
|
case OBJ_FEATURE: retval = destroyFeature((FEATURE *)psObj, gameTime); break;
|
|
default: SCRIPT_ASSERT(context, false, "Wrong game object type"); break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (psObj->type)
|
|
{
|
|
case OBJ_STRUCTURE: retval = removeStruct((STRUCTURE *)psObj, true); break;
|
|
case OBJ_DROID: retval = removeDroidBase((DROID *)psObj); break;
|
|
case OBJ_FEATURE: retval = removeFeature((FEATURE *)psObj); break;
|
|
default: SCRIPT_ASSERT(context, false, "Wrong game object type"); break;
|
|
}
|
|
}
|
|
return QScriptValue(retval);
|
|
}
|
|
|
|
//-- \subsection{console(strings...)}
|
|
//-- Print text to the player console.
|
|
// 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.toUtf8().constData(), CENTRE_JUSTIFY, SYSTEM_MESSAGE);
|
|
//permitNewConsoleMessages(false);
|
|
}
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{groupAddArea(group, x1, y1, x2, y2)}
|
|
//-- Add any droids inside the given area to the given group.
|
|
static QScriptValue js_groupAddArea(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
int groupId = context->argument(0).toInt32();
|
|
int player = engine->globalObject().property("me").toInt32();
|
|
int x1 = world_coord(context->argument(1).toInt32());
|
|
int y1 = world_coord(context->argument(2).toInt32());
|
|
int x2 = world_coord(context->argument(3).toInt32());
|
|
int y2 = world_coord(context->argument(4).toInt32());
|
|
QScriptValue groups = engine->globalObject().property("groupSizes");
|
|
|
|
for (DROID *psDroid = apsDroidLists[player]; psDroid; psDroid = psDroid->psNext)
|
|
{
|
|
if (psDroid->pos.x >= x1 && psDroid->pos.x <= x2 && psDroid->pos.y >= y1 && psDroid->pos.y <= y2)
|
|
{
|
|
groupAddObject(psDroid, groupId, engine);
|
|
}
|
|
}
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{groupAddDroid(group, droid)}
|
|
//-- Add given droid to given group. Deprecated since 3.2 - use groupAdd() instead.
|
|
static QScriptValue js_groupAddDroid(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
int groupId = context->argument(0).toInt32();
|
|
QScriptValue droidVal = context->argument(1);
|
|
int droidId = droidVal.property("id").toInt32();
|
|
int droidPlayer = droidVal.property("player").toInt32();
|
|
DROID *psDroid = IdToDroid(droidId, droidPlayer);
|
|
QScriptValue groups = engine->globalObject().property("groupSizes");
|
|
SCRIPT_ASSERT(context, psDroid, "Invalid droid index %d", droidId);
|
|
groupAddObject(psDroid, groupId, engine);
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{groupAdd(group, object)}
|
|
//-- Add given game object to the given group.
|
|
static QScriptValue js_groupAdd(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
int groupId = context->argument(0).toInt32();
|
|
QScriptValue val = context->argument(1);
|
|
int id = val.property("id").toInt32();
|
|
int player = val.property("player").toInt32();
|
|
OBJECT_TYPE type = (OBJECT_TYPE)val.property("type").toInt32();
|
|
BASE_OBJECT *psObj = IdToObject(type, id, player);
|
|
SCRIPT_ASSERT(context, psObj, "Invalid object index %d", id);
|
|
QScriptValue groups = engine->globalObject().property("groupSizes");
|
|
groupAddObject(psObj, groupId, engine);
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{distBetweenTwoPoints(x1, y1, x2, y2)}
|
|
//-- Return distance between two points.
|
|
static QScriptValue js_distBetweenTwoPoints(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
int x1 = context->argument(0).toNumber();
|
|
int y1 = context->argument(1).toNumber();
|
|
int x2 = context->argument(2).toNumber();
|
|
int y2 = context->argument(3).toNumber();
|
|
return QScriptValue(iHypot(x1 - x2, y1 - y2));
|
|
}
|
|
|
|
//-- \subsection{groupSize(group)}
|
|
//-- Return the number of droids currently in the given group. Note that you can use groupSizes[] instead.
|
|
static QScriptValue js_groupSize(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QScriptValue groups = engine->globalObject().property("groupSizes");
|
|
int groupId = context->argument(0).toInt32();
|
|
return groups.property(groupId).toInt32();
|
|
}
|
|
|
|
//-- \subsection{droidCanReach(droid, x, y)}
|
|
//-- Return whether or not the given droid could possibly drive to the given position. Does
|
|
//-- not take player built blockades into account.
|
|
static QScriptValue js_droidCanReach(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
QScriptValue droidVal = context->argument(0);
|
|
int id = droidVal.property("id").toInt32();
|
|
int player = droidVal.property("player").toInt32();
|
|
int x = context->argument(1).toInt32();
|
|
int y = context->argument(2).toInt32();
|
|
DROID *psDroid = IdToDroid(id, player);
|
|
SCRIPT_ASSERT(context, psDroid, "Droid id %d not found belonging to player %d", id, player);
|
|
const PROPULSION_STATS *psPropStats = asPropulsionStats + psDroid->asBits[COMP_PROPULSION];
|
|
return QScriptValue(fpathCheck(psDroid->pos, Vector3i(world_coord(x), world_coord(y), 0), psPropStats->propulsionType));
|
|
}
|
|
|
|
//-- \subsection{propulsionCanReach(propulsion, x1, y1, x2, y2)}
|
|
//-- Return true if a droid with a given propulsion is able to travel from (x1, y1) to (x2, y2).
|
|
//-- Does not take player built blockades into account.
|
|
static QScriptValue js_propulsionCanReach(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
QScriptValue propulsionValue = context->argument(0);
|
|
int propulsion = getCompFromName(COMP_PROPULSION, propulsionValue.toString());
|
|
SCRIPT_ASSERT(context, propulsion > 0, "No such propulsion: %s", propulsionValue.toString().toUtf8().constData());
|
|
int x1 = context->argument(1).toInt32();
|
|
int y1 = context->argument(2).toInt32();
|
|
int x2 = context->argument(3).toInt32();
|
|
int y2 = context->argument(4).toInt32();
|
|
const PROPULSION_STATS *psPropStats = asPropulsionStats + propulsion;
|
|
return QScriptValue(fpathCheck(Vector3i(world_coord(x1), world_coord(y1), 0), Vector3i(world_coord(x2), world_coord(y2), 0), psPropStats->propulsionType));
|
|
}
|
|
|
|
//-- \subsection{terrainType(x, y)}
|
|
//-- Returns tile type of a given map tile, such as TER_WATER for water tiles or TER_CLIFFFACE for cliffs.
|
|
//-- Tile types regulate which units may pass through this tile.
|
|
static QScriptValue js_terrainType(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
int x = context->argument(0).toInt32();
|
|
int y = context->argument(1).toInt32();
|
|
return QScriptValue(terrainType(mapTile(x, y)));
|
|
}
|
|
|
|
//-- \subsection{orderDroid(droid, order)}
|
|
//-- Give a droid an order to do something.
|
|
static QScriptValue js_orderDroid(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
QScriptValue droidVal = context->argument(0);
|
|
int id = droidVal.property("id").toInt32();
|
|
int player = droidVal.property("player").toInt32();
|
|
DROID *psDroid = IdToDroid(id, player);
|
|
SCRIPT_ASSERT(context, psDroid, "Droid id %d not found belonging to player %d", id, player);
|
|
DROID_ORDER order = (DROID_ORDER)context->argument(1).toInt32();
|
|
SCRIPT_ASSERT(context, order == DORDER_HOLD || order == DORDER_RTR || order == DORDER_STOP
|
|
|| order == DORDER_RTB || order == DORDER_REARM || order == DORDER_RECYCLE,
|
|
"Invalid order: %s", getDroidOrderName(order));
|
|
if (order == DORDER_REARM)
|
|
{
|
|
moveToRearm(psDroid);
|
|
}
|
|
else
|
|
{
|
|
orderDroid(psDroid, order, ModeQueue);
|
|
}
|
|
return QScriptValue(true);
|
|
}
|
|
|
|
//-- \subsection{orderDroidObj(droid, order, object)}
|
|
//-- Give a droid an order to do something to something.
|
|
static QScriptValue js_orderDroidObj(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
QScriptValue droidVal = context->argument(0);
|
|
int id = droidVal.property("id").toInt32();
|
|
int player = droidVal.property("player").toInt32();
|
|
DROID *psDroid = IdToDroid(id, player);
|
|
SCRIPT_ASSERT(context, psDroid, "Droid id %d not found belonging to player %d", id, player);
|
|
DROID_ORDER order = (DROID_ORDER)context->argument(1).toInt32();
|
|
QScriptValue objVal = context->argument(2);
|
|
int oid = objVal.property("id").toInt32();
|
|
int oplayer = objVal.property("player").toInt32();
|
|
OBJECT_TYPE otype = (OBJECT_TYPE)objVal.property("type").toInt32();
|
|
BASE_OBJECT *psObj = IdToObject(otype, oid, oplayer);
|
|
SCRIPT_ASSERT(context, psObj, "Object id %d not found belonging to player %d", oid, oplayer);
|
|
SCRIPT_ASSERT(context, validOrderForObj(order), "Invalid order: %s", getDroidOrderName(order));
|
|
orderDroidObj(psDroid, order, psObj, ModeQueue);
|
|
return QScriptValue(true);
|
|
}
|
|
|
|
//-- \subsection{orderDroidBuild(droid, order, structure type, x, y[, direction])}
|
|
//-- Give a droid an order to build someting at the given position. Returns true if allowed.
|
|
static QScriptValue js_orderDroidBuild(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
QScriptValue droidVal = context->argument(0);
|
|
int id = droidVal.property("id").toInt32();
|
|
int player = droidVal.property("player").toInt32();
|
|
DROID *psDroid = IdToDroid(id, player);
|
|
DROID_ORDER order = (DROID_ORDER)context->argument(1).toInt32();
|
|
QString statName = context->argument(2).toString();
|
|
int index = getStructStatFromName(statName.toUtf8().constData());
|
|
SCRIPT_ASSERT(context, index >= 0, "%s not found", statName.toUtf8().constData());
|
|
STRUCTURE_STATS *psStats = &asStructureStats[index];
|
|
int x = context->argument(3).toInt32();
|
|
int y = context->argument(4).toInt32();
|
|
uint16_t direction = 0;
|
|
|
|
SCRIPT_ASSERT(context, order == DORDER_BUILD, "Invalid order");
|
|
SCRIPT_ASSERT(context, psStats->id.compare("A0ADemolishStructure") != 0, "Cannot build demolition");
|
|
if (context->argumentCount() > 5)
|
|
{
|
|
direction = DEG(context->argument(5).toNumber());
|
|
}
|
|
orderDroidStatsLocDir(psDroid, order, psStats, world_coord(x) + TILE_UNITS / 2, world_coord(y) + TILE_UNITS / 2, direction, ModeQueue);
|
|
return QScriptValue(true);
|
|
}
|
|
|
|
//-- \subsection{orderDroidLoc(droid, order, x, y)}
|
|
//-- Give a droid an order to do something at the given location.
|
|
static QScriptValue js_orderDroidLoc(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
QScriptValue droidVal = context->argument(0);
|
|
int id = droidVal.property("id").toInt32();
|
|
int player = droidVal.property("player").toInt32();
|
|
QScriptValue orderVal = context->argument(1);
|
|
int x = context->argument(2).toInt32();
|
|
int y = context->argument(3).toInt32();
|
|
DROID_ORDER order = (DROID_ORDER)orderVal.toInt32();
|
|
SCRIPT_ASSERT(context, validOrderForLoc(order), "Invalid location based order: %s", getDroidOrderName(order));
|
|
DROID *psDroid = IdToDroid(id, player);
|
|
SCRIPT_ASSERT(context, psDroid, "Droid id %d not found belonging to player %d", id, player);
|
|
SCRIPT_ASSERT(context, tileOnMap(x, y), "Outside map bounds (%d, %d)", x, y);
|
|
orderDroidLoc(psDroid, order, world_coord(x), world_coord(y), ModeQueue);
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{setMissionTime(time)} Set mission countdown in seconds.
|
|
static QScriptValue js_setMissionTime(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
int value = context->argument(0).toInt32() * GAME_TICKS_PER_SEC;
|
|
mission.startTime = gameTime;
|
|
mission.time = value;
|
|
setMissionCountDown();
|
|
if (mission.time >= 0)
|
|
{
|
|
mission.startTime = gameTime;
|
|
addMissionTimerInterface();
|
|
}
|
|
else
|
|
{
|
|
intRemoveMissionTimer();
|
|
mission.cheatTime = 0;
|
|
}
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{getMissionTime()} Get time remaining on mission countdown in seconds.
|
|
static QScriptValue js_getMissionTime(QScriptContext *, QScriptEngine *)
|
|
{
|
|
return QScriptValue((mission.time - (gameTime - mission.startTime)) / GAME_TICKS_PER_SEC);
|
|
}
|
|
|
|
//-- \subsection{setTransporterExit(x, y, player)}
|
|
//-- Set the exit position for the mission transporter.
|
|
static QScriptValue js_setTransporterExit(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
int x = context->argument(0).toInt32();
|
|
int y = context->argument(1).toInt32();
|
|
int player = context->argument(2).toInt32();
|
|
SCRIPT_ASSERT_PLAYER(context, player);
|
|
missionSetTransporterExit(player, x, y);
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{startTransporterEntry(x, y, player)}
|
|
//-- Set the entry position for the mission transporter, and make it start flying in
|
|
//-- reinforcements. If you want the camera to follow it in, use cameraTrack() on it.
|
|
//-- The transport needs to be set up with the mission droids, and the first transport
|
|
//-- found will be used.
|
|
static QScriptValue js_startTransporterEntry(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
int x = context->argument(0).toInt32();
|
|
int y = context->argument(1).toInt32();
|
|
int player = context->argument(2).toInt32();
|
|
SCRIPT_ASSERT_PLAYER(context, player);
|
|
missionSetTransporterEntry(player, x, y);
|
|
missionFlyTransportersIn(player, false);
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{setReinforcementTime(time)} Set time for reinforcements to arrive. If time is
|
|
//-- negative, the reinforcement GUI is removed and the timer stopped. Time is in seconds.
|
|
static QScriptValue js_setReinforcementTime(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
int value = context->argument(0).toInt32() * GAME_TICKS_PER_SEC;
|
|
SCRIPT_ASSERT(context, value == LZ_COMPROMISED_TIME || value < 60 * 60,
|
|
"The transport timer cannot be set to more than 1 hour!");
|
|
mission.ETA = value;
|
|
if (missionCanReEnforce())
|
|
{
|
|
addTransporterTimerInterface();
|
|
}
|
|
if (value < 0)
|
|
{
|
|
DROID *psDroid;
|
|
|
|
intRemoveTransporterTimer();
|
|
/* Only remove the launch if haven't got a transporter droid since the scripts set the
|
|
* time to -1 at the between stage if there are not going to be reinforcements on the submap */
|
|
for (psDroid = apsDroidLists[selectedPlayer]; psDroid != NULL; psDroid = psDroid->psNext)
|
|
{
|
|
if (psDroid->droidType == DROID_TRANSPORTER || psDroid->droidType == DROID_SUPERTRANSPORTER)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
// if not found a transporter, can remove the launch button
|
|
if (psDroid == NULL)
|
|
{
|
|
intRemoveTransporterLaunch();
|
|
}
|
|
}
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{setStructureLimits(structure type, limit[, player])} Set build limits for a structure.
|
|
static QScriptValue js_setStructureLimits(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QString building = context->argument(0).toString();
|
|
int limit = context->argument(1).toInt32();
|
|
int player;
|
|
int structInc = getStructStatFromName(building.toUtf8().constData());
|
|
if (context->argumentCount() > 2)
|
|
{
|
|
player = context->argument(2).toInt32();
|
|
}
|
|
else
|
|
{
|
|
player = engine->globalObject().property("me").toInt32();
|
|
}
|
|
SCRIPT_ASSERT_PLAYER(context, player);
|
|
SCRIPT_ASSERT(context, limit < LOTS_OF && limit >= 0, "Invalid limit");
|
|
SCRIPT_ASSERT(context, structInc < numStructureStats && structInc >= 0, "Invalid structure");
|
|
|
|
STRUCTURE_LIMITS *psStructLimits = asStructLimits[player];
|
|
psStructLimits[structInc].limit = limit;
|
|
psStructLimits[structInc].globalLimit = limit;
|
|
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{centreView(x, y)}
|
|
//-- Center the player's camera at the given position.
|
|
static QScriptValue js_centreView(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
int x = context->argument(0).toInt32();
|
|
int y = context->argument(1).toInt32();
|
|
setViewPos(x, y, false);
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{playSound(sound[, x, y, z])}
|
|
//-- Play a sound, optionally at a location.
|
|
static QScriptValue js_playSound(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
int player = engine->globalObject().property("me").toInt32();
|
|
if (player != selectedPlayer)
|
|
{
|
|
return QScriptValue();
|
|
}
|
|
QString sound = context->argument(0).toString();
|
|
int soundID = audio_GetTrackID(sound.toUtf8().constData());
|
|
if (soundID == SAMPLE_NOT_FOUND)
|
|
{
|
|
soundID = audio_SetTrackVals(sound.toUtf8().constData(), false, 100, 1800);
|
|
}
|
|
if (context->argumentCount() > 1)
|
|
{
|
|
int x = world_coord(context->argument(1).toInt32());
|
|
int y = world_coord(context->argument(2).toInt32());
|
|
int z = world_coord(context->argument(3).toInt32());
|
|
audio_QueueTrackPos(soundID, x, y, z);
|
|
}
|
|
else
|
|
{
|
|
audio_QueueTrack(soundID);
|
|
/* -- FIXME properly (from original script func)
|
|
if(bInTutorial)
|
|
{
|
|
audio_QueueTrack(ID_SOUND_OF_SILENCE);
|
|
}
|
|
*/
|
|
}
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{gameOverMessage(won)}
|
|
//-- End game in victory or defeat.
|
|
static QScriptValue js_gameOverMessage(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
int player = engine->globalObject().property("me").toInt32();
|
|
const MESSAGE_TYPE msgType = MSG_MISSION; // always
|
|
bool gameWon = context->argument(0).toBool();
|
|
VIEWDATA *psViewData;
|
|
if (gameWon)
|
|
{
|
|
psViewData = getViewData("WIN");
|
|
addConsoleMessage(_("YOU ARE VICTORIOUS!"), DEFAULT_JUSTIFY, SYSTEM_MESSAGE);
|
|
}
|
|
else
|
|
{
|
|
psViewData = getViewData("END"); // FIXME: rename to FAILED|LOST ?
|
|
addConsoleMessage(_("YOU WERE DEFEATED!"), DEFAULT_JUSTIFY, SYSTEM_MESSAGE);
|
|
}
|
|
ASSERT(psViewData, "Viewdata not found");
|
|
MESSAGE *psMessage = addMessage(msgType, false, player);
|
|
if (psMessage)
|
|
{
|
|
//set the data
|
|
psMessage->pViewData = (MSG_VIEWDATA *)psViewData;
|
|
displayImmediateMessage(psMessage);
|
|
stopReticuleButtonFlash(IDRET_INTEL_MAP);
|
|
|
|
//we need to set this here so the VIDEO_QUIT callback is not called
|
|
setScriptWinLoseVideo(gameWon ? PLAY_WIN : PLAY_LOSE);
|
|
}
|
|
displayGameOver(gameWon);
|
|
if (challengeActive)
|
|
{
|
|
updateChallenge(gameWon);
|
|
}
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{completeResearch(research[, player])}
|
|
//-- Finish a research for the given player.
|
|
static QScriptValue js_completeResearch(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QString researchName = context->argument(0).toString();
|
|
int player;
|
|
if (context->argumentCount() > 1)
|
|
{
|
|
player = context->argument(1).toInt32();
|
|
}
|
|
else
|
|
{
|
|
player = engine->globalObject().property("me").toInt32();
|
|
}
|
|
RESEARCH *psResearch = getResearch(researchName.toUtf8().constData());
|
|
SCRIPT_ASSERT(context, psResearch, "No such research %s for player %d", researchName.toUtf8().constData(), player);
|
|
SCRIPT_ASSERT(context, psResearch->index < asResearch.size(), "Research index out of bounds");
|
|
if (bMultiMessages && (gameTime > 2))
|
|
{
|
|
SendResearch(player, psResearch->index, false);
|
|
// Wait for our message before doing anything.
|
|
}
|
|
else
|
|
{
|
|
researchResult(psResearch->index, player, false, NULL, false);
|
|
}
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{enableResearch(research[, player])}
|
|
//-- Enable a research for the given player, allowing it to be researched.
|
|
static QScriptValue js_enableResearch(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QString researchName = context->argument(0).toString();
|
|
int player;
|
|
if (context->argumentCount() > 1)
|
|
{
|
|
player = context->argument(1).toInt32();
|
|
}
|
|
else
|
|
{
|
|
player = engine->globalObject().property("me").toInt32();
|
|
}
|
|
RESEARCH *psResearch = getResearch(researchName.toUtf8().constData());
|
|
SCRIPT_ASSERT(context, psResearch, "No such research %s for player %d", researchName.toUtf8().constData(), player);
|
|
if (!enableResearch(psResearch, player))
|
|
{
|
|
debug(LOG_ERROR, "Unable to enable research %s for player %d", researchName.toUtf8().constData(), player);
|
|
}
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{extraPowerTime(time, player)}
|
|
//-- Increase a player's power as if that player had power income equal to current income
|
|
//-- over the given amount of extra time.
|
|
static QScriptValue js_extraPowerTime(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
int ticks = context->argument(0).toInt32() * GAME_UPDATES_PER_SEC;
|
|
int player;
|
|
if (context->argumentCount() > 1)
|
|
{
|
|
player = context->argument(1).toInt32();
|
|
SCRIPT_ASSERT_PLAYER(context, player);
|
|
}
|
|
else
|
|
{
|
|
player = engine->globalObject().property("me").toInt32();
|
|
}
|
|
updatePlayerPower(player, ticks);
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{setPower(power[, player])}
|
|
//-- Set a player's power directly. (Do not use this in an AI script.)
|
|
static QScriptValue js_setPower(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
int power = context->argument(0).toInt32();
|
|
int player;
|
|
if (context->argumentCount() > 1)
|
|
{
|
|
player = context->argument(1).toInt32();
|
|
SCRIPT_ASSERT_PLAYER(context, player);
|
|
}
|
|
else
|
|
{
|
|
player = engine->globalObject().property("me").toInt32();
|
|
}
|
|
setPower(player, power);
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{setPowerModifier(power[, player])}
|
|
//-- Set a player's power modifier percentage. (Do not use this in an AI script.)
|
|
static QScriptValue js_setPowerModifier(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
int power = context->argument(0).toInt32();
|
|
int player;
|
|
if (context->argumentCount() > 1)
|
|
{
|
|
player = context->argument(1).toInt32();
|
|
SCRIPT_ASSERT_PLAYER(context, player);
|
|
}
|
|
else
|
|
{
|
|
player = engine->globalObject().property("me").toInt32();
|
|
}
|
|
setPowerModifier(player, power);
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{enableStructure(structure type[, player])}
|
|
//-- The given structure type is made available to the given player. It will appear in the
|
|
//-- player's build list.
|
|
static QScriptValue js_enableStructure(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QString building = context->argument(0).toString();
|
|
int index = getStructStatFromName(building.toUtf8().constData());
|
|
int player;
|
|
if (context->argumentCount() > 1)
|
|
{
|
|
player = context->argument(1).toInt32();
|
|
SCRIPT_ASSERT_PLAYER(context, player);
|
|
}
|
|
else
|
|
{
|
|
player = engine->globalObject().property("me").toInt32();
|
|
}
|
|
SCRIPT_ASSERT(context, index >= 0 && index < numStructureStats, "Invalid structure stat");
|
|
// enable the appropriate structure
|
|
apStructTypeLists[player][index] = AVAILABLE;
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{setTutorialMode(bool)} Sets a number of restrictions appropriate for tutorial if set to true.
|
|
static QScriptValue js_setTutorialMode(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
bInTutorial = context->argument(0).toBool();
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{setMiniMap(bool)} Turns visible minimap on or off in the GUI.
|
|
static QScriptValue js_setMiniMap(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
radarPermitted = context->argument(0).toBool();
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{setDesign(bool)} Whether to allow player to design stuff.
|
|
static QScriptValue js_setDesign(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
DROID_TEMPLATE *psCurr;
|
|
allowDesign = context->argument(0).toBool();
|
|
// Switch on or off future templates
|
|
// FIXME: This dual data structure for templates is just plain insane.
|
|
for (psCurr = apsDroidTemplates[selectedPlayer]; psCurr != NULL; psCurr = psCurr->psNext)
|
|
{
|
|
bool researched = researchedTemplate(psCurr, selectedPlayer);
|
|
psCurr->enabled = (researched || allowDesign);
|
|
}
|
|
for (std::list<DROID_TEMPLATE>::iterator i = localTemplates.begin(); i != localTemplates.end(); ++i)
|
|
{
|
|
psCurr = &*i;
|
|
bool researched = researchedTemplate(psCurr, selectedPlayer);
|
|
psCurr->enabled = (researched || allowDesign);
|
|
}
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{enableTemplate(template name)} Enable a specific template (even if design is disabled).
|
|
static QScriptValue js_enableTemplate(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
DROID_TEMPLATE *psCurr;
|
|
QString templateName = context->argument(0).toString();
|
|
bool found = false;
|
|
// FIXME: This dual data structure for templates is just plain insane.
|
|
for (psCurr = apsDroidTemplates[selectedPlayer]; psCurr != NULL; psCurr = psCurr->psNext)
|
|
{
|
|
if (templateName.compare(psCurr->id) == 0)
|
|
{
|
|
psCurr->enabled = true;
|
|
found = true;
|
|
}
|
|
}
|
|
if (!found)
|
|
{
|
|
debug(LOG_ERROR, "Template %s was not found!", templateName.toUtf8().constData());
|
|
return QScriptValue(false);
|
|
}
|
|
for (std::list<DROID_TEMPLATE>::iterator i = localTemplates.begin(); i != localTemplates.end(); ++i)
|
|
{
|
|
psCurr = &*i;
|
|
if (templateName.compare(psCurr->id) == 0)
|
|
{
|
|
psCurr->enabled = true;
|
|
}
|
|
}
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{setReticuleButton(id, filename, filenameHigh, tooltip, callback)} Add reticule button. id is which
|
|
//-- button to change, where zero is zero is the middle button, then going clockwise from the uppermost
|
|
//-- button. filename is button graphics and filenameHigh is for highlighting. The tooltip is the text you see when you
|
|
//-- mouse over the button. Finally, the callback is which scripting function to call. Hide and show the user interface
|
|
//-- for such changes to take effect.
|
|
static QScriptValue js_setReticuleButton(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
int button = context->argument(0).toInt32();
|
|
SCRIPT_ASSERT(context, button >= 0 && button <= 6, "Invalid button %d", button);
|
|
QString tip = context->argument(1).toString();
|
|
QString file = context->argument(2).toString();
|
|
QString fileDown = context->argument(3).toString();
|
|
setReticuleStats(button, tip, file, fileDown);
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{showInterface()} Show user interface.
|
|
static QScriptValue js_showInterface(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
intAddReticule();
|
|
intShowPowerBar();
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{hideInterface(button type)} Hide user interface.
|
|
static QScriptValue js_hideInterface(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
intRemoveReticule();
|
|
intHidePowerBar();
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{removeReticuleButton(button type)} Remove reticule button. DO NOT USE FOR ANYTHING.
|
|
static QScriptValue js_removeReticuleButton(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{applyLimitSet()} Mix user set limits with script set limits and defaults.
|
|
static QScriptValue js_applyLimitSet(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
Q_UNUSED(context);
|
|
Q_UNUSED(engine);
|
|
applyLimitSet();
|
|
return QScriptValue();
|
|
}
|
|
|
|
static void setComponent(QString name, int player, int value)
|
|
{
|
|
COMPONENT_STATS *psComp = getCompStatsFromName(name);
|
|
ASSERT_OR_RETURN(, psComp, "Bad component %s", name.toUtf8().constData());
|
|
apCompLists[player][psComp->compType][psComp->index] = value;
|
|
}
|
|
|
|
//-- \subsection{enableComponent(component, player)}
|
|
//-- The given component is made available for research for the given player.
|
|
static QScriptValue js_enableComponent(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QString componentName = context->argument(0).toString();
|
|
int player = context->argument(1).toInt32();
|
|
|
|
SCRIPT_ASSERT_PLAYER(context, player);
|
|
setComponent(componentName, player, FOUND);
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{makeComponentAvailable(component, player)}
|
|
//-- The given component is made available to the given player. This means the player can
|
|
//-- actually build designs with it.
|
|
static QScriptValue js_makeComponentAvailable(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QString componentName = context->argument(0).toString();
|
|
int player = context->argument(1).toInt32();
|
|
|
|
SCRIPT_ASSERT_PLAYER(context, player);
|
|
setComponent(componentName, player, AVAILABLE);
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{allianceExistsBetween(player, player)}
|
|
//-- Returns true if an alliance exists between the two players, or they are the same player.
|
|
static QScriptValue js_allianceExistsBetween(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
int player1 = context->argument(0).toInt32();
|
|
int player2 = context->argument(1).toInt32();
|
|
SCRIPT_ASSERT(context, player1 < MAX_PLAYERS && player1 >= 0, "Invalid player");
|
|
SCRIPT_ASSERT(context, player2 < MAX_PLAYERS && player2 >= 0, "Invalid player");
|
|
return QScriptValue(alliances[player1][player2] == ALLIANCE_FORMED);
|
|
}
|
|
|
|
//-- \subsection{_(string)}
|
|
//-- Mark string for translation.
|
|
static QScriptValue js_translate(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
return QScriptValue(gettext(context->argument(0).toString().toUtf8().constData()));
|
|
}
|
|
|
|
//-- \subsection{playerPower(player)}
|
|
//-- Return amount of power held by the given player.
|
|
static QScriptValue js_playerPower(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
int player = context->argument(0).toInt32();
|
|
SCRIPT_ASSERT_PLAYER(context, player);
|
|
return QScriptValue(getPower(player));
|
|
}
|
|
|
|
//-- \subsection{queuedPower(player)}
|
|
//-- Return amount of power queued up for production by the given player.
|
|
static QScriptValue js_queuedPower(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
int player = context->argument(0).toInt32();
|
|
SCRIPT_ASSERT_PLAYER(context, player);
|
|
return QScriptValue(getQueuedPower(player));
|
|
}
|
|
|
|
//-- \subsection{isStructureAvailable(structure type[, player])}
|
|
//-- Returns true if given structure can be built. It checks both research and unit limits.
|
|
static QScriptValue js_isStructureAvailable(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QString building = context->argument(0).toString();
|
|
int index = getStructStatFromName(building.toUtf8().constData());
|
|
SCRIPT_ASSERT(context, index >= 0, "%s not found", building.toUtf8().constData());
|
|
int player;
|
|
if (context->argumentCount() > 1)
|
|
{
|
|
player = context->argument(1).toInt32();
|
|
}
|
|
else
|
|
{
|
|
player = engine->globalObject().property("me").toInt32();
|
|
}
|
|
return QScriptValue(apStructTypeLists[player][index] == AVAILABLE
|
|
&& asStructLimits[player][index].currentQuantity < asStructLimits[player][index].limit);
|
|
}
|
|
|
|
//-- \subsection{isVTOL(droid)}
|
|
//-- Returns true if given droid is a VTOL (not including transports).
|
|
static QScriptValue js_isVTOL(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QScriptValue droidVal = context->argument(0);
|
|
int id = droidVal.property("id").toInt32();
|
|
int player = droidVal.property("player").toInt32();
|
|
DROID *psDroid = IdToDroid(id, player);
|
|
SCRIPT_ASSERT(context, psDroid, "No such droid id %d belonging to player %d", id, player);
|
|
return QScriptValue(isVtolDroid(psDroid));
|
|
}
|
|
|
|
//-- \subsection{hackGetObj(type, player, id)}
|
|
//-- Function to find and return a game object of DROID, FEATURE or STRUCTURE types, if it exists.
|
|
//-- Otherwise, it will return null. This function is deprecated by getObject().
|
|
static QScriptValue js_hackGetObj(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
OBJECT_TYPE type = (OBJECT_TYPE)context->argument(0).toInt32();
|
|
int player = context->argument(1).toInt32();
|
|
int id = context->argument(2).toInt32();
|
|
SCRIPT_ASSERT_PLAYER(context, player);
|
|
return QScriptValue(convMax(IdToObject(type, id, player), engine));
|
|
}
|
|
|
|
//-- \subsection{hackChangeMe(player)}
|
|
//-- Change the 'me' who owns this script to the given player. This needs to be run
|
|
//-- first in \emph{eventGameInit} to make sure things do not get out of control.
|
|
// This is only intended for use in campaign scripts until we get a way to add
|
|
// scripts for each player.
|
|
static QScriptValue js_hackChangeMe(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
int me = context->argument(0).toInt32();
|
|
SCRIPT_ASSERT_PLAYER(context, me);
|
|
engine->globalObject().setProperty("me", me);
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{receiveAllEvents(bool)}
|
|
//-- Make the current script receive all events, even those not meant for 'me'.
|
|
static QScriptValue js_receiveAllEvents(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
if (context->argumentCount() > 0)
|
|
{
|
|
bool value = context->argument(0).toBool();
|
|
engine->globalObject().setProperty("isReceivingAllEvents", value, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
}
|
|
return engine->globalObject().property("isReceivingAllEvents");
|
|
}
|
|
|
|
//-- \subsection{hackAssert(condition, message...)}
|
|
//-- Function to perform unit testing. It will throw a script error and a game assert.
|
|
static QScriptValue js_hackAssert(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
bool condition = context->argument(0).toBool();
|
|
if (condition)
|
|
{
|
|
return QScriptValue(); // pass
|
|
}
|
|
// fail
|
|
QString result;
|
|
for (int i = 1; i < context->argumentCount(); ++i)
|
|
{
|
|
if (i != 1)
|
|
{
|
|
result.append(QLatin1String(" "));
|
|
}
|
|
QString s = context->argument(i).toString();
|
|
if (context->state() == QScriptContext::ExceptionState)
|
|
{
|
|
break;
|
|
}
|
|
result.append(s);
|
|
}
|
|
context->throwError(QScriptContext::ReferenceError, result + " in " + QString(__FUNCTION__) + " at line " + QString::number(__LINE__));
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{objFromId(fake game object)}
|
|
//-- Broken function meant to make porting from the old scripting system easier. Do not use for new code.
|
|
//-- Instead, use labels.
|
|
static QScriptValue js_objFromId(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QScriptValue droidVal = context->argument(0);
|
|
int id = droidVal.property("id").toInt32();
|
|
BASE_OBJECT *psObj = getBaseObjFromId(id);
|
|
SCRIPT_ASSERT(context, psObj, "No such object id %d", id);
|
|
return QScriptValue(convMax(psObj, engine));
|
|
}
|
|
|
|
//-- \subsection{setDroidExperience(droid, experience)}
|
|
//-- Set the amount of experience a droid has. Experience is read using floating point precision.
|
|
static QScriptValue js_setDroidExperience(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QScriptValue droidVal = context->argument(0);
|
|
int id = droidVal.property("id").toInt32();
|
|
int player = droidVal.property("player").toInt32();
|
|
DROID *psDroid = IdToDroid(id, player);
|
|
SCRIPT_ASSERT(context, psDroid, "No such droid id %d belonging to player %d", id, player);
|
|
psDroid->experience = context->argument(1).toNumber() * 65536;
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{donateObject(object, to)}
|
|
//-- Donate a game object (currently restricted to droids) to another player. Returns true if
|
|
//-- donation was successful. May return false if this donation would push the receiving player
|
|
//-- over unit limits.
|
|
static QScriptValue js_donateObject(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QScriptValue val = context->argument(0);
|
|
uint32_t id = val.property("id").toUInt32();
|
|
uint8_t player = val.property("player").toInt32();
|
|
OBJECT_TYPE type = (OBJECT_TYPE)val.property("type").toInt32();
|
|
uint8_t to = context->argument(1).toInt32();
|
|
if (type == OBJ_DROID)
|
|
{
|
|
// Check unit limits.
|
|
DROID *psDroid = IdToDroid(id, player);
|
|
SCRIPT_ASSERT(context, psDroid, "No such droid id %u belonging to player %u", id, player);
|
|
if ((psDroid->droidType == DROID_COMMAND && getNumCommandDroids(to) + 1 > getMaxCommanders(to))
|
|
|| (psDroid->droidType == DROID_CONSTRUCT && getNumConstructorDroids(to) + 1 > getMaxConstructors(to))
|
|
|| getNumDroids(to) + 1 > getMaxDroids(to))
|
|
{
|
|
return QScriptValue(false);
|
|
}
|
|
uint8_t giftType = DROID_GIFT;
|
|
NETbeginEncode(NETgameQueue(selectedPlayer), GAME_GIFT);
|
|
NETuint8_t(&giftType);
|
|
NETuint8_t(&player);
|
|
NETuint8_t(&to);
|
|
NETuint32_t(&id);
|
|
NETend();
|
|
}
|
|
return QScriptValue(true);
|
|
}
|
|
|
|
//-- \subsection{donatePower(amount, to)}
|
|
//-- Donate power to another player. Returns true.
|
|
static QScriptValue js_donatePower(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
int amount = context->argument(0).toInt32();
|
|
int to = context->argument(1).toInt32();
|
|
int from = engine->globalObject().property("me").toInt32();
|
|
giftPower(from, to, amount, true);
|
|
return QScriptValue(true);
|
|
}
|
|
|
|
//-- \subsection{safeDest(player, x, y)} Returns true if given player is safe from hostile fire at
|
|
//-- the given location, to the best of that player's map knowledge.
|
|
static QScriptValue js_safeDest(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
int player = context->argument(0).toInt32();
|
|
SCRIPT_ASSERT_PLAYER(context, player);
|
|
int x = context->argument(1).toInt32();
|
|
int y = context->argument(2).toInt32();
|
|
SCRIPT_ASSERT(context, tileOnMap(x, y), "Out of bounds coordinates(%d, %d)", x, y);
|
|
return QScriptValue(!(auxTile(x, y, player) & AUXBITS_DANGER));
|
|
}
|
|
|
|
//-- \subsection{addStructure(structure type, player, x, y)}
|
|
//-- Create a structure on the given position. Returns the structure on success, null otherwise.
|
|
static QScriptValue js_addStructure(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QString building = context->argument(0).toString();
|
|
int index = getStructStatFromName(building.toUtf8().constData());
|
|
SCRIPT_ASSERT(context, index >= 0, "%s not found", building.toUtf8().constData());
|
|
int player = context->argument(1).toInt32();
|
|
SCRIPT_ASSERT_PLAYER(context, player);
|
|
int x = context->argument(2).toInt32();
|
|
int y = context->argument(3).toInt32();
|
|
STRUCTURE_STATS *psStat = &asStructureStats[index];
|
|
STRUCTURE *psStruct = buildStructure(psStat, x, y, player, false);
|
|
if (psStruct)
|
|
{
|
|
psStruct->status = SS_BUILT;
|
|
buildingComplete(psStruct);
|
|
return QScriptValue(convStructure(psStruct, engine));
|
|
}
|
|
return QScriptValue::NullValue;
|
|
}
|
|
|
|
//-- \subsection{getStructureLimit(structure type[, player])}
|
|
//-- Returns build limits for a structure.
|
|
static QScriptValue js_getStructureLimit(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QString building = context->argument(0).toString();
|
|
int index = getStructStatFromName(building.toUtf8().constData());
|
|
SCRIPT_ASSERT(context, index >= 0, "%s not found", building.toUtf8().constData());
|
|
int player;
|
|
if (context->argumentCount() > 1)
|
|
{
|
|
player = context->argument(1).toInt32();
|
|
}
|
|
else
|
|
{
|
|
player = engine->globalObject().property("me").toInt32();
|
|
}
|
|
return QScriptValue(asStructLimits[player][index].limit);
|
|
}
|
|
|
|
//-- \subsection{countStruct(structure type[, player])}
|
|
//-- Count the number of structures of a given type.
|
|
//-- The player parameter can be a specific player, ALL_PLAYERS, ALLIES or ENEMIES.
|
|
static QScriptValue js_countStruct(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QString building = context->argument(0).toString();
|
|
int index = getStructStatFromName(building.toUtf8().constData());
|
|
int me = engine->globalObject().property("me").toInt32();
|
|
int player = me;
|
|
int quantity = 0;
|
|
if (context->argumentCount() > 1)
|
|
{
|
|
player = context->argument(1).toInt32();
|
|
}
|
|
SCRIPT_ASSERT(context, index < numStructureStats && index >= 0, "Structure %s not found", building.toUtf8().constData());
|
|
for (int i = 0; i < MAX_PLAYERS; i++)
|
|
{
|
|
if (player == i || player == ALL_PLAYERS
|
|
|| (player == ALLIES && aiCheckAlliances(i, me))
|
|
|| (player == ENEMIES && !aiCheckAlliances(i, me)))
|
|
{
|
|
quantity += asStructLimits[i][index].currentQuantity;
|
|
}
|
|
}
|
|
return QScriptValue(quantity);
|
|
}
|
|
|
|
//-- \subsection{countDroid([droid type[, player]])}
|
|
//-- Count the number of droids that a given player has. Droid type must be either
|
|
//-- DROID_ANY, DROID_COMMAND or DROID_CONSTRUCT.
|
|
//-- The player parameter can be a specific player, ALL_PLAYERS, ALLIES or ENEMIES.
|
|
static QScriptValue js_countDroid(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
int me = engine->globalObject().property("me").toInt32();
|
|
int player = me;
|
|
int quantity = 0;
|
|
int type = DROID_ANY;
|
|
if (context->argumentCount() > 0)
|
|
{
|
|
type = context->argument(0).toInt32();
|
|
}
|
|
SCRIPT_ASSERT(context, type <= DROID_ANY, "Bad droid type parameter");
|
|
if (context->argumentCount() > 1)
|
|
{
|
|
player = context->argument(1).toInt32();
|
|
}
|
|
for (int i = 0; i < MAX_PLAYERS; i++)
|
|
{
|
|
if (player == i || player == ALL_PLAYERS
|
|
|| (player == ALLIES && aiCheckAlliances(i, me))
|
|
|| (player == ENEMIES && !aiCheckAlliances(i, me)))
|
|
{
|
|
if (type == DROID_ANY)
|
|
{
|
|
quantity += getNumDroids(i);
|
|
}
|
|
else if (type == DROID_CONSTRUCT)
|
|
{
|
|
quantity += getNumConstructorDroids(i);
|
|
}
|
|
else if (type == DROID_COMMAND)
|
|
{
|
|
quantity += getNumCommandDroids(i);
|
|
}
|
|
}
|
|
}
|
|
return QScriptValue(quantity);
|
|
}
|
|
|
|
//-- \subsection{setNoGoArea(x1, y1, x2, y2, player)}
|
|
//-- Creates an area on the map on which nothing can be built. If player is zero,
|
|
//-- then landing lights are placed. If player is -1, then a limbo landing zone
|
|
//-- is created and limbo droids placed.
|
|
// FIXME: missing a way to call initNoGoAreas(); check if we can call this in
|
|
// every level start instead of through scripts
|
|
static QScriptValue js_setNoGoArea(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
const int x1 = context->argument(0).toInt32();
|
|
const int y1 = context->argument(1).toInt32();
|
|
const int x2 = context->argument(2).toInt32();
|
|
const int y2 = context->argument(3).toInt32();
|
|
const int player = context->argument(4).toInt32();
|
|
|
|
SCRIPT_ASSERT(context, x1 >= 0, "Minimum scroll x value %d is less than zero - ", x1);
|
|
SCRIPT_ASSERT(context, y1 >= 0, "Minimum scroll y value %d is less than zero - ", y1);
|
|
SCRIPT_ASSERT(context, x2 <= mapWidth, "Maximum scroll x value %d is greater than mapWidth %d", x2, (int)mapWidth);
|
|
SCRIPT_ASSERT(context, y2 <= mapHeight, "Maximum scroll y value %d is greater than mapHeight %d", y2, (int)mapHeight);
|
|
SCRIPT_ASSERT(context, player < MAX_PLAYERS && player >= -1, "Bad player value %d", player);
|
|
|
|
if (player == -1)
|
|
{
|
|
setNoGoArea(x1, y1, x2, y2, LIMBO_LANDING);
|
|
placeLimboDroids(); // this calls the Droids from the Limbo list onto the map
|
|
}
|
|
else
|
|
{
|
|
setNoGoArea(x1, y1, x2, y2, player);
|
|
}
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{setScrollLimits(x1, y1, x2, y2)}
|
|
//-- Limit the scrollable area of the map to the given rectangle.
|
|
static QScriptValue js_setScrollLimits(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
const int minX = context->argument(0).toInt32();
|
|
const int minY = context->argument(1).toInt32();
|
|
const int maxX = context->argument(2).toInt32();
|
|
const int maxY = context->argument(3).toInt32();
|
|
|
|
SCRIPT_ASSERT(context, minX >= 0, "Minimum scroll x value %d is less than zero - ", minX);
|
|
SCRIPT_ASSERT(context, minY >= 0, "Minimum scroll y value %d is less than zero - ", minY);
|
|
SCRIPT_ASSERT(context, maxX <= mapWidth, "Maximum scroll x value %d is greater than mapWidth %d", maxX, (int)mapWidth);
|
|
SCRIPT_ASSERT(context, maxY <= mapHeight, "Maximum scroll y value %d is greater than mapHeight %d", maxY, (int)mapHeight);
|
|
|
|
const int prevMinX = scrollMinX;
|
|
const int prevMinY = scrollMinY;
|
|
const int prevMaxX = scrollMaxX;
|
|
const int prevMaxY = scrollMaxY;
|
|
|
|
scrollMinX = minX;
|
|
scrollMaxX = maxX;
|
|
scrollMinY = minY;
|
|
scrollMaxY = maxY;
|
|
|
|
// When the scroll limits change midgame - need to redo the lighting
|
|
initLighting(prevMinX < scrollMinX ? prevMinX : scrollMinX,
|
|
prevMinY < scrollMinY ? prevMinY : scrollMinY,
|
|
prevMaxX < scrollMaxX ? prevMaxX : scrollMaxX,
|
|
prevMaxY < scrollMaxY ? prevMaxY : scrollMaxY);
|
|
|
|
// need to reset radar to take into account of new size
|
|
resizeRadar();
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{getScrollLimits()}
|
|
//-- Get the limits of the scrollable area of the map as an area object.
|
|
static QScriptValue js_getScrollLimits(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QScriptValue ret = engine->newObject();
|
|
ret.setProperty("x", scrollMinX, QScriptValue::ReadOnly);
|
|
ret.setProperty("y", scrollMinY, QScriptValue::ReadOnly);
|
|
ret.setProperty("x2", scrollMaxX, QScriptValue::ReadOnly);
|
|
ret.setProperty("y2", scrollMaxY, QScriptValue::ReadOnly);
|
|
ret.setProperty("type", SCRIPT_AREA, QScriptValue::ReadOnly);
|
|
return ret;
|
|
}
|
|
|
|
//-- \subsection{loadLevel(level name)}
|
|
//-- Load the level with the given name.
|
|
static QScriptValue js_loadLevel(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
QString level = context->argument(0).toString();
|
|
|
|
sstrcpy(aLevelName, level.toUtf8().constData());
|
|
|
|
// Find the level dataset
|
|
LEVEL_DATASET *psNewLevel = levFindDataSet(level.toUtf8().constData());
|
|
SCRIPT_ASSERT(context, psNewLevel, "Could not find level data for %s", level.toUtf8().constData());
|
|
|
|
// Get the mission rolling...
|
|
nextMissionType = psNewLevel->type;
|
|
loopMissionState = LMS_CLEAROBJECTS;
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{enumRange(x, y, range[, filter[, seen]])}
|
|
//-- Returns an array of game objects seen within range of given position that passes the optional filter
|
|
//-- which can be one of a player index, ALL_PLAYERS, ALLIES or ENEMIES. By default, filter is
|
|
//-- ALL_PLAYERS. Finally an optional parameter can specify whether only visible objects should be
|
|
//-- returned; by default only visible objects are returned. Calling this function is much faster than
|
|
//-- iterating over all game objects using other enum functions. (3.2+ only)
|
|
static QScriptValue js_enumRange(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
int player = engine->globalObject().property("me").toInt32();
|
|
int x = world_coord(context->argument(0).toInt32());
|
|
int y = world_coord(context->argument(1).toInt32());
|
|
int range = world_coord(context->argument(2).toInt32());
|
|
int filter = ALL_PLAYERS;
|
|
bool seen = true;
|
|
if (context->argumentCount() > 3)
|
|
{
|
|
filter = context->argument(3).toInt32();
|
|
}
|
|
if (context->argumentCount() > 4)
|
|
{
|
|
seen = context->argument(4).toBool();
|
|
}
|
|
static GridList gridList; // static to avoid allocations.
|
|
gridList = gridStartIterate(x, y, range);
|
|
QList<BASE_OBJECT *> list;
|
|
for (GridIterator gi = gridList.begin(); gi != gridList.end(); ++gi)
|
|
{
|
|
BASE_OBJECT *psObj = *gi;
|
|
if ((psObj->visible[player] || !seen) && !psObj->died)
|
|
{
|
|
if ((filter >= 0 && psObj->player == filter) || filter == ALL_PLAYERS
|
|
|| (filter == ALLIES && psObj->type != OBJ_FEATURE && aiCheckAlliances(psObj->player, player))
|
|
|| (filter == ENEMIES && psObj->type != OBJ_FEATURE && !aiCheckAlliances(psObj->player, player)))
|
|
{
|
|
list.append(psObj);
|
|
}
|
|
}
|
|
}
|
|
QScriptValue value = engine->newArray(list.size());
|
|
for (int i = 0; i < list.size(); i++)
|
|
{
|
|
value.setProperty(i, convMax(list[i], engine), QScriptValue::ReadOnly);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
//-- \subsection{enumArea(<x1, y1, x2, y2 | label>[, filter[, seen]])}
|
|
//-- Returns an array of game objects seen within the given area that passes the optional filter
|
|
//-- which can be one of a player index, ALL_PLAYERS, ALLIES or ENEMIES. By default, filter is
|
|
//-- ALL_PLAYERS. Finally an optional parameter can specify whether only visible objects should be
|
|
//-- returned; by default only visible objects are returned. The label can either be actual
|
|
//-- positions or a label to an AREA. Calling this function is much faster than iterating over all
|
|
//-- game objects using other enum functions. (3.2+ only)
|
|
static QScriptValue js_enumArea(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
int player = engine->globalObject().property("me").toInt32();
|
|
int x1, y1, x2, y2, nextparam;
|
|
int filter = ALL_PLAYERS;
|
|
bool seen = true;
|
|
if (context->argument(0).isString())
|
|
{
|
|
QString label = context->argument(0).toString();
|
|
nextparam = 1;
|
|
SCRIPT_ASSERT(context, labels.contains(label), "Label %s not found", label.toUtf8().constData());
|
|
labeltype p = labels.value(label);
|
|
SCRIPT_ASSERT(context, p.type == SCRIPT_AREA, "Wrong label type for %s", label.toUtf8().constData());
|
|
x1 = p.p1.x;
|
|
y1 = p.p1.y;
|
|
x2 = p.p2.x;
|
|
y2 = p.p2.y;
|
|
}
|
|
else
|
|
{
|
|
x1 = world_coord(context->argument(0).toInt32());
|
|
y1 = world_coord(context->argument(1).toInt32());
|
|
x2 = world_coord(context->argument(2).toInt32());
|
|
y2 = world_coord(context->argument(3).toInt32());
|
|
nextparam = 4;
|
|
}
|
|
if (context->argumentCount() > nextparam++)
|
|
{
|
|
filter = context->argument(nextparam - 1).toInt32();
|
|
}
|
|
if (context->argumentCount() > nextparam++)
|
|
{
|
|
seen = context->argument(nextparam - 1).toBool();
|
|
}
|
|
static GridList gridList; // static to avoid allocations.
|
|
gridList = gridStartIterateArea(x1, y1, x2, y2);
|
|
QList<BASE_OBJECT *> list;
|
|
for (GridIterator gi = gridList.begin(); gi != gridList.end(); ++gi)
|
|
{
|
|
BASE_OBJECT *psObj = *gi;
|
|
if ((psObj->visible[player] || !seen) && !psObj->died)
|
|
{
|
|
if ((filter >= 0 && psObj->player == filter) || filter == ALL_PLAYERS
|
|
|| (filter == ALLIES && psObj->type != OBJ_FEATURE && aiCheckAlliances(psObj->player, player))
|
|
|| (filter == ENEMIES && psObj->type != OBJ_FEATURE && !aiCheckAlliances(psObj->player, player)))
|
|
{
|
|
list.append(psObj);
|
|
}
|
|
}
|
|
}
|
|
QScriptValue value = engine->newArray(list.size());
|
|
for (int i = 0; i < list.size(); i++)
|
|
{
|
|
value.setProperty(i, convMax(list[i], engine), QScriptValue::ReadOnly);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
//-- \subsection{addBeacon(x, y, target player[, message])}
|
|
//-- Send a beacon message to target player. Target may also be \emph{ALLIES}.
|
|
//-- Message is currently unused. Returns a boolean that is true on success. (3.2+ only)
|
|
static QScriptValue js_addBeacon(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
int x = world_coord(context->argument(0).toInt32());
|
|
int y = world_coord(context->argument(1).toInt32());
|
|
int target = context->argument(2).toInt32();
|
|
QString message = context->argument(3).toString();
|
|
int me = engine->globalObject().property("me").toInt32();
|
|
SCRIPT_ASSERT(context, target >= 0 || target == ALLIES, "Message to invalid player %d", target);
|
|
for (int i = 0; i < MAX_PLAYERS; i++)
|
|
{
|
|
if (i != me && (i == target || (target == ALLIES && aiCheckAlliances(i, me))))
|
|
{
|
|
debug(LOG_MSG, "adding script beacon to %d from %d", i, me);
|
|
sendBeaconToPlayer(x, y, i, me, message.toUtf8().constData());
|
|
}
|
|
}
|
|
return QScriptValue(true);
|
|
}
|
|
|
|
//-- \subsection{removeBeacon(target player)}
|
|
//-- Remove a beacon message sent to target player. Target may also be \emph{ALLIES}.
|
|
//-- Returns a boolean that is true on success. (3.2+ only)
|
|
static QScriptValue js_removeBeacon(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
int me = engine->globalObject().property("me").toInt32();
|
|
int target = context->argument(0).toInt32();
|
|
SCRIPT_ASSERT(context, target >= 0 || target == ALLIES, "Message to invalid player %d", target);
|
|
for (int i = 0; i < MAX_PLAYERS; i++)
|
|
{
|
|
if (i == target || (target == ALLIES && aiCheckAlliances(i, me)))
|
|
{
|
|
MESSAGE *psMessage = findBeaconMsg(i, me);
|
|
if (psMessage)
|
|
{
|
|
removeMessage(psMessage, i);
|
|
triggerEventBeaconRemoved(me, i);
|
|
}
|
|
}
|
|
}
|
|
return QScriptValue(true);
|
|
}
|
|
|
|
//-- \subsection{chat(target player, message)}
|
|
//-- Send a message to target player. Target may also be \emph{ALL_PLAYERS} or \emph{ALLIES}.
|
|
//-- Returns a boolean that is true on success. (3.2+ only)
|
|
static QScriptValue js_chat(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
int player = engine->globalObject().property("me").toInt32();
|
|
int target = context->argument(0).toInt32();
|
|
QString message = context->argument(1).toString();
|
|
SCRIPT_ASSERT(context, target >= 0 || target == ALL_PLAYERS || target == ALLIES, "Message to invalid player %d", target);
|
|
if (target == ALL_PLAYERS) // all
|
|
{
|
|
return QScriptValue(sendTextMessage(message.toUtf8().constData(), true, player));
|
|
}
|
|
else if (target == ALLIES) // allies
|
|
{
|
|
return QScriptValue(sendTextMessage(QString(". " + message).toUtf8().constData(), false, player));
|
|
}
|
|
else // specific player
|
|
{
|
|
QString tmp = QString::number(NetPlay.players[target].position) + message;
|
|
return QScriptValue(sendTextMessage(tmp.toUtf8().constData(), false, player));
|
|
}
|
|
}
|
|
|
|
//-- \subsection{setAlliance(player1, player2, value)}
|
|
//-- Set alliance status between two players to either true or false. (3.2+ only)
|
|
static QScriptValue js_setAlliance(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
int player1 = context->argument(0).toInt32();
|
|
int player2 = context->argument(1).toInt32();
|
|
bool value = context->argument(2).toBool();
|
|
if (value)
|
|
{
|
|
formAlliance(player1, player2, true, false, true);
|
|
}
|
|
else
|
|
{
|
|
breakAlliance(player1, player2, true, true);
|
|
}
|
|
return QScriptValue(true);
|
|
}
|
|
|
|
//-- \subsection{setAssemblyPoint(structure, x, y)}
|
|
//-- Set the assembly point droids go to when built for the specified structure. (3.2+ only)
|
|
static QScriptValue js_setAssemblyPoint(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QScriptValue structVal = context->argument(0);
|
|
int id = structVal.property("id").toInt32();
|
|
int player = structVal.property("player").toInt32();
|
|
STRUCTURE *psStruct = IdToStruct(id, player);
|
|
SCRIPT_ASSERT(context, psStruct, "No such structure id %d belonging to player %d", id, player);
|
|
int x = context->argument(1).toInt32();
|
|
int y = context->argument(2).toInt32();
|
|
SCRIPT_ASSERT(context, psStruct->pStructureType->type == REF_FACTORY
|
|
|| psStruct->pStructureType->type == REF_CYBORG_FACTORY
|
|
|| psStruct->pStructureType->type == REF_VTOL_FACTORY, "Structure not a factory");
|
|
setAssemblyPoint(((FACTORY *)psStruct->pFunctionality)->psAssemblyPoint, x, y, player, true);
|
|
return QScriptValue(true);
|
|
}
|
|
|
|
//-- \subsection{hackNetOff()}
|
|
//-- Turn off network transmissions. FIXME - find a better way.
|
|
static QScriptValue js_hackNetOff(QScriptContext *, QScriptEngine *)
|
|
{
|
|
bMultiPlayer = false;
|
|
bMultiMessages = false;
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{hackNetOn()}
|
|
//-- Turn on network transmissions. FIXME - find a better way.
|
|
static QScriptValue js_hackNetOn(QScriptContext *, QScriptEngine *)
|
|
{
|
|
bMultiPlayer = true;
|
|
bMultiMessages = true;
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{getDroidProduction(factory)}
|
|
//-- Return droid in production in given factory. Note that this droid is fully
|
|
//-- virtual, and should never be passed anywhere.
|
|
static QScriptValue js_getDroidProduction(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QScriptValue structVal = context->argument(0);
|
|
int id = structVal.property("id").toInt32();
|
|
int player = structVal.property("player").toInt32();
|
|
STRUCTURE *psStruct = IdToStruct(id, player);
|
|
SCRIPT_ASSERT(context, psStruct, "No such structure id %d belonging to player %d", id, player);
|
|
FACTORY *psFactory = &psStruct->pFunctionality->factory;
|
|
DROID_TEMPLATE *psTemp = psFactory->psSubject;
|
|
if (!psTemp)
|
|
{
|
|
return QScriptValue::NullValue;
|
|
}
|
|
DROID sDroid(0, player), *psDroid = &sDroid;
|
|
psDroid->pos = psStruct->pos;
|
|
psDroid->rot = psStruct->rot;
|
|
psDroid->experience = 0;
|
|
droidSetName(psDroid, getName(psTemp));
|
|
droidSetBits(psTemp, psDroid);
|
|
psDroid->weight = calcDroidWeight(psTemp);
|
|
psDroid->baseSpeed = calcDroidBaseSpeed(psTemp, psDroid->weight, player);
|
|
return convDroid(psDroid, engine);
|
|
}
|
|
|
|
//-- \subsection{getDroidLimit([player[, unit type]])}
|
|
//-- Return maximum number of droids that this player can produce. This limit is usually
|
|
//-- fixed throughout a game and the same for all players. If no arguments are passed,
|
|
//-- returns general unit limit for the current player. If a second, unit type argument
|
|
//-- is passed, the limit for this unit type is returned, which may be different from
|
|
//-- the general unit limit (eg for commanders and construction droids). (3.2+ only)
|
|
static QScriptValue js_getDroidLimit(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
if (context->argumentCount() > 1)
|
|
{
|
|
DROID_TYPE type = (DROID_TYPE)context->argument(1).toInt32();
|
|
if (type == DROID_COMMAND)
|
|
{
|
|
return QScriptValue(getMaxCommanders(context->argument(0).toInt32()));
|
|
}
|
|
else if (type == DROID_CONSTRUCT)
|
|
{
|
|
return QScriptValue(getMaxConstructors(context->argument(0).toInt32()));
|
|
}
|
|
// else return general unit limit
|
|
}
|
|
if (context->argumentCount() > 0)
|
|
{
|
|
return QScriptValue(getMaxDroids(context->argument(0).toInt32()));
|
|
}
|
|
return QScriptValue(getMaxDroids(engine->globalObject().property("me").toInt32()));
|
|
}
|
|
|
|
//-- \subsection{getExperienceModifier(player)}
|
|
//-- Get the % of experience this player droids are going to gain. (3.2+ only)
|
|
static QScriptValue js_getExperienceModifier(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
int player = context->argument(0).toInt32();
|
|
return QScriptValue(getExpGain(player));
|
|
}
|
|
|
|
//-- \subsection{setExperienceModifier(player, percent)}
|
|
//-- Set the % of experience this player droids are going to gain. (3.2+ only)
|
|
static QScriptValue js_setExperienceModifier(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
int player = context->argument(0).toInt32();
|
|
int percent = context->argument(1).toInt32();
|
|
setExpGain(player, percent);
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{setDroidLimit(player, value[, droid type])}
|
|
//-- Set the maximum number of droids that this player can produce. If a third
|
|
//-- parameter is added, this is the droid type to limit. It can be DROID_ANY
|
|
//-- for droids in general, DROID_CONSTRUCT for constructors, or DROID_COMMAND
|
|
//-- for commanders. (3.2+ only)
|
|
static QScriptValue js_setDroidLimit(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
int player = context->argument(0).toInt32();
|
|
int value = context->argument(1).toInt32();
|
|
DROID_TYPE type = DROID_ANY;
|
|
if (context->argumentCount() > 1)
|
|
{
|
|
type = (DROID_TYPE)context->argument(2).toInt32();
|
|
}
|
|
switch (type)
|
|
{
|
|
case DROID_CONSTRUCT:
|
|
setMaxConstructors(player, value);
|
|
break;
|
|
case DROID_COMMAND:
|
|
setMaxCommanders(player, value);
|
|
break;
|
|
default:
|
|
case DROID_ANY:
|
|
setMaxDroids(player, value);
|
|
break;
|
|
}
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{setCommanderLimit(player, value)}
|
|
//-- Set the maximum number of commanders that this player can produce.
|
|
//-- THIS FUNCTION IS DEPRECATED AND WILL BE REMOVED!
|
|
static QScriptValue js_setCommanderLimit(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
int player = context->argument(0).toInt32();
|
|
int value = context->argument(1).toInt32();
|
|
setMaxCommanders(player, value);
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{setConstructorLimit(player, value)}
|
|
//-- Set the maximum number of constructors that this player can produce.
|
|
//-- THIS FUNCTION IS DEPRECATED AND WILL BE REMOVED!
|
|
static QScriptValue js_setConstructorLimit(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
int player = context->argument(0).toInt32();
|
|
int value = context->argument(1).toInt32();
|
|
setMaxConstructors(player, value);
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{hackAddMessage(message, type, player, immediate)}
|
|
//-- See wzscript docs for info, to the extent any exist. (3.2+ only)
|
|
static QScriptValue js_hackAddMessage(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
QString mess = context->argument(0).toString();
|
|
MESSAGE_TYPE msgType = (MESSAGE_TYPE)context->argument(1).toInt32();
|
|
int player = context->argument(2).toInt32();
|
|
bool immediate = context->argument(3).toBool();
|
|
MESSAGE *psMessage = addMessage(msgType, false, player);
|
|
if (psMessage)
|
|
{
|
|
VIEWDATA *psViewData = getViewData(mess.toUtf8().constData());
|
|
SCRIPT_ASSERT(context, psViewData, "Viewdata not found");
|
|
psMessage->pViewData = (MSG_VIEWDATA *)psViewData;
|
|
debug(LOG_MSG, "Adding %s pViewData=%p", psViewData->pName, psMessage->pViewData);
|
|
if (msgType == MSG_PROXIMITY)
|
|
{
|
|
VIEW_PROXIMITY *psProx = (VIEW_PROXIMITY *)psViewData->pData;
|
|
// check the z value is at least the height of the terrain
|
|
int height = map_Height(psProx->x, psProx->y);
|
|
if (psProx->z < height)
|
|
{
|
|
psProx->z = height;
|
|
}
|
|
}
|
|
if (immediate)
|
|
{
|
|
displayImmediateMessage(psMessage);
|
|
}
|
|
}
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{hackRemoveMessage(message, type, player)}
|
|
//-- See wzscript docs for info, to the extent any exist. (3.2+ only)
|
|
static QScriptValue js_hackRemoveMessage(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
QString mess = context->argument(0).toString();
|
|
MESSAGE_TYPE msgType = (MESSAGE_TYPE)context->argument(1).toInt32();
|
|
int player = context->argument(2).toInt32();
|
|
VIEWDATA *psViewData = getViewData(mess.toUtf8().constData());
|
|
SCRIPT_ASSERT(context, psViewData, "Viewdata not found");
|
|
MESSAGE *psMessage = findMessage((MSG_VIEWDATA *)psViewData, msgType, player);
|
|
if (psMessage)
|
|
{
|
|
debug(LOG_MSG, "Removing %s", psViewData->pName);
|
|
removeMessage(psMessage, player);
|
|
}
|
|
else
|
|
{
|
|
debug(LOG_ERROR, "cannot find message - %s", psViewData->pName);
|
|
}
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{setSunPosition(x, y, z)}
|
|
//-- Move the position of the Sun, which in turn moves where shadows are cast. (3.2+ only)
|
|
static QScriptValue js_setSunPosition(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
float x = context->argument(0).toNumber();
|
|
float y = context->argument(1).toNumber();
|
|
float z = context->argument(2).toNumber();
|
|
setTheSun(Vector3f(x, y, z));
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{setSunIntensity(ambient r, g, b, diffuse r, g, b, specular r, g, b)}
|
|
//-- Set the ambient, diffuse and specular colour intensities of the Sun lighting source. (3.2+ only)
|
|
static QScriptValue js_setSunIntensity(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
float ambient[4];
|
|
float diffuse[4];
|
|
float specular[4];
|
|
ambient[0] = context->argument(0).toNumber();
|
|
ambient[1] = context->argument(1).toNumber();
|
|
ambient[2] = context->argument(2).toNumber();
|
|
ambient[3] = 1.0f;
|
|
diffuse[0] = context->argument(3).toNumber();
|
|
diffuse[1] = context->argument(4).toNumber();
|
|
diffuse[2] = context->argument(5).toNumber();
|
|
diffuse[3] = 1.0f;
|
|
specular[0] = context->argument(6).toNumber();
|
|
specular[1] = context->argument(7).toNumber();
|
|
specular[2] = context->argument(8).toNumber();
|
|
specular[3] = 1.0f;
|
|
pie_Lighting0(LIGHT_AMBIENT, ambient);
|
|
pie_Lighting0(LIGHT_DIFFUSE, diffuse);
|
|
pie_Lighting0(LIGHT_SPECULAR, specular);
|
|
pie_SetupLighting();
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{setWeather(weather type)}
|
|
//-- Set the current weather. This should be one of WEATHER_RAIN, WEATHER_SNOW or WEATHER_CLEAR. (3.2+ only)
|
|
static QScriptValue js_setWeather(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
WT_CLASS weather = (WT_CLASS)context->argument(0).toInt32();
|
|
SCRIPT_ASSERT(context, weather >= 0 && weather <= WT_NONE, "Bad weather type");
|
|
atmosSetWeatherType(weather);
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{setSky(texture file, wind speed, skybox scale)}
|
|
//-- Change the skybox. (3.2+ only)
|
|
static QScriptValue js_setSky(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
QString page = context->argument(0).toString();
|
|
float wind = context->argument(1).toNumber();
|
|
float scale = context->argument(2).toNumber();
|
|
setSkyBox(page.toUtf8().constData(), wind, scale);
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{hackMarkTiles([label | x, y[, x2, y2]])}
|
|
//-- Mark the given tile(s) on the map. Either give a POSITION or AREA label,
|
|
//-- or a tile x, y position, or four positions for a square area. If no parameter
|
|
//-- is given, all marked tiles are cleared.
|
|
static QScriptValue js_hackMarkTiles(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
if (context->argumentCount() == 4) // square area
|
|
{
|
|
int x1 = context->argument(0).toInt32();
|
|
int y1 = context->argument(1).toInt32();
|
|
int x2 = context->argument(2).toInt32();
|
|
int y2 = context->argument(3).toInt32();
|
|
for (int x = x1; x < x2; x++)
|
|
{
|
|
for (int y = y1; y < y2; y++)
|
|
{
|
|
MAPTILE *psTile = mapTile(x, y);
|
|
psTile->tileInfoBits |= BITS_MARKED;
|
|
}
|
|
}
|
|
}
|
|
else if (context->argumentCount() == 2) // single tile
|
|
{
|
|
int x = context->argument(0).toInt32();
|
|
int y = context->argument(1).toInt32();
|
|
MAPTILE *psTile = mapTile(x, y);
|
|
psTile->tileInfoBits |= BITS_MARKED;
|
|
}
|
|
else if (context->argumentCount() == 1) // label
|
|
{
|
|
QString label = context->argument(0).toString();
|
|
SCRIPT_ASSERT(context, labels.contains(label), "Label %s not found", label.toUtf8().constData());
|
|
labeltype &l = labels[label];
|
|
for (int x = map_coord(l.p1.x); x < map_coord(l.p2.x); x++)
|
|
{
|
|
for (int y = map_coord(l.p1.y); y < map_coord(l.p2.y); y++)
|
|
{
|
|
MAPTILE *psTile = mapTile(x, y);
|
|
psTile->tileInfoBits |= BITS_MARKED;
|
|
}
|
|
}
|
|
}
|
|
else // clear all marks
|
|
{
|
|
clearMarks();
|
|
}
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{cameraSlide(x, y)}
|
|
//-- Slide the camera over to the given position on the map. (3.2+ only)
|
|
static QScriptValue js_cameraSlide(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
float x = context->argument(0).toNumber();
|
|
float y = context->argument(1).toNumber();
|
|
requestRadarTrack(x, y);
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{cameraZoom(z, speed)}
|
|
//-- Slide the camera to the given zoom distance. Normal camera zoom ranges between 500 and 5000.
|
|
static QScriptValue js_cameraZoom(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
float z = context->argument(0).toNumber();
|
|
float speed = context->argument(1).toNumber();
|
|
setZoom(speed, z);
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{cameraTrack(droid)}
|
|
//-- Make the camera follow the given droid object around. Pass in a null object to stop. (3.2+ only)
|
|
static QScriptValue js_cameraTrack(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
if (context->argument(0).isNull())
|
|
{
|
|
setWarCamActive(false);
|
|
}
|
|
else
|
|
{
|
|
QScriptValue droidVal = context->argument(0);
|
|
int id = droidVal.property("id").toInt32();
|
|
int player = droidVal.property("player").toInt32();
|
|
DROID *targetDroid = IdToDroid(id, player);
|
|
SCRIPT_ASSERT(context, targetDroid, "No such droid id %d belonging to player %d", id, player);
|
|
for (DROID *psDroid = apsDroidLists[selectedPlayer]; psDroid!=NULL; psDroid = psDroid->psNext)
|
|
{
|
|
psDroid->selected = (psDroid == targetDroid); // select only the target droid
|
|
}
|
|
setWarCamActive(true);
|
|
}
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{addSpotter(x, y, player, range, type, expiry)}
|
|
//-- Add an invisible viewer at a given position for given player that shows map in given range. \emph{type}
|
|
//-- is zero for vision reveal, or one for radar reveal. The difference is that a radar reveal can be obstructed
|
|
//-- by ECM jammers. \emph{expiry}, if non-zero, is the game time at which the spotter shall automatically be
|
|
//-- removed. The function returns a unique ID that can be used to remove the spotter with \emph{removeSpotter}.
|
|
static QScriptValue js_addSpotter(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
int x = context->argument(0).toInt32();
|
|
int y = context->argument(1).toInt32();
|
|
int player = context->argument(2).toInt32();
|
|
int range = context->argument(3).toInt32();
|
|
bool radar = context->argument(4).toBool();
|
|
uint32_t expiry = context->argument(5).toUInt32();
|
|
uint32_t id = addSpotter(x, y, player, range, radar, expiry);
|
|
return QScriptValue(id);
|
|
}
|
|
|
|
//-- \subsection{removeSpotter(id)}
|
|
//-- Remove a spotter given its unique ID.
|
|
static QScriptValue js_removeSpotter(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
uint32_t id = context->argument(0).toUInt32();
|
|
removeSpotter(id);
|
|
return QScriptValue();
|
|
}
|
|
|
|
//-- \subsection{syncRandom(limit)}
|
|
//-- Generate a synchronized random number in range 0...(limit - 1) that will be the same if this function is
|
|
//-- run on all network peers in the same game frame. If it is called on just one peer (such as would be
|
|
//-- the case for AIs, for instance), then game sync will break.
|
|
static QScriptValue js_syncRandom(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
uint32_t limit = context->argument(0).toInt32();
|
|
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();
|
|
}
|
|
|
|
//-- \subsection{replaceTexture(old_filename, new_filename)}
|
|
//-- Replace one texture with another. This can be used to for example give buildings on a specific tileset different
|
|
//-- looks, or to add variety to the looks of droids in campaign missions.
|
|
static QScriptValue js_replaceTexture(QScriptContext *context, QScriptEngine *)
|
|
{
|
|
QString oldfile = context->argument(0).toString();
|
|
QString newfile = context->argument(1).toString();
|
|
replaceTexture(oldfile, newfile);
|
|
return QScriptValue();
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------------------
|
|
// Register functions with scripting system
|
|
|
|
bool unregisterFunctions(QScriptEngine *engine)
|
|
{
|
|
GROUPMAP *psMap = groups.value(engine);
|
|
int num = groups.remove(engine);
|
|
delete psMap;
|
|
ASSERT(num == 1, "Number of engines removed from group map is %d!", num);
|
|
labels.clear();
|
|
labelModel = NULL;
|
|
return true;
|
|
}
|
|
|
|
// Call this just before launching game; we can't do everything in registerFunctions()
|
|
// since all game state may not be fully loaded by then
|
|
void prepareLabels()
|
|
{
|
|
// load the label group data into every scripting context, with the same negative group id
|
|
for (ENGINEMAP::iterator iter = groups.begin(); iter != groups.end(); ++iter)
|
|
{
|
|
QScriptEngine *engine = iter.key();
|
|
for (LABELMAP::iterator i = labels.begin(); i != labels.end(); ++i)
|
|
{
|
|
struct labeltype l = i.value();
|
|
if (l.type == SCRIPT_GROUP)
|
|
{
|
|
QScriptValue groupMembers = iter.key()->globalObject().property("groupSizes");
|
|
groupMembers.setProperty(l.id, l.idlist.length(), QScriptValue::ReadOnly);
|
|
for (QList<int>::iterator j = l.idlist.begin(); j != l.idlist.end(); j++)
|
|
{
|
|
int id = (*j);
|
|
BASE_OBJECT *psObj = IdToPointer(id, l.player);
|
|
ASSERT(psObj, "Unit %d belonging to player %d not found", id, l.player);
|
|
if (psObj)
|
|
{
|
|
groupAddObject(psObj, l.id, engine);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
updateLabelModel();
|
|
}
|
|
|
|
// flag all droids as requiring update on next frame
|
|
static void dirtyAllDroids(int player)
|
|
{
|
|
for (DROID *psDroid = apsDroidLists[player]; psDroid != NULL; psDroid = psDroid->psNext)
|
|
{
|
|
psDroid->flags |= BASEFLAG_DIRTY;
|
|
}
|
|
for (DROID *psDroid = mission.apsDroidLists[player]; psDroid != NULL; psDroid = psDroid->psNext)
|
|
{
|
|
psDroid->flags |= BASEFLAG_DIRTY;
|
|
}
|
|
for (DROID *psDroid = apsLimboDroids[player]; psDroid != NULL; psDroid = psDroid->psNext)
|
|
{
|
|
psDroid->flags |= BASEFLAG_DIRTY;
|
|
}
|
|
}
|
|
|
|
static void dirtyAllStructures(int player)
|
|
{
|
|
for (STRUCTURE *psCurr = apsStructLists[player]; psCurr; psCurr = psCurr->psNext)
|
|
{
|
|
psCurr->flags |= BASEFLAG_DIRTY;
|
|
}
|
|
for (STRUCTURE *psCurr = mission.apsStructLists[player]; psCurr; psCurr = psCurr->psNext)
|
|
{
|
|
psCurr->flags |= BASEFLAG_DIRTY;
|
|
}
|
|
}
|
|
|
|
QScriptValue js_stats(QScriptContext *context, QScriptEngine *engine)
|
|
{
|
|
QScriptValue callee = context->callee();
|
|
int type = callee.property("type").toInt32();
|
|
int player = callee.property("player").toInt32();
|
|
int index = callee.property("index").toInt32();
|
|
QString name = callee.property("name").toString();
|
|
if (context->argumentCount() == 1) // setter
|
|
{
|
|
int value = context->argument(0).toInt32();
|
|
if (type == COMP_BODY)
|
|
{
|
|
BODY_STATS *psStats = asBodyStats + index;
|
|
if (name == "HitPoints")
|
|
{
|
|
psStats->upgrade[player].body = value;
|
|
dirtyAllDroids(player);
|
|
}
|
|
else if (name == "Armour")
|
|
{
|
|
psStats->upgrade[player].armour = value;
|
|
}
|
|
else if (name == "Thermal")
|
|
{
|
|
psStats->upgrade[player].thermal = value;
|
|
}
|
|
else if (name == "Power")
|
|
{
|
|
psStats->upgrade[player].power = value;
|
|
}
|
|
else if (name == "Resistance")
|
|
{
|
|
// TBD FIXME - not updating resistance points in droids...
|
|
psStats->upgrade[player].resistance = value;
|
|
}
|
|
else
|
|
{
|
|
SCRIPT_ASSERT(context, false, "Upgrade component %s not found", name.toUtf8().constData());
|
|
}
|
|
}
|
|
else if (type == COMP_SENSOR)
|
|
{
|
|
SENSOR_STATS *psStats = asSensorStats + index;
|
|
SCRIPT_ASSERT(context, name == "Range", "Invalid sensor parameter");
|
|
psStats->upgrade[player].range = value;
|
|
dirtyAllDroids(player);
|
|
dirtyAllStructures(player);
|
|
}
|
|
else if (type == COMP_ECM)
|
|
{
|
|
ECM_STATS *psStats = asECMStats + index;
|
|
SCRIPT_ASSERT(context, name == "Range", "Invalid ECM parameter");
|
|
psStats->upgrade[player].range = value;
|
|
dirtyAllDroids(player);
|
|
dirtyAllStructures(player);
|
|
}
|
|
else if (type == COMP_CONSTRUCT)
|
|
{
|
|
CONSTRUCT_STATS *psStats = asConstructStats + index;
|
|
SCRIPT_ASSERT(context, name == "ConstructorPoints", "Invalid constructor parameter");
|
|
psStats->upgrade[player].constructPoints = value;
|
|
}
|
|
else if (type == COMP_WEAPON)
|
|
{
|
|
WEAPON_STATS *psStats = asWeaponStats + index;
|
|
if (name == "MaxRange") psStats->upgrade[player].maxRange = value;
|
|
else if (name == "MinRange") psStats->upgrade[player].minRange = value;
|
|
else if (name == "HitChance") psStats->upgrade[player].hitChance = value;
|
|
else if (name == "FirePause") psStats->upgrade[player].firePause = value;
|
|
else if (name == "Rounds") psStats->upgrade[player].numRounds = value;
|
|
else if (name == "ReloadTime") psStats->upgrade[player].reloadTime = value;
|
|
else if (name == "Damage") psStats->upgrade[player].damage = value;
|
|
else if (name == "MinimumDamage") psStats->upgrade[player].minimumDamage = value;
|
|
else if (name == "Radius") psStats->upgrade[player].radius = value;
|
|
else if (name == "RadiusDamage") psStats->upgrade[player].radiusDamage = value;
|
|
else if (name == "RepeatDamage") psStats->upgrade[player].periodicalDamage = value;
|
|
else if (name == "RepeatTime") psStats->upgrade[player].periodicalDamageTime = value;
|
|
else if (name == "RepeatRadius") psStats->upgrade[player].periodicalDamageRadius = value;
|
|
else SCRIPT_ASSERT(context, false, "Invalid weapon method");
|
|
}
|
|
else if (type == SCRCB_RES || type == SCRCB_REP || type == SCRCB_POW || type == SCRCB_CON || type == SCRCB_REA
|
|
|| type == SCRCB_HIT || type == SCRCB_ELW || type == SCRCB_ARM || type == SCRCB_HEA)
|
|
{
|
|
STRUCTURE_STATS *psStats = asStructureStats + index;
|
|
switch (type)
|
|
{
|
|
case SCRCB_RES: psStats->upgrade[player].research = value; break;
|
|
case SCRCB_REP: psStats->upgrade[player].repair = value; break;
|
|
case SCRCB_POW: psStats->upgrade[player].power = value; break;
|
|
case SCRCB_CON: psStats->upgrade[player].production = value; break;
|
|
case SCRCB_REA: psStats->upgrade[player].rearm = value; break;
|
|
case SCRCB_HEA: psStats->upgrade[player].thermal = value; break;
|
|
case SCRCB_ARM: psStats->upgrade[player].armour = value; break;
|
|
case SCRCB_ELW:
|
|
// Update resistance points for all structures, to avoid making them damaged
|
|
// FIXME - this is _really_ slow! we could be doing this for dozens of buildings one at a time!
|
|
for (STRUCTURE *psCurr = apsStructLists[player]; psCurr; psCurr = psCurr->psNext)
|
|
{
|
|
if (psStats == psCurr->pStructureType && psStats->upgrade[player].resistance < value)
|
|
{
|
|
psCurr->resistance = value;
|
|
}
|
|
}
|
|
for (STRUCTURE *psCurr = mission.apsStructLists[player]; psCurr; psCurr = psCurr->psNext)
|
|
{
|
|
if (psStats == psCurr->pStructureType && psStats->upgrade[player].resistance < value)
|
|
{
|
|
psCurr->resistance = value;
|
|
}
|
|
}
|
|
psStats->upgrade[player].resistance = value;
|
|
break;
|
|
case SCRCB_HIT:
|
|
// Update body points for all structures, to avoid making them damaged
|
|
// FIXME - this is _really_ slow! we could be doing this for
|
|
// dozens of buildings one at a time!
|
|
for (STRUCTURE *psCurr = apsStructLists[player]; psCurr; psCurr = psCurr->psNext)
|
|
{
|
|
if (psStats == psCurr->pStructureType && psStats->upgrade[player].hitpoints < value)
|
|
{
|
|
psCurr->body = (psCurr->body * value) / psStats->upgrade[player].hitpoints;
|
|
}
|
|
}
|
|
for (STRUCTURE *psCurr = mission.apsStructLists[player]; psCurr; psCurr = psCurr->psNext)
|
|
{
|
|
if (psStats == psCurr->pStructureType && psStats->upgrade[player].hitpoints < value)
|
|
{
|
|
psCurr->body = (psCurr->body * value) / psStats->upgrade[player].hitpoints;
|
|
}
|
|
}
|
|
psStats->upgrade[player].hitpoints = value;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SCRIPT_ASSERT(context, false, "Component type not found for upgrade");
|
|
}
|
|
}
|
|
// Now read value and return it
|
|
if (type == COMP_BODY)
|
|
{
|
|
BODY_STATS *psStats = asBodyStats + index;
|
|
if (name == "HitPoints") return psStats->upgrade[player].body;
|
|
else if (name == "Armour") return psStats->upgrade[player].armour;
|
|
else if (name == "Thermal") return psStats->upgrade[player].thermal;
|
|
else if (name == "Power") return psStats->upgrade[player].power;
|
|
else if (name == "Resistance") return psStats->upgrade[player].resistance;
|
|
else SCRIPT_ASSERT(context, false, "Upgrade component %s not found", name.toUtf8().constData());
|
|
}
|
|
else if (type == COMP_SENSOR)
|
|
{
|
|
SENSOR_STATS *psStats = asSensorStats + index;
|
|
SCRIPT_ASSERT(context, name == "Range", "Invalid sensor parameter");
|
|
return psStats->upgrade[player].range;
|
|
}
|
|
else if (type == COMP_ECM)
|
|
{
|
|
ECM_STATS *psStats = asECMStats + index;
|
|
SCRIPT_ASSERT(context, name == "Range", "Invalid ECM parameter");
|
|
return psStats->upgrade[player].range;
|
|
}
|
|
else if (type == COMP_CONSTRUCT)
|
|
{
|
|
CONSTRUCT_STATS *psStats = asConstructStats + index;
|
|
SCRIPT_ASSERT(context, name == "ConstructorPoints", "Invalid constructor parameter");
|
|
return psStats->upgrade[player].constructPoints;
|
|
}
|
|
else if (type == COMP_WEAPON)
|
|
{
|
|
WEAPON_STATS *psStats = asWeaponStats + index;
|
|
if (name == "MaxRange") return psStats->upgrade[player].maxRange;
|
|
else if (name == "MinRange") return psStats->upgrade[player].minRange;
|
|
else if (name == "HitChance") return psStats->upgrade[player].hitChance;
|
|
else if (name == "FirePause") return psStats->upgrade[player].firePause;
|
|
else if (name == "Rounds") return psStats->upgrade[player].numRounds;
|
|
else if (name == "ReloadTime") return psStats->upgrade[player].reloadTime;
|
|
else if (name == "Damage") return psStats->upgrade[player].damage;
|
|
else if (name == "MinimumDamage") return psStats->upgrade[player].minimumDamage;
|
|
else if (name == "Radius") return psStats->upgrade[player].radius;
|
|
else if (name == "RadiusDamage") return psStats->upgrade[player].radiusDamage;
|
|
else if (name == "RepeatDamage") return psStats->upgrade[player].periodicalDamage;
|
|
else if (name == "RepeatTime") return psStats->upgrade[player].periodicalDamageTime;
|
|
else if (name == "RepeatRadius") return psStats->upgrade[player].periodicalDamageRadius;
|
|
else SCRIPT_ASSERT(context, false, "Invalid weapon method");
|
|
}
|
|
else if (type == SCRCB_RES || type == SCRCB_REP || type == SCRCB_POW || type == SCRCB_CON || type == SCRCB_REA
|
|
|| type == SCRCB_HIT || type == SCRCB_ELW || type == SCRCB_ARM || type == SCRCB_HEA)
|
|
{
|
|
STRUCTURE_STATS *psStats = asStructureStats + index;
|
|
switch (type)
|
|
{
|
|
case SCRCB_RES: return psStats->upgrade[player].research; break;
|
|
case SCRCB_REP: return psStats->upgrade[player].repair; break;
|
|
case SCRCB_POW: return psStats->upgrade[player].power; break;
|
|
case SCRCB_CON: return psStats->upgrade[player].production; break;
|
|
case SCRCB_REA: return psStats->upgrade[player].rearm; break;
|
|
case SCRCB_ELW: return psStats->upgrade[player].resistance; break;
|
|
case SCRCB_HEA: return psStats->upgrade[player].thermal; break;
|
|
case SCRCB_ARM: return psStats->upgrade[player].armour; break;
|
|
case SCRCB_HIT: return psStats->upgrade[player].hitpoints;
|
|
default: SCRIPT_ASSERT(context, false, "Component type not found for upgrade"); break;
|
|
}
|
|
}
|
|
return QScriptValue::NullValue;
|
|
}
|
|
|
|
static void setStatsFunc(QScriptValue &base, QScriptEngine *engine, QString name, int player, int type, int index, int value)
|
|
{
|
|
QScriptValue v = engine->newFunction(js_stats);
|
|
base.setProperty(name, v, QScriptValue::PropertyGetter | QScriptValue::PropertySetter);
|
|
v.setProperty("player", player, QScriptValue::SkipInEnumeration | QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
v.setProperty("type", type, QScriptValue::SkipInEnumeration | QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
v.setProperty("index", index, QScriptValue::SkipInEnumeration | QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
v.setProperty("name", name, QScriptValue::SkipInEnumeration | QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
}
|
|
|
|
bool registerFunctions(QScriptEngine *engine, QString scriptName)
|
|
{
|
|
debug(LOG_WZ, "Loading functions for engine %p, script %s", engine, scriptName.toUtf8().constData());
|
|
|
|
// Create group map
|
|
GROUPMAP *psMap = new GROUPMAP;
|
|
groups.insert(engine, psMap);
|
|
|
|
/// Register 'Stats' object. It is a read-only representation of basic game component states.
|
|
//== \item[Stats] A sparse, read-only array containing rules information for game entity types.
|
|
//== (For now only the highest level member attributes are documented here. Use the 'jsdebug' cheat
|
|
//== to see them all.)
|
|
//== These values are defined:
|
|
//== \begin{description}
|
|
QScriptValue stats = engine->newObject();
|
|
{
|
|
//== \item[Body] Droid bodies
|
|
QScriptValue bodybase = engine->newObject();
|
|
for (int j = 0; j < numBodyStats; j++)
|
|
{
|
|
BODY_STATS *psStats = asBodyStats + j;
|
|
QScriptValue body = engine->newObject();
|
|
body.setProperty("Id", psStats->id, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
body.setProperty("Weight", psStats->weight, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
body.setProperty("BuildPower", psStats->buildPower, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
body.setProperty("BuildTime", psStats->buildPoints, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
body.setProperty("HitPoints", psStats->body, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
body.setProperty("Power", psStats->base.power, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
body.setProperty("Armour", psStats->base.armour, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
body.setProperty("Thermal", psStats->base.thermal, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
body.setProperty("Resistance", psStats->base.resistance, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
body.setProperty("Size", psStats->size, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
body.setProperty("WeaponSlots", psStats->weaponSlots, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
body.setProperty("BodyClass", psStats->bodyClass, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
bodybase.setProperty(psStats->name, body, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
}
|
|
stats.setProperty("Body", bodybase, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
//== \item[Sensor] Sensor turrets
|
|
QScriptValue sensorbase = engine->newObject();
|
|
for (int j = 0; j < numSensorStats; j++)
|
|
{
|
|
SENSOR_STATS *psStats = asSensorStats + j;
|
|
QScriptValue sensor = engine->newObject();
|
|
sensor.setProperty("Id", psStats->id, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
sensor.setProperty("Weight", psStats->weight, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
sensor.setProperty("BuildPower", psStats->buildPower, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
sensor.setProperty("BuildTime", psStats->buildPoints, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
sensor.setProperty("HitPoints", psStats->body, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
sensor.setProperty("Range", psStats->base.range, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
sensorbase.setProperty(psStats->name, sensor, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
}
|
|
stats.setProperty("Sensor", sensorbase, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
//== \item[ECM] ECM (Electronic Counter-Measure) turrets
|
|
QScriptValue ecmbase = engine->newObject();
|
|
for (int j = 0; j < numECMStats; j++)
|
|
{
|
|
ECM_STATS *psStats = asECMStats + j;
|
|
QScriptValue ecm = engine->newObject();
|
|
ecm.setProperty("Id", psStats->id, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
ecm.setProperty("Weight", psStats->weight, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
ecm.setProperty("BuildPower", psStats->buildPower, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
ecm.setProperty("BuildTime", psStats->buildPoints, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
ecm.setProperty("HitPoints", psStats->body, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
ecm.setProperty("Range", psStats->base.range, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
ecmbase.setProperty(psStats->name, ecm, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
}
|
|
stats.setProperty("ECM", ecmbase, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
//== \item[Repair] Repair turrets (not used, incidentially, for repair centers)
|
|
QScriptValue repairbase = engine->newObject();
|
|
for (int j = 0; j < numRepairStats; j++)
|
|
{
|
|
REPAIR_STATS *psStats = asRepairStats + j;
|
|
QScriptValue repair = engine->newObject();
|
|
repair.setProperty("Id", psStats->id, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
repair.setProperty("Weight", psStats->weight, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
repair.setProperty("BuildPower", psStats->buildPower, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
repair.setProperty("BuildTime", psStats->buildPoints, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
repair.setProperty("HitPoints", psStats->body, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
repair.setProperty("RepairPoints", psStats->base.repairPoints, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
repairbase.setProperty(psStats->name, repair, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
}
|
|
stats.setProperty("Repair", repairbase, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
//== \item[Construct] Constructor turrets (eg for trucks)
|
|
QScriptValue conbase = engine->newObject();
|
|
for (int j = 0; j < numConstructStats; j++)
|
|
{
|
|
CONSTRUCT_STATS *psStats = asConstructStats + j;
|
|
QScriptValue con = engine->newObject();
|
|
con.setProperty("Id", psStats->id, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
con.setProperty("Weight", psStats->weight, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
con.setProperty("BuildPower", psStats->buildPower, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
con.setProperty("BuildTime", psStats->buildPoints, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
con.setProperty("HitPoints", psStats->body, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
con.setProperty("ConstructorPoints", psStats->base.constructPoints, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
conbase.setProperty(psStats->name, con, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
}
|
|
stats.setProperty("Construct", conbase, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
//== \item[Weapon] Weapon turrets
|
|
QScriptValue wbase = engine->newObject();
|
|
for (int j = 0; j < numWeaponStats; j++)
|
|
{
|
|
WEAPON_STATS *psStats = asWeaponStats + j;
|
|
QScriptValue weap = engine->newObject();
|
|
weap.setProperty("Id", psStats->id, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
weap.setProperty("Weight", psStats->weight, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
weap.setProperty("BuildPower", psStats->buildPower, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
weap.setProperty("BuildTime", psStats->buildPoints, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
weap.setProperty("MaxRange", psStats->base.maxRange, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
weap.setProperty("MinRange", psStats->base.minRange, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
weap.setProperty("HitChance", psStats->base.hitChance, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
weap.setProperty("FirePause", psStats->base.firePause, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
weap.setProperty("ReloadTime", psStats->base.reloadTime, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
weap.setProperty("Rounds", psStats->base.numRounds, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
weap.setProperty("Damage", psStats->base.damage, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
weap.setProperty("MinimumDamage", psStats->base.minimumDamage, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
weap.setProperty("RadiusDamage", psStats->base.radiusDamage, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
weap.setProperty("RepeatDamage", psStats->base.periodicalDamage, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
weap.setProperty("RepeatRadius", psStats->base.periodicalDamageRadius, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
weap.setProperty("RepeatTime", psStats->base.periodicalDamageTime, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
weap.setProperty("Radius", psStats->base.radius, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
weap.setProperty("ImpactType", psStats->weaponClass == WC_KINETIC ? "KINETIC" : "HEAT",
|
|
QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
weap.setProperty("RepeatType", psStats->periodicalDamageWeaponClass == WC_KINETIC ? "KINETIC" : "HEAT",
|
|
QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
weap.setProperty("ImpactClass", getWeaponSubClass(psStats->weaponSubClass),
|
|
QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
weap.setProperty("RepeatClass", getWeaponSubClass(psStats->periodicalDamageWeaponSubClass),
|
|
QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
weap.setProperty("FireOnMove", psStats->fireOnMove, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
wbase.setProperty(psStats->name, weap, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
}
|
|
stats.setProperty("Weapon", wbase, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
//== \item[WeaponClass] Defined weapon classes
|
|
QScriptValue weaptypes = engine->newArray(WSC_NUM_WEAPON_SUBCLASSES);
|
|
for (int j = 0; j < WSC_NUM_WEAPON_SUBCLASSES; j++)
|
|
{
|
|
weaptypes.setProperty(j, getWeaponSubClass((WEAPON_SUBCLASS)j), QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
}
|
|
stats.setProperty("WeaponClass", weaptypes, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
//== \item[Building] Buildings
|
|
QScriptValue structbase = engine->newObject();
|
|
for (int j = 0; j < numStructureStats; j++)
|
|
{
|
|
STRUCTURE_STATS *psStats = asStructureStats + j;
|
|
QScriptValue strct = engine->newObject();
|
|
strct.setProperty("Id", psStats->id, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
if (psStats->type == REF_DEFENSE || psStats->type == REF_WALL || psStats->type == REF_WALLCORNER
|
|
|| psStats->type == REF_GENERIC || psStats->type == REF_GATE)
|
|
{
|
|
strct.setProperty("Type", "Wall", QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
}
|
|
else if (psStats->type != REF_DEMOLISH)
|
|
{
|
|
strct.setProperty("Type", "Structure", QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
}
|
|
else
|
|
{
|
|
strct.setProperty("Type", "Demolish", QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
}
|
|
strct.setProperty("ResearchPoints", psStats->base.research, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
strct.setProperty("RepairPoints", psStats->base.repair, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
strct.setProperty("PowerPoints", psStats->base.power, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
strct.setProperty("ProductionPoints", psStats->base.production, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
strct.setProperty("RearmPoints", psStats->base.rearm, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
strct.setProperty("Armour", psStats->base.armour, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
strct.setProperty("Thermal", psStats->base.thermal, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
strct.setProperty("HitPoints", psStats->base.hitpoints, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
strct.setProperty("Resistance", psStats->base.resistance, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
structbase.setProperty(psStats->name, strct, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
}
|
|
stats.setProperty("Building", structbase, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
}
|
|
engine->globalObject().setProperty("Stats", stats, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
//== \end{description}
|
|
|
|
//== \item[Upgrades] A special array containing per-player rules information for game entity types,
|
|
//== which can be written to in order to implement upgrades and other dynamic rules changes. Each item in the
|
|
//== array contains a subset of the sparse array of rules information in the \emph{Stats} global.
|
|
//== These values are defined:
|
|
//== \begin{description}
|
|
QScriptValue upgrades = engine->newArray(MAX_PLAYERS);
|
|
for (int i = 0; i < MAX_PLAYERS; i++)
|
|
{
|
|
QScriptValue node = engine->newObject();
|
|
//== \item[Body] Droid bodies
|
|
QScriptValue bodybase = engine->newObject();
|
|
for (int j = 0; j < numBodyStats; j++)
|
|
{
|
|
BODY_STATS *psStats = asBodyStats + j;
|
|
QScriptValue body = engine->newObject();
|
|
setStatsFunc(body, engine, "HitPoints", i, COMP_BODY, j, psStats->upgrade[i].body);
|
|
setStatsFunc(body, engine, "Power", i, COMP_BODY, j, psStats->upgrade[i].power);
|
|
setStatsFunc(body, engine, "Armour", i, COMP_BODY, j, psStats->upgrade[i].armour);
|
|
setStatsFunc(body, engine, "Thermal", i, COMP_BODY, j, psStats->upgrade[i].thermal);
|
|
setStatsFunc(body, engine, "Resistance", i, COMP_BODY, j, psStats->upgrade[i].resistance);
|
|
bodybase.setProperty(psStats->name, body, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
}
|
|
node.setProperty("Body", bodybase, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
//== \item[Sensor] Sensor turrets
|
|
QScriptValue sensorbase = engine->newObject();
|
|
for (int j = 0; j < numSensorStats; j++)
|
|
{
|
|
SENSOR_STATS *psStats = asSensorStats + j;
|
|
QScriptValue sensor = engine->newObject();
|
|
setStatsFunc(sensor, engine, "Range", i, COMP_SENSOR, j, psStats->upgrade[i].range);
|
|
sensorbase.setProperty(psStats->name, sensor, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
}
|
|
node.setProperty("Sensor", sensorbase, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
//== \item[ECM] ECM (Electronic Counter-Measure) turrets
|
|
QScriptValue ecmbase = engine->newObject();
|
|
for (int j = 0; j < numECMStats; j++)
|
|
{
|
|
ECM_STATS *psStats = asECMStats + j;
|
|
QScriptValue ecm = engine->newObject();
|
|
setStatsFunc(ecm, engine, "Range", i, COMP_ECM, j, psStats->upgrade[i].range);
|
|
ecmbase.setProperty(psStats->name, ecm, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
}
|
|
node.setProperty("ECM", ecmbase, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
//== \item[Repair] Repair turrets (not used, incidentially, for repair centers)
|
|
QScriptValue repairbase = engine->newObject();
|
|
for (int j = 0; j < numRepairStats; j++)
|
|
{
|
|
REPAIR_STATS *psStats = asRepairStats + j;
|
|
QScriptValue repair = engine->newObject();
|
|
setStatsFunc(repair, engine, "RepairPoints", i, COMP_REPAIRUNIT, j, psStats->upgrade[i].repairPoints);
|
|
repairbase.setProperty(psStats->name, repair, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
}
|
|
node.setProperty("Repair", repairbase, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
//== \item[Construct] Constructor turrets (eg for trucks)
|
|
QScriptValue conbase = engine->newObject();
|
|
for (int j = 0; j < numConstructStats; j++)
|
|
{
|
|
CONSTRUCT_STATS *psStats = asConstructStats + j;
|
|
QScriptValue con = engine->newObject();
|
|
setStatsFunc(con, engine, "ConstructorPoints", i, COMP_CONSTRUCT, j, psStats->upgrade[i].constructPoints);
|
|
conbase.setProperty(psStats->name, con, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
}
|
|
node.setProperty("Construct", conbase, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
//== \item[Weapon] Weapon turrets
|
|
QScriptValue wbase = engine->newObject();
|
|
for (int j = 0; j < numWeaponStats; j++)
|
|
{
|
|
WEAPON_STATS *psStats = asWeaponStats + j;
|
|
QScriptValue weap = engine->newObject();
|
|
setStatsFunc(weap, engine, "MaxRange", i, COMP_WEAPON, j, psStats->upgrade[i].maxRange);
|
|
setStatsFunc(weap, engine, "MinRange", i, COMP_WEAPON, j, psStats->upgrade[i].minRange);
|
|
setStatsFunc(weap, engine, "HitChance", i, COMP_WEAPON, j, psStats->upgrade[i].hitChance);
|
|
setStatsFunc(weap, engine, "FirePause", i, COMP_WEAPON, j, psStats->upgrade[i].firePause);
|
|
setStatsFunc(weap, engine, "ReloadTime", i, COMP_WEAPON, j, psStats->upgrade[i].reloadTime);
|
|
setStatsFunc(weap, engine, "Rounds", i, COMP_WEAPON, j, psStats->upgrade[i].numRounds);
|
|
setStatsFunc(weap, engine, "Radius", i, COMP_WEAPON, j, psStats->upgrade[i].radius);
|
|
setStatsFunc(weap, engine, "Damage", i, COMP_WEAPON, j, psStats->upgrade[i].damage);
|
|
setStatsFunc(weap, engine, "MinimumDamage", i, COMP_WEAPON, j, psStats->upgrade[i].minimumDamage);
|
|
setStatsFunc(weap, engine, "RadiusDamage", i, COMP_WEAPON, j, psStats->upgrade[i].radiusDamage);
|
|
setStatsFunc(weap, engine, "RepeatDamage", i, COMP_WEAPON, j, psStats->upgrade[i].periodicalDamage);
|
|
setStatsFunc(weap, engine, "RepeatTime", i, COMP_WEAPON, j, psStats->upgrade[i].periodicalDamageTime);
|
|
setStatsFunc(weap, engine, "RepeatRadius", i, COMP_WEAPON, j, psStats->upgrade[i].periodicalDamageRadius);
|
|
wbase.setProperty(psStats->name, weap, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
}
|
|
node.setProperty("Weapon", wbase, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
//== \item[Building] Buildings
|
|
QScriptValue structbase = engine->newObject();
|
|
for (int j = 0; j < numStructureStats; j++)
|
|
{
|
|
STRUCTURE_STATS *psStats = asStructureStats + j;
|
|
QScriptValue strct = engine->newObject();
|
|
setStatsFunc(strct, engine, "ResearchPoints", i, SCRCB_RES, j, psStats->upgrade[i].research);
|
|
setStatsFunc(strct, engine, "RepairPoints", i, SCRCB_REP, j, psStats->upgrade[i].repair);
|
|
setStatsFunc(strct, engine, "PowerPoints", i, SCRCB_POW, j, psStats->upgrade[i].power);
|
|
setStatsFunc(strct, engine, "ProductionPoints", i, SCRCB_CON, j, psStats->upgrade[i].production);
|
|
setStatsFunc(strct, engine, "RearmPoints", i, SCRCB_REA, j, psStats->upgrade[i].rearm);
|
|
setStatsFunc(strct, engine, "Armour", i, SCRCB_ARM, j, psStats->upgrade[i].armour);
|
|
setStatsFunc(strct, engine, "Resistance", i, SCRCB_ELW, j, psStats->upgrade[i].resistance);
|
|
setStatsFunc(strct, engine, "Thermal", i, SCRCB_HEA, j, psStats->upgrade[i].thermal);
|
|
setStatsFunc(strct, engine, "HitPoints", i, SCRCB_HIT, j, psStats->upgrade[i].hitpoints);
|
|
structbase.setProperty(psStats->name, strct, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
}
|
|
node.setProperty("Building", structbase, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
// Finally
|
|
upgrades.setProperty(i, node, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
}
|
|
engine->globalObject().setProperty("Upgrades", upgrades, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
//== \end{description}
|
|
|
|
// Register functions to the script engine here
|
|
engine->globalObject().setProperty("_", engine->newFunction(js_translate));
|
|
engine->globalObject().setProperty("dump", engine->newFunction(js_dump));
|
|
engine->globalObject().setProperty("syncRandom", engine->newFunction(js_syncRandom));
|
|
engine->globalObject().setProperty("label", engine->newFunction(js_getObject)); // deprecated
|
|
engine->globalObject().setProperty("getObject", engine->newFunction(js_getObject));
|
|
engine->globalObject().setProperty("addLabel", engine->newFunction(js_addLabel));
|
|
engine->globalObject().setProperty("removeLabel", engine->newFunction(js_removeLabel));
|
|
engine->globalObject().setProperty("getLabel", engine->newFunction(js_getLabel));
|
|
engine->globalObject().setProperty("enumLabels", engine->newFunction(js_enumLabels));
|
|
engine->globalObject().setProperty("enumGateways", engine->newFunction(js_enumGateways));
|
|
engine->globalObject().setProperty("enumTemplates", engine->newFunction(js_enumTemplates));
|
|
engine->globalObject().setProperty("makeTemplate", engine->newFunction(js_makeTemplate));
|
|
engine->globalObject().setProperty("setAlliance", engine->newFunction(js_setAlliance));
|
|
engine->globalObject().setProperty("setAssemblyPoint", engine->newFunction(js_setAssemblyPoint));
|
|
engine->globalObject().setProperty("setSunPosition", engine->newFunction(js_setSunPosition));
|
|
engine->globalObject().setProperty("setSunIntensity", engine->newFunction(js_setSunIntensity));
|
|
engine->globalObject().setProperty("setWeather", engine->newFunction(js_setWeather));
|
|
engine->globalObject().setProperty("setSky", engine->newFunction(js_setSky));
|
|
engine->globalObject().setProperty("cameraSlide", engine->newFunction(js_cameraSlide));
|
|
engine->globalObject().setProperty("cameraTrack", engine->newFunction(js_cameraTrack));
|
|
engine->globalObject().setProperty("cameraZoom", engine->newFunction(js_cameraZoom));
|
|
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));
|
|
engine->globalObject().setProperty("replaceTexture", engine->newFunction(js_replaceTexture));
|
|
|
|
// horrible hacks follow -- do not rely on these being present!
|
|
engine->globalObject().setProperty("hackNetOff", engine->newFunction(js_hackNetOff));
|
|
engine->globalObject().setProperty("hackNetOn", engine->newFunction(js_hackNetOn));
|
|
engine->globalObject().setProperty("hackAddMessage", engine->newFunction(js_hackAddMessage));
|
|
engine->globalObject().setProperty("hackRemoveMessage", engine->newFunction(js_hackRemoveMessage));
|
|
engine->globalObject().setProperty("objFromId", engine->newFunction(js_objFromId));
|
|
engine->globalObject().setProperty("hackGetObj", engine->newFunction(js_hackGetObj));
|
|
engine->globalObject().setProperty("hackChangeMe", engine->newFunction(js_hackChangeMe));
|
|
engine->globalObject().setProperty("hackAssert", engine->newFunction(js_hackAssert));
|
|
engine->globalObject().setProperty("hackMarkTiles", engine->newFunction(js_hackMarkTiles));
|
|
engine->globalObject().setProperty("receiveAllEvents", engine->newFunction(js_receiveAllEvents));
|
|
|
|
// General functions -- geared for use in AI scripts
|
|
engine->globalObject().setProperty("debug", engine->newFunction(js_debug));
|
|
engine->globalObject().setProperty("console", engine->newFunction(js_console));
|
|
engine->globalObject().setProperty("structureIdle", engine->newFunction(js_structureIdle));
|
|
engine->globalObject().setProperty("enumStruct", engine->newFunction(js_enumStruct));
|
|
engine->globalObject().setProperty("enumStructOffWorld", engine->newFunction(js_enumStructOffWorld));
|
|
engine->globalObject().setProperty("enumDroid", engine->newFunction(js_enumDroid));
|
|
engine->globalObject().setProperty("enumGroup", engine->newFunction(js_enumGroup));
|
|
engine->globalObject().setProperty("enumFeature", engine->newFunction(js_enumFeature));
|
|
engine->globalObject().setProperty("enumBlips", engine->newFunction(js_enumBlips));
|
|
engine->globalObject().setProperty("enumSelected", engine->newFunction(js_enumSelected));
|
|
engine->globalObject().setProperty("enumResearch", engine->newFunction(js_enumResearch));
|
|
engine->globalObject().setProperty("enumRange", engine->newFunction(js_enumRange));
|
|
engine->globalObject().setProperty("enumArea", engine->newFunction(js_enumArea));
|
|
engine->globalObject().setProperty("getResearch", engine->newFunction(js_getResearch));
|
|
engine->globalObject().setProperty("pursueResearch", engine->newFunction(js_pursueResearch));
|
|
engine->globalObject().setProperty("findResearch", engine->newFunction(js_findResearch));
|
|
engine->globalObject().setProperty("distBetweenTwoPoints", engine->newFunction(js_distBetweenTwoPoints));
|
|
engine->globalObject().setProperty("newGroup", engine->newFunction(js_newGroup));
|
|
engine->globalObject().setProperty("groupAddArea", engine->newFunction(js_groupAddArea));
|
|
engine->globalObject().setProperty("groupAddDroid", engine->newFunction(js_groupAddDroid));
|
|
engine->globalObject().setProperty("groupAdd", engine->newFunction(js_groupAdd));
|
|
engine->globalObject().setProperty("groupSize", engine->newFunction(js_groupSize));
|
|
engine->globalObject().setProperty("orderDroidLoc", engine->newFunction(js_orderDroidLoc));
|
|
engine->globalObject().setProperty("playerPower", engine->newFunction(js_playerPower));
|
|
engine->globalObject().setProperty("queuedPower", engine->newFunction(js_queuedPower));
|
|
engine->globalObject().setProperty("isStructureAvailable", engine->newFunction(js_isStructureAvailable));
|
|
engine->globalObject().setProperty("pickStructLocation", engine->newFunction(js_pickStructLocation));
|
|
engine->globalObject().setProperty("droidCanReach", engine->newFunction(js_droidCanReach));
|
|
engine->globalObject().setProperty("propulsionCanReach", engine->newFunction(js_propulsionCanReach));
|
|
engine->globalObject().setProperty("terrainType", engine->newFunction(js_terrainType));
|
|
engine->globalObject().setProperty("orderDroidBuild", engine->newFunction(js_orderDroidBuild));
|
|
engine->globalObject().setProperty("orderDroidObj", engine->newFunction(js_orderDroidObj));
|
|
engine->globalObject().setProperty("orderDroid", engine->newFunction(js_orderDroid));
|
|
engine->globalObject().setProperty("buildDroid", engine->newFunction(js_buildDroid));
|
|
engine->globalObject().setProperty("addDroid", engine->newFunction(js_addDroid));
|
|
engine->globalObject().setProperty("addFeature", engine->newFunction(js_addFeature));
|
|
engine->globalObject().setProperty("componentAvailable", engine->newFunction(js_componentAvailable));
|
|
engine->globalObject().setProperty("isVTOL", engine->newFunction(js_isVTOL));
|
|
engine->globalObject().setProperty("safeDest", engine->newFunction(js_safeDest));
|
|
engine->globalObject().setProperty("activateStructure", engine->newFunction(js_activateStructure));
|
|
engine->globalObject().setProperty("chat", engine->newFunction(js_chat));
|
|
engine->globalObject().setProperty("addBeacon", engine->newFunction(js_addBeacon));
|
|
engine->globalObject().setProperty("removeBeacon", engine->newFunction(js_removeBeacon));
|
|
engine->globalObject().setProperty("getDroidProduction", engine->newFunction(js_getDroidProduction));
|
|
engine->globalObject().setProperty("getDroidLimit", engine->newFunction(js_getDroidLimit));
|
|
engine->globalObject().setProperty("getExperienceModifier", engine->newFunction(js_getExperienceModifier));
|
|
engine->globalObject().setProperty("setDroidLimit", engine->newFunction(js_setDroidLimit));
|
|
engine->globalObject().setProperty("setCommanderLimit", engine->newFunction(js_setCommanderLimit));
|
|
engine->globalObject().setProperty("setConstructorLimit", engine->newFunction(js_setConstructorLimit));
|
|
engine->globalObject().setProperty("setExperienceModifier", engine->newFunction(js_setExperienceModifier));
|
|
engine->globalObject().setProperty("getWeaponInfo", engine->newFunction(js_getWeaponInfo));
|
|
engine->globalObject().setProperty("enumCargo", engine->newFunction(js_enumCargo));
|
|
|
|
// Functions that operate on the current player only
|
|
engine->globalObject().setProperty("centreView", engine->newFunction(js_centreView));
|
|
engine->globalObject().setProperty("playSound", engine->newFunction(js_playSound));
|
|
engine->globalObject().setProperty("gameOverMessage", engine->newFunction(js_gameOverMessage));
|
|
|
|
// Global state manipulation -- not for use with skirmish AI (unless you want it to cheat, obviously)
|
|
engine->globalObject().setProperty("setStructureLimits", engine->newFunction(js_setStructureLimits));
|
|
engine->globalObject().setProperty("applyLimitSet", engine->newFunction(js_applyLimitSet));
|
|
engine->globalObject().setProperty("setMissionTime", engine->newFunction(js_setMissionTime));
|
|
engine->globalObject().setProperty("getMissionTime", engine->newFunction(js_getMissionTime));
|
|
engine->globalObject().setProperty("setReinforcementTime", engine->newFunction(js_setReinforcementTime));
|
|
engine->globalObject().setProperty("completeResearch", engine->newFunction(js_completeResearch));
|
|
engine->globalObject().setProperty("enableResearch", engine->newFunction(js_enableResearch));
|
|
engine->globalObject().setProperty("setPower", engine->newFunction(js_setPower));
|
|
engine->globalObject().setProperty("setPowerModifier", engine->newFunction(js_setPowerModifier));
|
|
engine->globalObject().setProperty("extraPowerTime", engine->newFunction(js_extraPowerTime));
|
|
engine->globalObject().setProperty("setTutorialMode", engine->newFunction(js_setTutorialMode));
|
|
engine->globalObject().setProperty("setDesign", engine->newFunction(js_setDesign));
|
|
engine->globalObject().setProperty("enableTemplate", engine->newFunction(js_enableTemplate));
|
|
engine->globalObject().setProperty("setMiniMap", engine->newFunction(js_setMiniMap));
|
|
engine->globalObject().setProperty("setReticuleButton", engine->newFunction(js_setReticuleButton));
|
|
engine->globalObject().setProperty("showInterface", engine->newFunction(js_showInterface));
|
|
engine->globalObject().setProperty("hideInterface", engine->newFunction(js_hideInterface));
|
|
engine->globalObject().setProperty("addReticuleButton", engine->newFunction(js_removeReticuleButton)); // deprecated!!
|
|
engine->globalObject().setProperty("removeReticuleButton", engine->newFunction(js_removeReticuleButton));
|
|
engine->globalObject().setProperty("enableStructure", engine->newFunction(js_enableStructure));
|
|
engine->globalObject().setProperty("makeComponentAvailable", engine->newFunction(js_makeComponentAvailable));
|
|
engine->globalObject().setProperty("enableComponent", engine->newFunction(js_enableComponent));
|
|
engine->globalObject().setProperty("allianceExistsBetween", engine->newFunction(js_allianceExistsBetween));
|
|
engine->globalObject().setProperty("removeStruct", engine->newFunction(js_removeStruct));
|
|
engine->globalObject().setProperty("removeObject", engine->newFunction(js_removeObject));
|
|
engine->globalObject().setProperty("setScrollParams", engine->newFunction(js_setScrollLimits)); // deprecated!!
|
|
engine->globalObject().setProperty("setScrollLimits", engine->newFunction(js_setScrollLimits));
|
|
engine->globalObject().setProperty("getScrollLimits", engine->newFunction(js_getScrollLimits));
|
|
engine->globalObject().setProperty("addStructure", engine->newFunction(js_addStructure));
|
|
engine->globalObject().setProperty("getStructureLimit", engine->newFunction(js_getStructureLimit));
|
|
engine->globalObject().setProperty("countStruct", engine->newFunction(js_countStruct));
|
|
engine->globalObject().setProperty("countDroid", engine->newFunction(js_countDroid));
|
|
engine->globalObject().setProperty("loadLevel", engine->newFunction(js_loadLevel));
|
|
engine->globalObject().setProperty("setDroidExperience", engine->newFunction(js_setDroidExperience));
|
|
engine->globalObject().setProperty("donateObject", engine->newFunction(js_donateObject));
|
|
engine->globalObject().setProperty("donatePower", engine->newFunction(js_donatePower));
|
|
engine->globalObject().setProperty("setNoGoArea", engine->newFunction(js_setNoGoArea));
|
|
engine->globalObject().setProperty("startTransporterEntry", engine->newFunction(js_startTransporterEntry));
|
|
engine->globalObject().setProperty("setTransporterExit", engine->newFunction(js_setTransporterExit));
|
|
|
|
// Set some useful constants
|
|
engine->globalObject().setProperty("TER_WATER", TER_WATER, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("TER_CLIFFFACE", TER_CLIFFFACE, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("WEATHER_CLEAR", WT_NONE, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("WEATHER_RAIN", WT_RAINING, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("WEATHER_SNOW", WT_SNOWING, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DORDER_ATTACK", DORDER_ATTACK, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DORDER_OBSERVE", DORDER_OBSERVE, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DORDER_RECOVER", DORDER_RECOVER, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DORDER_MOVE", DORDER_MOVE, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DORDER_SCOUT", DORDER_SCOUT, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DORDER_BUILD", DORDER_BUILD, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DORDER_HELPBUILD", DORDER_HELPBUILD, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DORDER_LINEBUILD", DORDER_LINEBUILD, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DORDER_REPAIR", DORDER_REPAIR, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DORDER_RETREAT", DORDER_RETREAT, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DORDER_PATROL", DORDER_PATROL, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DORDER_DEMOLISH", DORDER_DEMOLISH, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DORDER_EMBARK", DORDER_EMBARK, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DORDER_DISEMBARK", DORDER_DISEMBARK, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DORDER_FIRESUPPORT", DORDER_FIRESUPPORT, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DORDER_HOLD", DORDER_HOLD, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DORDER_RTR", DORDER_RTR, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DORDER_RTB", DORDER_RTB, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DORDER_STOP", DORDER_STOP, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DORDER_REARM", DORDER_REARM, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DORDER_RECYCLE", DORDER_RECYCLE, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("COMMAND", IDRET_COMMAND, QScriptValue::ReadOnly | QScriptValue::Undeletable); // deprecated
|
|
engine->globalObject().setProperty("BUILD", IDRET_BUILD, QScriptValue::ReadOnly | QScriptValue::Undeletable); // deprecated
|
|
engine->globalObject().setProperty("MANUFACTURE", IDRET_MANUFACTURE, QScriptValue::ReadOnly | QScriptValue::Undeletable); // deprecated
|
|
engine->globalObject().setProperty("RESEARCH", IDRET_RESEARCH, QScriptValue::ReadOnly | QScriptValue::Undeletable); // deprecated
|
|
engine->globalObject().setProperty("INTELMAP", IDRET_INTEL_MAP, QScriptValue::ReadOnly | QScriptValue::Undeletable); // deprecated
|
|
engine->globalObject().setProperty("DESIGN", IDRET_DESIGN, QScriptValue::ReadOnly | QScriptValue::Undeletable); // deprecated
|
|
engine->globalObject().setProperty("CANCEL", IDRET_CANCEL, QScriptValue::ReadOnly | QScriptValue::Undeletable); // deprecated
|
|
engine->globalObject().setProperty("CAMP_CLEAN", CAMP_CLEAN, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("CAMP_BASE", CAMP_BASE, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("CAMP_WALLS", CAMP_WALLS, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("NO_ALLIANCES", NO_ALLIANCES, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("ALLIANCES", ALLIANCES, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("ALLIANCES_TEAMS", ALLIANCES_TEAMS, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("ALLIANCES_UNSHARED", ALLIANCES_UNSHARED, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("BEING_BUILT", SS_BEING_BUILT, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("BUILT", SS_BUILT, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DROID_CONSTRUCT", DROID_CONSTRUCT, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DROID_WEAPON", DROID_WEAPON, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DROID_PERSON", DROID_PERSON, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DROID_REPAIR", DROID_REPAIR, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DROID_SENSOR", DROID_SENSOR, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DROID_ECM", DROID_ECM, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DROID_CYBORG", DROID_CYBORG, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DROID_TRANSPORTER", DROID_TRANSPORTER, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DROID_SUPERTRANSPORTER", DROID_SUPERTRANSPORTER, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DROID_COMMAND", DROID_COMMAND, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DROID_ANY", DROID_ANY, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("OIL_RESOURCE", FEAT_OIL_RESOURCE, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("OIL_DRUM", FEAT_OIL_DRUM, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("ARTIFACT", FEAT_GEN_ARTE, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("BUILDING", FEAT_BUILDING, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("HQ", REF_HQ, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("FACTORY", REF_FACTORY, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("POWER_GEN", REF_POWER_GEN, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("RESOURCE_EXTRACTOR", REF_RESOURCE_EXTRACTOR, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DEFENSE", REF_DEFENSE, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("LASSAT", FAKE_REF_LASSAT, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("WALL", REF_WALL, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("RESEARCH_LAB", REF_RESEARCH, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("REPAIR_FACILITY", REF_REPAIR_FACILITY, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("CYBORG_FACTORY", REF_CYBORG_FACTORY, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("VTOL_FACTORY", REF_VTOL_FACTORY, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("REARM_PAD", REF_REARM_PAD, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("SAT_UPLINK", REF_SAT_UPLINK, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("GATE", REF_GATE, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("COMMAND_CONTROL", REF_COMMAND_CONTROL, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("EASY", DIFFICULTY_EASY, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("MEDIUM", DIFFICULTY_MEDIUM, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("HARD", DIFFICULTY_HARD, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("INSANE", DIFFICULTY_INSANE, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("STRUCTURE", OBJ_STRUCTURE, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("DROID", OBJ_DROID, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("FEATURE", OBJ_FEATURE, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("ALL_PLAYERS", ALL_PLAYERS, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("ALLIES", ALLIES, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("ENEMIES", ENEMIES, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("POSITION", SCRIPT_POSITION, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("AREA", SCRIPT_AREA, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("GROUP", SCRIPT_GROUP, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("PLAYER_DATA", SCRIPT_PLAYER, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("RESEARCH_DATA", SCRIPT_RESEARCH, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
// the constants below are subject to change without notice...
|
|
engine->globalObject().setProperty("PROX_MSG", MSG_PROXIMITY, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("CAMP_MSG", MSG_CAMPAIGN, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("MISS_MSG", MSG_MISSION, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("RES_MSG", MSG_RESEARCH, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
|
|
/// Place to store group sizes
|
|
//== \item[groupSizes] A sparse array of group sizes. If a group has never been used, the entry in this array will
|
|
//== be undefined.
|
|
engine->globalObject().setProperty("groupSizes", engine->newObject());
|
|
|
|
// Static knowledge about players
|
|
//== \item[playerData] An array of information about the players in a game. Each item in the array is an object
|
|
//== containing the following variables: difficulty (see \emph{difficulty} global constant), colour, position,
|
|
//== isAI (3.2+ only), isHuman (3.2+ only), name (3.2+ only), and team.
|
|
QScriptValue playerData = engine->newArray(game.maxPlayers);
|
|
for (int i = 0; i < game.maxPlayers; i++)
|
|
{
|
|
QScriptValue vector = engine->newObject();
|
|
vector.setProperty("name", NetPlay.players[i].name, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
vector.setProperty("difficulty", NetPlay.players[i].difficulty, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
vector.setProperty("colour", NetPlay.players[i].colour, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
vector.setProperty("position", NetPlay.players[i].position, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
vector.setProperty("team", NetPlay.players[i].team, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
vector.setProperty("isAI", !NetPlay.players[i].allocated && NetPlay.players[i].ai >= 0, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
vector.setProperty("isHuman", NetPlay.players[i].allocated, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
vector.setProperty("type", SCRIPT_PLAYER, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
playerData.setProperty(i, vector, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
}
|
|
engine->globalObject().setProperty("playerData", playerData, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
|
|
// Static map knowledge about start positions
|
|
//== \item[derrickPositions] An array of derrick starting positions on the current map. Each item in the array is an
|
|
//== object containing the x and y variables for a derrick.
|
|
//== \item[startPositions] An array of player start positions on the current map. Each item in the array is an
|
|
//== object containing the x and y variables for a player start position.
|
|
QScriptValue startPositions = engine->newArray(game.maxPlayers);
|
|
for (int i = 0; i < game.maxPlayers; i++)
|
|
{
|
|
QScriptValue vector = engine->newObject();
|
|
vector.setProperty("x", map_coord(positions[i].x), QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
vector.setProperty("y", map_coord(positions[i].y), QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
vector.setProperty("type", SCRIPT_POSITION, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
startPositions.setProperty(i, vector, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
}
|
|
QScriptValue derrickPositions = engine->newArray(derricks.size());
|
|
for (int i = 0; i < derricks.size(); i++)
|
|
{
|
|
QScriptValue vector = engine->newObject();
|
|
vector.setProperty("x", map_coord(derricks[i].x), QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
vector.setProperty("y", map_coord(derricks[i].y), QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
vector.setProperty("type", SCRIPT_POSITION, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
derrickPositions.setProperty(i, vector, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
}
|
|
engine->globalObject().setProperty("derrickPositions", derrickPositions, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
engine->globalObject().setProperty("startPositions", startPositions, QScriptValue::ReadOnly | QScriptValue::Undeletable);
|
|
|
|
// Clear previous log file
|
|
PHYSFS_delete(QString("logs/" + scriptName +".log").toUtf8().constData());
|
|
|
|
return true;
|
|
}
|