757 lines
21 KiB
JavaScript
757 lines
21 KiB
JavaScript
function GuiInterface() {}
|
|
|
|
GuiInterface.prototype.Schema =
|
|
"<a:component type='system'/><empty/>";
|
|
|
|
GuiInterface.prototype.Serialize = function()
|
|
{
|
|
// This component isn't network-synchronised so we mustn't serialise
|
|
// its non-deterministic data. Instead just return an empty object.
|
|
return {};
|
|
};
|
|
|
|
GuiInterface.prototype.Deserialize = function(obj)
|
|
{
|
|
this.Init();
|
|
};
|
|
|
|
GuiInterface.prototype.Init = function()
|
|
{
|
|
this.placementEntity = undefined; // = undefined or [templateName, entityID]
|
|
this.rallyPoints = undefined;
|
|
this.notifications = [];
|
|
this.renamedEntities = [];
|
|
};
|
|
|
|
/*
|
|
* All of the functions defined below are called via Engine.GuiInterfaceCall(name, arg)
|
|
* from GUI scripts, and executed here with arguments (player, arg).
|
|
*
|
|
* CAUTION: The input to the functions in this module is not network-synchronised, so it
|
|
* mustn't affect the simulation state (i.e. the data that is serialised and can affect
|
|
* the behaviour of the rest of the simulation) else it'll cause out-of-sync errors.
|
|
*/
|
|
|
|
/**
|
|
* Returns global information about the current game state.
|
|
* This is used by the GUI and also by AI scripts.
|
|
*/
|
|
GuiInterface.prototype.GetSimulationState = function(player)
|
|
{
|
|
var ret = {
|
|
"players": []
|
|
};
|
|
|
|
var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
|
|
var n = cmpPlayerMan.GetNumPlayers();
|
|
for (var i = 0; i < n; ++i)
|
|
{
|
|
var playerEnt = cmpPlayerMan.GetPlayerByID(i);
|
|
var cmpPlayerBuildLimits = Engine.QueryInterface(playerEnt, IID_BuildLimits);
|
|
var cmpPlayer = Engine.QueryInterface(playerEnt, IID_Player);
|
|
|
|
// store player ally/enemy data as arrays
|
|
var allies = [];
|
|
var enemies = [];
|
|
for (var j = 0; j <= n; ++j)
|
|
{
|
|
allies[j] = cmpPlayer.IsAlly(j);
|
|
enemies[j] = cmpPlayer.IsEnemy(j);
|
|
}
|
|
var playerData = {
|
|
"name": cmpPlayer.GetName(),
|
|
"civ": cmpPlayer.GetCiv(),
|
|
"colour": cmpPlayer.GetColour(),
|
|
"popCount": cmpPlayer.GetPopulationCount(),
|
|
"popLimit": cmpPlayer.GetPopulationLimit(),
|
|
"popMax": cmpPlayer.GetMaxPopulation(),
|
|
"resourceCounts": cmpPlayer.GetResourceCounts(),
|
|
"trainingQueueBlocked": cmpPlayer.IsTrainingQueueBlocked(),
|
|
"state": cmpPlayer.GetState(),
|
|
"team": cmpPlayer.GetTeam(),
|
|
"phase": cmpPlayer.GetPhase(),
|
|
"isAlly": allies,
|
|
"isEnemy": enemies,
|
|
"buildLimits": cmpPlayerBuildLimits.GetLimits(),
|
|
"buildCounts": cmpPlayerBuildLimits.GetCounts()
|
|
};
|
|
ret.players.push(playerData);
|
|
}
|
|
|
|
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
|
|
if (cmpRangeManager)
|
|
{
|
|
ret.circularMap = cmpRangeManager.GetLosCircular();
|
|
}
|
|
|
|
// Add timeElapsed
|
|
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
|
ret.timeElapsed = cmpTimer.GetTime();
|
|
|
|
return ret;
|
|
};
|
|
|
|
GuiInterface.prototype.GetExtendedSimulationState = function(player)
|
|
{
|
|
// Get basic simulation info
|
|
var ret = this.GetSimulationState();
|
|
|
|
// Add statistics to each player
|
|
var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
|
|
var n = cmpPlayerMan.GetNumPlayers();
|
|
for (var i = 0; i < n; ++i)
|
|
{
|
|
var playerEnt = cmpPlayerMan.GetPlayerByID(i);
|
|
var cmpPlayerStatisticsTracker = Engine.QueryInterface(playerEnt, IID_StatisticsTracker);
|
|
ret.players[i].statistics = cmpPlayerStatisticsTracker.GetStatistics();
|
|
}
|
|
|
|
return ret;
|
|
};
|
|
|
|
GuiInterface.prototype.GetRenamedEntities = function(player)
|
|
{
|
|
return this.renamedEntities;
|
|
};
|
|
|
|
GuiInterface.prototype.ClearRenamedEntities = function(player)
|
|
{
|
|
this.renamedEntities = [];
|
|
};
|
|
|
|
GuiInterface.prototype.GetEntityState = function(player, ent)
|
|
{
|
|
var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
|
|
|
|
// All units must have a template; if not then it's a nonexistent entity id
|
|
var template = cmpTempMan.GetCurrentTemplateName(ent);
|
|
if (!template)
|
|
return null;
|
|
|
|
var ret = {
|
|
"id": ent,
|
|
"template": template
|
|
}
|
|
|
|
var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
|
|
if (cmpIdentity)
|
|
{
|
|
ret.identity = {
|
|
"rank": cmpIdentity.GetRank(),
|
|
"classes": cmpIdentity.GetClassesList(),
|
|
"selectionGroupName": cmpIdentity.GetSelectionGroupName()
|
|
};
|
|
}
|
|
|
|
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
|
|
if (cmpPosition && cmpPosition.IsInWorld())
|
|
{
|
|
ret.position = cmpPosition.GetPosition();
|
|
}
|
|
|
|
var cmpHealth = Engine.QueryInterface(ent, IID_Health);
|
|
if (cmpHealth)
|
|
{
|
|
ret.hitpoints = cmpHealth.GetHitpoints();
|
|
ret.maxHitpoints = cmpHealth.GetMaxHitpoints();
|
|
ret.needsRepair = cmpHealth.IsRepairable() && (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints());
|
|
}
|
|
|
|
var cmpAttack = Engine.QueryInterface(ent, IID_Attack);
|
|
if (cmpAttack)
|
|
{
|
|
var type = cmpAttack.GetBestAttack(); // TODO: how should we decide which attack to show?
|
|
ret.attack = cmpAttack.GetAttackStrengths(type);
|
|
}
|
|
|
|
var cmpArmour = Engine.QueryInterface(ent, IID_DamageReceiver);
|
|
if (cmpArmour)
|
|
{
|
|
ret.armour = cmpArmour.GetArmourStrengths();
|
|
}
|
|
|
|
var cmpBuilder = Engine.QueryInterface(ent, IID_Builder);
|
|
if (cmpBuilder)
|
|
{
|
|
ret.buildEntities = cmpBuilder.GetEntitiesList();
|
|
}
|
|
|
|
var cmpTrainingQueue = Engine.QueryInterface(ent, IID_TrainingQueue);
|
|
if (cmpTrainingQueue)
|
|
{
|
|
ret.training = {
|
|
"entities": cmpTrainingQueue.GetEntitiesList(),
|
|
"queue": cmpTrainingQueue.GetQueue(),
|
|
};
|
|
}
|
|
|
|
var cmpFoundation = Engine.QueryInterface(ent, IID_Foundation);
|
|
if (cmpFoundation)
|
|
{
|
|
ret.foundation = {
|
|
"progress": cmpFoundation.GetBuildPercentage()
|
|
};
|
|
}
|
|
|
|
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
|
|
if (cmpOwnership)
|
|
{
|
|
ret.player = cmpOwnership.GetOwner();
|
|
}
|
|
|
|
var cmpResourceSupply = Engine.QueryInterface(ent, IID_ResourceSupply);
|
|
if (cmpResourceSupply)
|
|
{
|
|
ret.resourceSupply = {
|
|
"max": cmpResourceSupply.GetMaxAmount(),
|
|
"amount": cmpResourceSupply.GetCurrentAmount(),
|
|
"type": cmpResourceSupply.GetType()
|
|
};
|
|
}
|
|
|
|
var cmpResourceGatherer = Engine.QueryInterface(ent, IID_ResourceGatherer);
|
|
if (cmpResourceGatherer)
|
|
{
|
|
ret.resourceGatherRates = cmpResourceGatherer.GetGatherRates();
|
|
ret.resourceCarrying = cmpResourceGatherer.GetCarryingStatus();
|
|
}
|
|
|
|
var cmpResourceDropsite = Engine.QueryInterface(ent, IID_ResourceDropsite);
|
|
if (cmpResourceDropsite)
|
|
{
|
|
ret.resourceDropsite = {
|
|
"types": cmpResourceDropsite.GetTypes()
|
|
};
|
|
}
|
|
|
|
var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
|
|
if (cmpRallyPoint)
|
|
{
|
|
ret.rallyPoint = {'position': cmpRallyPoint.GetPosition()}; // undefined or {x,z} object
|
|
}
|
|
|
|
var cmpGarrisonHolder = Engine.QueryInterface(ent, IID_GarrisonHolder);
|
|
if (cmpGarrisonHolder)
|
|
{
|
|
ret.garrisonHolder = {
|
|
"entities": cmpGarrisonHolder.GetEntities(),
|
|
"allowedClasses": cmpGarrisonHolder.GetAllowedClassesList()
|
|
};
|
|
}
|
|
|
|
var cmpPromotion = Engine.QueryInterface(ent, IID_Promotion);
|
|
if (cmpPromotion)
|
|
{
|
|
ret.promotion = {
|
|
"curr": cmpPromotion.GetCurrentXp(),
|
|
"req": cmpPromotion.GetRequiredXp()
|
|
};
|
|
}
|
|
|
|
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
|
|
if (cmpUnitAI)
|
|
{
|
|
ret.unitAI = {
|
|
// TODO: reading properties directly is kind of violating abstraction
|
|
"state": cmpUnitAI.fsmStateName,
|
|
"orders": cmpUnitAI.orderQueue,
|
|
};
|
|
}
|
|
|
|
if (!cmpFoundation && cmpIdentity && cmpIdentity.HasClass("BarterMarket"))
|
|
{
|
|
var cmpBarter = Engine.QueryInterface(SYSTEM_ENTITY, IID_Barter);
|
|
ret.barterMarket = { "prices": cmpBarter.GetPrices() };
|
|
}
|
|
|
|
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
|
|
ret.visibility = cmpRangeManager.GetLosVisibility(ent, player, false);
|
|
|
|
return ret;
|
|
};
|
|
|
|
GuiInterface.prototype.GetTemplateData = function(player, name)
|
|
{
|
|
var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
|
|
var template = cmpTempMan.GetTemplate(name);
|
|
|
|
if (!template)
|
|
return null;
|
|
|
|
var ret = {};
|
|
|
|
if (template.Armour)
|
|
{
|
|
ret.armour = {
|
|
"hack": +template.Armour.Hack,
|
|
"pierce": +template.Armour.Pierce,
|
|
"crush": +template.Armour.Crush,
|
|
};
|
|
}
|
|
|
|
if (template.Attack)
|
|
{
|
|
ret.attack = {};
|
|
for (var type in template.Attack)
|
|
{
|
|
ret.attack[type] = {
|
|
"hack": (+template.Attack[type].Hack || 0),
|
|
"pierce": (+template.Attack[type].Pierce || 0),
|
|
"crush": (+template.Attack[type].Crush || 0),
|
|
};
|
|
}
|
|
}
|
|
|
|
if (template.Cost)
|
|
{
|
|
ret.cost = {};
|
|
if (template.Cost.Resources.food) ret.cost.food = +template.Cost.Resources.food;
|
|
if (template.Cost.Resources.wood) ret.cost.wood = +template.Cost.Resources.wood;
|
|
if (template.Cost.Resources.stone) ret.cost.stone = +template.Cost.Resources.stone;
|
|
if (template.Cost.Resources.metal) ret.cost.metal = +template.Cost.Resources.metal;
|
|
if (template.Cost.Population) ret.cost.population = +template.Cost.Population;
|
|
if (template.Cost.PopulationBonus) ret.cost.populationBonus = +template.Cost.PopulationBonus;
|
|
}
|
|
|
|
if (template.Health)
|
|
{
|
|
ret.health = +template.Health.Max;
|
|
}
|
|
|
|
if (template.Identity)
|
|
{
|
|
ret.selectionGroupName = template.Identity.SelectionGroupName;
|
|
ret.name = {
|
|
"specific": (template.Identity.SpecificName || template.Identity.GenericName),
|
|
"generic": template.Identity.GenericName
|
|
};
|
|
ret.icon = template.Identity.Icon;
|
|
ret.tooltip = template.Identity.Tooltip;
|
|
}
|
|
|
|
if (template.UnitMotion)
|
|
{
|
|
ret.speed = {
|
|
"walk": +template.UnitMotion.WalkSpeed,
|
|
};
|
|
if (template.UnitMotion.Run) ret.speed.run = +template.UnitMotion.Run.Speed;
|
|
}
|
|
|
|
return ret;
|
|
};
|
|
|
|
GuiInterface.prototype.PushNotification = function(notification)
|
|
{
|
|
this.notifications.push(notification);
|
|
};
|
|
|
|
GuiInterface.prototype.GetNextNotification = function()
|
|
{
|
|
if (this.notifications.length)
|
|
return this.notifications.pop();
|
|
else
|
|
return "";
|
|
};
|
|
|
|
GuiInterface.prototype.CanMoveEntsIntoFormation = function(player, data)
|
|
{
|
|
return CanMoveEntsIntoFormation(data.ents, data.formationName);
|
|
};
|
|
|
|
GuiInterface.prototype.IsStanceSelected = function(player, data)
|
|
{
|
|
for each (var ent in data.ents)
|
|
{
|
|
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
|
|
if (cmpUnitAI)
|
|
{
|
|
if (cmpUnitAI.GetStanceName() == data.stance)
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
GuiInterface.prototype.SetSelectionHighlight = function(player, cmd)
|
|
{
|
|
var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
|
|
|
|
var playerColours = {}; // cache of owner -> colour map
|
|
|
|
for each (var ent in cmd.entities)
|
|
{
|
|
var cmpSelectable = Engine.QueryInterface(ent, IID_Selectable);
|
|
if (!cmpSelectable)
|
|
continue;
|
|
|
|
if (cmd.alpha == 0)
|
|
{
|
|
cmpSelectable.SetSelectionHighlight({"r":0, "g":0, "b":0, "a":0});
|
|
continue;
|
|
}
|
|
|
|
// Find the entity's owner's colour:
|
|
|
|
var owner = -1;
|
|
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
|
|
if (cmpOwnership)
|
|
owner = cmpOwnership.GetOwner();
|
|
|
|
var colour = playerColours[owner];
|
|
if (!colour)
|
|
{
|
|
colour = {"r":1, "g":1, "b":1};
|
|
var cmpPlayer = Engine.QueryInterface(cmpPlayerMan.GetPlayerByID(owner), IID_Player);
|
|
if (cmpPlayer)
|
|
colour = cmpPlayer.GetColour();
|
|
playerColours[owner] = colour;
|
|
}
|
|
|
|
cmpSelectable.SetSelectionHighlight({"r":colour.r, "g":colour.g, "b":colour.b, "a":cmd.alpha});
|
|
}
|
|
};
|
|
|
|
GuiInterface.prototype.SetStatusBars = function(player, cmd)
|
|
{
|
|
for each (var ent in cmd.entities)
|
|
{
|
|
var cmpStatusBars = Engine.QueryInterface(ent, IID_StatusBars);
|
|
if (cmpStatusBars)
|
|
cmpStatusBars.SetEnabled(cmd.enabled);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Displays the rally point of a given list of entities (carried in cmd.entities).
|
|
*
|
|
* The 'cmd' object may carry its own x/z coordinate pair indicating the location where the rally point should
|
|
* be rendered, in order to support instantaneously rendering a rally point marker at a specified location
|
|
* instead of incurring a delay while PostNetworkCommand processes the set-rallypoint command (see input.js).
|
|
* If cmd doesn't carry a custom location, then the position to render the marker at will be read from the
|
|
* RallyPoint component.
|
|
*/
|
|
GuiInterface.prototype.DisplayRallyPoint = function(player, cmd)
|
|
{
|
|
var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
|
|
var cmpPlayer = Engine.QueryInterface(cmpPlayerMan.GetPlayerByID(player), IID_Player);
|
|
|
|
// If there are some rally points already displayed, first hide them
|
|
for each (var ent in this.entsRallyPointsDisplayed)
|
|
{
|
|
var cmpRallyPointRenderer = Engine.QueryInterface(ent, IID_RallyPointRenderer);
|
|
if (cmpRallyPointRenderer)
|
|
cmpRallyPointRenderer.SetDisplayed(false);
|
|
}
|
|
|
|
this.entsRallyPointsDisplayed = [];
|
|
|
|
// Show the rally points for the passed entities
|
|
for each (var ent in cmd.entities)
|
|
{
|
|
var cmpRallyPointRenderer = Engine.QueryInterface(ent, IID_RallyPointRenderer);
|
|
if (!cmpRallyPointRenderer)
|
|
continue;
|
|
|
|
// entity must have a rally point component to display a rally point marker
|
|
// (regardless of whether cmd specifies a custom location)
|
|
var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
|
|
if (!cmpRallyPoint)
|
|
continue;
|
|
|
|
// Verify the owner
|
|
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
|
|
if (!(cmpPlayer && cmpPlayer.CanControlAllUnits()))
|
|
if (!cmpOwnership || cmpOwnership.GetOwner() != player)
|
|
continue;
|
|
|
|
// If the command was passed an explicit position, use that and
|
|
// override the real rally point position; otherwise use the real position
|
|
var pos;
|
|
if (cmd.x && cmd.z)
|
|
pos = cmd;
|
|
else
|
|
pos = cmpRallyPoint.GetPosition(); // may return undefined if no rally point is set
|
|
|
|
if (pos)
|
|
{
|
|
cmpRallyPointRenderer.SetPosition({'x': pos.x, 'y': pos.z}); // SetPosition takes a CFixedVector2D which has X/Y components, not X/Z
|
|
cmpRallyPointRenderer.SetDisplayed(true);
|
|
|
|
// remember which entities have their rally points displayed so we can hide them again
|
|
this.entsRallyPointsDisplayed.push(ent);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Display the building placement preview.
|
|
* cmd.template is the name of the entity template, or "" to disable the preview.
|
|
* cmd.x, cmd.z, cmd.angle give the location.
|
|
* Returns true if the placement is okay (everything is valid and the entity is not obstructed by others).
|
|
*/
|
|
GuiInterface.prototype.SetBuildingPlacementPreview = function(player, cmd)
|
|
{
|
|
// See if we're changing template
|
|
if (!this.placementEntity || this.placementEntity[0] != cmd.template)
|
|
{
|
|
// Destroy the old preview if there was one
|
|
if (this.placementEntity)
|
|
Engine.DestroyEntity(this.placementEntity[1]);
|
|
|
|
// Load the new template
|
|
if (cmd.template == "")
|
|
{
|
|
this.placementEntity = undefined;
|
|
}
|
|
else
|
|
{
|
|
this.placementEntity = [cmd.template, Engine.AddLocalEntity("preview|" + cmd.template)];
|
|
}
|
|
}
|
|
|
|
if (this.placementEntity)
|
|
{
|
|
var ent = this.placementEntity[1];
|
|
|
|
// Move the preview into the right location
|
|
var pos = Engine.QueryInterface(ent, IID_Position);
|
|
if (pos)
|
|
{
|
|
pos.JumpTo(cmd.x, cmd.z);
|
|
pos.SetYRotation(cmd.angle);
|
|
}
|
|
|
|
// Check whether it's in a visible or fogged region
|
|
// tell GetLosVisibility to force RetainInFog because preview entities set this to false,
|
|
// which would show them as hidden instead of fogged
|
|
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
|
|
var visible = (cmpRangeManager && cmpRangeManager.GetLosVisibility(ent, player, true) != "hidden");
|
|
var validPlacement = false;
|
|
|
|
if (visible)
|
|
{ // Check whether it's obstructed by other entities or invalid terrain
|
|
var cmpBuildRestrictions = Engine.QueryInterface(ent, IID_BuildRestrictions);
|
|
if (!cmpBuildRestrictions)
|
|
error("cmpBuildRestrictions not defined");
|
|
|
|
validPlacement = (cmpBuildRestrictions && cmpBuildRestrictions.CheckPlacement(player));
|
|
}
|
|
|
|
var ok = (visible && validPlacement);
|
|
|
|
// Set it to a red shade if this is an invalid location
|
|
var cmpVisual = Engine.QueryInterface(ent, IID_Visual);
|
|
if (cmpVisual)
|
|
{
|
|
if (!ok)
|
|
cmpVisual.SetShadingColour(1.4, 0.4, 0.4, 1);
|
|
else
|
|
cmpVisual.SetShadingColour(1, 1, 1, 1);
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
GuiInterface.prototype.GetFoundationSnapData = function(player, data)
|
|
{
|
|
var cmpTemplateMgr = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
|
|
var template = cmpTemplateMgr.GetTemplate(data.template);
|
|
|
|
if (template.BuildRestrictions.Category == "Dock")
|
|
{
|
|
var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
|
|
var cmpWaterManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_WaterManager);
|
|
if (!cmpTerrain || !cmpWaterManager)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Get footprint size
|
|
var halfSize = 0;
|
|
if (template.Footprint.Square)
|
|
{
|
|
halfSize = Math.max(template.Footprint.Square["@depth"], template.Footprint.Square["@width"])/2;
|
|
}
|
|
else if (template.Footprint.Circle)
|
|
{
|
|
halfSize = template.Footprint.Circle["@radius"];
|
|
}
|
|
|
|
/* Find direction of most open water, algorithm:
|
|
* 1. Pick points in a circle around dock
|
|
* 2. If point is in water, add to array
|
|
* 3. Scan array looking for consecutive points
|
|
* 4. Find longest sequence of consecutive points
|
|
* 5. If sequence equals all points, no direction can be determined,
|
|
* expand search outward and try (1) again
|
|
* 6. Calculate angle using average of sequence
|
|
*/
|
|
const numPoints = 16;
|
|
for (var dist = 0; dist < 4; ++dist)
|
|
{
|
|
var waterPoints = [];
|
|
for (var i = 0; i < numPoints; ++i)
|
|
{
|
|
var angle = (i/numPoints)*2*Math.PI;
|
|
var d = halfSize*(dist+1);
|
|
var nx = data.x - d*Math.sin(angle);
|
|
var nz = data.z + d*Math.cos(angle);
|
|
|
|
if (cmpTerrain.GetGroundLevel(nx, nz) < cmpWaterManager.GetWaterLevel(nx, nz))
|
|
{
|
|
waterPoints.push(i);
|
|
}
|
|
}
|
|
var consec = [];
|
|
var length = waterPoints.length;
|
|
for (var i = 0; i < length; ++i)
|
|
{
|
|
var count = 0;
|
|
for (var j = 0; j < (length-1); ++j)
|
|
{
|
|
if (((waterPoints[(i + j) % length]+1) % numPoints) == waterPoints[(i + j + 1) % length])
|
|
{
|
|
++count;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
consec[i] = count;
|
|
}
|
|
var start = 0;
|
|
var count = 0;
|
|
for (var c in consec)
|
|
{
|
|
if (consec[c] > count)
|
|
{
|
|
start = c;
|
|
count = consec[c];
|
|
}
|
|
}
|
|
|
|
// If we've found a shoreline, stop searching
|
|
if (count != numPoints-1)
|
|
{
|
|
return {"x": data.x, "z": data.z, "angle": -(((waterPoints[start] + consec[start]/2) % numPoints)/numPoints*2*Math.PI)};
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
GuiInterface.prototype.PlaySound = function(player, data)
|
|
{
|
|
// Ignore if no entity was passed
|
|
if (!data.entity)
|
|
return;
|
|
|
|
PlaySound(data.name, data.entity);
|
|
};
|
|
|
|
function isIdleUnit(ent, idleClass)
|
|
{
|
|
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
|
|
var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
|
|
|
|
// TODO: Do something with garrisoned idle units
|
|
return (cmpUnitAI && cmpIdentity && cmpUnitAI.IsIdle() && !cmpUnitAI.IsGarrisoned() && idleClass && cmpIdentity.HasClass(idleClass));
|
|
}
|
|
|
|
GuiInterface.prototype.FindIdleUnit = function(player, data)
|
|
{
|
|
var rangeMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
|
|
var playerEntities = rangeMan.GetEntitiesByPlayer(player);
|
|
|
|
// Find the first matching entity that is after the previous selection,
|
|
// so that we cycle around in a predictable order
|
|
for each (var ent in playerEntities)
|
|
{
|
|
if (ent > data.prevUnit && isIdleUnit(ent, data.idleClass))
|
|
return ent;
|
|
}
|
|
|
|
// No idle entities left in the class
|
|
return 0;
|
|
};
|
|
|
|
GuiInterface.prototype.SetPathfinderDebugOverlay = function(player, enabled)
|
|
{
|
|
var cmpPathfinder = Engine.QueryInterface(SYSTEM_ENTITY, IID_Pathfinder);
|
|
cmpPathfinder.SetDebugOverlay(enabled);
|
|
};
|
|
|
|
GuiInterface.prototype.SetObstructionDebugOverlay = function(player, enabled)
|
|
{
|
|
var cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager);
|
|
cmpObstructionManager.SetDebugOverlay(enabled);
|
|
};
|
|
|
|
GuiInterface.prototype.SetMotionDebugOverlay = function(player, data)
|
|
{
|
|
for each (var ent in data.entities)
|
|
{
|
|
var cmpUnitMotion = Engine.QueryInterface(ent, IID_UnitMotion);
|
|
if (cmpUnitMotion)
|
|
cmpUnitMotion.SetDebugOverlay(data.enabled);
|
|
}
|
|
};
|
|
|
|
GuiInterface.prototype.SetRangeDebugOverlay = function(player, enabled)
|
|
{
|
|
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
|
|
cmpRangeManager.SetDebugOverlay(enabled);
|
|
};
|
|
|
|
GuiInterface.prototype.OnGlobalEntityRenamed = function(msg)
|
|
{
|
|
this.renamedEntities.push(msg);
|
|
}
|
|
|
|
// List the GuiInterface functions that can be safely called by GUI scripts.
|
|
// (GUI scripts are non-deterministic and untrusted, so these functions must be
|
|
// appropriately careful. They are called with a first argument "player", which is
|
|
// trusted and indicates the player associated with the current client; no data should
|
|
// be returned unless this player is meant to be able to see it.)
|
|
var exposedFunctions = {
|
|
|
|
"GetSimulationState": 1,
|
|
"GetExtendedSimulationState": 1,
|
|
"GetRenamedEntities": 1,
|
|
"ClearRenamedEntities": 1,
|
|
"GetEntityState": 1,
|
|
"GetTemplateData": 1,
|
|
"GetNextNotification": 1,
|
|
|
|
"CanMoveEntsIntoFormation": 1,
|
|
"IsStanceSelected": 1,
|
|
|
|
"SetSelectionHighlight": 1,
|
|
"SetStatusBars": 1,
|
|
"DisplayRallyPoint": 1,
|
|
"SetBuildingPlacementPreview": 1,
|
|
"GetFoundationSnapData": 1,
|
|
"PlaySound": 1,
|
|
"FindIdleUnit": 1,
|
|
|
|
"SetPathfinderDebugOverlay": 1,
|
|
"SetObstructionDebugOverlay": 1,
|
|
"SetMotionDebugOverlay": 1,
|
|
"SetRangeDebugOverlay": 1,
|
|
};
|
|
|
|
GuiInterface.prototype.ScriptCall = function(player, name, args)
|
|
{
|
|
if (exposedFunctions[name])
|
|
return this[name](player, args);
|
|
else
|
|
throw new Error("Invalid GuiInterface Call name \""+name+"\"");
|
|
};
|
|
|
|
Engine.RegisterComponentType(IID_GuiInterface, "GuiInterface", GuiInterface);
|