Changeset View
Standalone View
binaries/data/mods/public/simulation/components/Formation.js
Show First 20 Lines • Show All 60 Lines • ▼ Show 20 Lines | "<optional>" + | ||||
"</element>" + | "</element>" + | ||||
"</optional>" + | "</optional>" + | ||||
"<element name='UnitSeparationWidthMultiplier' a:help='Place the units in the formation closer or further to each other. The standard separation is the footprint size.'>" + | "<element name='UnitSeparationWidthMultiplier' a:help='Place the units in the formation closer or further to each other. The standard separation is the footprint size.'>" + | ||||
"<ref name='nonNegativeDecimal'/>" + | "<ref name='nonNegativeDecimal'/>" + | ||||
"</element>" + | "</element>" + | ||||
"<element name='UnitSeparationDepthMultiplier' a:help='Place the units in the formation closer or further to each other. The standard separation is the footprint size.'>" + | "<element name='UnitSeparationDepthMultiplier' a:help='Place the units in the formation closer or further to each other. The standard separation is the footprint size.'>" + | ||||
"<ref name='nonNegativeDecimal'/>" + | "<ref name='nonNegativeDecimal'/>" + | ||||
"</element>" + | "</element>" + | ||||
"<element name='Animations' a:help='Give a list of animation variants to use for the particular formation members, based on their positions'>" + | "<element name='AnimationVariants' a:help='Give a list of animation variants to use for the particular formation members, based on their positions'>" + | ||||
"<text a:help='example text: \"1..1,1..-1:animation1;2..2,1..-1;animation2\", this will set animation1 for the first row, and animation2 for the second row. The first part of the numbers (1..1 and 2..2) means the row range. Every row between (and including) those values will switch animations. The second part of the numbers (1..-1) denote the columns inside those rows that will be affected. Note that in both cases, you can use -1 for the last row/column, -2 for the second to last, etc.'/>" + | "<text a:help='example text: \"1..1,1..-1:animationVariant1;2..2,1..-1;animationVariant2\", this will set animationVariant1 for the first row, and animation2 for the second row. The first part of the numbers (1..1 and 2..2) means the row range. Every row between (and including) those values will switch animationvariants. The second part of the numbers (1..-1) denote the columns inside those rows that will be affected. Note that in both cases, you can use -1 for the last row/column, -2 for the second to last, etc.'/>" + | ||||
"</element>"; | "</element>"; | ||||
var g_ColumnDistanceThreshold = 128; // distance at which we'll switch between column/box formations | var g_ColumnDistanceThreshold = 128; // distance at which we'll switch between column/box formations | ||||
Formation.prototype.Init = function() | Formation.prototype.variablesToSerialize = [ | ||||
"lastOrderVariant", | |||||
"members", | |||||
"memberPositions", | |||||
"maxRowsUsed", | |||||
"maxColumnsUsed", | |||||
"inPosition", | |||||
"columnar", | |||||
"rearrange", | |||||
"formationMembersWithAura", | |||||
"width", | |||||
"depth", | |||||
"oldOrientation", | |||||
"twinFormations", | |||||
"formationSeparation", | |||||
"offsets" | |||||
]; | |||||
Formation.prototype.Init = function(deserialized = false) | |||||
{ | { | ||||
this.formationShape = this.template.FormationShape; | |||||
this.sortingClasses = this.template.SortingClasses.split(/\s+/g); | this.sortingClasses = this.template.SortingClasses.split(/\s+/g); | ||||
this.sortingOrder = this.template.SortingOrder; | |||||
this.shiftRows = this.template.ShiftRows == "true"; | this.shiftRows = this.template.ShiftRows == "true"; | ||||
this.separationMultiplier = { | this.separationMultiplier = { | ||||
"width": +this.template.UnitSeparationWidthMultiplier, | "width": +this.template.UnitSeparationWidthMultiplier, | ||||
"depth": +this.template.UnitSeparationDepthMultiplier | "depth": +this.template.UnitSeparationDepthMultiplier | ||||
}; | }; | ||||
this.sloppyness = +this.template.Sloppyness; | this.sloppyness = +this.template.Sloppyness; | ||||
this.widthDepthRatio = +this.template.WidthDepthRatio; | this.widthDepthRatio = +this.template.WidthDepthRatio; | ||||
this.minColumns = +(this.template.MinColumns || 0); | this.minColumns = +(this.template.MinColumns || 0); | ||||
this.maxColumns = +(this.template.MaxColumns || 0); | this.maxColumns = +(this.template.MaxColumns || 0); | ||||
this.maxRows = +(this.template.MaxRows || 0); | this.maxRows = +(this.template.MaxRows || 0); | ||||
this.centerGap = +(this.template.CenterGap || 0); | this.centerGap = +(this.template.CenterGap || 0); | ||||
this.animations = []; | if (this.template.AnimationVariants) | ||||
Stan: Why serialize it at all? | |||||
if (this.template.Animations) | |||||
{ | { | ||||
let differentAnimations = this.template.Animations.split(/\s*;\s*/); | this.animationvariants = []; | ||||
// loop over the different rectangulars that will map to different animations | let differentAnimationVariants = this.template.AnimationVariants.split(/\s*;\s*/); | ||||
for (var rectAnimation of differentAnimations) | // loop over the different rectangulars that will map to different animation variants | ||||
{ | for (let rectAnimationVariant of differentAnimationVariants) | ||||
var rect, replacementAnimationName; | { | ||||
[rect, replacementAnimationName] = rectAnimation.split(/\s*:\s*/); | let rect, replacementAnimationVariant; | ||||
var rows, columns; | [rect, replacementAnimationVariant] = rectAnimationVariant.split(/\s*:\s*/); | ||||
let rows, columns; | |||||
[rows, columns] = rect.split(/\s*,\s*/); | [rows, columns] = rect.split(/\s*,\s*/); | ||||
var minRow, maxRow, minColumn, maxColumn; | let minRow, maxRow, minColumn, maxColumn; | ||||
[minRow, maxRow] = rows.split(/\s*\.\.\s*/); | [minRow, maxRow] = rows.split(/\s*\.\.\s*/); | ||||
[minColumn, maxColumn] = columns.split(/\s*\.\.\s*/); | [minColumn, maxColumn] = columns.split(/\s*\.\.\s*/); | ||||
this.animations.push({ | this.animationvariants.push({ | ||||
"minRow": +minRow, | "minRow": +minRow, | ||||
"maxRow": +maxRow, | "maxRow": +maxRow, | ||||
"minColumn": +minColumn, | "minColumn": +minColumn, | ||||
"maxColumn": +maxColumn, | "maxColumn": +maxColumn, | ||||
"animation": replacementAnimationName | "name": replacementAnimationVariant | ||||
}); | }); | ||||
} | } | ||||
} | } | ||||
this.lastOrderVariant = undefined; | this.lastOrderVariant = undefined; | ||||
this.members = []; // entity IDs currently belonging to this formation | this.members = []; // entity IDs currently belonging to this formation | ||||
this.memberPositions = {}; | this.memberPositions = {}; | ||||
this.maxRowsUsed = 0; | this.maxRowsUsed = 0; | ||||
this.maxColumnsUsed = []; | this.maxColumnsUsed = []; | ||||
this.inPosition = []; // entities that have reached their final position | this.inPosition = []; // entities that have reached their final position | ||||
this.columnar = false; // whether we're travelling in column (vs box) formation | this.columnar = false; // whether we're travelling in column (vs box) formation | ||||
this.rearrange = true; // whether we should rearrange all formation members | this.rearrange = true; // whether we should rearrange all formation members | ||||
this.formationMembersWithAura = []; // Members with a formation aura | this.formationMembersWithAura = []; // Members with a formation aura | ||||
this.width = 0; | this.width = 0; | ||||
this.depth = 0; | this.depth = 0; | ||||
this.oldOrientation = {"sin": 0, "cos": 0}; | this.oldOrientation = {"sin": 0, "cos": 0}; | ||||
this.twinFormations = []; | this.twinFormations = []; | ||||
// distance from which two twin formations will merge into one. | // distance from which two twin formations will merge into one. | ||||
this.formationSeparation = 0; | this.formationSeparation = 0; | ||||
if (deserialized) | |||||
return; | |||||
Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer) | Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer) | ||||
.SetInterval(this.entity, IID_Formation, "ShapeUpdate", 1000, 1000, null); | .SetInterval(this.entity, IID_Formation, "ShapeUpdate", 1000, 1000, null); | ||||
}; | }; | ||||
Formation.prototype.Serialize = function() | |||||
{ | |||||
let result = {}; | |||||
Not Done Inline ActionsI guess one could not serialize undefined values, but not sure it's worth going through all the keys. Stan: I guess one could not serialize undefined values, but not sure it's worth going through all the… | |||||
for (let key of this.variablesToSerialize) | |||||
result[key] = this[key]; | |||||
return result; | |||||
}; | |||||
Formation.prototype.Deserialize = function(data) | |||||
{ | |||||
this.Init(true); | |||||
for (let key in data) | |||||
this[key] = data[key]; | |||||
}; | |||||
/** | /** | ||||
* Set the value from which two twin formations will become one. | * Set the value from which two twin formations will become one. | ||||
*/ | */ | ||||
Not Done Inline Actionsduplicaiton once per line, could put the serializable property names on an array of the prototype and iterate over those. Perhaps its easier to make a blacklist instead of a whitelist (serialize each property except one of them?) (Changing serialization should warrant a rejoin test) elexis: duplicaiton once per line, could put the serializable property names on an array of the… | |||||
Done Inline Actionswould not be better local variable for that instead of keeping it in memory whole time ? Silier: would not be better local variable for that instead of keeping it in memory whole time ?
I… | |||||
Not Done Inline ActionsSerialize is called at least every time a client(observer) rejoins. It also must be called every N turns to compute the full hash unless I miss something. I guess creating one array has little performance impact in comparison to the rest of Serialization code (then again who wants to neglect performance differences while writing code). Whitelist may be faster because it avoids the condition testing whether the property is blacklisted, but thats string comparison and probably (?) quicker than creating a garbage-collection tracked JS object. How many blacklisted items are there? If its only 1-2 then it seems much more practical in terms of code maintenance and duplication-antipattern to test against the blacklist (for...x..in; if x != "blacklisted" ...). elexis: Serialize is called at least every time a client(observer) rejoins. It also must be called… | |||||
Done Inline Actions11 blacklisted, 14 whitelisted Silier: 11 blacklisted, 14 whitelisted | |||||
Formation.prototype.SetFormationSeparation = function(value) | Formation.prototype.SetFormationSeparation = function(value) | ||||
{ | { | ||||
this.formationSeparation = value; | this.formationSeparation = value; | ||||
}; | }; | ||||
Formation.prototype.GetSize = function() | Formation.prototype.GetSize = function() | ||||
{ | { | ||||
Not Done Inline ActionsIsn't this the default Deserialize i.e. redundant? elexis: Isn't this the default Deserialize i.e. redundant?
(missing semicolon) | |||||
Done Inline Actionspossibly, i did not check Silier: possibly, i did not check | |||||
Not Done Inline Actions(pretty sure thats the default, the default script de/serialization code is found in simulation(2)/system/) elexis: (pretty sure thats the default, the default script de/serialization code is found in simulation… | |||||
Done Inline Actionsit is not, nuking that will cause errors Silier: it is not, nuking that will cause errors | |||||
return {"width": this.width, "depth": this.depth}; | return {"width": this.width, "depth": this.depth}; | ||||
}; | }; | ||||
Formation.prototype.GetSpeedMultiplier = function() | Formation.prototype.GetSpeedMultiplier = function() | ||||
{ | { | ||||
return +this.template.SpeedMultiplier; | return +this.template.SpeedMultiplier; | ||||
}; | }; | ||||
▲ Show 20 Lines • Show All 44 Lines • ▼ Show 20 Lines | |||||
* the arbitrary first one. | * the arbitrary first one. | ||||
*/ | */ | ||||
Formation.prototype.GetPrimaryMember = function() | Formation.prototype.GetPrimaryMember = function() | ||||
{ | { | ||||
return this.members[0]; | return this.members[0]; | ||||
}; | }; | ||||
/** | /** | ||||
* Get the formation animation for a certain member of this formation | * Get the formation animation variant for a certain member of this formation | ||||
* @param entity The entity ID to get the animation for | * @param entity The entity ID to get the animation for | ||||
* @return The name of the transformed animation as defined in the template | * @return The name of the animation variant as defined in the template | ||||
* E.g. "testudo_row1" or undefined if does not exist | * E.g. "testudo_row1" or undefined if does not exist | ||||
*/ | */ | ||||
Formation.prototype.GetFormationAnimation = function(entity) | Formation.prototype.GetFormationAnimationVariant = function(entity) | ||||
{ | { | ||||
var animationGroup = this.animations; | if (!this.animationvariants || !this.animationvariants.length || this.columnar || !this.memberPositions[entity]) | ||||
if (!animationGroup.length || this.columnar || !this.memberPositions[entity]) | |||||
return undefined; | return undefined; | ||||
var row = this.memberPositions[entity].row; | let row = this.memberPositions[entity].row; | ||||
var column = this.memberPositions[entity].column; | let column = this.memberPositions[entity].column; | ||||
for (var i = 0; i < animationGroup.length; ++i) | for (let i = 0; i < this.animationvariants.length; ++i) | ||||
{ | { | ||||
var minRow = animationGroup[i].minRow; | let minRow = this.animationvariants[i].minRow; | ||||
if (minRow < 0) | if (minRow < 0) | ||||
minRow += this.maxRowsUsed + 1; | minRow += this.maxRowsUsed + 1; | ||||
if (row < minRow) | if (row < minRow) | ||||
continue; | continue; | ||||
var maxRow = animationGroup[i].maxRow; | let maxRow = this.animationvariants[i].maxRow; | ||||
if (maxRow < 0) | if (maxRow < 0) | ||||
maxRow += this.maxRowsUsed + 1; | maxRow += this.maxRowsUsed + 1; | ||||
if (row > maxRow) | if (row > maxRow) | ||||
continue; | continue; | ||||
var minColumn = animationGroup[i].minColumn; | let minColumn = this.animationvariants[i].minColumn; | ||||
if (minColumn < 0) | if (minColumn < 0) | ||||
minColumn += this.maxColumnsUsed[row] + 1; | minColumn += this.maxColumnsUsed[row] + 1; | ||||
if (column < minColumn) | if (column < minColumn) | ||||
continue; | continue; | ||||
var maxColumn = animationGroup[i].maxColumn; | let maxColumn = this.animationvariants[i].maxColumn; | ||||
if (maxColumn < 0) | if (maxColumn < 0) | ||||
maxColumn += this.maxColumnsUsed[row] + 1; | maxColumn += this.maxColumnsUsed[row] + 1; | ||||
if (column > maxColumn) | if (column > maxColumn) | ||||
continue; | continue; | ||||
return animationGroup[i].animation; | return this.animationvariants[i].name; | ||||
} | } | ||||
return undefined; | return undefined; | ||||
}; | }; | ||||
/** | /** | ||||
* Permits formation members to register that they've reached their destination. | * Permits formation members to register that they've reached their destination. | ||||
*/ | */ | ||||
Formation.prototype.SetInPosition = function(ent) | Formation.prototype.SetInPosition = function(ent) | ||||
▲ Show 20 Lines • Show All 378 Lines • ▼ Show 20 Lines | for (var c = 0; c < sortingClasses.length; ++c) | ||||
} | } | ||||
} | } | ||||
if (!done) | if (!done) | ||||
types["Unknown"].push({"ent": active[i], "pos": positions[i]}); | types["Unknown"].push({"ent": active[i], "pos": positions[i]}); | ||||
} | } | ||||
var count = active.length; | var count = active.length; | ||||
var shape = this.formationShape; | let shape = this.template.FormationShape; | ||||
var shiftRows = this.shiftRows; | var shiftRows = this.shiftRows; | ||||
var centerGap = this.centerGap; | var centerGap = this.centerGap; | ||||
var sortingOrder = this.sortingOrder; | let sortingOrder = this.template.SortingOrder; | ||||
var offsets = []; | var offsets = []; | ||||
// Choose a sensible size/shape for the various formations, depending on number of units | // Choose a sensible size/shape for the various formations, depending on number of units | ||||
var cols; | var cols; | ||||
if (this.columnar) | if (this.columnar) | ||||
{ | { | ||||
shape = "square"; | shape = "square"; | ||||
cols = Math.min(count,3); | cols = Math.min(count,3); | ||||
shiftRows = false; | shiftRows = false; | ||||
centerGap = 0; | centerGap = 0; | ||||
sortingOrder = null; | sortingOrder = null; | ||||
Not Done Inline Actions(behavior change, code now relies on templates not specifying sortingOrder for column formations, or it supports sorting order for column formations now which would be a design change that should be mentioned then) elexis: (behavior change, code now relies on templates not specifying sortingOrder for column… | |||||
Done Inline Actionsthat variable is actually not used Silier: that variable is actually not used | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
var depth = Math.sqrt(count / this.widthDepthRatio); | let depth = Math.sqrt(count / this.widthDepthRatio); | ||||
if (this.maxRows && depth > this.maxRows) | if (this.maxRows && depth > this.maxRows) | ||||
Not Done Inline Actionsinline elexis: inline | |||||
depth = this.maxRows; | depth = this.maxRows; | ||||
cols = Math.ceil(count / Math.ceil(depth) + (this.shiftRows ? 0.5 : 0)); | cols = Math.ceil(count / Math.ceil(depth) + (this.shiftRows ? 0.5 : 0)); | ||||
if (cols < this.minColumns) | if (cols < this.minColumns) | ||||
cols = Math.min(count, this.minColumns); | cols = Math.min(count, this.minColumns); | ||||
if (this.maxColumns && cols > this.maxColumns && this.maxRows != depth) | if (this.maxColumns && cols > this.maxColumns && this.maxRows != depth) | ||||
cols = this.maxColumns; | cols = this.maxColumns; | ||||
} | } | ||||
▲ Show 20 Lines • Show All 51 Lines • ▼ Show 20 Lines | while (left > 0) | ||||
else | else | ||||
var x = side * Math.ceil(c/2) * separation.width; | var x = side * Math.ceil(c/2) * separation.width; | ||||
if (centerGap) | if (centerGap) | ||||
{ | { | ||||
if (x == 0) // don't use the center position with a center gap | if (x == 0) // don't use the center position with a center gap | ||||
continue; | continue; | ||||
x += side * centerGap / 2; | x += side * centerGap / 2; | ||||
} | } | ||||
var column = Math.ceil(n/2) + Math.ceil(c/2) * side; | var column = Math.ceil(n/2) + Math.ceil(c/2) * side; | ||||
var r1 = randFloat(-1, 1) * this.sloppyness; | let r1 = randFloat(-1, 1) * this.sloppyness; | ||||
Not Done Inline Actionslet here too? Stan: let here too? | |||||
Done Inline Actionswill do when cleaning this file, actually i forgot to revert let back to var as i am not changing lines below anymore but i ll not do update just to revert that :) Silier: will do when cleaning this file, actually i forgot to revert let back to var as i am not… | |||||
var r2 = randFloat(-1, 1) * this.sloppyness; | let r2 = randFloat(-1, 1) * this.sloppyness; | ||||
offsets.push(new Vector2D(x + r1, z + r2)); | offsets.push(new Vector2D(x + r1, z + r2)); | ||||
offsets[offsets.length - 1].row = r+1; | offsets[offsets.length - 1].row = r+1; | ||||
offsets[offsets.length - 1].column = column; | offsets[offsets.length - 1].column = column; | ||||
left--; | left--; | ||||
} | } | ||||
++r; | ++r; | ||||
this.maxColumnsUsed[r] = n; | this.maxColumnsUsed[r] = n; | ||||
} | } | ||||
this.maxRowsUsed = r; | this.maxRowsUsed = r; | ||||
} | } | ||||
// make sure the average offset is zero, as the formation is centered around that | // make sure the average offset is zero, as the formation is centered around that | ||||
// calculating offset distances without a zero average makes no sense, as the formation | // calculating offset distances without a zero average makes no sense, as the formation | ||||
// will jump to a different position any time | // will jump to a different position any time | ||||
var avgoffset = Vector2D.average(offsets); | var avgoffset = Vector2D.average(offsets); | ||||
offsets.forEach(function (o) {o.sub(avgoffset);}); | offsets.forEach(function (o) {o.sub(avgoffset);}); | ||||
// sort the available places in certain ways | // sort the available places in certain ways | ||||
// the places first in the list will contain the heaviest units as defined by the order | // the places first in the list will contain the heaviest units as defined by the order | ||||
// of the types list | // of the types list | ||||
if (sortingOrder == "fillFromTheSides") | if (sortingOrder == "fillFromTheSides") | ||||
Done Inline Actionslooks like rP14524 wanted to not this. but it did Silier: looks like rP14524 wanted to not this. but it did
on the other hand it added sortingclasses… | |||||
offsets.sort(function(o1, o2) { return Math.abs(o1.x) < Math.abs(o2.x);}); | offsets.sort(function(o1, o2) { return Math.abs(o1.x) < Math.abs(o2.x);}); | ||||
else if (sortingOrder == "fillToTheCenter") | else if (sortingOrder == "fillToTheCenter") | ||||
offsets.sort(function(o1, o2) { | offsets.sort(function(o1, o2) { | ||||
return Math.max(Math.abs(o1.x), Math.abs(o1.y)) < Math.max(Math.abs(o2.x), Math.abs(o2.y)); | return Math.max(Math.abs(o1.x), Math.abs(o1.y)) < Math.max(Math.abs(o2.x), Math.abs(o2.y)); | ||||
}); | }); | ||||
// query the 2D position of the formation | // query the 2D position of the formation | ||||
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | ||||
▲ Show 20 Lines • Show All 111 Lines • ▼ Show 20 Lines | Formation.prototype.ComputeMotionParameters = function() | ||||
minSpeed *= this.GetSpeedMultiplier(); | minSpeed *= this.GetSpeedMultiplier(); | ||||
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); | ||||
cmpUnitMotion.SetSpeedMultiplier(minSpeed / cmpUnitMotion.GetWalkSpeed()); | cmpUnitMotion.SetSpeedMultiplier(minSpeed / cmpUnitMotion.GetWalkSpeed()); | ||||
}; | }; | ||||
Formation.prototype.ShapeUpdate = function() | Formation.prototype.ShapeUpdate = function() | ||||
{ | { | ||||
// Check the distance to twin formations, and merge if when | // Check the distance to twin formations, and merge if when | ||||
Done Inline Actionsups, remove this Silier: ups, remove this | |||||
// the formations could collide | // the formations could collide | ||||
for (var i = this.twinFormations.length - 1; i >= 0; --i) | for (var i = this.twinFormations.length - 1; i >= 0; --i) | ||||
{ | { | ||||
// only do the check on one side | // only do the check on one side | ||||
if (this.twinFormations[i] <= this.entity) | if (this.twinFormations[i] <= this.entity) | ||||
continue; | continue; | ||||
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); | ||||
▲ Show 20 Lines • Show All 128 Lines • Show Last 20 Lines |
Why serialize it at all?