Take the box formation out of the special case formations

This was SVN commit r14524.
This commit is contained in:
sanderd17
2014-01-06 13:32:48 +00:00
parent 5cde6f7046
commit f19a8d6b41
8 changed files with 316 additions and 79 deletions
@@ -0,0 +1,176 @@
/*
DESCRIPTION : Some functions to make colour fades on GUI elements (f.e. used for hero and group icons)
NOTES :
*/
// Used for storing object names of running color fades in order to stop them, if the fade is restarted before the old ended
var g_colorFade = {};
g_colorFade["id"] = {};
g_colorFade["tick"] = {};
/**
* starts fading a colour of a GUI object using the sprite argument
* name: name of the object which colour should be faded
* changeInterval: interval in ms when the next colour change should be made
* duration: maximal duration of the complete fade
* colour: RGB + opacity object with keys r,g,b and o
* fun_colorTransform: function which transform the colors;
* arguments: [colour object, tickCounter]
* fun_smoothRestart [optional]: a function, which returns a smooth tick counter, if the fade should be started;
* arguments: [tickCounter of current fade; not smaller than 1 or it restarts at 0] returns: smooth tick counter value
* tickCounter [optional]: should not be set by hand! - how often the function was called recursively
*/
function fadeColour(name, changeInterval, duration, colour, fun_colorTransform, fun_smoothRestart, tickCounter)
{
// get the overlay
var overlay = Engine.GetGUIObjectByName(name);
if (overlay)
{
// check, if fade overlay was started just now
if (!tickCounter)
{
tickCounter = 1;
overlay.hidden = false;
// check, if another animation is running and restart it, if it's the case
if (isColourFadeRunning(name))
{
restartColourFade(name, changeInterval, duration, colour, fun_colorTransform, fun_smoothRestart, g_colorFade.tick[name]);
return;
}
}
// get colors
fun_colorTransform(colour, tickCounter);
// set new colour
overlay.sprite="colour: "+colour.r+" "+colour.g+" "+colour.b+" "+colour.o;
// recusive call, if duration is positive
duration-= changeInterval;
if (duration > 0 && colour.o > 0)
{
var id = setTimeout(function() { fadeColour(name, changeInterval, duration, colour, fun_colorTransform, fun_smoothRestart, ++tickCounter); }, changeInterval);
g_colorFade.id[name] = id;
g_colorFade.tick[name] = tickCounter;
}
else {
overlay.hidden = true;
stopColourFade(name);
}
}
}
/**
* checks, if a colour fade on that object is running
* name: name of the object which colour fade should be checked
* return: true a running fade was found
*/
function isColourFadeRunning(name)
{
return name in g_colorFade.id;
}
/**
* stops fading a colour
* name: name of the object which colour fade should be stopped
* return: true a running fade was stopped
*/
function stopColourFade(name, doNotHideOverlay)
{
if (isColourFadeRunning(name))
{
clearTimeout(g_colorFade.id[name]);
delete g_colorFade.id[name];
delete g_colorFade.tick[name];
// get the overlay and hide it
if (doNotHideOverlay != true)
{
var overlay = Engine.GetGUIObjectByName(name);
if(overlay)
overlay.hidden = true;
}
return true;
}
return false;
}
/**
* restarts a colour fade
* see paramter in fadeColour function
*/
function restartColourFade(name, changeInterval, duration, colour, fun_colorTransform, fun_smoothRestart, tickCounter)
{
if (isColourFadeRunning(name))
{
// check, if fade can be restarted smoothly
if (fun_smoothRestart)
{
tickCounter = fun_smoothRestart(colour, tickCounter);
// set new function to existing timer
var fun = function() { fadeColour(name, changeInterval, duration, colour, fun_colorTransform, fun_smoothRestart, tickCounter); };
setNewTimerFunction(g_colorFade.id[name], fun);
}
// stop it and restart it
else
{
stopColourFade(name, true);
fadeColour(name, changeInterval, duration, colour, fun_colorTransform);
}
}
}
/********************************************************************************************************/
/* PREDEFINED FUNCTIONS */
/********************************************************************************************************/
/**
* rgb: colour object with keys r,g,b and o
* tickCounter: how often the fade was executed
*/
function colourFade_attackUnit(rgb, tickCounter)
{
// blinking
if (tickCounter < 50)
{
// slow that process down
if (tickCounter%5 != 0)
return;
rgb.g = rgb.g == 0 ? 255 : rgb.g = 0;
rgb.b = rgb.b == 0 ? 255 : rgb.b = 0;
}
// wait a short time and then colour fade from red to grey to nothing
else if ( tickCounter > 54)
{
rgb.g = rgb.g < 255 ? rgb.g += 3*Math.sqrt(tickCounter-50) : 255;
rgb.b = rgb.g;
// start with fading it out
if (rgb.g > 100)
rgb.o = rgb.o > 3 ? rgb.o -= 3 : 0;
}
}
/**
* makes a smooth fade, if the attack on the unit has not stopped yet
* rgb: colour object with keys r,g,b and o
* tickCounter: how often the fade was executed
*/
function smoothColourFadeRestart_attackUnit(rgb, tickCounter)
{
// check, if in blinking phase
if (tickCounter < 50)
{
// get rgb to current state
for (var i = 1; i <= tickCounter; i++)
colourFade_attackUnit(rgb, i);
// set the tick counter back to start
return (tickCounter%10)+1;
}
return 1;
}
@@ -4,6 +4,8 @@ var g_Time = Date.now();
/**
* Set a timeout to call func() after 'delay' msecs.
* func: function to call
* delay: delay in ms
* Returns an id that can be passed to clearTimeout.
*/
function setTimeout(func, delay)
@@ -13,11 +15,27 @@ function setTimeout(func, delay)
return id;
}
/**
* deletes a timer
* id: of the timer
*/
function clearTimeout(id)
{
delete g_Timers[id];
}
/**
* alters an function call
* id: of the timer
* func: function to call
*/
function setNewTimerFunction(id, func)
{
if (id in g_Timers) {
g_Timers[id][1] = func;
}
}
/**
* If you want to use timers, then you must call this function regularly
* (e.g. in a Tick handler)
@@ -51,6 +51,9 @@ var g_ShowGuarding = false;
var g_ShowGuarded = false;
var g_AdditionalHighlight = [];
// for saving the hitpoins of the hero (is there a better way to do that?)
var g_heroHitpoints = undefined;
function GetSimState()
{
if (!g_SimState)
@@ -485,17 +488,59 @@ function onSimulationUpdate()
var battleState = Engine.GuiInterfaceCall("GetBattleState", Engine.GetPlayerID());
if (battleState)
global.music.setState(global.music.states[battleState]);
}
/**
* updates a status bar on the GUI
* nameOfBar: name of the bar
* points: points to show
* maxPoints: max points
* direction: gets less from (right to left) 0; (top to bottom) 1; (left to right) 2; (bottom to top) 3;
*/
function updateGUIStatusBar(nameOfBar, points, maxPoints, direction)
{
// check, if optional direction parameter is valid.
if (!direction || !(direction>=0 && direction<4))
direction = 0;
// get the bar and update it
var statusBar = Engine.GetGUIObjectByName(nameOfBar);
if (statusBar)
{
var healthSize = statusBar.size;
var value = 100*Math.max(0, Math.min(1, points / maxPoints));
// inverse bar
if(direction == 2 || direction == 3)
value = 100 - value;
if(direction == 0)
healthSize.rright = value;
else if(direction == 1)
healthSize.rbottom = value;
else if(direction == 2)
healthSize.rleft = value;
else if(direction == 3)
healthSize.rtop = value;
// update bar
statusBar.size = healthSize;
}
}
function updateHero()
{
var simState = GetSimState();
var playerState = simState.players[Engine.GetPlayerID()];
var unitHeroPanel = Engine.GetGUIObjectByName("unitHeroPanel");
var heroButton = Engine.GetGUIObjectByName("unitHeroButton");
if (!playerState || playerState.heroes.length <= 0)
{
heroButton.hidden = true;
g_heroHitpoints = undefined;
unitHeroPanel.hidden = true;
return;
}
@@ -512,7 +557,7 @@ function updateHero()
g_Selection.addList([hero]);
};
heroButton.ondoublepress = function() { selectAndMoveTo(getEntityOrHolder(hero)); };
heroButton.hidden = false;
unitHeroPanel.hidden = false;
// Setup tooltip
var tooltip = "[font=\"serif-bold-16\"]" + template.name.specific + "[/font]";
@@ -527,7 +572,25 @@ function updateHero()
tooltip += "\n" + template.tooltip;
heroButton.tooltip = tooltip;
};
// update heros health bar
updateGUIStatusBar("heroHealthBar", heroState.hitpoints, heroState.maxHitpoints);
// define the hit points if not defined
if (!g_heroHitpoints)
g_heroHitpoints = heroState.hitpoints;
// check, if the health of the hero changed since the last update
if (heroState.hitpoints < g_heroHitpoints)
{
g_heroHitpoints = heroState.hitpoints;
// trigger the animation
fadeColour("heroHitOverlay", 100, 10000, {"r": 175,"g": 0,"b": 0,"o": 100}, colourFade_attackUnit, smoothColourFadeRestart_attackUnit);
return;
}
}
function updateGroups()
{
@@ -7,6 +7,7 @@
<script file="gui/common/functions_global_object.js" />
<script file="gui/common/music.js"/>
<script file="gui/common/timer.js"/>
<script file="gui/common/colorFades.js"/>
<script file="gui/session/session.js"/>
<script file="gui/session/selection.js"/>
<script file="gui/session/placement.js"/>
@@ -864,14 +865,27 @@
<!-- ================================ ================================ -->
<!-- Hero Selection -->
<!-- ================================ ================================ -->
<object
<object
name="unitHeroPanel"
size="0 36 50 86"
size="0 36 50 93"
hidden="true"
>
<object name="unitHeroButton" size="0 0 50 50" type="button" hidden="false" style="iconButton"
<object name="unitHeroButton" size="0 0 50 50" type="button" style="iconButton"
tooltip_style="sessionToolTip" tooltip="Attack and Armor">
<object name="unitHeroImage" size="5 5 100%-5 100%-5" type="image" ghost="true"/>
<object name="heroHitOverlay" hidden="true" type="image" ghost="true" size="5 5 100%-5 100%-5"/>
</object>
<!-- Hero Health bar -->
<object size="3 100%-7 100%-3 100%-2" name="heroHealthSection" ghost="true">
<object size="0 0 100% 5" name="heroHealth" type="image" ghost="true">
<object type="image" sprite="barBorder" ghost="true" size="-1 -1 100%+1 100%+1"/>
<object type="image" sprite="healthBackground" ghost="true"/>
<object type="image" sprite="healthForeground" ghost="true" name="heroHealthBar"/>
<object type="image" sprite="statsBarShaderHorizontal" ghost="true"/>
</object>
</object>
<!-- Hit overlay -->
</object>
<!-- ================================ ================================ -->
@@ -13,6 +13,14 @@ Formation.prototype.Schema =
"<element name='ShiftRows' a:help='Set the value to true to shift subsequent rows'>" +
"<text/>" +
"</element>" +
"<element name='SortingClasses' a:help='Classes will be added to the formation in this order. Where the classes will be added first depends on the formation'>" +
"<text/>" +
"</element>" +
"<optional>" +
"<element name='SortingOrder' a:help='The order of sorting. This defaults to an order where the formation is filled from the first row to the last, and the center of each row to the sides. Other possible sort orders are \"fillFromTheSides\", where the most important units are on the sides of each row, and \"fillToTheCenter\", where the most vulerable units are right in the center of the formation. '>" +
"<text/>" +
"</element>" +
"</optional>" +
"<element name='WidthDepthRatio' a:help='Average width/depth, counted in number of units.'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
@@ -48,6 +56,8 @@ var g_ColumnDistanceThreshold = 128; // distance at which we'll switch between c
Formation.prototype.Init = function()
{
this.formationShape = this.template.FormationShape;
this.sortingClasses = this.template.SortingClasses.split(/\s+/g);
this.sortingOrder = this.template.SortingOrder;
this.shiftRows = this.template.ShiftRows == "true";
this.separationMultiplier = {
"width": +this.template.UnitSeparationWidthMultiplier,
@@ -514,27 +524,27 @@ Formation.prototype.ComputeFormationOffsets = function(active, positions)
separation.width *= this.separationMultiplier.width;
separation.depth *= this.separationMultiplier.depth;
if (this.columnar)
var sortingClasses = ["Cavalry","Infantry"];
else
var sortingClasses = this.sortingClasses;
// the entities will be assigned to positions in the formation in
// the same order as the types list is ordered
var types = {
"Cavalry" : [],
"Hero" : [],
"Melee" : [],
"Ranged" : [],
"Support" : [],
"Unknown": []
};
var types = {"Unknown": []};
for (var i = 0; i < sortingClasses.length; ++i)
types[sortingClasses[i]] = [];
for (var i in active)
{
var cmpIdentity = Engine.QueryInterface(active[i], IID_Identity);
var classes = cmpIdentity.GetClassesList();
var done = false;
for each (var cla in classes)
for (var c = 0; c < sortingClasses.length; ++c)
{
if (cla in types)
if (classes.indexOf(sortingClasses[c]) > -1)
{
types[cla].push({"ent": active[i], "pos": positions[i]});
types[sortingClasses[c]].push({"ent": active[i], "pos": positions[i]});
done = true;
break;
}
@@ -548,7 +558,7 @@ Formation.prototype.ComputeFormationOffsets = function(active, positions)
var shape = this.formationShape;
var shiftRows = this.shiftRows;
var centerGap = this.centerGap;
var ordering = [];
var sortingOrder = this.sortingOrder;
var offsets = [];
@@ -561,6 +571,7 @@ Formation.prototype.ComputeFormationOffsets = function(active, positions)
cols = Math.min(count,3);
shiftRows = false;
centerGap = 0;
sortingOrder = null;
}
else
{
@@ -575,66 +586,18 @@ Formation.prototype.ComputeFormationOffsets = function(active, positions)
}
// define special formations here
switch(this.formationName)
if (this.formationName == "Scatter")
{
case "Scatter":
var width = Math.sqrt(count) * (separation.width + separation.depth) * 2.5;
for (var i = 0; i < count; ++i)
offsets.push({"x": Math.random()*width, "z": Math.random()*width});
break;
case "Box":
var root = Math.ceil(Math.sqrt(count));
var left = count;
var meleeleft = types["Melee"].length;
for (var sq = Math.floor(root/2); sq >= 0; --sq)
{
var width = sq * 2 + 1;
var stodo;
if (sq == 0)
{
stodo = left;
}
else
{
if (meleeleft >= width*width - (width-2)*(width-2)) // form a complete box
{
stodo = width*width - (width-2)*(width-2);
meleeleft -= stodo;
}
else // compact
{
stodo = Math.max(0, left - (width-2)*(width-2));
}
}
for (var r = -sq; r <= sq && stodo; ++r)
{
for (var c = -sq; c <= sq && stodo; ++c)
{
if (Math.abs(r) == sq || Math.abs(c) == sq)
{
var x = c * separation.width;
var z = -r * separation.depth;
offsets.push({"x": x, "z": z});
stodo--;
left--;
}
}
}
}
break;
case "Battle Line":
ordering.push("FillFromTheSides");
break;
default:
break;
}
// For non-special formations, calculate the positions based on the number of entities
if (shape != "special")
{
offsets = [];
var r = 0;
var left = count;
// while there are units left, start a new row in the formation
@@ -694,15 +657,12 @@ Formation.prototype.ComputeFormationOffsets = function(active, positions)
// sort the available places in certain ways
// the places first in the list will contain the heaviest units as defined by the order
// of the types list
for each (var order in ordering)
{
if (order == "FillFromTheSides")
offsets.sort(function(o1, o2) { return Math.abs(o1.x) < Math.abs(o2.x);});
else if (order == "FillFromTheCenter")
offsets.sort(function(o1, o2) { return Math.abs(o1.x) > Math.abs(o2.x);});
else if (order == "FillFromTheFront")
offsets.sort(function(o1, o2) { return o1.z < o2.z;});
}
if (this.sortingOrder == "fillFromTheSides")
offsets.sort(function(o1, o2) { return Math.abs(o1.x) < Math.abs(o2.x);});
else if (this.sortingOrder == "fillToTheCenter")
offsets.sort(function(o1, o2) {
return Math.max(Math.abs(o1.x), Math.abs(o1.z)) < Math.max(Math.abs(o2.x), Math.abs(o2.z));
});
// query the 2D position of the formation, and bring to the right coordinate system
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
@@ -714,8 +674,9 @@ Formation.prototype.ComputeFormationOffsets = function(active, positions)
// every soldier searches the closest available place in the formation
var newOffsets = [];
var realPositions = this.GetRealOffsetPositions(offsets, formationPos);
for each (var t in types)
for (var i = 0; i < sortingClasses.length; ++i)
{
var t = types[sortingClasses[i]];
var usedOffsets = offsets.splice(0,t.length);
var usedRealPositions = realPositions.splice(0, t.length);
for each (var entPos in t)
@@ -2,6 +2,8 @@
<Entity parent="template_formation">
<Formation>
<FormationName>Battle Line</FormationName>
<SortingClasses>Cavalry Melee Ranged</SortingClasses>
<SortingOrder>fillFromTheSides</SortingOrder>
<ShiftRows>true</ShiftRows>
<MinColumns>5</MinColumns>
<MaxColumns>8</MaxColumns>
@@ -2,6 +2,8 @@
<Entity parent="template_formation">
<Formation>
<FormationName>Box</FormationName>
<FormationShape>special</FormationShape>
<FormationShape>square</FormationShape>
<SortingClasses>Melee Ranged</SortingClasses>
<SortingOrder>fillToTheCenter</SortingOrder>
</Formation>
</Entity>
@@ -8,6 +8,7 @@
<Formation>
<SpeedMultiplier>1</SpeedMultiplier>
<FormationShape>square</FormationShape>
<SortingClasses>Hero Champion Cavalry Melee Ranged</SortingClasses>
<ShiftRows>false</ShiftRows>
<UnitSeparationWidthMultiplier>1</UnitSeparationWidthMultiplier>
<UnitSeparationDepthMultiplier>1</UnitSeparationDepthMultiplier>