mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-07-04 05:55:47 -07:00
Adds snapping to edges for buildings
Allows to place buildings a bit faster and more perfectly aligned. Also it helps to find a nearest placeable position in some cases. Reviewed By: elexis Comments By: Stan, wraitii Differential Revision: https://code.wildfiregames.com/D2079 This was SVN commit r23330.
This commit is contained in:
parent
cf7635f57b
commit
a8f241da5d
13 changed files with 308 additions and 16 deletions
|
|
@ -313,6 +313,7 @@ unloadtype = Shift ; Modifier to unload all units of type
|
||||||
deselectgroup = Ctrl ; Modifier to deselect units when clicking group icon, instead of selecting
|
deselectgroup = Ctrl ; Modifier to deselect units when clicking group icon, instead of selecting
|
||||||
rotate.cw = RightBracket ; Rotate building placement preview clockwise
|
rotate.cw = RightBracket ; Rotate building placement preview clockwise
|
||||||
rotate.ccw = LeftBracket ; Rotate building placement preview anticlockwise
|
rotate.ccw = LeftBracket ; Rotate building placement preview anticlockwise
|
||||||
|
snaptoedges = Ctrl ; Modifier to align new structures with nearby existing structure
|
||||||
|
|
||||||
[hotkey.session.gui]
|
[hotkey.session.gui]
|
||||||
toggle = "Alt+G" ; Toggle visibility of session GUI
|
toggle = "Alt+G" ; Toggle visibility of session GUI
|
||||||
|
|
@ -377,6 +378,7 @@ healrange = true ; Display heal range overlays of selected unit
|
||||||
rankabovestatusbar = true ; Show rank icons above status bars
|
rankabovestatusbar = true ; Show rank icons above status bars
|
||||||
experiencestatusbar = true ; Show an experience status bar above each selected unit
|
experiencestatusbar = true ; Show an experience status bar above each selected unit
|
||||||
respoptooltipsort = 0 ; Sorting players in the resources and population tooltip by value (0 - no sort, -1 - ascending, 1 - descending)
|
respoptooltipsort = 0 ; Sorting players in the resources and population tooltip by value (0 - no sort, -1 - ascending, 1 - descending)
|
||||||
|
snaptoedgesdistancethreshold = 15 ; On which distance we don't snap to edges
|
||||||
|
|
||||||
[gui.session.minimap]
|
[gui.session.minimap]
|
||||||
blinkduration = 1.7 ; The blink duration while pinging
|
blinkduration = 1.7 ; The blink duration while pinging
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,13 @@ Vector2D.prototype.set = function(x, y)
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Vector2D.prototype.setFrom = function(v)
|
||||||
|
{
|
||||||
|
this.x = v.x;
|
||||||
|
this.y = v.y;
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
Vector2D.prototype.add = function(v)
|
Vector2D.prototype.add = function(v)
|
||||||
{
|
{
|
||||||
this.x += v.x;
|
this.x += v.x;
|
||||||
|
|
@ -240,6 +247,11 @@ Vector2D.sum = function(vectorList)
|
||||||
return sum;
|
return sum;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Vector2D.dot = function(v1, v2)
|
||||||
|
{
|
||||||
|
return v1.x * v2.x + v1.y * v2.y;
|
||||||
|
};
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////
|
||||||
// Vector3D
|
// Vector3D
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -115,6 +115,7 @@ When you are ready to start, click the "Start game" button.
|
||||||
• If the cursor is over an enemy unit or building – Attack (instead of capture or gather)
|
• If the cursor is over an enemy unit or building – Attack (instead of capture or gather)
|
||||||
• Otherwise – Attack move (by default all enemy units and structures along the way are targeted)
|
• Otherwise – Attack move (by default all enemy units and structures along the way are targeted)
|
||||||
Ctrl + Q + Right Click with unit(s) selected – Attack move, only units along the way are targeted
|
Ctrl + Q + Right Click with unit(s) selected – Attack move, only units along the way are targeted
|
||||||
|
Ctrl + Mouse Move near structures – Align the new structure with an existing nearby structure
|
||||||
|
|
||||||
[font="sans-bold-14"]Overlays[font="sans-14"]
|
[font="sans-bold-14"]Overlays[font="sans-14"]
|
||||||
Alt + G – Hide/show the GUI
|
Alt + G – Hide/show the GUI
|
||||||
|
|
|
||||||
|
|
@ -722,11 +722,15 @@ function handleInputBeforeGui(ev, hoveredObject)
|
||||||
placementSupport.SetDefaultAngle();
|
placementSupport.SetDefaultAngle();
|
||||||
}
|
}
|
||||||
|
|
||||||
var snapData = Engine.GuiInterfaceCall("GetFoundationSnapData", {
|
let snapToEdges = Engine.HotkeyIsPressed("session.snaptoedges");
|
||||||
"template": placementSupport.template,
|
let snapData = Engine.GuiInterfaceCall("GetFoundationSnapData", {
|
||||||
"x": placementSupport.position.x,
|
"template": placementSupport.template,
|
||||||
"z": placementSupport.position.z
|
"x": placementSupport.position.x,
|
||||||
});
|
"z": placementSupport.position.z,
|
||||||
|
"angle": placementSupport.angle,
|
||||||
|
"snapToEdges": snapToEdges && Engine.GetEdgesOfStaticObstructionsOnScreenNearTo(
|
||||||
|
placementSupport.position.x, placementSupport.position.z)
|
||||||
|
});
|
||||||
if (snapData)
|
if (snapData)
|
||||||
{
|
{
|
||||||
placementSupport.angle = snapData.angle;
|
placementSupport.angle = snapData.angle;
|
||||||
|
|
@ -1045,11 +1049,14 @@ function handleInputAfterGui(ev)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
var snapData = Engine.GuiInterfaceCall("GetFoundationSnapData", {
|
let snapToEdges = Engine.HotkeyIsPressed("session.snaptoedges");
|
||||||
"template": placementSupport.template,
|
let snapData = Engine.GuiInterfaceCall("GetFoundationSnapData", {
|
||||||
"x": placementSupport.position.x,
|
"template": placementSupport.template,
|
||||||
"z": placementSupport.position.z,
|
"x": placementSupport.position.x,
|
||||||
});
|
"z": placementSupport.position.z,
|
||||||
|
"snapToEdges": snapToEdges && Engine.GetEdgesOfStaticObstructionsOnScreenNearTo(
|
||||||
|
placementSupport.position.x, placementSupport.position.z)
|
||||||
|
});
|
||||||
if (snapData)
|
if (snapData)
|
||||||
{
|
{
|
||||||
placementSupport.angle = snapData.angle;
|
placementSupport.angle = snapData.angle;
|
||||||
|
|
@ -1073,6 +1080,25 @@ function handleInputAfterGui(ev)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
placementSupport.position = Engine.GetTerrainAtScreenPoint(ev.x, ev.y);
|
placementSupport.position = Engine.GetTerrainAtScreenPoint(ev.x, ev.y);
|
||||||
|
|
||||||
|
let snapToEdges = Engine.HotkeyIsPressed("session.snaptoedges");
|
||||||
|
if (snapToEdges)
|
||||||
|
{
|
||||||
|
let snapData = Engine.GuiInterfaceCall("GetFoundationSnapData", {
|
||||||
|
"template": placementSupport.template,
|
||||||
|
"x": placementSupport.position.x,
|
||||||
|
"z": placementSupport.position.z,
|
||||||
|
"snapToEdges": Engine.GetEdgesOfStaticObstructionsOnScreenNearTo(
|
||||||
|
placementSupport.position.x, placementSupport.position.z)
|
||||||
|
});
|
||||||
|
if (snapData)
|
||||||
|
{
|
||||||
|
placementSupport.angle = snapData.angle;
|
||||||
|
placementSupport.position.x = snapData.x;
|
||||||
|
placementSupport.position.z = snapData.z;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
g_DragStart = new Vector2D(ev.x, ev.y);
|
g_DragStart = new Vector2D(ev.x, ev.y);
|
||||||
inputState = INPUT_BUILDING_CLICK;
|
inputState = INPUT_BUILDING_CLICK;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ GuiInterface.prototype.Init = function()
|
||||||
this.entsWithAuraAndStatusBars = new Set();
|
this.entsWithAuraAndStatusBars = new Set();
|
||||||
this.enabledVisualRangeOverlayTypes = {};
|
this.enabledVisualRangeOverlayTypes = {};
|
||||||
this.templateModified = {};
|
this.templateModified = {};
|
||||||
|
this.obstructionSnap = new ObstructionSnap();
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -1668,6 +1669,13 @@ GuiInterface.prototype.GetFoundationSnapData = function(player, data)
|
||||||
return minDistEntitySnapData;
|
return minDistEntitySnapData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (data.snapToEdges)
|
||||||
|
{
|
||||||
|
let position = this.obstructionSnap.getPosition(data, template);
|
||||||
|
if (position)
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
if (template.BuildRestrictions.PlacementType == "shore")
|
if (template.BuildRestrictions.PlacementType == "shore")
|
||||||
{
|
{
|
||||||
let angle = GetDockAngle(template, data.x, data.z);
|
let angle = GetDockAngle(template, data.x, data.z);
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
Engine.LoadHelperScript("ObstructionSnap.js");
|
||||||
Engine.LoadHelperScript("Player.js");
|
Engine.LoadHelperScript("Player.js");
|
||||||
Engine.LoadComponentScript("interfaces/Attack.js");
|
Engine.LoadComponentScript("interfaces/Attack.js");
|
||||||
Engine.LoadComponentScript("interfaces/AlertRaiser.js");
|
Engine.LoadComponentScript("interfaces/AlertRaiser.js");
|
||||||
|
|
|
||||||
154
binaries/data/mods/public/simulation/helpers/ObstructionSnap.js
Normal file
154
binaries/data/mods/public/simulation/helpers/ObstructionSnap.js
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
/**
|
||||||
|
* The class allows the player to position structures so that they are aligned
|
||||||
|
* with nearby structures.
|
||||||
|
*/
|
||||||
|
class ObstructionSnap
|
||||||
|
{
|
||||||
|
getValidEdges(allEdges, position, maxSide)
|
||||||
|
{
|
||||||
|
let edges = [];
|
||||||
|
let dir1 = new Vector2D();
|
||||||
|
let dir2 = new Vector2D();
|
||||||
|
for (let edge of allEdges)
|
||||||
|
{
|
||||||
|
let signedDistance = Vector2D.dot(edge.normal, position) -
|
||||||
|
Vector2D.dot(edge.normal, edge.begin);
|
||||||
|
// Negative signed distance means that the template position
|
||||||
|
// lays behind the edge.
|
||||||
|
if (signedDistance < -this.MinimalDistanceToSnap - maxSide ||
|
||||||
|
signedDistance > this.MinimalDistanceToSnap + maxSide)
|
||||||
|
continue;
|
||||||
|
dir1.setFrom(edge.begin).sub(edge.end).normalize();
|
||||||
|
dir2.setFrom(dir1).mult(-1);
|
||||||
|
let offsetDistance = Math.max(
|
||||||
|
Vector2D.dot(dir1, position) - Vector2D.dot(dir1, edge.begin),
|
||||||
|
Vector2D.dot(dir2, position) - Vector2D.dot(dir2, edge.end));
|
||||||
|
if (offsetDistance > this.MinimalDistanceToSnap + maxSide)
|
||||||
|
continue;
|
||||||
|
// If a projection of the template position on the edge is
|
||||||
|
// lying inside the edge then obviously we don't need to
|
||||||
|
// account the offset distance.
|
||||||
|
if (offsetDistance < 0)
|
||||||
|
offsetDistance = 0;
|
||||||
|
edge.signedDistance = signedDistance;
|
||||||
|
edge.offsetDistance = offsetDistance;
|
||||||
|
edges.push(edge);
|
||||||
|
}
|
||||||
|
return edges;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need a small padding to avoid unnecessary collisions
|
||||||
|
// because of loss of accuracy.
|
||||||
|
getPadding(edge)
|
||||||
|
{
|
||||||
|
const snapPadding = 0.05;
|
||||||
|
// We don't need to padding for edges with normals directed inside
|
||||||
|
// its entity, as we try to snap from an internal side of the edge.
|
||||||
|
return edge.order == "ccw" ? 0 : snapPadding;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pick a base edge, it will be the first axis and fix the angle.
|
||||||
|
// We can't just pick an edge by signed distance, because we might have
|
||||||
|
// a case when one segment is closer by signed distance than another
|
||||||
|
// one but much farther by actual (euclid) distance.
|
||||||
|
compareEdges(a, b)
|
||||||
|
{
|
||||||
|
const behindA = a.signedDistance < -this.EPS;
|
||||||
|
const behindB = b.signedDistance < -this.EPS;
|
||||||
|
const scoreA = Math.abs(a.signedDistance) + a.offsetDistance;
|
||||||
|
const scoreB = Math.abs(b.signedDistance) + b.offsetDistance;
|
||||||
|
if (Math.abs(scoreA - scoreB) < this.EPS)
|
||||||
|
{
|
||||||
|
if (behindA != behindB)
|
||||||
|
return behindA - behindB;
|
||||||
|
if (!behindA)
|
||||||
|
return a.offsetDistance - b.offsetDistance;
|
||||||
|
return -a.signedDistance - -b.signedDistance;
|
||||||
|
}
|
||||||
|
return scoreA - scoreB;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPosition(data, template)
|
||||||
|
{
|
||||||
|
if (!data.snapToEdges || !template.Obstruction || !template.Obstruction.Static)
|
||||||
|
return undefined;
|
||||||
|
|
||||||
|
let width = template.Obstruction.Static["@depth"] / 2;
|
||||||
|
let depth = template.Obstruction.Static["@width"] / 2;
|
||||||
|
const maxSide = Math.max(width, depth);
|
||||||
|
let templatePos = Vector2D.from3D(data);
|
||||||
|
let templateAngle = data.angle || 0;
|
||||||
|
|
||||||
|
let edges = this.getValidEdges(data.snapToEdges, templatePos, maxSide);
|
||||||
|
if (!edges.length)
|
||||||
|
return undefined;
|
||||||
|
|
||||||
|
let baseEdge = edges[0];
|
||||||
|
for (let edge of edges)
|
||||||
|
if (this.compareEdges(edge, baseEdge) < 0)
|
||||||
|
baseEdge = edge;
|
||||||
|
// Now we have the normal, we need to determine an angle,
|
||||||
|
// which side will be snapped first.
|
||||||
|
for (let dir = 0; dir < 4; ++dir)
|
||||||
|
{
|
||||||
|
const angleCandidate = baseEdge.angle + dir * Math.PI / 2;
|
||||||
|
// We need to find a minimal angle difference.
|
||||||
|
let difference = Math.abs(angleCandidate - templateAngle);
|
||||||
|
difference = Math.min(difference, Math.PI * 2 - difference);
|
||||||
|
if (difference < Math.PI / 4 + this.EPS)
|
||||||
|
{
|
||||||
|
// We need to swap sides for orthogonal cases.
|
||||||
|
if (dir % 2 == 0)
|
||||||
|
[width, depth] = [depth, width];
|
||||||
|
templateAngle = angleCandidate;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let distance = Vector2D.dot(baseEdge.normal, templatePos) - Vector2D.dot(baseEdge.normal, baseEdge.begin);
|
||||||
|
templatePos.sub(Vector2D.mult(baseEdge.normal, distance - width - this.getPadding(baseEdge)));
|
||||||
|
edges = this.getValidEdges(data.snapToEdges, templatePos, maxSide);
|
||||||
|
if (edges.length > 1)
|
||||||
|
{
|
||||||
|
let pairedEdges = [];
|
||||||
|
for (let edge of edges)
|
||||||
|
{
|
||||||
|
// We have to place a rectangle, so the angle between
|
||||||
|
// edges should be 90 degrees.
|
||||||
|
if (Math.abs(Vector2D.dot(baseEdge.normal, edge.normal)) > this.EPS)
|
||||||
|
continue;
|
||||||
|
let newEdge = {
|
||||||
|
"begin": edge.end,
|
||||||
|
"end": edge.begin,
|
||||||
|
"normal": Vector2D.mult(edge.normal, -1),
|
||||||
|
"signedDistance": -edge.signedDistance,
|
||||||
|
"offsetDistance": edge.offsetDistance,
|
||||||
|
"order": "ccw",
|
||||||
|
};
|
||||||
|
pairedEdges.push(edge);
|
||||||
|
pairedEdges.push(newEdge);
|
||||||
|
}
|
||||||
|
pairedEdges.sort(this.compareEdges.bind(this));
|
||||||
|
if (pairedEdges.length)
|
||||||
|
{
|
||||||
|
let secondEdge = pairedEdges[0];
|
||||||
|
for (let edge of pairedEdges)
|
||||||
|
if (this.compareEdges(edge, secondEdge) < 0)
|
||||||
|
secondEdge = edge;
|
||||||
|
let distance = Vector2D.dot(secondEdge.normal, templatePos) - Vector2D.dot(secondEdge.normal, secondEdge.begin);
|
||||||
|
templatePos.sub(Vector2D.mult(secondEdge.normal, distance - depth - this.getPadding(secondEdge)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
"x": templatePos.x,
|
||||||
|
"z": templatePos.y,
|
||||||
|
"angle": templateAngle
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ObstructionSnap.prototype.MinimalDistanceToSnap = 5;
|
||||||
|
|
||||||
|
ObstructionSnap.prototype.EPS = 1e-3;
|
||||||
|
|
||||||
|
Engine.RegisterGlobal("ObstructionSnap", ObstructionSnap);
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
/* Copyright (C) 2019 Wildfire Games.
|
/* Copyright (C) 2020 Wildfire Games.
|
||||||
* This file is part of 0 A.D.
|
* This file is part of 0 A.D.
|
||||||
*
|
*
|
||||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||||
|
|
@ -298,6 +298,7 @@ template<> void ScriptInterface::ToJSVal<char[N]>(JSContext* cx, JS::MutableHand
|
||||||
ToJSVal(cx, ret, static_cast<const char*>(val)); \
|
ToJSVal(cx, ret, static_cast<const char*>(val)); \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TOJSVAL_CHAR(3)
|
||||||
TOJSVAL_CHAR(5)
|
TOJSVAL_CHAR(5)
|
||||||
TOJSVAL_CHAR(6)
|
TOJSVAL_CHAR(6)
|
||||||
TOJSVAL_CHAR(7)
|
TOJSVAL_CHAR(7)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
/* Copyright (C) 2019 Wildfire Games.
|
/* Copyright (C) 2020 Wildfire Games.
|
||||||
* This file is part of 0 A.D.
|
* This file is part of 0 A.D.
|
||||||
*
|
*
|
||||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||||
|
|
@ -519,6 +519,11 @@ public:
|
||||||
return CFixedVector2D(m_Size0 / 2, m_Size1 / 2).Length();
|
return CFixedVector2D(m_Size0 / 2, m_Size1 / 2).Length();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual CFixedVector2D GetStaticSize() const
|
||||||
|
{
|
||||||
|
return m_Type == STATIC ? CFixedVector2D(m_Size0, m_Size1) : CFixedVector2D();
|
||||||
|
}
|
||||||
|
|
||||||
virtual void SetUnitClearance(const entity_pos_t& clearance)
|
virtual void SetUnitClearance(const entity_pos_t& clearance)
|
||||||
{
|
{
|
||||||
if (m_Type == UNIT)
|
if (m_Type == UNIT)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
/* Copyright (C) 2019 Wildfire Games.
|
/* Copyright (C) 2020 Wildfire Games.
|
||||||
* This file is part of 0 A.D.
|
* This file is part of 0 A.D.
|
||||||
*
|
*
|
||||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||||
|
|
@ -60,6 +60,8 @@ public:
|
||||||
|
|
||||||
virtual entity_pos_t GetSize() const = 0;
|
virtual entity_pos_t GetSize() const = 0;
|
||||||
|
|
||||||
|
virtual CFixedVector2D GetStaticSize() const = 0;
|
||||||
|
|
||||||
virtual entity_pos_t GetUnitRadius() const = 0;
|
virtual entity_pos_t GetUnitRadius() const = 0;
|
||||||
|
|
||||||
virtual EObstructionType GetObstructionType() const = 0;
|
virtual EObstructionType GetObstructionType() const = 0;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
/* Copyright (C) 2019 Wildfire Games.
|
/* Copyright (C) 2020 Wildfire Games.
|
||||||
* This file is part of 0 A.D.
|
* This file is part of 0 A.D.
|
||||||
*
|
*
|
||||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||||
|
|
@ -30,6 +30,7 @@ public:
|
||||||
virtual bool GetObstructionSquare(ICmpObstructionManager::ObstructionSquare& out) const { out = obstruction; return true; }
|
virtual bool GetObstructionSquare(ICmpObstructionManager::ObstructionSquare& out) const { out = obstruction; return true; }
|
||||||
virtual bool GetPreviousObstructionSquare(ICmpObstructionManager::ObstructionSquare& UNUSED(out)) const { return true; }
|
virtual bool GetPreviousObstructionSquare(ICmpObstructionManager::ObstructionSquare& UNUSED(out)) const { return true; }
|
||||||
virtual entity_pos_t GetSize() const { return entity_pos_t::Zero(); }
|
virtual entity_pos_t GetSize() const { return entity_pos_t::Zero(); }
|
||||||
|
virtual CFixedVector2D GetStaticSize() const { return CFixedVector2D(); }
|
||||||
virtual entity_pos_t GetUnitRadius() const { return entity_pos_t::Zero(); }
|
virtual entity_pos_t GetUnitRadius() const { return entity_pos_t::Zero(); }
|
||||||
virtual EObstructionType GetObstructionType() const { return ICmpObstruction::STATIC; }
|
virtual EObstructionType GetObstructionType() const { return ICmpObstruction::STATIC; }
|
||||||
virtual void SetUnitClearance(const entity_pos_t& UNUSED(clearance)) { }
|
virtual void SetUnitClearance(const entity_pos_t& UNUSED(clearance)) { }
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
/* Copyright (C) 2019 Wildfire Games.
|
/* Copyright (C) 2020 Wildfire Games.
|
||||||
* This file is part of 0 A.D.
|
* This file is part of 0 A.D.
|
||||||
*
|
*
|
||||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||||
|
|
@ -20,6 +20,7 @@
|
||||||
#include "JSInterface_Simulation.h"
|
#include "JSInterface_Simulation.h"
|
||||||
|
|
||||||
#include "graphics/GameView.h"
|
#include "graphics/GameView.h"
|
||||||
|
#include "ps/ConfigDB.h"
|
||||||
#include "ps/Game.h"
|
#include "ps/Game.h"
|
||||||
#include "ps/GameSetup/Config.h"
|
#include "ps/GameSetup/Config.h"
|
||||||
#include "ps/Pyrogenesis.h"
|
#include "ps/Pyrogenesis.h"
|
||||||
|
|
@ -29,9 +30,11 @@
|
||||||
#include "simulation2/components/ICmpAIManager.h"
|
#include "simulation2/components/ICmpAIManager.h"
|
||||||
#include "simulation2/components/ICmpCommandQueue.h"
|
#include "simulation2/components/ICmpCommandQueue.h"
|
||||||
#include "simulation2/components/ICmpGuiInterface.h"
|
#include "simulation2/components/ICmpGuiInterface.h"
|
||||||
|
#include "simulation2/components/ICmpPosition.h"
|
||||||
#include "simulation2/components/ICmpSelectable.h"
|
#include "simulation2/components/ICmpSelectable.h"
|
||||||
#include "simulation2/helpers/Selection.h"
|
#include "simulation2/helpers/Selection.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
JS::Value JSI_Simulation::GuiInterfaceCall(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& name, JS::HandleValue data)
|
JS::Value JSI_Simulation::GuiInterfaceCall(ScriptInterface::CxPrivate* pCxPrivate, const std::wstring& name, JS::HandleValue data)
|
||||||
|
|
@ -114,6 +117,79 @@ std::vector<entity_id_t> JSI_Simulation::GetEntitiesWithStaticObstructionOnScree
|
||||||
return EntitySelection::GetEntitiesWithComponentInRect<StaticObstructionFilter>(*g_Game->GetSimulation2(), IID_Obstruction, *g_Game->GetView()->GetCamera(), 0, 0, g_xres, g_yres);
|
return EntitySelection::GetEntitiesWithComponentInRect<StaticObstructionFilter>(*g_Game->GetSimulation2(), IID_Obstruction, *g_Game->GetView()->GetCamera(), 0, 0, g_xres, g_yres);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JS::Value JSI_Simulation::GetEdgesOfStaticObstructionsOnScreenNearTo(ScriptInterface::CxPrivate* pCxPrivate, entity_pos_t x, entity_pos_t z)
|
||||||
|
{
|
||||||
|
if (!g_Game)
|
||||||
|
return JS::UndefinedValue();
|
||||||
|
|
||||||
|
CSimulation2* sim = g_Game->GetSimulation2();
|
||||||
|
ENSURE(sim);
|
||||||
|
|
||||||
|
JSContext* cx = pCxPrivate->pScriptInterface->GetContext();
|
||||||
|
JSAutoRequest rq(cx);
|
||||||
|
JS::RootedValue edgeList(cx);
|
||||||
|
ScriptInterface::CreateArray(cx, &edgeList);
|
||||||
|
int edgeListIndex = 0;
|
||||||
|
|
||||||
|
float distanceThreshold = 10.0f;
|
||||||
|
CFG_GET_VAL("gui.session.snaptoedgesdistancethreshold", distanceThreshold);
|
||||||
|
CFixedVector2D entityPos(x, z);
|
||||||
|
|
||||||
|
std::vector<entity_id_t> entities = GetEntitiesWithStaticObstructionOnScreen(pCxPrivate);
|
||||||
|
for (entity_id_t entity : entities)
|
||||||
|
{
|
||||||
|
CmpPtr<ICmpObstruction> cmpObstruction(sim->GetSimContext(), entity);
|
||||||
|
if (!cmpObstruction)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
CmpPtr<ICmpPosition> cmpPosition(sim->GetSimContext(), entity);
|
||||||
|
if (!cmpPosition || !cmpPosition->IsInWorld())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
CFixedVector2D halfSize = cmpObstruction->GetStaticSize() / 2;
|
||||||
|
if (halfSize.X.IsZero() || halfSize.Y.IsZero() || std::max(halfSize.X, halfSize.Y) <= fixed::FromInt(2))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::array<CFixedVector2D, 4> corners = {
|
||||||
|
CFixedVector2D(-halfSize.X, -halfSize.Y),
|
||||||
|
CFixedVector2D(-halfSize.X, halfSize.Y),
|
||||||
|
halfSize,
|
||||||
|
CFixedVector2D(halfSize.X, -halfSize.Y)
|
||||||
|
};
|
||||||
|
fixed angle = cmpPosition->GetRotation().Y;
|
||||||
|
for (CFixedVector2D& corner : corners)
|
||||||
|
corner = corner.Rotate(angle) + cmpPosition->GetPosition2D();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < corners.size(); ++i)
|
||||||
|
{
|
||||||
|
JS::RootedValue edge(cx);
|
||||||
|
const CFixedVector2D& corner = corners[i];
|
||||||
|
const CFixedVector2D& nextCorner = corners[(i + 1) % corners.size()];
|
||||||
|
|
||||||
|
// TODO: calculate real distance;
|
||||||
|
fixed distanceToEdge = std::min(
|
||||||
|
(corner - entityPos).Length(),
|
||||||
|
(nextCorner - entityPos).Length());
|
||||||
|
if (distanceToEdge.ToFloat() > distanceThreshold)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
CFixedVector2D normal = -(nextCorner - corner).Perpendicular();
|
||||||
|
normal.Normalize();
|
||||||
|
ScriptInterface::CreateObject(
|
||||||
|
cx,
|
||||||
|
&edge,
|
||||||
|
"begin", corner,
|
||||||
|
"end", nextCorner,
|
||||||
|
"angle", angle,
|
||||||
|
"normal", normal,
|
||||||
|
"order", "cw");
|
||||||
|
|
||||||
|
pCxPrivate->pScriptInterface->SetPropertyInt(edgeList, edgeListIndex++, edge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return edgeList;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<entity_id_t> JSI_Simulation::PickSimilarPlayerEntities(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::string& templateName, bool includeOffScreen, bool matchRank, bool allowFoundations)
|
std::vector<entity_id_t> JSI_Simulation::PickSimilarPlayerEntities(ScriptInterface::CxPrivate* UNUSED(pCxPrivate), const std::string& templateName, bool includeOffScreen, bool matchRank, bool allowFoundations)
|
||||||
{
|
{
|
||||||
return EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, g_Game->GetViewedPlayerID(), includeOffScreen, matchRank, false, allowFoundations);
|
return EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, g_Game->GetViewedPlayerID(), includeOffScreen, matchRank, false, allowFoundations);
|
||||||
|
|
@ -140,6 +216,7 @@ void JSI_Simulation::RegisterScriptFunctions(const ScriptInterface& scriptInterf
|
||||||
scriptInterface.RegisterFunction<std::vector<entity_id_t>, int, &PickPlayerEntitiesOnScreen>("PickPlayerEntitiesOnScreen");
|
scriptInterface.RegisterFunction<std::vector<entity_id_t>, int, &PickPlayerEntitiesOnScreen>("PickPlayerEntitiesOnScreen");
|
||||||
scriptInterface.RegisterFunction<std::vector<entity_id_t>, &PickNonGaiaEntitiesOnScreen>("PickNonGaiaEntitiesOnScreen");
|
scriptInterface.RegisterFunction<std::vector<entity_id_t>, &PickNonGaiaEntitiesOnScreen>("PickNonGaiaEntitiesOnScreen");
|
||||||
scriptInterface.RegisterFunction<std::vector<entity_id_t>, &GetEntitiesWithStaticObstructionOnScreen>("GetEntitiesWithStaticObstructionOnScreen");
|
scriptInterface.RegisterFunction<std::vector<entity_id_t>, &GetEntitiesWithStaticObstructionOnScreen>("GetEntitiesWithStaticObstructionOnScreen");
|
||||||
|
scriptInterface.RegisterFunction<JS::Value, entity_pos_t, entity_pos_t, &GetEdgesOfStaticObstructionsOnScreenNearTo>("GetEdgesOfStaticObstructionsOnScreenNearTo");
|
||||||
scriptInterface.RegisterFunction<std::vector<entity_id_t>, std::string, bool, bool, bool, &PickSimilarPlayerEntities>("PickSimilarPlayerEntities");
|
scriptInterface.RegisterFunction<std::vector<entity_id_t>, std::string, bool, bool, bool, &PickSimilarPlayerEntities>("PickSimilarPlayerEntities");
|
||||||
scriptInterface.RegisterFunction<void, bool, &SetBoundingBoxDebugOverlay>("SetBoundingBoxDebugOverlay");
|
scriptInterface.RegisterFunction<void, bool, &SetBoundingBoxDebugOverlay>("SetBoundingBoxDebugOverlay");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
/* Copyright (C) 2019 Wildfire Games.
|
/* Copyright (C) 2020 Wildfire Games.
|
||||||
* This file is part of 0 A.D.
|
* This file is part of 0 A.D.
|
||||||
*
|
*
|
||||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
#define INCLUDED_JSI_SIMULATION
|
#define INCLUDED_JSI_SIMULATION
|
||||||
|
|
||||||
#include "scriptinterface/ScriptInterface.h"
|
#include "scriptinterface/ScriptInterface.h"
|
||||||
|
#include "simulation2/helpers/Position.h"
|
||||||
#include "simulation2/system/Entity.h"
|
#include "simulation2/system/Entity.h"
|
||||||
|
|
||||||
namespace JSI_Simulation
|
namespace JSI_Simulation
|
||||||
|
|
@ -31,6 +32,7 @@ namespace JSI_Simulation
|
||||||
std::vector<entity_id_t> PickPlayerEntitiesOnScreen(ScriptInterface::CxPrivate* pCxPrivate, int player);
|
std::vector<entity_id_t> PickPlayerEntitiesOnScreen(ScriptInterface::CxPrivate* pCxPrivate, int player);
|
||||||
std::vector<entity_id_t> PickNonGaiaEntitiesOnScreen(ScriptInterface::CxPrivate* pCxPrivate);
|
std::vector<entity_id_t> PickNonGaiaEntitiesOnScreen(ScriptInterface::CxPrivate* pCxPrivate);
|
||||||
std::vector<entity_id_t> GetEntitiesWithStaticObstructionOnScreen(ScriptInterface::CxPrivate* pCxPrivate);
|
std::vector<entity_id_t> GetEntitiesWithStaticObstructionOnScreen(ScriptInterface::CxPrivate* pCxPrivate);
|
||||||
|
JS::Value GetEdgesOfStaticObstructionsOnScreenNearTo(ScriptInterface::CxPrivate* pCxPrivate, entity_pos_t x, entity_pos_t z);
|
||||||
std::vector<entity_id_t> PickSimilarPlayerEntities(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName, bool includeOffScreen, bool matchRank, bool allowFoundations);
|
std::vector<entity_id_t> PickSimilarPlayerEntities(ScriptInterface::CxPrivate* pCxPrivate, const std::string& templateName, bool includeOffScreen, bool matchRank, bool allowFoundations);
|
||||||
JS::Value GetAIs(ScriptInterface::CxPrivate* pCxPrivate);
|
JS::Value GetAIs(ScriptInterface::CxPrivate* pCxPrivate);
|
||||||
void SetBoundingBoxDebugOverlay(ScriptInterface::CxPrivate* pCxPrivate, bool enabled);
|
void SetBoundingBoxDebugOverlay(ScriptInterface::CxPrivate* pCxPrivate, bool enabled);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue