mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
# Primitive melee combat support in new simulation system
This was SVN commit r7309.
This commit is contained in:
parent
c50fe9b8c0
commit
b21e798243
19 changed files with 474 additions and 19 deletions
|
|
@ -13,8 +13,74 @@ var inputState = INPUT_NORMAL;
|
|||
|
||||
var placementEntity = "";
|
||||
|
||||
var mouseX = 0;
|
||||
var mouseY = 0;
|
||||
|
||||
function updateCursor()
|
||||
{
|
||||
var action = determineAction(mouseX, mouseY);
|
||||
if (action)
|
||||
{
|
||||
if (action.type != "move")
|
||||
{
|
||||
Engine.SetCursor("action-" + action.type);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Engine.SetCursor("arrow-default");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the context-sensitive action that should be performed when the mouse is at (x,y)
|
||||
*/
|
||||
function determineAction(x, y)
|
||||
{
|
||||
var selection = getEntitySelection();
|
||||
|
||||
// No action if there's no selection
|
||||
if (!selection.length)
|
||||
return;
|
||||
|
||||
// If the selection isn't friendly units, no action
|
||||
var entState = Engine.GuiInterfaceCall("GetEntityState", selection[0]);
|
||||
var player = Engine.GetPlayerID();
|
||||
if (entState.player != player)
|
||||
return;
|
||||
|
||||
var targets = Engine.PickEntitiesAtPoint(x, y);
|
||||
|
||||
// If there's no unit, just walk
|
||||
if (!targets.length)
|
||||
return {"type": "move"};
|
||||
|
||||
// Look at the first targeted entity
|
||||
// (TODO: maybe we eventually want to look at more, and be more context-sensitive?
|
||||
// e.g. prefer to attack an enemy unit, even if some friendly units are closer to the mouse)
|
||||
var targetState = Engine.GuiInterfaceCall("GetEntityState", targets[0]);
|
||||
|
||||
// Different owner -> attack
|
||||
if (entState.attack && targetState.player != player)
|
||||
return {"type": "attack", "target": targets[0]};
|
||||
|
||||
// TODO: need more actions
|
||||
|
||||
// If we don't do anything more specific, just walk
|
||||
return {"type": "move"};
|
||||
}
|
||||
|
||||
function handleInputBeforeGui(ev)
|
||||
{
|
||||
switch (ev.type)
|
||||
{
|
||||
case "mousebuttonup":
|
||||
case "mousebuttondown":
|
||||
case "mousemotion":
|
||||
mouseX = ev.x;
|
||||
mouseY = ev.y;
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -54,17 +120,25 @@ function handleInputAfterGui(ev)
|
|||
resetEntitySelection();
|
||||
addEntitySelection([ents[0]]);
|
||||
|
||||
Engine.PostNetworkCommand({"type": "spin", "entities": [ents[0]]});
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (ev.button == SDL_BUTTON_RIGHT)
|
||||
{
|
||||
var ents = getEntitySelection();
|
||||
if (ents.length)
|
||||
var action = determineAction(ev.x, ev.y);
|
||||
if (!action)
|
||||
break;
|
||||
|
||||
var selection = getEntitySelection();
|
||||
|
||||
switch (action.type)
|
||||
{
|
||||
case "move":
|
||||
var target = Engine.GetTerrainAtPoint(ev.x, ev.y);
|
||||
Engine.PostNetworkCommand({"type": "walk", "entities": ents, "x": target.x, "z": target.z});
|
||||
Engine.PostNetworkCommand({"type": "walk", "entities": selection, "x": target.x, "z": target.z});
|
||||
return true;
|
||||
|
||||
case "attack":
|
||||
Engine.PostNetworkCommand({"type": "attack", "entities": selection, "target": action.target});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,6 @@ function getEntitySelection()
|
|||
{
|
||||
var ents = [];
|
||||
for (var ent in g_Selection)
|
||||
ents.push(ent);
|
||||
ents.push(+ent); // convert from string to number and push
|
||||
return ents;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,11 @@ function init()
|
|||
updateDebug();
|
||||
}
|
||||
|
||||
function onTick()
|
||||
{
|
||||
updateCursor();
|
||||
}
|
||||
|
||||
function onSimulationUpdate()
|
||||
{
|
||||
updateDebug();
|
||||
|
|
|
|||
|
|
@ -32,6 +32,10 @@
|
|||
initSession();
|
||||
</action>
|
||||
|
||||
<action on="Tick">
|
||||
onTick();
|
||||
</action>
|
||||
|
||||
<action on="SimulationUpdate">
|
||||
onSimulationUpdate();
|
||||
</action>
|
||||
|
|
|
|||
23
binaries/data/mods/public/simulation/components/Armour.js
Normal file
23
binaries/data/mods/public/simulation/components/Armour.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
function Armour() {}
|
||||
|
||||
Armour.prototype.Init = function()
|
||||
{
|
||||
};
|
||||
|
||||
Armour.prototype.TakeDamage = function(hack, pierce, crush)
|
||||
{
|
||||
// Adjust damage values based on armour
|
||||
// (Default armour values to 0 if undefined)
|
||||
var adjHack = Math.max(0, hack - (this.template.Hack || 0));
|
||||
var adjPierce = Math.max(0, pierce - (this.template.Pierce || 0));
|
||||
var adjCrush = Math.max(0, crush - (this.template.Crush || 0));
|
||||
|
||||
// Total is sum of individual damages, with minimum damage 1
|
||||
var total = Math.max(1, adjHack + adjPierce + adjCrush);
|
||||
|
||||
// Reduce health
|
||||
var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
|
||||
cmpHealth.Reduce(total);
|
||||
};
|
||||
|
||||
Engine.RegisterComponentType(IID_DamageReceiver, "Armour", Armour);
|
||||
76
binaries/data/mods/public/simulation/components/Attack.js
Normal file
76
binaries/data/mods/public/simulation/components/Attack.js
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
function Attack() {}
|
||||
|
||||
Attack.prototype.Init = function()
|
||||
{
|
||||
};
|
||||
|
||||
/*
|
||||
* TODO: to handle secondary attacks in the future, what we might do is
|
||||
* add a 'mode' parameter to most of these functions, to indicate which
|
||||
* attack mode we're trying to use, and some other function that allows
|
||||
* UnitAI to pick the best attack mode (based on range, damage, etc)
|
||||
*/
|
||||
|
||||
Attack.prototype.GetTimers = function()
|
||||
{
|
||||
var prepare = +(this.template.PrepareTime || 0);
|
||||
var repeat = +(this.template.RepeatTime || 1000);
|
||||
return { "prepare": prepare, "repeat": repeat, "recharge": repeat - prepare };
|
||||
};
|
||||
|
||||
Attack.prototype.GetAttackStrengths = function()
|
||||
{
|
||||
// Convert attack values to numbers, default 0 if unspecified
|
||||
return {
|
||||
hack: +(this.template.Hack || 0),
|
||||
pierce: +(this.template.Pierce || 0),
|
||||
crush: +(this.template.Crush || 0)
|
||||
};
|
||||
};
|
||||
|
||||
function hypot2(x, y)
|
||||
{
|
||||
return x*x + y*y;
|
||||
}
|
||||
|
||||
Attack.prototype.CheckRange = function(target)
|
||||
{
|
||||
// Target must be in the world
|
||||
var cmpPositionTarget = Engine.QueryInterface(target, IID_Position);
|
||||
if (!cmpPositionTarget || !cmpPositionTarget.IsInWorld())
|
||||
return { "error": "not-in-world" };
|
||||
|
||||
// We must be in the world
|
||||
var cmpPositionSelf = Engine.QueryInterface(this.entity, IID_Position);
|
||||
if (!cmpPositionSelf || !cmpPositionSelf.IsInWorld())
|
||||
return { "error": "not-in-world" };
|
||||
|
||||
// Target must be within range
|
||||
var posTarget = cmpPositionTarget.GetPosition();
|
||||
var posSelf = cmpPositionSelf.GetPosition();
|
||||
var dist2 = hypot2(posTarget.x - posSelf.x, posTarget.z - posSelf.z);
|
||||
// TODO: ought to be distance to closest point in footprint, not to center
|
||||
var maxrange = +this.template.Range;
|
||||
if (dist2 > maxrange*maxrange)
|
||||
return { "error": "out-of-range", "maxrange": maxrange };
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Attack the target entity. This should only be called after a successful CheckRange,
|
||||
* and should only be called after GetTimers().repeat msec has passed since the last
|
||||
* call to PerformAttack.
|
||||
*/
|
||||
Attack.prototype.PerformAttack = function(target)
|
||||
{
|
||||
var strengths = this.GetAttackStrengths();
|
||||
|
||||
// Inflict damage on the target
|
||||
var cmpDamageReceiver = Engine.QueryInterface(target, IID_DamageReceiver);
|
||||
if (!cmpDamageReceiver)
|
||||
return;
|
||||
cmpDamageReceiver.TakeDamage(strengths.hack, strengths.pierce, strengths.crush);
|
||||
};
|
||||
|
||||
Engine.RegisterComponentType(IID_Attack, "Attack", Attack);
|
||||
|
|
@ -38,12 +38,30 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
|
|||
"position": cmpPosition.GetPosition()
|
||||
};
|
||||
|
||||
var cmpHealth = Engine.QueryInterface(ent, IID_Health);
|
||||
if (cmpHealth)
|
||||
{
|
||||
ret.hitpoints = cmpHealth.GetHitpoints();
|
||||
}
|
||||
|
||||
var cmpAttack = Engine.QueryInterface(ent, IID_Attack);
|
||||
if (cmpAttack)
|
||||
{
|
||||
ret.attack = cmpAttack.GetAttackStrengths();
|
||||
}
|
||||
|
||||
var cmpBuilder = Engine.QueryInterface(ent, IID_Builder);
|
||||
if (cmpBuilder)
|
||||
{
|
||||
ret.buildEntities = cmpBuilder.GetEntitiesList();
|
||||
}
|
||||
|
||||
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
|
||||
if (cmpOwnership)
|
||||
{
|
||||
ret.player = cmpOwnership.GetOwner();
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
|
|
|||
26
binaries/data/mods/public/simulation/components/Health.js
Normal file
26
binaries/data/mods/public/simulation/components/Health.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
function Health() {}
|
||||
|
||||
Health.prototype.Init = function()
|
||||
{
|
||||
this.hitpoints = +this.template.Max;
|
||||
};
|
||||
|
||||
Health.prototype.GetHitpoints = function()
|
||||
{
|
||||
return this.hitpoints;
|
||||
};
|
||||
|
||||
Health.prototype.Reduce = function(amount)
|
||||
{
|
||||
if (amount >= this.hitpoints)
|
||||
{
|
||||
this.hitpoints = 0;
|
||||
// TODO: need to destroy this entity, set up a corpse, etc
|
||||
}
|
||||
else
|
||||
{
|
||||
this.hitpoints -= amount;
|
||||
}
|
||||
}
|
||||
|
||||
Engine.RegisterComponentType(IID_Health, "Health", Health);
|
||||
56
binaries/data/mods/public/simulation/components/Timer.js
Normal file
56
binaries/data/mods/public/simulation/components/Timer.js
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
function Timer() {}
|
||||
|
||||
Timer.prototype.Init = function()
|
||||
{
|
||||
this.id = 0;
|
||||
this.time = 0;
|
||||
this.timers = {};
|
||||
};
|
||||
|
||||
Timer.prototype.GetTime = function()
|
||||
{
|
||||
return this.time;
|
||||
}
|
||||
|
||||
Timer.prototype.OnUpdate = function(msg)
|
||||
{
|
||||
var dt = Math.round(msg.turnLength * 1000);
|
||||
this.time += dt;
|
||||
|
||||
// Collect the timers that need to run
|
||||
// (We do this in two stages to avoid deleting from the timer list while
|
||||
// we're in the middle of iterating through it)
|
||||
var run = [];
|
||||
for (var id in this.timers)
|
||||
{
|
||||
if (this.timers[id][3] <= this.time)
|
||||
run.push(id);
|
||||
}
|
||||
for each (var id in run)
|
||||
{
|
||||
var t = this.timers[id];
|
||||
var cmp = Engine.QueryInterface(t[0], t[1]);
|
||||
try {
|
||||
cmp[t[2]](t[4]);
|
||||
} catch (e) {
|
||||
print("Error in timer on entity "+t[0]+", IID "+t[1]+", function "+t[2]+": "+e);
|
||||
// TODO: should report in an error log
|
||||
}
|
||||
delete this.timers[id];
|
||||
}
|
||||
}
|
||||
|
||||
Timer.prototype.SetTimeout = function(ent, iid, funcname, time, data)
|
||||
{
|
||||
var id = ++this.id;
|
||||
this.timers[id] = [ent, iid, funcname, this.time + time, data];
|
||||
return id;
|
||||
};
|
||||
|
||||
Timer.prototype.CancelTimer = function(id)
|
||||
{
|
||||
delete this.timers[id];
|
||||
};
|
||||
|
||||
Engine.RegisterComponentType(IID_Timer, "Timer", Timer);
|
||||
|
||||
140
binaries/data/mods/public/simulation/components/UnitAI.js
Normal file
140
binaries/data/mods/public/simulation/components/UnitAI.js
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
/*
|
||||
|
||||
This is currently just a very simplistic state machine that lets units be commanded around
|
||||
and then autonomously carry out the orders.
|
||||
|
||||
*/
|
||||
|
||||
const STATE_IDLE = 0;
|
||||
const STATE_WALKING = 1;
|
||||
const STATE_ATTACKING = 2;
|
||||
|
||||
/* Attack process:
|
||||
* When starting attack:
|
||||
* Activate attack animation (with appropriate repeat speed and offset)
|
||||
* Set this.attackTimer to run at maximum of:
|
||||
* GetTimers().prepare msec from now
|
||||
* this.attackRechargeTime
|
||||
* Loop:
|
||||
* Wait for the timer
|
||||
* Perform the attack
|
||||
* Set this.attackRechargeTime to now plus GetTimers().recharge
|
||||
* Set this.attackTimer to run after GetTimers().repeat
|
||||
* At any point it's safe to cancel the attack and switch to a different action
|
||||
* (The rechargeTime is to prevent people spamming the attack command and getting
|
||||
* faster-than-normal attacks)
|
||||
*/
|
||||
|
||||
function UnitAI() {}
|
||||
|
||||
UnitAI.prototype.Init = function()
|
||||
{
|
||||
this.state = STATE_IDLE;
|
||||
|
||||
// The earliest time at which we'll have 'recovered' from the previous attack, and
|
||||
// can start preparing a new attack
|
||||
this.attackRechargeTime = 0;
|
||||
// Timer for AttackTimeout
|
||||
this.attackTimer = undefined;
|
||||
};
|
||||
|
||||
UnitAI.prototype.OnDestroy = function()
|
||||
{
|
||||
if (this.attackTimer)
|
||||
{
|
||||
cmpTimer.CancelTimer(this.attackTimer);
|
||||
this.attackTimer = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
//// Interface functions ////
|
||||
|
||||
UnitAI.prototype.Walk = function(x, z)
|
||||
{
|
||||
var motion = Engine.QueryInterface(this.entity, IID_UnitMotion);
|
||||
if (!motion)
|
||||
return;
|
||||
|
||||
motion.MoveToPoint(x, z);
|
||||
this.state = STATE_WALKING;
|
||||
};
|
||||
|
||||
UnitAI.prototype.Attack = function(target)
|
||||
{
|
||||
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
|
||||
if (!cmpAttack)
|
||||
return;
|
||||
|
||||
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||
|
||||
// Cancel any previous attack timer
|
||||
if (this.attackTimer)
|
||||
cmpTimer.CancelTimer(this.attackTimer);
|
||||
|
||||
// TODO: start the attack animation here
|
||||
|
||||
// TODO: should check the range and move closer before attempting to attack
|
||||
|
||||
// Perform the attack after the prepare time, but not before the previous attack's recharge
|
||||
var timers = cmpAttack.GetTimers();
|
||||
var time = Math.max(timers.prepare, this.attackRechargeTime - cmpTimer.GetTime());
|
||||
|
||||
var data = { "target": target, "timers": timers };
|
||||
this.attackTimer = cmpTimer.SetTimeout(this.entity, IID_UnitAI, "AttackTimeout", time, data);
|
||||
|
||||
this.state = STATE_ATTACKING;
|
||||
};
|
||||
|
||||
//// Private functions ////
|
||||
|
||||
UnitAI.prototype.MoveToTarget = function(target)
|
||||
{
|
||||
var cmpPositionTarget = Engine.QueryInterface(target, IID_Position);
|
||||
if (!cmpPositionTarget || !cmpPositionTarget.IsInWorld())
|
||||
return;
|
||||
|
||||
var cmpMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
|
||||
|
||||
var pos = cmpPositionTarget.GetPosition();
|
||||
cmpMotion.MoveToPoint(pos.x, pos.z);
|
||||
};
|
||||
|
||||
UnitAI.prototype.AttackTimeout = function(data)
|
||||
{
|
||||
// If we stopped attacking before this timeout, then don't do any processing here
|
||||
if (this.state != STATE_ATTACKING)
|
||||
return;
|
||||
|
||||
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||
|
||||
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
|
||||
|
||||
// Check if we can still reach the target
|
||||
var rangeStatus = cmpAttack.CheckRange(data.target);
|
||||
if (rangeStatus.error)
|
||||
{
|
||||
if (rangeStatus.error == "out-of-range")
|
||||
{
|
||||
// Out of range => need to move closer
|
||||
this.MoveToTarget(data.target);
|
||||
// Try again in a couple of seconds
|
||||
// (TODO: ought to have a cleverer way of detecting once we're back in range)
|
||||
this.attackTimer = cmpTimer.SetTimeout(this.entity, IID_UnitAI, "AttackTimeout", 2000, data);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise it's impossible to reach the target, so give up
|
||||
// and switch back to idle
|
||||
this.state = STATE_IDLE;
|
||||
return;
|
||||
}
|
||||
|
||||
// Hit the target
|
||||
cmpAttack.PerformAttack(data.target);
|
||||
|
||||
// Set a timer to hit the target again
|
||||
this.attackRechargeTime = cmpTimer.GetTime() + data.timers.recharge;
|
||||
this.attackTimer = cmpTimer.SetTimeout(this.entity, IID_UnitAI, "AttackTimeout", data.timers.repeat, data);
|
||||
};
|
||||
|
||||
Engine.RegisterComponentType(IID_UnitAI, "UnitAI", UnitAI);
|
||||
|
|
@ -0,0 +1 @@
|
|||
Engine.RegisterInterface("Attack");
|
||||
|
|
@ -0,0 +1 @@
|
|||
Engine.RegisterInterface("DamageReceiver");
|
||||
|
|
@ -0,0 +1 @@
|
|||
Engine.RegisterInterface("Health");
|
||||
|
|
@ -0,0 +1 @@
|
|||
Engine.RegisterInterface("Timer");
|
||||
|
|
@ -0,0 +1 @@
|
|||
Engine.RegisterInterface("UnitAI");
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
Engine.LoadComponentScript("interfaces/Attack.js");
|
||||
Engine.LoadComponentScript("interfaces/Builder.js");
|
||||
Engine.LoadComponentScript("interfaces/Health.js");
|
||||
Engine.LoadComponentScript("GuiInterface.js");
|
||||
|
||||
var cmp = ConstructComponent(SYSTEM_ENTITY, "GuiInterface");
|
||||
|
|
@ -35,6 +37,12 @@ AddMock(10, IID_Position, {
|
|||
}
|
||||
});
|
||||
|
||||
AddMock(10, IID_Health, {
|
||||
GetHitpoints: function() {
|
||||
return 50;
|
||||
}
|
||||
});
|
||||
|
||||
AddMock(10, IID_Builder, {
|
||||
GetEntitiesList: function() {
|
||||
return ["test1", "test2"];
|
||||
|
|
@ -45,5 +53,6 @@ var state = cmp.GetEntityState(-1, 10);
|
|||
TS_ASSERT_UNEVAL_EQUALS(state, {
|
||||
template: "example",
|
||||
position: {x:1, y:2, z:3},
|
||||
hitpoints: 50,
|
||||
buildEntities: ["test1", "test2"]
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,23 +4,23 @@ function ProcessCommand(player, cmd)
|
|||
|
||||
switch (cmd.type)
|
||||
{
|
||||
case "spin":
|
||||
for each (var ent in cmd.entities)
|
||||
{
|
||||
var pos = Engine.QueryInterface(ent, IID_Position);
|
||||
if (! pos)
|
||||
continue;
|
||||
pos.SetYRotation(pos.GetRotation().y + 1);
|
||||
}
|
||||
break;
|
||||
|
||||
case "walk":
|
||||
for each (var ent in cmd.entities)
|
||||
{
|
||||
var motion = Engine.QueryInterface(ent, IID_UnitMotion);
|
||||
if (! motion)
|
||||
var ai = Engine.QueryInterface(ent, IID_UnitAI);
|
||||
if (!ai)
|
||||
continue;
|
||||
motion.MoveToPoint(cmd.x, cmd.z);
|
||||
ai.Walk(cmd.x, cmd.z);
|
||||
}
|
||||
break;
|
||||
|
||||
case "attack":
|
||||
for each (var ent in cmd.entities)
|
||||
{
|
||||
var ai = Engine.QueryInterface(ent, IID_UnitAI);
|
||||
if (!ai)
|
||||
continue;
|
||||
ai.Attack(cmd.target);
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@
|
|||
#include "ps/Game.h"
|
||||
#include "ps/Overlay.h"
|
||||
#include "ps/Player.h"
|
||||
#include "ps/GameSetup/Config.h"
|
||||
#include "simulation2/Simulation2.h"
|
||||
#include "simulation2/components/ICmpCommandQueue.h"
|
||||
#include "simulation2/components/ICmpGuiInterface.h"
|
||||
|
|
@ -168,6 +169,20 @@ CFixedVector3D GetTerrainAtPoint(void* UNUSED(cbdata), int x, int y)
|
|||
return CFixedVector3D(CFixed_23_8::FromFloat(pos.X), CFixed_23_8::FromFloat(pos.Y), CFixed_23_8::FromFloat(pos.Z));
|
||||
}
|
||||
|
||||
std::wstring SetCursor(void* UNUSED(cbdata), std::wstring name)
|
||||
{
|
||||
std::wstring old = g_CursorName;
|
||||
g_CursorName = name;
|
||||
return old;
|
||||
}
|
||||
|
||||
int GetPlayerID(void* UNUSED(cbdata))
|
||||
{
|
||||
if (g_Game && g_Game->GetLocalPlayer())
|
||||
return g_Game->GetLocalPlayer()->GetPlayerID();
|
||||
return -1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void GuiScriptingInit(ScriptInterface& scriptInterface)
|
||||
|
|
@ -187,4 +202,7 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
|
|||
scriptInterface.RegisterFunction<std::vector<entity_id_t>, int, int, &PickEntitiesAtPoint>("PickEntitiesAtPoint");
|
||||
scriptInterface.RegisterFunction<CFixedVector3D, int, int, &GetTerrainAtPoint>("GetTerrainAtPoint");
|
||||
|
||||
// Misc functions
|
||||
scriptInterface.RegisterFunction<std::wstring, std::wstring, &SetCursor>("SetCursor");
|
||||
scriptInterface.RegisterFunction<int, &GetPlayerID>("GetPlayerID");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ public:
|
|||
|
||||
LOAD_SCRIPTED_COMPONENT("GuiInterface");
|
||||
LOAD_SCRIPTED_COMPONENT("PlayerManager");
|
||||
LOAD_SCRIPTED_COMPONENT("Timer");
|
||||
|
||||
#undef LOAD_SCRIPTED_COMPONENT
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue