Index: binaries/data/mods/public/gui/credits/texts/programming.json
===================================================================
--- binaries/data/mods/public/gui/credits/texts/programming.json (revision 18608)
+++ binaries/data/mods/public/gui/credits/texts/programming.json (working copy)
@@ -110,6 +110,7 @@
{"nick": "kingadami", "name": "Adam Winsor"},
{"nick": "kingbasil", "name": "Giannis Fafalios"},
{"nick": "lafferjm", "name": "Justin Lafferty"},
+ {"nick": "LeanderH", "name": "Leander Hemelhof"},
{"nick": "leper", "name": "Georg Kilzer"},
{"nick": "LittleDev"},
{"nick": "livingaftermidnight", "name": "Will Dull"},
Index: binaries/data/mods/public/simulation/components/Attack.js
===================================================================
--- binaries/data/mods/public/simulation/components/Attack.js (revision 18608)
+++ binaries/data/mods/public/simulation/components/Attack.js (working copy)
@@ -460,6 +460,12 @@
*/
Attack.prototype.PerformAttack = function(type, target)
{
+ let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
+ if (!cmpOwnership || cmpOwnership.GetOwner() == -1)
+ return;
+ let attackerOwner = cmpOwnership.GetOwner();
+
+ let cmpDamage = Engine.QueryInterface(SYSTEM_ENTITY, IID_Damage);
// If this is a ranged attack, then launch a projectile
if (type == "Ranged")
{
@@ -527,16 +533,27 @@
let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);
let id = cmpProjectileManager.LaunchProjectileAtPoint(this.entity, realTargetPosition, horizSpeed, gravity);
- let playerId = Engine.QueryInterface(this.entity, IID_Ownership).GetOwner();
cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
- cmpTimer.SetTimeout(this.entity, IID_Attack, "MissileHit", timeToTarget * 1000, {
+ let data = {
"type": type,
+ "attacker": this.entity,
"target": target,
+ "strengths": this.GetAttackStrengths(type),
"position": realTargetPosition,
"direction": missileDirection,
"projectileId": id,
- "playerId":playerId
- });
+ "multiplier": this.GetAttackBonus(type, target),
+ "isSplash": false,
+ "attackerOwner": attackerOwner
+ };
+ if (this.template.Ranged.Splash)
+ {
+ data.friendlyFire = this.template.Ranged.Splash.FriendlyFire;
+ data.radius = +this.template.Ranged.Splash.Range;
+ data.shape = this.template.Ranged.Splash.Shape;
+ data.isSplash = true;
+ }
+ cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Damage, "MissileHit", timeToTarget * 1000, data);
}
else if (type == "Capture")
{
@@ -546,149 +563,34 @@
return;
multiplier *= cmpHealth.GetMaxHitpoints() / (0.1 * cmpHealth.GetMaxHitpoints() + 0.9 * cmpHealth.GetHitpoints());
- let cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
- if (!cmpOwnership || cmpOwnership.GetOwner() == -1)
- return;
-
- let owner = cmpOwnership.GetOwner();
let cmpCapturable = Engine.QueryInterface(target, IID_Capturable);
- if (!cmpCapturable || !cmpCapturable.CanCapture(owner))
+ if (!cmpCapturable || !cmpCapturable.CanCapture(attackerOwner))
return;
let strength = this.GetAttackStrengths("Capture").value * multiplier;
- if (cmpCapturable.Reduce(strength, owner))
+ if (cmpCapturable.Reduce(strength, attackerOwner))
Engine.PostMessage(target, MT_Attacked, {
"attacker": this.entity,
"target": target,
"type": type,
- "damage": strength
+ "damage": strength,
+ "attackerOwner": attackerOwner
});
}
else
{
// Melee attack - hurt the target immediately
- Damage.CauseDamage({
+ cmpDamage.CauseDamage({
"strengths": this.GetAttackStrengths(type),
"target": target,
"attacker": this.entity,
"multiplier": this.GetAttackBonus(type, target),
- "type":type
+ "type":type,
+ "attackerOwner": attackerOwner
});
}
};
-Attack.prototype.InterpolatedLocation = function(ent, lateness)
-{
- let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
- let turnLength = cmpTimer.GetLatestTurnLength()/1000;
- let cmpTargetPosition = Engine.QueryInterface(ent, IID_Position);
- if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld()) // TODO: handle dead target properly
- return undefined;
- let curPos = cmpTargetPosition.GetPosition();
- let prevPos = cmpTargetPosition.GetPreviousPosition();
- lateness /= 1000;
- return new Vector3D((curPos.x * (turnLength - lateness) + prevPos.x * lateness) / turnLength,
- 0,
- (curPos.z * (turnLength - lateness) + prevPos.z * lateness) / turnLength);
-};
-
-// Tests whether it point is inside of ent's footprint
-Attack.prototype.testCollision = function(ent, point, lateness)
-{
- let targetPosition = this.InterpolatedLocation(ent, lateness);
- if (!targetPosition)
- return false;
- let cmpFootprint = Engine.QueryInterface(ent, IID_Footprint);
- if (!cmpFootprint)
- return false;
- let targetShape = cmpFootprint.GetShape();
-
- if (!targetShape || !targetPosition)
- return false;
-
- if (targetShape.type === 'circle')
- {
- // Use VectorDistanceSquared and square targetShape.radius to avoid square roots.
- return (targetPosition.horizDistanceTo(point) < (targetShape.radius * targetShape.radius));
- }
- else
- {
- let angle = Engine.QueryInterface(ent, IID_Position).GetRotation().y;
-
- let d = Vector3D.sub(point, targetPosition);
- d = Vector2D.from3D(d).rotate(-angle);
-
- return d.x < Math.abs(targetShape.width/2) && d.y < Math.abs(targetShape.depth/2);
- }
-};
-
-Attack.prototype.MissileHit = function(data, lateness)
-{
- let targetPosition = this.InterpolatedLocation(data.target, lateness);
- if (!targetPosition)
- return;
-
- // Do this first in case the direct hit kills the target
- if (this.template.Ranged.Splash)
- {
- let playersToDamage;
-
- if (this.template.Ranged.Splash.FriendlyFire == "false")
- {
- let cmpPlayer = QueryPlayerIDInterface(data.playerId);
- playersToDamage = cmpPlayer.GetEnemies();
- }
-
- Damage.CauseSplashDamage({
- "attacker": this.entity,
- "origin": Vector2D.from3D(data.position),
- "radius": this.template.Ranged.Splash.Range,
- "shape": this.template.Ranged.Splash.Shape,
- "strengths": this.GetAttackStrengths(data.type),
- "direction": data.direction,
- "playersToDamage": playersToDamage,
- "type": data.type
- });
- }
-
- if (this.testCollision(data.target, data.position, lateness))
- {
- data.attacker = this.entity;
- data.multiplier = this.GetAttackBonus(data.type, data.target);
- data.strengths = this.GetAttackStrengths(data.type);
- // Hit the primary target
- Damage.CauseDamage(data);
-
- // Remove the projectile
- let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);
- cmpProjectileManager.RemoveProjectile(data.projectileId);
- }
- else
- {
- // If we didn't hit the main target look for nearby units
- let cmpPlayer = QueryPlayerIDInterface(data.playerId);
- let ents = Damage.EntitiesNearPoint(Vector2D.from3D(data.position), targetPosition.horizDistanceTo(data.position) * 2, cmpPlayer.GetEnemies());
-
- for (let i = 0; i < ents.length; ++i)
- {
- if (!this.testCollision(ents[i], data.position, lateness))
- continue;
-
- Damage.CauseDamage({
- "strengths": this.GetAttackStrengths(data.type),
- "target": ents[i],
- "attacker": this.entity,
- "multiplier": this.GetAttackBonus(data.type, ents[i]),
- "type": data.type
- });
-
- // Remove the projectile
- let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);
- cmpProjectileManager.RemoveProjectile(data.projectileId);
- }
- }
-};
-
Attack.prototype.OnValueModification = function(msg)
{
if (msg.component != "Attack")
Index: binaries/data/mods/public/simulation/components/AttackDetection.js
===================================================================
--- binaries/data/mods/public/simulation/components/AttackDetection.js (revision 18608)
+++ binaries/data/mods/public/simulation/components/AttackDetection.js (working copy)
@@ -50,23 +50,26 @@
Engine.PostMessage(msg.target, MT_MinimapPing);
- this.AttackAlert(msg.target, msg.attacker);
+ this.AttackAlert(msg.target, msg.attacker, msg.attackerOwner);
};
//// External interface ////
-AttackDetection.prototype.AttackAlert = function(target, attacker)
+AttackDetection.prototype.AttackAlert = function(target, attacker, attackerOwner)
{
- var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
- var cmpTargetOwnership = Engine.QueryInterface(target, IID_Ownership);
+ let cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
+ if (!cmpPlayer)
+ return;
+ let playerID = cmpPlayer.GetPlayerID()
+ let cmpTargetPlayer = QueryOwnerInterface(target);
// Don't register attacks dealt against other players
- if (cmpTargetOwnership.GetOwner() != cmpPlayer.GetPlayerID())
+ if (!cmpTargetPlayer || cmpTargetPlayer != cmpPlayer)
return;
- var cmpAttackerOwnership = Engine.QueryInterface(attacker, IID_Ownership);
+
// Don't register attacks dealt by myself
- if (cmpAttackerOwnership.GetOwner() == cmpPlayer.GetPlayerID())
+ if (attackerOwner == playerID)
return;
-
+
// Since livestock can be attacked/gathered by other players
// and generally are not so valuable as other units/buildings,
// we have a lower priority notification for it, which can be
@@ -117,12 +120,12 @@
if (!isPriorityIncreased)
this.AddSuppression(event);
- Engine.PostMessage(this.entity, MT_AttackDetected, { "player": cmpPlayer.GetPlayerID(), "event": event });
+ Engine.PostMessage(this.entity, MT_AttackDetected, { "player": playerID, "event": event });
Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).PushNotification({
"type": "attack",
"target": target,
- "players": [cmpPlayer.GetPlayerID()],
- "attacker": cmpAttackerOwnership.GetOwner(),
+ "players": [playerID],
+ "attacker": attackerOwner,
"targetIsDomesticAnimal": targetIsDomesticAnimal
});
PlaySound("attacked", target);
Index: binaries/data/mods/public/simulation/components/Damage.js
===================================================================
--- binaries/data/mods/public/simulation/components/Damage.js (nonexistent)
+++ binaries/data/mods/public/simulation/components/Damage.js (working copy)
@@ -0,0 +1,288 @@
+function Damage() {}
+
+Damage.prototype.Schema =
+ "";
+
+Damage.prototype.Init = function(){};
+
+/**
+ * Gives the position of the given entity, taking the lateness into account.
+ * @param {number} ent - entity id of the entity we are finding the location for.
+ * @param {number} lateness - the time passed since the expected time to fire the function.
+ * @return {Vector3D} - the location of the entity.
+ */
+Damage.prototype.InterpolatedLocation = function(ent, lateness)
+{
+ let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
+ let turnLength = cmpTimer.GetLatestTurnLength() / 1000;
+ let cmpTargetPosition = Engine.QueryInterface(ent, IID_Position);
+ if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld()) // TODO: handle dead target properly
+ return undefined;
+ let curPos = cmpTargetPosition.GetPosition();
+ let prevPos = cmpTargetPosition.GetPreviousPosition();
+ lateness /= 1000;
+ return new Vector3D(
+ (curPos.x * (turnLength - lateness) + prevPos.x * lateness) / turnLength,
+ 0,
+ (curPos.z * (turnLength - lateness) + prevPos.z * lateness) / turnLength);
+};
+
+/**
+ * Test if a point is inside of an entity's footprint.
+ * @param {int} ent - id of the entity we are checking with.
+ * @param {Vector3D} point - the point we are checking with.
+ * @param {int} lateness - the time passed since the expected time to fire the function.
+ * @return {boolean} - true if the point is inside of the entity's footprint.
+ */
+Damage.prototype.TestCollision = function(ent, point, lateness)
+{
+ let targetPosition = this.InterpolatedLocation(ent, lateness);
+ if (!targetPosition)
+ return false;
+
+ let cmpFootprint = Engine.QueryInterface(ent, IID_Footprint);
+ if (!cmpFootprint)
+ return false;
+
+ let targetShape = cmpFootprint.GetShape();
+
+ if (!targetShape || !targetPosition)
+ return false;
+
+ if (targetShape.type === 'circle')
+ {
+ // Use VectorDistanceSquared and square targetShape.radius to avoid square roots.
+ return targetPosition.horizDistanceTo(point) < (targetShape.radius * targetShape.radius);
+ }
+ else
+ {
+ let angle = Engine.QueryInterface(ent, IID_Position).GetRotation().y;
+
+ let distance = Vector3D.sub(point, targetPosition);
+ distance = Vector2D.from3D(distance).rotate(-angle);
+
+ return distance.x < Math.abs(targetShape.width/2) && distance.y < Math.abs(targetShape.depth/2);
+ }
+};
+
+/**
+ * Handles hit logic after the projectile travel time has passed.
+ * @param {Object} data - the data sent by the caller.
+ * @param {number} data.attacker - the entity id of the attacker.
+ * @param {number} data.target - the entity id of the target.
+ * @param {Vector2D} data.origin - the origin of the projectile hit.
+ * @param {Object} data.strengths - data of the form { 'hack': , 'pierce': , 'crush': }.
+ * @param {string} data.type - the type of damage.
+ * @param {number} data.attackerOwner - the player id of the owner of the attacker.
+ * @param {boolean} data.isSplash - a flag indicating if it's splash damage.
+ * @param {Vector3D} data.position - the expected position of the target.
+ * @param {number} data.projectileId - the id of the projectile.
+ * @param {Vector3D} data.direction - The unit vector defining the direction
+ * ***When splash damage***
+ * @param {boolean} data.friendlyFire - a flag indicating if allied entities are also damaged.
+ * @param {number} data.radius - the radius of the splash damage.
+ * @param {string} data.shape - the shape of the splash range.
+ */
+Damage.prototype.MissileHit = function(data, lateness)
+{
+ let targetPosition = this.InterpolatedLocation(data.target, lateness);
+ if (!targetPosition || !data.position)
+ return;
+
+ // Do this first in case the direct hit kills the target
+ if (data.isSplash)
+ {
+ let playersToDamage;
+
+ if (!data.friendlyFire)
+ {
+ let cmpPlayer = QueryPlayerIDInterface(data.attackerOwner);
+ playersToDamage = cmpPlayer.GetEnemies();
+ }
+
+ this.CauseSplashDamage({
+ "attacker": data.attacker,
+ "origin": Vector2D.from3D(data.position),
+ "radius": data.radius,
+ "shape": data.shape,
+ "strengths": data.strengths,
+ "direction": data.direction,
+ "playersToDamage": playersToDamage,
+ "type": data.type,
+ "attackerOwner": data.attackerOwner
+ });
+ }
+
+ if (this.TestCollision(data.target, data.position, lateness))
+ {
+ this.CauseDamage(data);
+
+ let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);
+ cmpProjectileManager.RemoveProjectile(data.projectileId);
+ }
+ else
+ {
+ // If we didn't hit the main target look for nearby units
+ let cmpPlayer = QueryPlayerIDInterface(data.attackerOwner);
+ let ents = this.EntitiesNearPoint(Vector2D.from3D(data.position), targetPosition.horizDistanceTo(data.position) * 2, cmpPlayer.GetEnemies());
+
+ for (let ent of ents)
+ {
+ if (!this.TestCollision(ent, data.position, lateness))
+ continue;
+
+ this.CauseDamage({
+ "strengths": data.strengths,
+ "target": ent,
+ "attacker": data.attacker,
+ "multiplier": data.multiplier,
+ "type": data.type,
+ "attackerOwner": data.attackerOwner
+ });
+
+ let cmpProjectileManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ProjectileManager);
+ cmpProjectileManager.RemoveProjectile(data.projectileId);
+ }
+ }
+};
+
+/**
+ * Damages units around a given origin.
+ * @param {Object} data - the data sent by the caller.
+ * @param {number} data.attacker - the entity id of the attacker.
+ * @param {Vector2D} data.origin - the origin of the projectile hit.
+ * @param {number} data.radius - the radius of the splash damage.
+ * @param {string} data.shape - the shape of the radius.
+ * @param {Object} data.strengths - data of the form { 'hack': , 'pierce': , 'crush': }.
+ * @param {string} data.type - the type of damage.
+ * @param {number} data.attackerOwner - the player id of the attacker.
+ * @param {Vector3D} data.direction - the unit vector defining the direction.
+ * @param {number[]} [data.playersToDamage] - the array of player id's to damage.
+ */
+Damage.prototype.CauseSplashDamage = function(data)
+{
+ // Get nearby entities and define variables
+ let nearEnts = this.EntitiesNearPoint(data.origin, data.radius, data.playersToDamage);
+ let damageMultiplier = 1;
+ // Cycle through all the nearby entities and damage it appropriately based on its distance from the origin.
+ for (let ent of nearEnts)
+ {
+ let entityPosition = Engine.QueryInterface(ent, IID_Position).GetPosition2D();
+ if (data.shape == 'Circular') // circular effect with quadratic falloff in every direction
+ damageMultiplier = 1 - data.origin.distanceToSquared(entityPosition) / (data.radius * data.radius);
+ else if (data.shape == 'Linear') // linear effect with quadratic falloff in two directions (only used for certain missiles)
+ {
+ // Get position of entity relative to splash origin.
+ let relativePos = entityPosition.sub(data.origin);
+
+ // The width of linear splash is one fifth of the normal splash radius.
+ let width = data.radius/5;
+
+ // Effectivly rotate the axis to align with the missile direction.
+ let parallelDist = relativePos.dot(data.direction); // z axis
+ let perpDist = Math.abs(relativePos.cross(data.direction)); // y axis
+
+ // Check that the unit is within the distance at which it will get damaged.
+ if (parallelDist > -width && perpDist < width) // If in radius, quadratic falloff in both directions
+ damageMultiplier = (data.radius * data.radius - parallelDist * parallelDist) / (data.radius * data.radius) *
+ (width * width - perpDist * perpDist) / (width * width);
+ else
+ damageMultiplier = 0;
+ }
+ else // In case someone calls this function with an invalid shape.
+ {
+ warn("The " + data.shape + " splash damage shape is not implemented!");
+ }
+ // Call CauseDamage which reduces the hitpoints, posts network command, plays sounds....
+ this.CauseDamage({
+ "strengths":data.strengths,
+ "target":ent,
+ "attacker":data.attacker,
+ "multiplier":damageMultiplier,
+ "type":data.type + ".Splash",
+ "attackerOwner": data.attackerOwner
+ });
+ }
+};
+
+/**
+ * Causes damage on a given unit.
+ * @param {Object} data - the data passed by the caller.
+ * @param {Object} data.strengths - data in the form of { 'hack': , 'pierce': , 'crush': }.
+ * @param {number} data.target - the entity id of the target.
+ * @param {number} data.attacker - the entity id og the attacker.
+ * @param {float} data.multiplier - the damage multiplier (between 0 and 1).
+ * @param {string} data.type - the type of damage.
+ * @param {number} data.attackerOwner - the player id of the attacker.
+ */
+Damage.prototype.CauseDamage = function(data)
+{
+ let cmpDamageReceiver = Engine.QueryInterface(data.target, IID_DamageReceiver);
+ let cmpHealth = Engine.QueryInterface(data.target, IID_Health);
+ if (!cmpDamageReceiver || !cmpHealth)
+ return;
+
+ let targetState = cmpDamageReceiver.TakeDamage(data.strengths.hack * data.multiplier, data.strengths.pierce * data.multiplier, data.strengths.crush * data.multiplier);
+
+ let cmpPromotion = Engine.QueryInterface(data.attacker, IID_Promotion);
+ let cmpLoot = Engine.QueryInterface(data.target, IID_Loot);
+ if (cmpPromotion && cmpLoot && cmpLoot.GetXp() > 0)
+ cmpPromotion.IncreaseXp(cmpLoot.GetXp() * -targetState.change / cmpHealth.GetMaxHitpoints());
+
+ if (targetState.killed)
+ this.TargetKilled(data.attacker, data.target, data.attackerOwner);
+ Engine.PostMessage(data.target, MT_Attacked, { "attacker": data.attacker, "target": data.target, "type": data.type, "damage": -targetState.change, "attackerOwner": data.attackerOwner });
+
+ PlaySound("attack_impact", data.attacker);
+};
+
+/**
+ * Gets entities near a give point for given players.
+ * @param {Vector2D} origin - the point to check around.
+ * @param {number} radius - the radius around the point to check.
+ * @param {number[]} [players] - the players of which we need to check entities. If players is not included, entities from all players are used.
+ * @return {number[]} - the id's of the entities in range of the given point.
+ */
+Damage.prototype.EntitiesNearPoint = function(origin, radius, players)
+{
+ // If there is insufficient data return an empty array.
+ if (!origin || !radius)
+ return [];
+
+ // If the players parameter is not specified use all players.
+ if (!players)
+ {
+ let playerEntities = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetAllPlayerEntities();
+ players = playerEntities.map((ent) => Engine.QueryInterface(ent, IID_Player).GetPlayerID());
+ }
+
+ // Call RangeManager with dummy entity and return the result.
+ let rangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
+ let rangeQuery = rangeManager.ExecuteQueryAroundPos(origin, 0, radius, players, IID_DamageReceiver);
+ return rangeQuery;
+};
+
+/**
+ * Called when a unit kills something (another unit, building, animal etc)
+ * @param {number} killerEntity - the entity id of the killer.
+ * @param {number} targetEntity - the entity id of the target.
+ * @param {number} attackerOwner - the player id of the attacker.
+ */
+Damage.prototype.TargetKilled = function(killerEntity, targetEntity, attackerOwner)
+{
+ // Add to killer statistics.
+ let cmpKillerPlayerStatisticsTracker = QueryPlayerIDInterface(attackerOwner, IID_StatisticsTracker);
+ if (cmpKillerPlayerStatisticsTracker)
+ cmpKillerPlayerStatisticsTracker.KilledEntity(targetEntity);
+ // Add to loser statistics.
+ let cmpTargetPlayerStatisticsTracker = QueryOwnerInterface(targetEntity, IID_StatisticsTracker);
+ if (cmpTargetPlayerStatisticsTracker)
+ cmpTargetPlayerStatisticsTracker.LostEntity(targetEntity);
+
+ // If killer can collect loot, let's try to collect it.
+ let cmpLooter = Engine.QueryInterface(killerEntity, IID_Looter);
+ if (cmpLooter)
+ cmpLooter.Collect(targetEntity);
+};
+
+Engine.RegisterSystemComponentType(IID_Damage, "Damage", Damage);
Property changes on: binaries/data/mods/public/simulation/components/Damage.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: binaries/data/mods/public/simulation/components/interfaces/Attack.js
===================================================================
--- binaries/data/mods/public/simulation/components/interfaces/Attack.js (revision 18608)
+++ binaries/data/mods/public/simulation/components/interfaces/Attack.js (working copy)
@@ -1,7 +1,7 @@
Engine.RegisterInterface("Attack");
/**
- * Message of the form { "attacker": number, "target": number, "type": string, "damage": number }
- * sent from Attack component and by Damage helper to the target entity, each time the target is attacked or damaged.
+ * Message of the form { "attacker": number, "target": number, "type": string, "damage": number, "attackerOwner": number }
+ * sent from Attack component and by Damage component to the target entity, each time the target is attacked or damaged.
*/
Engine.RegisterMessageType("Attacked");
Index: binaries/data/mods/public/simulation/components/interfaces/Damage.js
===================================================================
--- binaries/data/mods/public/simulation/components/interfaces/Damage.js (nonexistent)
+++ binaries/data/mods/public/simulation/components/interfaces/Damage.js (working copy)
@@ -0,0 +1 @@
+Engine.RegisterInterface("Damage");
Property changes on: binaries/data/mods/public/simulation/components/interfaces/Damage.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: binaries/data/mods/public/simulation/components/tests/test_Damage.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_Damage.js (nonexistent)
+++ binaries/data/mods/public/simulation/components/tests/test_Damage.js (working copy)
@@ -0,0 +1,96 @@
+Engine.LoadHelperScript("Player.js");
+Engine.LoadHelperScript("Sound.js");
+Engine.LoadComponentScript("interfaces/Damage.js");
+Engine.LoadComponentScript("interfaces/Timer.js");
+Engine.LoadComponentScript("interfaces/Health.js");
+Engine.LoadComponentScript("interfaces/DamageReceiver.js");
+Engine.LoadComponentScript("interfaces/Promotion.js");
+Engine.LoadComponentScript("interfaces/Loot.js");
+Engine.LoadComponentScript("interfaces/Sound.js");
+Engine.LoadComponentScript("interfaces/AttackDetection.js");
+Engine.LoadComponentScript("interfaces/Player.js");
+Engine.LoadComponentScript("interfaces/Attack.js");
+Engine.LoadComponentScript("Damage.js");
+Engine.LoadComponentScript("Timer.js");
+Engine.LoadComponentScript("PlayerManager.js")
+
+let cmpDamage = ConstructComponent(SYSTEM_ENTITY, "Damage");
+let cmpTimer = ConstructComponent(SYSTEM_ENTITY, "Timer");
+cmpTimer.OnUpdate({ turnLength: 1 });
+let attacker = 11;
+let atkPlayerEntity = 1;
+let attackerOwner = 6;
+let damage = 5;
+let target = 21;
+let targetOwner = 7;
+let targetPos = new Vector3D(3, 0, 3);
+
+let damageTaken = false;
+
+let data = {
+ "attacker": attacker,
+ "target": target,
+ "type": "Melee",
+ "strengths": { "hack": 0, "pierce": 0, "crush": damage },
+ "multiplier": 1.0,
+ "attackerOwner": attackerOwner,
+ "position": targetPos,
+ "isSplash": false,
+ "projectileId": 9
+};
+
+AddMock(atkPlayerEntity, IID_Player, {
+ GetEnemies: () => [targetOwner],
+});
+
+AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
+ GetPlayerByID: (id) => atkPlayerEntity,
+ GetAllPlayerEntities: () => [attackerOwner, targetOwner],
+});
+
+AddMock(SYSTEM_ENTITY, IID_ProjectileManager, {
+ RemoveProjectile: () => {},
+});
+
+AddMock(target, IID_Position, {
+ GetPosition: () => targetPos,
+ GetPreviousPosition: () => targetPos,
+ GetPosition2D: () => new Vector2D(3, 3),
+ IsInWorld: () => true,
+});
+
+AddMock(target, IID_Health, {});
+
+AddMock(target, IID_DamageReceiver, {
+ TakeDamage: (hack, pierce, crush) => { damageTaken = true; return { "killed": false, "change": -crush }; },
+});
+
+Engine.PostMessage = function(ent, iid, message)
+{
+ TS_ASSERT_UNEVAL_EQUALS({ "attacker": attacker, "target": target, "type": data.type, "damage": damage, "attackerOwner": attackerOwner }, message);
+};
+
+AddMock(target, IID_Footprint, {
+ GetShape: () => ({ "type": "circle", "radius": 20 }),
+});
+
+function TestDamage()
+{
+ cmpTimer.OnUpdate({ turnLength: 1 });
+ TS_ASSERT(damageTaken);
+ damageTaken = false;
+}
+
+cmpDamage.CauseDamage(data);
+TestDamage();
+
+data.type = "Ranged";
+cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Damage, "MissileHit", 1000, data);
+TestDamage();
+
+data.friendlyFire = false;
+data.range = 10;
+data.shape = "Circular";
+data.isSplash = true;
+cmpTimer.SetTimeout(SYSTEM_ENTITY, IID_Damage, "MissileHit", 1000, data);
+TestDamage();
Property changes on: binaries/data/mods/public/simulation/components/tests/test_Damage.js
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: binaries/data/mods/public/simulation/helpers/Damage.js
===================================================================
--- binaries/data/mods/public/simulation/helpers/Damage.js (revision 18608)
+++ binaries/data/mods/public/simulation/helpers/Damage.js (nonexistent)
@@ -1,144 +0,0 @@
-// Create global Damage object.
-var Damage = {};
-
-/**
- * Damages units around a given origin.
- * data.attacker =
- * data.origin =
- * data.radius =
- * data.shape =
- * data.strengths = {'hack':, 'pierce':, 'crush':}
- * data.type =
- * ***Optional Variables***
- * data.direction =
- * data.playersToDamage =
- */
-Damage.CauseSplashDamage = function(data)
-{
- // Get nearby entities and define variables
- var nearEnts = Damage.EntitiesNearPoint(data.origin, data.radius, data.playersToDamage);
- var damageMultiplier = 1;
- // Cycle through all the nearby entities and damage it appropriately based on its distance from the origin.
- for each (var entity in nearEnts)
- {
- var entityPosition = Engine.QueryInterface(entity, IID_Position).GetPosition2D();
- if(data.shape == 'Circular') // circular effect with quadratic falloff in every direction
- {
- var squaredDistanceFromOrigin = data.origin.distanceToSquared(entityPosition);
- damageMultiplier = 1 - squaredDistanceFromOrigin / (data.radius * data.radius);
- }
- else if(data.shape == 'Linear') // linear effect with quadratic falloff in two directions (only used for certain missiles)
- {
- // Get position of entity relative to splash origin.
- var relativePos = entityPosition.sub(data.origin);
-
- // The width of linear splash is one fifth of the normal splash radius.
- var width = data.radius/5;
-
- // Effectivly rotate the axis to align with the missile direction.
- var parallelDist = relativePos.dot(data.direction); // z axis
- var perpDist = Math.abs(relativePos.cross(data.direction)); // y axis
-
- // Check that the unit is within the distance at which it will get damaged.
- if (parallelDist > -width && perpDist < width) // If in radius, quadratic falloff in both directions
- damageMultiplier = (data.radius * data.radius - parallelDist * parallelDist) / (data.radius * data.radius)
- * (width * width - perpDist * perpDist) / (width * width);
- else
- damageMultiplier = 0;
- }
- else // In case someone calls this function with an invalid shape.
- {
- warn("The " + data.shape + " splash damage shape is not implemented!");
- }
- // Call CauseDamage which reduces the hitpoints, posts network command, plays sounds....
- Damage.CauseDamage({"strengths":data.strengths, "target":entity, "attacker":data.attacker, "multiplier":damageMultiplier, "type":data.type + ".Splash"});
- }
-};
-
-/**
- * Causes damage on a given unit
- * data.strengths = {'hack':, 'pierce':, 'crush':}
- * data.target =
- * data.attacker =
- * data.multiplier =
- * data.type =
- */
-Damage.CauseDamage = function(data)
-{
- // Check the target can be damaged otherwise don't do anything.
- var cmpDamageReceiver = Engine.QueryInterface(data.target, IID_DamageReceiver);
- var cmpHealth = Engine.QueryInterface(data.target, IID_Health);
- if (!cmpDamageReceiver || !cmpHealth)
- return;
-
- // Damage the target
- var targetState = cmpDamageReceiver.TakeDamage(data.strengths.hack * data.multiplier, data.strengths.pierce * data.multiplier, data.strengths.crush * data.multiplier);
-
- var cmpPromotion = Engine.QueryInterface(data.attacker, IID_Promotion);
- var cmpLoot = Engine.QueryInterface(data.target, IID_Loot);
- if (cmpPromotion && cmpLoot && cmpLoot.GetXp() > 0)
- cmpPromotion.IncreaseXp(cmpLoot.GetXp() * -targetState.change / cmpHealth.GetMaxHitpoints());
-
- // If the target was killed run some cleanup
- if (targetState.killed)
- Damage.TargetKilled(data.attacker, data.target);
-
- // Post the network command (make it work in multiplayer)
- Engine.PostMessage(data.target, MT_Attacked, {"attacker":data.attacker, "target":data.target, "type":data.type, "damage":-targetState.change});
-
- // Play attacking sounds
- PlaySound("attack_impact", data.attacker);
-};
-
-/**
- * Gets entities near a give point for given players.
- * origin =
- * radius =
- * players =
- * If players is not included, entities from all players are used.
- */
-Damage.EntitiesNearPoint = function(origin, radius, players)
-{
- // If there is insufficient data return an empty array.
- if (!origin || !radius)
- return [];
-
- // If the players parameter is not specified use all players.
- if (!players)
- {
- var playerEntities = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetAllPlayerEntities();
- players = [];
- for each (var entity in playerEntities)
- players.push(Engine.QueryInterface(entity, IID_Player).GetPlayerID());
- }
-
- // Call RangeManager with dummy entity and return the result.
- var rangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
- var rangeQuery = rangeManager.ExecuteQueryAroundPos(origin, 0, radius, players, IID_DamageReceiver);
- return rangeQuery;
-};
-
-/**
- * Called when some units kills something (another unit, building, animal etc)
- * killerEntity =
- * targetEntity =
- */
-Damage.TargetKilled = function(killerEntity, targetEntity)
-{
- // Add to killer statistics.
- var cmpKillerPlayerStatisticsTracker = QueryOwnerInterface(killerEntity, IID_StatisticsTracker);
- if (cmpKillerPlayerStatisticsTracker)
- cmpKillerPlayerStatisticsTracker.KilledEntity(targetEntity);
- // Add to loser statistics.
- var cmpTargetPlayerStatisticsTracker = QueryOwnerInterface(targetEntity, IID_StatisticsTracker);
- if (cmpTargetPlayerStatisticsTracker)
- cmpTargetPlayerStatisticsTracker.LostEntity(targetEntity);
-
- // If killer can collect loot, let's try to collect it.
- var cmpLooter = Engine.QueryInterface(killerEntity, IID_Looter);
- if (cmpLooter)
- cmpLooter.Collect(targetEntity);
-};
-
-Engine.RegisterGlobal("Damage", Damage);
-
Property changes on: binaries/data/mods/public/simulation/helpers/Damage.js
___________________________________________________________________
Deleted: svn:eol-style
## -1 +0,0 ##
-native
\ No newline at end of property