diff --git a/binaries/data/mods/public/gui/common/colorFades.js b/binaries/data/mods/public/gui/common/colorFades.js
new file mode 100644
index 0000000000..212cbf8849
--- /dev/null
+++ b/binaries/data/mods/public/gui/common/colorFades.js
@@ -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;
+}
\ No newline at end of file
diff --git a/binaries/data/mods/public/gui/common/timer.js b/binaries/data/mods/public/gui/common/timer.js
index 5391ba7c8c..bf8c097309 100644
--- a/binaries/data/mods/public/gui/common/timer.js
+++ b/binaries/data/mods/public/gui/common/timer.js
@@ -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)
diff --git a/binaries/data/mods/public/gui/session/session.js b/binaries/data/mods/public/gui/session/session.js
index 1981eb3c22..b0f7e12c5e 100644
--- a/binaries/data/mods/public/gui/session/session.js
+++ b/binaries/data/mods/public/gui/session/session.js
@@ -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()
{
diff --git a/binaries/data/mods/public/gui/session/session.xml b/binaries/data/mods/public/gui/session/session.xml
index 240efc4cb3..16d59d4b72 100644
--- a/binaries/data/mods/public/gui/session/session.xml
+++ b/binaries/data/mods/public/gui/session/session.xml
@@ -7,6 +7,7 @@
+
@@ -864,14 +865,27 @@
-
diff --git a/binaries/data/mods/public/simulation/components/Formation.js b/binaries/data/mods/public/simulation/components/Formation.js
index 2ecad5135e..28e58fb2b3 100644
--- a/binaries/data/mods/public/simulation/components/Formation.js
+++ b/binaries/data/mods/public/simulation/components/Formation.js
@@ -13,6 +13,14 @@ Formation.prototype.Schema =
"" +
"" +
"" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
"" +
"" +
"" +
@@ -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)
diff --git a/binaries/data/mods/public/simulation/templates/formations/battle_line.xml b/binaries/data/mods/public/simulation/templates/formations/battle_line.xml
index 27a48da2f4..c08bac4f4b 100644
--- a/binaries/data/mods/public/simulation/templates/formations/battle_line.xml
+++ b/binaries/data/mods/public/simulation/templates/formations/battle_line.xml
@@ -2,6 +2,8 @@
Battle Line
+ Cavalry Melee Ranged
+ fillFromTheSidestrue58
diff --git a/binaries/data/mods/public/simulation/templates/formations/box.xml b/binaries/data/mods/public/simulation/templates/formations/box.xml
index 8820ffc8cb..710a6f1d6a 100644
--- a/binaries/data/mods/public/simulation/templates/formations/box.xml
+++ b/binaries/data/mods/public/simulation/templates/formations/box.xml
@@ -2,6 +2,8 @@
Box
- special
+ square
+ Melee Ranged
+ fillToTheCenter
diff --git a/binaries/data/mods/public/simulation/templates/template_formation.xml b/binaries/data/mods/public/simulation/templates/template_formation.xml
index deaf33de0c..ec52ac4ef5 100644
--- a/binaries/data/mods/public/simulation/templates/template_formation.xml
+++ b/binaries/data/mods/public/simulation/templates/template_formation.xml
@@ -8,6 +8,7 @@
1square
+ Hero Champion Cavalry Melee Rangedfalse11