forked from mirrors/0ad
Take the box formation out of the special case formations
This was SVN commit r14524.
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user