diff --git a/binaries/data/mods/public/simulation/components/Attack.js b/binaries/data/mods/public/simulation/components/Attack.js index 9376d3a9ca..8850137ab4 100644 --- a/binaries/data/mods/public/simulation/components/Attack.js +++ b/binaries/data/mods/public/simulation/components/Attack.js @@ -70,6 +70,7 @@ Attack.prototype.Schema = "800" + "1600" + "50.0" + + "2.5" + "" + "" + "Cavalry" + @@ -77,6 +78,14 @@ Attack.prototype.Schema = "" + "" + "Champion" + + "" + + "Circular" + + "20" + + "false" + + "0.0" + + "10.0" + + "0.0" + + "" + "" + "" + "10.0" + @@ -119,9 +128,23 @@ Attack.prototype.Schema = "" + "" + "" + + "" + bonusesSchema + preferredClassesSchema + restrictedClassesSchema + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + bonusesSchema + + "" + + "" + + "" + "" + "" + "" + @@ -287,6 +310,14 @@ Attack.prototype.GetAttackStrengths = function(type) // Work out the attack values with technology effects var self = this; + var template = this.template[type]; + var splash = ""; + if (!template) + { + template = this.template[type.split(".")[0]].Splash; + splash = "/Splash"; + } + var cmpTechMan = QueryOwnerInterface(this.entity, IID_TechnologyManager); var applyTechs = function(damageType) { @@ -294,8 +325,8 @@ Attack.prototype.GetAttackStrengths = function(type) if (cmpTechMan) { // All causes caching problems so disable it for now. - //var allComponent = cmpTechMan.ApplyModifications("Attack/" + type + "/All", strength, self.entity) - self.template[type][damageType]; - strength = cmpTechMan.ApplyModifications("Attack/" + type + "/" + damageType, strength, self.entity); + //var allComponent = cmpTechMan.ApplyModifications("Attack/" + type + splash + "/All", strength, self.entity) - self.template[type][damageType]; + strength = cmpTechMan.ApplyModifications("Attack/" + type + splash + "/" + damageType, strength, self.entity); } return strength; }; @@ -326,16 +357,20 @@ Attack.prototype.GetRange = function(type) Attack.prototype.GetAttackBonus = function(type, target) { var attackBonus = 1; - if (this.template[type].Bonuses) + var template = this.template[type]; + if (!template) + template = this.template[type.split(".")[0]].Splash; + + if (template.Bonuses) { var cmpIdentity = Engine.QueryInterface(target, IID_Identity); if (!cmpIdentity) return 1; // Multiply the bonuses for all matching classes - for (var key in this.template[type].Bonuses) + for (var key in template.Bonuses) { - var bonus = this.template[type].Bonuses[key]; + var bonus = template.Bonuses[key]; var hasClasses = true; if (bonus.Classes){ @@ -352,6 +387,20 @@ Attack.prototype.GetAttackBonus = function(type, target) return attackBonus; }; +// Returns a 2d random distribution scaled for a spread of scale 1. +// The current implementation is a 2d gaussian with sigma = 1 +Attack.prototype.GetNormalDistribution = function(){ + + // Use the Box-Muller transform to get a gaussian distribution + var a = Math.random(); + var b = Math.random(); + + var c = Math.sqrt(-2*Math.log(a)) * Math.cos(2*Math.PI*b); + var d = Math.sqrt(-2*Math.log(a)) * Math.sin(2*Math.PI*b); + + return [c, d]; +}; + /** * Attack the target entity. This should only be called after a successful range check, * and should only be called after GetTimers().repeat msec has passed since the last @@ -362,32 +411,18 @@ Attack.prototype.PerformAttack = function(type, target) // If this is a ranged attack, then launch a projectile if (type == "Ranged") { - // To implement (in)accuracy, for arrows and javelins, we want to do the following: - // * Compute an accuracy rating, based on the entity's characteristics and the distance to the target - // * Pick a random point 'close' to the target (based on the accuracy) which is the real target point - // * Pick a real target unit, based on their footprint's proximity to the real target point - // * If there is none, then harmlessly shoot to the real target point instead - // * If the real target unit moves after being targeted, the projectile will follow it and hit it anyway - // - // In the future this should be extended: - // * If the target unit moves too far, the projectile should 'detach' and not hit it, so that - // players can dodge projectiles. (Or it should pick a new target after detaching, so it can still - // hit somebody.) + // In the future this could be extended: // * Obstacles like trees could reduce the probability of the target being hit // * Obstacles like walls should block projectiles entirely - // * There should be more control over the probabilities of hitting enemy units vs friendly units vs missing, - // for gameplay balance tweaks - // * Larger, slower projectiles (catapults etc) shouldn't pick targets first, they should just - // hurt anybody near their landing point // Get some data about the entity var horizSpeed = +this.template[type].ProjectileSpeed; var gravity = 9.81; // this affects the shape of the curve; assume it's constant for now - var accuracy = 6; // TODO: get from entity template - - //horizSpeed /= 8; gravity /= 8; // slow it down for testing - - // Find the distance to the target + + var spread = this.template.Ranged.Spread; + + //horizSpeed /= 2; gravity /= 2; // slow it down for testing + var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); if (!cmpPosition || !cmpPosition.IsInWorld()) return; @@ -396,48 +431,47 @@ Attack.prototype.PerformAttack = function(type, target) if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld()) return; var targetPosition = cmpTargetPosition.GetPosition(); - var horizDistance = Math.sqrt(Math.pow(targetPosition.x - selfPosition.x, 2) + Math.pow(targetPosition.z - selfPosition.z, 2)); - - // Compute the real target point (based on accuracy) - var angle = Math.random() * 2*Math.PI; - var r = 1 - Math.sqrt(Math.random()); // triangular distribution [0,1] (cluster around the center) - var offset = r * accuracy; // TODO: should be affected by range - var offsetX = offset * Math.sin(angle); - var offsetZ = offset * Math.cos(angle); - - var realTargetPosition = { "x": targetPosition.x + offsetX, "y": targetPosition.y, "z": targetPosition.z + offsetZ }; - - // TODO: what we should really do here is select the unit whose footprint is closest to the realTargetPosition - // (and harmlessly hit the ground if there's none), but as a simplification let's just randomly decide whether to - // hit the original target or not. - var realTargetUnit = undefined; - if (Math.random() < 0.5) // TODO: this is yucky and hardcoded - { - // Hit the original target - realTargetUnit = target; - realTargetPosition = targetPosition; - } - else - { - // Hit the ground - // TODO: ought to make sure Y is on the ground - } - - // Hurt the target after the appropriate time - if (realTargetUnit) - { - var realHorizDistance = Math.sqrt(Math.pow(realTargetPosition.x - selfPosition.x, 2) + Math.pow(realTargetPosition.z - selfPosition.z, 2)); - var timeToTarget = realHorizDistance / horizSpeed; - var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); - cmpTimer.SetTimeout(this.entity, IID_Attack, "CauseDamage", timeToTarget*1000, {"type": type, "target": target}); - } + + var relativePosition = {"x": targetPosition.x - selfPosition.x, "z": targetPosition.z - selfPosition.z} + var previousTargetPosition = Engine.QueryInterface(target, IID_Position).GetPreviousPosition(); + + var targetVelocity = {"x": (targetPosition.x - previousTargetPosition.x) / this.turnLength, "z": (targetPosition.z - previousTargetPosition.z) / this.turnLength} + // the component of the targets velocity radially away from the archer + var radialSpeed = this.VectorDot(relativePosition, targetVelocity) / this.VectorLength(relativePosition); + + var horizDistance = this.VectorDistance(targetPosition, selfPosition); + + // This is an approximation of the time ot the target, it assumes that the target has a constant radial + // velocity, but since units move in straight lines this is not true. The exact value would be more + // difficult to calculate and I think this is sufficiently accurate. (I tested and for cavalry it was + // about 5% of the units radius out in the worst case) + var timeToTarget = horizDistance / (horizSpeed - radialSpeed); + + // Predict where the unit is when the missile lands. + var predictedPosition = {"x": targetPosition.x + targetVelocity.x * timeToTarget, + "z": targetPosition.z + targetVelocity.z * timeToTarget}; + + // Compute the real target point (based on spread and target speed) + var randNorm = this.GetNormalDistribution(); + var offsetX = randNorm[0] * spread * (1 + this.VectorLength(targetVelocity) / 20); + var offsetZ = randNorm[1] * spread * (1 + this.VectorLength(targetVelocity) / 20); + var realTargetPosition = { "x": predictedPosition.x + offsetX, "y": targetPosition.y, "z": predictedPosition.z + offsetZ }; + + // Calculate when the missile will hit the target position + var realHorizDistance = this.VectorDistance(realTargetPosition, selfPosition); + var timeToTarget = realHorizDistance / horizSpeed; + + var missileDirection = {"x": (realTargetPosition.x - selfPosition.x) / realHorizDistance, "z": (realTargetPosition.z - selfPosition.z) / realHorizDistance}; + + // Make the arrow appear to land slightly behind the target so that arrows landing next to a guys foot don't count but arrows that go through the torso do + var graphicalPosition = {"x": realTargetPosition.x + 2*missileDirection.x, "y": realTargetPosition.y + 2*missileDirection.y}; // Launch the graphical projectile var cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager); - if (realTargetUnit) - cmpProjectileManager.LaunchProjectileAtEntity(this.entity, realTargetUnit, horizSpeed, gravity); - else - cmpProjectileManager.LaunchProjectileAtPoint(this.entity, realTargetPosition, horizSpeed, gravity); + var id = cmpProjectileManager.LaunchProjectileAtPoint(this.entity, realTargetPosition, horizSpeed, gravity); + + var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); + cmpTimer.SetTimeout(this.entity, IID_Attack, "MissileHit", timeToTarget*1000, {"type": type, "target": target, "position": realTargetPosition, "direction": missileDirection, "projectileId": id}); } else { @@ -465,6 +499,170 @@ Attack.prototype.TargetKilled = function(killerEntity, targetEntity) } }; +Attack.prototype.InterpolatedLocation = function(ent, lateness) +{ + var targetPositionCmp = Engine.QueryInterface(ent, IID_Position); + if (!targetPositionCmp) // TODO: handle dead target properly + return undefined; + var curPos = targetPositionCmp.GetPosition(); + var prevPos = targetPositionCmp.GetPreviousPosition(); + lateness /= 1000; + return {"x": (curPos.x * (this.turnLength - lateness) + prevPos.x * lateness) / this.turnLength, + "z": (curPos.z * (this.turnLength - lateness) + prevPos.z * lateness) / this.turnLength}; +}; + +Attack.prototype.VectorDistance = function(p1, p2) +{ + return Math.sqrt((p1.x - p2.x)*(p1.x - p2.x) + (p1.z - p2.z)*(p1.z - p2.z)); +}; + +Attack.prototype.VectorDot = function(p1, p2) +{ + return (p1.x * p2.x + p1.z * p2.z); +}; + +Attack.prototype.VectorCross = function(p1, p2) +{ + return (p1.x * p2.z - p1.z * p2.x); +}; + +Attack.prototype.VectorLength = function(p) +{ + return Math.sqrt(p.x*p.x + p.z*p.z); +}; + +// Tests whether it point is inside of ent's footprint +Attack.prototype.testCollision = function(ent, point, lateness) +{ + var targetPosition = this.InterpolatedLocation(ent, lateness); + var targetShape = Engine.QueryInterface(ent, IID_Footprint).GetShape(); + + if (!targetShape || !targetPosition) + return false; + + if (targetShape.type === 'circle') + { + return (this.VectorDistance(point, targetPosition) < targetShape.radius); + } + else + { + var targetRotation = Engine.QueryInterface(ent, IID_Position).GetRotation().y; + + var dx = point.x - targetPosition.x; + var dz = point.z - targetPosition.z; + + var dxr = Math.cos(targetRotation) * dx - Math.sin(targetRotation) * dz; + var dzr = Math.sin(targetRotation) * dx + Math.cos(targetRotation) * dz; + + return (-targetShape.width/2 <= dxr && dxr < targetShape.width/2 && -targetShape.depth/2 <= dzr && dzr < targetShape.depth/2); + } +}; + +Attack.prototype.MissileHit = function(data, lateness) +{ + + var targetPosition = this.InterpolatedLocation(data.target, lateness); + if (!targetPosition) + return; + + if (this.template.Ranged.Splash) // splash damage, do this first in case the direct hit kills the target + { + var friendlyFire = this.template.Ranged.Splash.FriendlyFire; + var splashRadius = this.template.Ranged.Splash.Range; + var splashShape = this.template.Ranged.Splash.Shape; + + var ents = this.GetNearbyEntities(data.target, this.VectorDistance(data.position, targetPosition) * 2 + splashRadius, friendlyFire); + ents.push(data.target); // Add the original unit to the list of splash damage targets + + for (var i = 0; i < ents.length; i++) + { + var entityPosition = this.InterpolatedLocation(ents[i], lateness); + var radius = this.VectorDistance(data.position, entityPosition); + + if (radius < splashRadius) + { + var multiplier = 1; + if (splashShape == "Circular") // quadratic falloff + { + multiplier *= 1 - ((radius * radius) / (splashRadius * splashRadius)); + } + else if (splashShape == "Linear") + { + // position of entity relative to where the missile hit + var relPos = {"x": entityPosition.x - data.position.x, "z": entityPosition.z - data.position.z}; + + var splashWidth = splashRadius / 5; + var parallelDist = this.VectorDot(relPos, data.direction); + var perpDist = Math.abs(this.VectorCross(relPos, data.direction)); + + // Check that the unit is within the distance splashWidth of the line starting at the missile's + // landing point which extends in the direction of the missile for length splashRadius. + if (parallelDist > -splashWidth && perpDist < splashWidth) + { + // Use a quadratic falloff in both directions + multiplier = (splashRadius*splashRadius - parallelDist*parallelDist) / (splashRadius*splashRadius) + * (splashWidth*splashWidth - perpDist*perpDist) / (splashWidth*splashWidth); + } + else + { + multiplier = 0; + } + } + var newData = {"type": data.type + ".Splash", "target": ents[i], "damageMultiplier": multiplier}; + this.CauseDamage(newData); + } + } + } + + if (this.testCollision(data.target, data.position, lateness)) + { + // Hit the primary target + this.CauseDamage(data); + + // Remove the projectile + var cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager); + cmpProjectileManager.RemoveProjectile(data.projectileId); + } + else + { + // If we didn't hit the main target look for nearby units + var ents = this.GetNearbyEntities(data.target, this.VectorDistance(data.position, targetPosition) * 2); + + for (var i = 0; i < ents.length; i++) + { + if (this.testCollision(ents[i], data.position, lateness)) + { + var newData = {"type": data.type, "target": ents[i]}; + this.CauseDamage(newData); + + // Remove the projectile + var cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager); + cmpProjectileManager.RemoveProjectile(data.projectileId); + } + } + } +}; + +Attack.prototype.GetNearbyEntities = function(startEnt, range, friendlyFire) +{ + var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); + var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); + var owner = cmpOwnership.GetOwner(); + var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(owner), IID_Player); + var numPlayers = cmpPlayerManager.GetNumPlayers(); + var players = []; + + for (var i = 1; i < numPlayers; ++i) + { + // Only target enemies unless friendly fire is on + if (cmpPlayer.IsEnemy(i) || friendlyFire) + players.push(i); + } + + var rangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); + return rangeManager.ExecuteQuery(startEnt, 0, range, players, IID_DamageReceiver); +} + /** * Inflict damage on the target */ @@ -472,12 +670,14 @@ Attack.prototype.CauseDamage = function(data) { var strengths = this.GetAttackStrengths(data.type); - var attackBonus = this.GetAttackBonus(data.type, data.target); + var damageMultiplier = this.GetAttackBonus(data.type, data.target); + if (data.damageMultiplier !== undefined) + damageMultiplier *= data.damageMultiplier; var cmpDamageReceiver = Engine.QueryInterface(data.target, IID_DamageReceiver); if (!cmpDamageReceiver) return; - var targetState = cmpDamageReceiver.TakeDamage(strengths.hack * attackBonus, strengths.pierce * attackBonus, strengths.crush * attackBonus); + var targetState = cmpDamageReceiver.TakeDamage(strengths.hack * damageMultiplier, strengths.pierce * damageMultiplier, strengths.crush * damageMultiplier); // if target killed pick up loot and credit experience if (targetState.killed == true) { @@ -490,4 +690,9 @@ Attack.prototype.CauseDamage = function(data) PlaySound("attack_impact", this.entity); }; +Attack.prototype.OnUpdate = function(msg) +{ + this.turnLength = msg.turnLength; +} + Engine.RegisterComponentType(IID_Attack, "Attack", Attack); diff --git a/binaries/data/mods/public/simulation/templates/other/plane.xml b/binaries/data/mods/public/simulation/templates/other/plane.xml index 5804ffe33e..beb0266137 100644 --- a/binaries/data/mods/public/simulation/templates/other/plane.xml +++ b/binaries/data/mods/public/simulation/templates/other/plane.xml @@ -10,6 +10,7 @@ 60.0 0 2000 + 1.5 diff --git a/binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml b/binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml index 6fdbbc3e85..328bbea350 100644 --- a/binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml +++ b/binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml @@ -15,6 +15,7 @@ 75.0 1200 2000 + 1.5 diff --git a/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml b/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml index 117091aae4..2eebed85a0 100644 --- a/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml +++ b/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml @@ -15,6 +15,7 @@ 75.0 1200 2000 + 1.5 diff --git a/binaries/data/mods/public/simulation/templates/template_structure_defense_defense_tower.xml b/binaries/data/mods/public/simulation/templates/template_structure_defense_defense_tower.xml index 698b7d545e..1aab30451b 100644 --- a/binaries/data/mods/public/simulation/templates/template_structure_defense_defense_tower.xml +++ b/binaries/data/mods/public/simulation/templates/template_structure_defense_defense_tower.xml @@ -15,6 +15,7 @@ 75.0 1200 2000 + 1.5 diff --git a/binaries/data/mods/public/simulation/templates/template_structure_defense_outpost.xml b/binaries/data/mods/public/simulation/templates/template_structure_defense_outpost.xml index a0a9ff2123..70faa84dfe 100644 --- a/binaries/data/mods/public/simulation/templates/template_structure_defense_outpost.xml +++ b/binaries/data/mods/public/simulation/templates/template_structure_defense_outpost.xml @@ -15,6 +15,7 @@ 75.0 1200 2000 + 1.5 diff --git a/binaries/data/mods/public/simulation/templates/template_structure_defense_wall_tower.xml b/binaries/data/mods/public/simulation/templates/template_structure_defense_wall_tower.xml index 768ae53792..1b74475255 100644 --- a/binaries/data/mods/public/simulation/templates/template_structure_defense_wall_tower.xml +++ b/binaries/data/mods/public/simulation/templates/template_structure_defense_wall_tower.xml @@ -15,6 +15,7 @@ 75.0 1200 2000 + 1.5 diff --git a/binaries/data/mods/public/simulation/templates/template_structure_defense_wallset.xml b/binaries/data/mods/public/simulation/templates/template_structure_defense_wallset.xml index b81955617a..55b8a0d296 100644 --- a/binaries/data/mods/public/simulation/templates/template_structure_defense_wallset.xml +++ b/binaries/data/mods/public/simulation/templates/template_structure_defense_wallset.xml @@ -9,8 +9,7 @@ Town Wall City Wall Wall off your town for a stout defense. - structures/wall.png - phase_town + phase_town 0.85 diff --git a/binaries/data/mods/public/simulation/templates/template_structure_economic_market.xml b/binaries/data/mods/public/simulation/templates/template_structure_economic_market.xml index ee01600549..fa118c71a2 100644 --- a/binaries/data/mods/public/simulation/templates/template_structure_economic_market.xml +++ b/binaries/data/mods/public/simulation/templates/template_structure_economic_market.xml @@ -38,11 +38,6 @@ - - - armor_trade_convoys - - @@ -56,6 +51,9 @@ 65536 + + armor_trade_convoys + units/{civ}_support_trader diff --git a/binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml b/binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml index 4bf8898e37..c80e862e98 100644 --- a/binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml +++ b/binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml @@ -15,6 +15,7 @@ 75.0 1200 2000 + 1.5 diff --git a/binaries/data/mods/public/simulation/templates/template_unit_cavalry_ranged.xml b/binaries/data/mods/public/simulation/templates/template_unit_cavalry_ranged.xml index e8c69229c9..70c120f84f 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_cavalry_ranged.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_cavalry_ranged.xml @@ -12,6 +12,7 @@ 1500 Organic StoneWall + 1.5 diff --git a/binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry_javelinist.xml b/binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry_javelinist.xml index 5617109d77..3195b2f97f 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry_javelinist.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_champion_cavalry_javelinist.xml @@ -15,16 +15,17 @@ 50.0 1200 2000 - - - Infantry Bow - 2.0 - - - Cavalry Sword - 1.5 - - + 1.5 + + + Infantry Bow + 2.0 + + + Cavalry Sword + 1.5 + + StoneWall diff --git a/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_archer.xml b/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_archer.xml index 1dbc01456b..f1c46dbd70 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_archer.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_archer.xml @@ -15,16 +15,17 @@ 75.0 1200 2000 - - - Infantry Sword - 2.0 - - - Cavalry Spear - 1.5 - - + 1.5 + + + Infantry Sword + 2.0 + + + Cavalry Spear + 1.5 + + Organic StoneWall diff --git a/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_javelinist.xml b/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_javelinist.xml index cefb140905..f5dc4cfa7e 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_javelinist.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_champion_infantry_javelinist.xml @@ -15,24 +15,25 @@ 50.0 1200 2000 - - - Infantry Spear - 1.5 - - - Cavalry Bow - 1.5 - - - Elephant - 1.5 - - - Chariot - 1.5 - - + 1.5 + + + Infantry Spear + 1.5 + + + Cavalry Bow + 1.5 + + + Elephant + 1.5 + + + Chariot + 1.5 + + Organic StoneWall diff --git a/binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_archer.xml b/binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_archer.xml index db0d130d9d..05a0a2072a 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_archer.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_hero_cavalry_archer.xml @@ -15,16 +15,17 @@ 75.0 1200 2000 - - - Infantry Spear - 2.0 - - - Infantry Sword - 1.5 - - + 1.5 + + + Infantry Spear + 2.0 + + + Infantry Sword + 1.5 + + diff --git a/binaries/data/mods/public/simulation/templates/template_unit_hero_ranged.xml b/binaries/data/mods/public/simulation/templates/template_unit_hero_ranged.xml index 43081adfdf..6473439609 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_hero_ranged.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_hero_ranged.xml @@ -14,6 +14,7 @@ 25.0 1200 2000 + 1.5 diff --git a/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml b/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml index 560b817358..fafc67d327 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_infantry_ranged.xml @@ -10,6 +10,7 @@ 28.0 1200 2000 + 1.5 Organic StoneWall diff --git a/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_bireme.xml b/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_bireme.xml index ac0e53f6d5..e162f725e1 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_bireme.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_bireme.xml @@ -15,6 +15,7 @@ 60.0 2000 2000 + 1.5 diff --git a/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_quinquereme.xml b/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_quinquereme.xml index 84b1c0552e..9edcf40ca2 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_quinquereme.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_quinquereme.xml @@ -15,6 +15,7 @@ 40.0 2000 4000 + 1.5 diff --git a/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_trireme.xml b/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_trireme.xml index 86c53455db..0ec2d706ce 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_trireme.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_trireme.xml @@ -15,6 +15,7 @@ 60.0 1000 2000 + 1.5 diff --git a/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_ballista.xml b/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_ballista.xml index f3c643fb13..79b442aae0 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_ballista.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_ballista.xml @@ -7,19 +7,28 @@ 0.0 - 50.0 - 50.0 + 20.0 + 20.0 60 8.0 + 6.0 60.0 5000 5000 - - - Organic - 2.0 - - + + + Organic + 2.0 + + + + Linear + 12 + false + 0.0 + 30.0 + 30.0 + diff --git a/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_onager.xml b/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_onager.xml index bee696e698..2519059700 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_onager.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_onager.xml @@ -6,20 +6,29 @@ - 50.0 + 40.0 0.0 - 50.0 + 40.0 68 12.0 30.0 5000 5000 + 6.0 Structure 2.0 + + Circular + 12 + true + 12.0 + 0.0 + 12.0 + diff --git a/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_tower.xml b/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_tower.xml index 5e6d2b9e11..72b8523c67 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_tower.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_tower.xml @@ -14,6 +14,7 @@ 75.0 1200 2000 + 1.5 diff --git a/binaries/data/mods/public/simulation/templates/units/theb_mechanical_siege_fireraiser.xml b/binaries/data/mods/public/simulation/templates/units/theb_mechanical_siege_fireraiser.xml index 019372cfca..7d56409857 100644 --- a/binaries/data/mods/public/simulation/templates/units/theb_mechanical_siege_fireraiser.xml +++ b/binaries/data/mods/public/simulation/templates/units/theb_mechanical_siege_fireraiser.xml @@ -10,6 +10,7 @@ 10.0 2000 2000 + 1.5 diff --git a/source/simulation2/components/CCmpObstructionManager.cpp b/source/simulation2/components/CCmpObstructionManager.cpp index 68089e8092..2f5eed0167 100644 --- a/source/simulation2/components/CCmpObstructionManager.cpp +++ b/source/simulation2/components/CCmpObstructionManager.cpp @@ -944,9 +944,18 @@ bool CCmpObstructionManager::FindMostImportantObstruction(const IObstructionTest // Then look for obstructions that cover the target point when expanded by r // (i.e. if the target is not inside an object but closer than we can get to it) - - // TODO: actually do that - // (This might matter when you tell a unit to walk too close to the edge of a building) + + GetObstructionsInRange(filter, x-r, z-r, x+r, z+r, squares); + // Building squares are more important but returned last, so check backwards + for (std::vector::reverse_iterator it = squares.rbegin(); it != squares.rend(); ++it) + { + CFixedVector2D halfSize(it->hw + r, it->hh + r); + if (Geometry::PointIsInSquare(CFixedVector2D(it->x, it->z) - center, it->u, it->v, halfSize)) + { + square = *it; + return true; + } + } return false; } diff --git a/source/simulation2/components/CCmpPosition.cpp b/source/simulation2/components/CCmpPosition.cpp index 94e0385721..984e9c6b88 100644 --- a/source/simulation2/components/CCmpPosition.cpp +++ b/source/simulation2/components/CCmpPosition.cpp @@ -66,7 +66,9 @@ public: // Dynamic state: bool m_InWorld; - entity_pos_t m_X, m_Z, m_LastX, m_LastZ; // these values contain undefined junk if !InWorld + // m_LastX/Z contain the position from the start of the most recent turn + // m_PrevX/Z conatain the position from the turn before that + entity_pos_t m_X, m_Z, m_LastX, m_LastZ, m_PrevX, m_PrevZ; // these values contain undefined junk if !InWorld entity_pos_t m_YOffset; bool m_RelativeToGround; // whether m_YOffset is relative to terrain/water plane, or an absolute height @@ -201,8 +203,8 @@ public: if (!m_InWorld) { m_InWorld = true; - m_LastX = m_X; - m_LastZ = m_Z; + m_LastX = m_PrevX = m_X; + m_LastZ = m_PrevZ = m_Z; } AdvertisePositionChanges(); @@ -210,8 +212,8 @@ public: virtual void JumpTo(entity_pos_t x, entity_pos_t z) { - m_LastX = m_X = x; - m_LastZ = m_Z = z; + m_LastX = m_PrevX = m_X = x; + m_LastZ = m_PrevZ = m_Z = z; m_InWorld = true; AdvertisePositionChanges(); @@ -278,6 +280,43 @@ public: return CFixedVector2D(m_X, m_Z); } + virtual CFixedVector3D GetPreviousPosition() + { + if (!m_InWorld) + { + LOGERROR(L"CCmpPosition::GetPreviousPosition called on entity when IsInWorld is false"); + return CFixedVector3D(); + } + + entity_pos_t baseY; + if (m_RelativeToGround) + { + CmpPtr cmpTerrain(GetSimContext(), SYSTEM_ENTITY); + if (cmpTerrain) + baseY = cmpTerrain->GetGroundLevel(m_PrevX, m_PrevZ); + + if (m_Floating) + { + CmpPtr cmpWaterMan(GetSimContext(), SYSTEM_ENTITY); + if (cmpWaterMan) + baseY = std::max(baseY, cmpWaterMan->GetWaterLevel(m_PrevX, m_PrevZ)); + } + } + + return CFixedVector3D(m_PrevX, baseY + m_YOffset, m_PrevZ); + } + + virtual CFixedVector2D GetPreviousPosition2D() + { + if (!m_InWorld) + { + LOGERROR(L"CCmpPosition::GetPreviousPosition2D called on entity when IsInWorld is false"); + return CFixedVector2D(); + } + + return CFixedVector2D(m_PrevX, m_PrevZ); + } + virtual void TurnTo(entity_angle_t y) { m_RotY = y; @@ -408,6 +447,10 @@ public: } case MT_TurnStart: { + // Store the positions from the turn before + m_PrevX = m_LastX; + m_PrevZ = m_LastZ; + m_LastX = m_X; m_LastZ = m_Z; diff --git a/source/simulation2/components/CCmpProjectileManager.cpp b/source/simulation2/components/CCmpProjectileManager.cpp index 8444427c79..b61261c38e 100644 --- a/source/simulation2/components/CCmpProjectileManager.cpp +++ b/source/simulation2/components/CCmpProjectileManager.cpp @@ -20,6 +20,8 @@ #include "simulation2/system/Component.h" #include "ICmpProjectileManager.h" +#include "ICmpObstruction.h" +#include "ICmpObstructionManager.h" #include "ICmpPosition.h" #include "ICmpRangeManager.h" #include "ICmpTerrain.h" @@ -58,6 +60,7 @@ public: virtual void Init(const CParamNode& UNUSED(paramNode)) { m_ActorSeed = 0; + m_Id = 1; } virtual void Deinit() @@ -86,7 +89,7 @@ public: case MT_Interpolate: { const CMessageInterpolate& msgData = static_cast (msg); - Interpolate(msgData.frameTime, msgData.offset); + Interpolate(msgData.frameTime); break; } case MT_RenderSubmit: @@ -98,15 +101,12 @@ public: } } - virtual void LaunchProjectileAtEntity(entity_id_t source, entity_id_t target, fixed speed, fixed gravity) + virtual uint32_t LaunchProjectileAtPoint(entity_id_t source, CFixedVector3D target, fixed speed, fixed gravity) { - LaunchProjectile(source, CFixedVector3D(), target, speed, gravity); - } - - virtual void LaunchProjectileAtPoint(entity_id_t source, CFixedVector3D target, fixed speed, fixed gravity) - { - LaunchProjectile(source, target, INVALID_ENTITY, speed, gravity); + return LaunchProjectile(source, target, speed, gravity); } + + virtual void RemoveProjectile(uint32_t); private: struct Projectile @@ -114,36 +114,38 @@ private: CUnit* unit; CVector3D pos; CVector3D target; - entity_id_t targetEnt; // INVALID_ENTITY if the target is just a point float timeLeft; float speedFactor; float gravity; bool stopped; + uint32_t id; }; std::vector m_Projectiles; uint32_t m_ActorSeed; + + uint32_t m_Id; - void LaunchProjectile(entity_id_t source, CFixedVector3D targetPoint, entity_id_t targetEnt, fixed speed, fixed gravity); + uint32_t LaunchProjectile(entity_id_t source, CFixedVector3D targetPoint, fixed speed, fixed gravity); - void AdvanceProjectile(Projectile& projectile, float dt, float frameOffset); + void AdvanceProjectile(Projectile& projectile, float dt); - void Interpolate(float frameTime, float frameOffset); + void Interpolate(float frameTime); void RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling); }; REGISTER_COMPONENT_TYPE(ProjectileManager) -void CCmpProjectileManager::LaunchProjectile(entity_id_t source, CFixedVector3D targetPoint, entity_id_t targetEnt, fixed speed, fixed gravity) +uint32_t CCmpProjectileManager::LaunchProjectile(entity_id_t source, CFixedVector3D targetPoint, fixed speed, fixed gravity) { if (!GetSimContext().HasUnitManager()) - return; // do nothing if graphics are disabled + return 0; // do nothing if graphics are disabled CmpPtr cmpSourceVisual(GetSimContext(), source); if (!cmpSourceVisual) - return; + return 0; std::wstring name = cmpSourceVisual->GetProjectileActor(); if (name.empty()) @@ -151,7 +153,7 @@ void CCmpProjectileManager::LaunchProjectile(entity_id_t source, CFixedVector3D // If the actor was actually loaded, complain that it doesn't have a projectile if (!cmpSourceVisual->GetActorShortName().empty()) LOGERROR(L"Unit with actor '%ls' launched a projectile but has no actor on 'projectile' attachpoint", cmpSourceVisual->GetActorShortName().c_str()); - return; + return 0; } CVector3D sourceVec(cmpSourceVisual->GetProjectileLaunchPoint()); @@ -161,7 +163,7 @@ void CCmpProjectileManager::LaunchProjectile(entity_id_t source, CFixedVector3D CmpPtr sourcePos(GetSimContext(), source); if (!sourcePos) - return; + return 0; sourceVec = sourcePos->GetPosition(); sourceVec.Y += 3.f; @@ -169,31 +171,20 @@ void CCmpProjectileManager::LaunchProjectile(entity_id_t source, CFixedVector3D CVector3D targetVec; - if (targetEnt == INVALID_ENTITY) - { - targetVec = CVector3D(targetPoint); - } - else - { - CmpPtr cmpTargetPosition(GetSimContext(), targetEnt); - if (!cmpTargetPosition) - return; - - targetVec = CVector3D(cmpTargetPosition->GetPosition()); - } + targetVec = CVector3D(targetPoint); Projectile projectile; std::set selections; projectile.unit = GetSimContext().GetUnitManager().CreateUnit(name, m_ActorSeed++, selections); + projectile.id = m_Id++; if (!projectile.unit) { // The error will have already been logged - return; + return 0; } projectile.pos = sourceVec; projectile.target = targetVec; - projectile.targetEnt = targetEnt; CVector3D offset = projectile.target - projectile.pos; float horizDistance = sqrtf(offset.X*offset.X + offset.Z*offset.Z); @@ -205,9 +196,11 @@ void CCmpProjectileManager::LaunchProjectile(entity_id_t source, CFixedVector3D projectile.gravity = gravity.ToFloat(); m_Projectiles.push_back(projectile); + + return projectile.id; } -void CCmpProjectileManager::AdvanceProjectile(Projectile& projectile, float dt, float frameOffset) +void CCmpProjectileManager::AdvanceProjectile(Projectile& projectile, float dt) { // Do special processing if we've already reached the target if (projectile.timeLeft <= 0) @@ -223,24 +216,6 @@ void CCmpProjectileManager::AdvanceProjectile(Projectile& projectile, float dt, // apply a bit of drag to them projectile.speedFactor *= powf(1.0f - 0.4f*projectile.speedFactor, dt); } - else - { - // Projectile hasn't reached the target yet: - // Track the target entity (if there is one, and it's still alive) - if (projectile.targetEnt != INVALID_ENTITY) - { - CmpPtr cmpTargetPosition(GetSimContext(), projectile.targetEnt); - if (cmpTargetPosition && cmpTargetPosition->IsInWorld()) - { - CMatrix3D t = cmpTargetPosition->GetInterpolatedTransform(frameOffset, false); - projectile.target = t.GetTranslation(); - projectile.target.Y += 2.f; // TODO: ought to aim towards a random point in the solid body of the target - - // TODO: if the unit is moving, we should probably aim a bit in front of it - // so we don't have to curve so much just before reaching it - } - } - } CVector3D offset = (projectile.target - projectile.pos) * projectile.speedFactor; @@ -296,11 +271,11 @@ void CCmpProjectileManager::AdvanceProjectile(Projectile& projectile, float dt, projectile.unit->GetModel().SetTransform(transform); } -void CCmpProjectileManager::Interpolate(float frameTime, float frameOffset) +void CCmpProjectileManager::Interpolate(float frameTime) { for (size_t i = 0; i < m_Projectiles.size(); ++i) { - AdvanceProjectile(m_Projectiles[i], frameTime, frameOffset); + AdvanceProjectile(m_Projectiles[i], frameTime); } // Remove the ones that have reached their target @@ -310,7 +285,7 @@ void CCmpProjectileManager::Interpolate(float frameTime, float frameOffset) // Those hitting the ground stay for a while, because it looks pretty. if (m_Projectiles[i].timeLeft <= 0.f) { - if (m_Projectiles[i].targetEnt == INVALID_ENTITY && m_Projectiles[i].timeLeft > -PROJECTILE_DECAY_TIME) + if (m_Projectiles[i].timeLeft > -PROJECTILE_DECAY_TIME) { // Keep the projectile until it exceeds the decay time } @@ -328,6 +303,22 @@ void CCmpProjectileManager::Interpolate(float frameTime, float frameOffset) } } +void CCmpProjectileManager::RemoveProjectile(uint32_t id) +{ + // Scan through the projectile list looking for one with the correct id to remove + for (size_t i = 0; i < m_Projectiles.size(); i++) + { + if (m_Projectiles[i].id == id) + { + // Delete in-place by swapping with the last in the list + std::swap(m_Projectiles[i], m_Projectiles.back()); + GetSimContext().GetUnitManager().DeleteUnit(m_Projectiles.back().unit); + m_Projectiles.pop_back(); + return; + } + } +} + void CCmpProjectileManager::RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling) { CmpPtr cmpRangeManager(GetSimContext(), SYSTEM_ENTITY); diff --git a/source/simulation2/components/ICmpPosition.cpp b/source/simulation2/components/ICmpPosition.cpp index 1e3c8b0efb..9aaeecc801 100644 --- a/source/simulation2/components/ICmpPosition.cpp +++ b/source/simulation2/components/ICmpPosition.cpp @@ -32,6 +32,8 @@ DEFINE_INTERFACE_METHOD_1("SetHeightFixed", void, ICmpPosition, SetHeightFixed, DEFINE_INTERFACE_METHOD_0("IsFloating", bool, ICmpPosition, IsFloating) DEFINE_INTERFACE_METHOD_0("GetPosition", CFixedVector3D, ICmpPosition, GetPosition) DEFINE_INTERFACE_METHOD_0("GetPosition2D", CFixedVector2D, ICmpPosition, GetPosition2D) +DEFINE_INTERFACE_METHOD_0("GetPreviousPosition", CFixedVector3D, ICmpPosition, GetPreviousPosition) +DEFINE_INTERFACE_METHOD_0("GetPreviousPosition2D", CFixedVector2D, ICmpPosition, GetPreviousPosition2D) DEFINE_INTERFACE_METHOD_1("TurnTo", void, ICmpPosition, TurnTo, entity_angle_t) DEFINE_INTERFACE_METHOD_1("SetYRotation", void, ICmpPosition, SetYRotation, entity_angle_t) DEFINE_INTERFACE_METHOD_2("SetXZRotation", void, ICmpPosition, SetXZRotation, entity_angle_t, entity_angle_t) diff --git a/source/simulation2/components/ICmpPosition.h b/source/simulation2/components/ICmpPosition.h index 4a4b274534..3e454ced41 100644 --- a/source/simulation2/components/ICmpPosition.h +++ b/source/simulation2/components/ICmpPosition.h @@ -108,6 +108,19 @@ public: */ virtual CFixedVector2D GetPosition2D() = 0; + /** + * Returns the previous turn's x,y,z position (no interpolation). + * Depends on the current terrain heightmap. + * Must not be called unless IsInWorld is true. + */ + virtual CFixedVector3D GetPreviousPosition() = 0; + + /** + * Returns the previous turn's x,z position (no interpolation). + * Must not be called unless IsInWorld is true. + */ + virtual CFixedVector2D GetPreviousPosition2D() = 0; + /** * Rotate smoothly to the given angle around the upwards axis. * @param y clockwise radians from the +Z axis. diff --git a/source/simulation2/components/ICmpProjectileManager.cpp b/source/simulation2/components/ICmpProjectileManager.cpp index f0bc5a552d..c235a96b44 100644 --- a/source/simulation2/components/ICmpProjectileManager.cpp +++ b/source/simulation2/components/ICmpProjectileManager.cpp @@ -22,6 +22,6 @@ #include "simulation2/system/InterfaceScripted.h" BEGIN_INTERFACE_WRAPPER(ProjectileManager) -DEFINE_INTERFACE_METHOD_4("LaunchProjectileAtEntity", void, ICmpProjectileManager, LaunchProjectileAtEntity, entity_id_t, entity_id_t, fixed, fixed) -DEFINE_INTERFACE_METHOD_4("LaunchProjectileAtPoint", void, ICmpProjectileManager, LaunchProjectileAtPoint, entity_id_t, CFixedVector3D, fixed, fixed) +DEFINE_INTERFACE_METHOD_4("LaunchProjectileAtPoint", uint32_t, ICmpProjectileManager, LaunchProjectileAtPoint, entity_id_t, CFixedVector3D, fixed, fixed) +DEFINE_INTERFACE_METHOD_1("RemoveProjectile", void, ICmpProjectileManager, RemoveProjectile, uint32_t) END_INTERFACE_WRAPPER(ProjectileManager) diff --git a/source/simulation2/components/ICmpProjectileManager.h b/source/simulation2/components/ICmpProjectileManager.h index 0b80b16466..399b27d5a6 100644 --- a/source/simulation2/components/ICmpProjectileManager.h +++ b/source/simulation2/components/ICmpProjectileManager.h @@ -31,14 +31,6 @@ class ICmpProjectileManager : public IComponent { public: - /** - * Launch a projectile from entity @p source to entity @p target. - * @param source source entity; the projectile will determined from the "projectile" prop in its actor - * @param target target entity; the projectile will automatically track the target to ensure it always hits precisely - * @param speed horizontal speed in m/s - * @param gravity gravitational acceleration in m/s^2 (determines the height of the ballistic curve) - */ - virtual void LaunchProjectileAtEntity(entity_id_t source, entity_id_t target, fixed speed, fixed gravity) = 0; /** * Launch a projectile from entity @p source to point @p target. @@ -46,8 +38,15 @@ public: * @param target target point * @param speed horizontal speed in m/s * @param gravity gravitational acceleration in m/s^2 (determines the height of the ballistic curve) + * @return id of the created projectile */ - virtual void LaunchProjectileAtPoint(entity_id_t source, CFixedVector3D target, fixed speed, fixed gravity) = 0; + virtual uint32_t LaunchProjectileAtPoint(entity_id_t source, CFixedVector3D target, fixed speed, fixed gravity) = 0; + + /** + * Removes a projectile, used when the projectile has hit a target + * @param id of the projectile to remove + */ + virtual void RemoveProjectile(uint32_t id) = 0; DECLARE_INTERFACE_TYPE(ProjectileManager) }; diff --git a/source/simulation2/components/tests/test_RangeManager.h b/source/simulation2/components/tests/test_RangeManager.h index e965ff0091..cf2ee7134d 100644 --- a/source/simulation2/components/tests/test_RangeManager.h +++ b/source/simulation2/components/tests/test_RangeManager.h @@ -50,6 +50,8 @@ public: virtual bool IsFloating() { return false; } virtual CFixedVector3D GetPosition() { return CFixedVector3D(); } virtual CFixedVector2D GetPosition2D() { return CFixedVector2D(); } + virtual CFixedVector3D GetPreviousPosition() { return CFixedVector3D(); } + virtual CFixedVector2D GetPreviousPosition2D() { return CFixedVector2D(); } virtual void TurnTo(entity_angle_t UNUSED(y)) { } virtual void SetYRotation(entity_angle_t UNUSED(y)) { } virtual void SetXZRotation(entity_angle_t UNUSED(x), entity_angle_t UNUSED(z)) { } diff --git a/source/simulation2/docs/SimulationDocs.h b/source/simulation2/docs/SimulationDocs.h index fbdfb7fd0e..3eebd59cde 100644 --- a/source/simulation2/docs/SimulationDocs.h +++ b/source/simulation2/docs/SimulationDocs.h @@ -395,7 +395,8 @@ exposed (in template files etc) with the name "ExampleTwo", and implementing the The @c Init and @c Deinit functions are optional. Unlike C++, there are no @c Serialize/Deserialize functions - each JS component instance is automatically serialized and restored. (This automatic serialization restricts what you can store as properties in the object - e.g. you cannot store function closures, -because they're too hard to serialize. The details should be documented on some other page eventually.) +because they're too hard to serialize. This will serialize Strings, numbers, bools, null, undefined, arrays of serializable +values whose property names are purely numeric, objects whose properties are serializable values. Cyclic structures are allowed.) Instead of @c ClassInit and @c HandleMessage, you simply add functions of the form OnMessageType. (If you want the equivalent of SubscribeGloballyToMessageType, then use OnGlobalMessageType instead.)