mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 13:23:56 -07:00
Give an elevation advantage to ranged units. Patch by sanderd17. Fix #1960.
This was SVN commit r13626.
This commit is contained in:
parent
3b294d3ad4
commit
8c74df2acd
13 changed files with 608 additions and 60 deletions
|
|
@ -121,7 +121,23 @@ function updateBuildingPlacementPreview()
|
|||
// Show placement info tooltip if invalid position
|
||||
placementSupport.tooltipError = !result.success;
|
||||
placementSupport.tooltipMessage = result.success ? "" : result.message;
|
||||
return result.success;
|
||||
|
||||
if (!result.success)
|
||||
return false;
|
||||
|
||||
if (placementSupport.attack)
|
||||
{
|
||||
// building can be placed here, and has an attack
|
||||
// show the range advantage in the tooltip
|
||||
var cmd = {x: placementSupport.position.x,
|
||||
z: placementSupport.position.z,
|
||||
range: placementSupport.attack.maxRange,
|
||||
elevationBonus: placementSupport.attack.elevationBonus,
|
||||
};
|
||||
var averageRange = Engine.GuiInterfaceCall("GetAverageRangeForBuildings",cmd);
|
||||
placementSupport.tooltipMessage = "Basic range: "+Math.round(cmd.range/4)+"\nAverage bonus range: "+Math.round((averageRange - cmd.range)/4);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (placementSupport.mode === "wall")
|
||||
|
|
@ -1494,6 +1510,14 @@ function startBuildingPlacement(buildTemplate, playerState)
|
|||
placementSupport.template = buildTemplate;
|
||||
inputState = INPUT_BUILDING_PLACEMENT;
|
||||
}
|
||||
|
||||
if (templateData.attack &&
|
||||
templateData.attack.Ranged &&
|
||||
templateData.attack.Ranged.maxRange)
|
||||
{
|
||||
// add attack information to display a good tooltip
|
||||
placementSupport.attack = templateData.attack.Ranged;
|
||||
}
|
||||
}
|
||||
|
||||
// Called by GUI when user changes preferred trading goods
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ PlacementSupport.prototype.Reset = function()
|
|||
|
||||
this.SetDefaultAngle();
|
||||
this.RandomizeActorSeed();
|
||||
|
||||
this.attack = null;
|
||||
|
||||
Engine.GuiInterfaceCall("SetBuildingPlacementPreview", {"template": ""});
|
||||
Engine.GuiInterfaceCall("SetWallPlacementPreview", {"wallSet": null});
|
||||
|
|
|
|||
|
|
@ -206,24 +206,42 @@ function displaySingle(entState, template)
|
|||
else
|
||||
{
|
||||
// TODO: we should require all entities to have icons, so this case never occurs
|
||||
getGUIObjectByName("icon").sprite = "bkFillBlack";
|
||||
}
|
||||
|
||||
// Attack and Armor
|
||||
var type = "";
|
||||
if (entState.attack)
|
||||
type = entState.attack.type + " ";
|
||||
|
||||
attack = "[font=\"serif-bold-13\"]"+type+"Attack:[/font] " + damageTypeDetails(entState.attack);
|
||||
// Show max attack range if ranged attack, also convert to tiles (4m per tile)
|
||||
if (entState.attack && entState.attack.type == "Ranged")
|
||||
attack += ", [font=\"serif-bold-13\"]Range:[/font] " + Math.round(entState.attack.maxRange/4);
|
||||
getGUIObjectByName("attackAndArmorStats").tooltip = attack + "\n[font=\"serif-bold-13\"]Armor:[/font] " + armorTypeDetails(entState.armour);
|
||||
|
||||
// Icon Tooltip
|
||||
var iconTooltip = "";
|
||||
|
||||
if (genericName)
|
||||
getGUIObjectByName("icon").sprite = "bkFillBlack";
|
||||
}
|
||||
|
||||
// Attack and Armor
|
||||
var type = "";
|
||||
var attack = "[font=\"serif-bold-13\"]"+type+"Attack:[/font] " + damageTypeDetails(entState.attack);
|
||||
if (entState.attack)
|
||||
{
|
||||
type = entState.attack.type + " ";
|
||||
|
||||
// Show max attack range if ranged attack, also convert to tiles (4m per tile)
|
||||
if (entState.attack.type == "Ranged")
|
||||
{
|
||||
var realRange = entState.attack.elevationAdaptedRange;
|
||||
var range = entState.attack.maxRange;
|
||||
attack += ", [font=\"serif-bold-13\"]Range:[/font] " +
|
||||
Math.round(range/4);
|
||||
|
||||
if (Math.round((realRange - range)/4) > 0)
|
||||
{
|
||||
attack += " (+" + Math.round((realRange - range)/4) + ")";
|
||||
}
|
||||
else if (Math.round((realRange - range)/4) < 0)
|
||||
{
|
||||
attack += " (" + Math.round((realRange - range)/4) + ")";
|
||||
} // don't show when it's 0
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
getGUIObjectByName("attackAndArmorStats").tooltip = attack + "\n[font=\"serif-bold-13\"]Armor:[/font] " + armorTypeDetails(entState.armour);
|
||||
|
||||
// Icon Tooltip
|
||||
var iconTooltip = "";
|
||||
|
||||
if (genericName)
|
||||
iconTooltip = "[font=\"serif-bold-16\"]" + genericName + "[/font]";
|
||||
|
||||
if (template.tooltip)
|
||||
|
|
|
|||
|
|
@ -67,6 +67,9 @@ Attack.prototype.Schema =
|
|||
"<Crush>0.0</Crush>" +
|
||||
"<MaxRange>44.0</MaxRange>" +
|
||||
"<MinRange>20.0</MinRange>" +
|
||||
"<optional>"+
|
||||
"<element name='ElevationBonus' a:help='give an elevation advantage (in meters)'><ref name='nonNegativeDecimal'/></element>" +
|
||||
"</optional>" +
|
||||
"<PrepareTime>800</PrepareTime>" +
|
||||
"<RepeatTime>1600</RepeatTime>" +
|
||||
"<ProjectileSpeed>50.0</ProjectileSpeed>" +
|
||||
|
|
@ -125,6 +128,9 @@ Attack.prototype.Schema =
|
|||
"<element name='Crush' a:help='Crush damage strength'><ref name='nonNegativeDecimal'/></element>" +
|
||||
"<element name='MaxRange' a:help='Maximum attack range (in metres)'><ref name='nonNegativeDecimal'/></element>" +
|
||||
"<element name='MinRange' a:help='Minimum attack range (in metres)'><ref name='nonNegativeDecimal'/></element>" +
|
||||
"<optional>"+
|
||||
"<element name='ElevationBonus' a:help='give an elevation advantage (in meters)'><ref name='nonNegativeDecimal'/></element>" +
|
||||
"</optional>" +
|
||||
"<element name='PrepareTime' a:help='Time from the start of the attack command until the attack actually occurs (in milliseconds). This value relative to RepeatTime should closely match the \"event\" point in the actor's attack animation'>" +
|
||||
"<data type='nonNegativeInteger'/>" +
|
||||
"</element>" +
|
||||
|
|
@ -360,8 +366,11 @@ Attack.prototype.GetRange = function(type)
|
|||
|
||||
var min = +(this.template[type].MinRange || 0);
|
||||
min = ApplyTechModificationsToEntity("Attack/" + type + "/MinRange", min, this.entity);
|
||||
|
||||
var elevationBonus = +(this.template[type].ElevationBonus || 0);
|
||||
elevationBonus = ApplyTechModificationsToEntity("Attack/" + type + "/ElevationBonus", elevationBonus, this.entity);
|
||||
|
||||
return { "max": max, "min": min };
|
||||
return { "max": max, "min": min, "elevationBonus": elevationBonus};
|
||||
};
|
||||
|
||||
// Calculate the attack damage multiplier against a target
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ BuildingAI.prototype.Schema =
|
|||
"<ref name='nonNegativeDecimal'/>" +
|
||||
"</element>";
|
||||
|
||||
|
||||
/**
|
||||
* Initialize BuildingAI Component
|
||||
*/
|
||||
|
|
@ -99,7 +100,7 @@ BuildingAI.prototype.SetupRangeQuery = function(owner)
|
|||
if (cmpAttack)
|
||||
{
|
||||
var range = cmpAttack.GetRange("Ranged");
|
||||
this.enemyUnitsQuery = cmpRangeManager.CreateActiveQuery(this.entity, range.min, range.max, players, IID_DamageReceiver, cmpRangeManager.GetEntityFlagMask("normal"));
|
||||
this.enemyUnitsQuery = cmpRangeManager.CreateActiveParabolicQuery(this.entity, range.min, range.max, range.elevationBonus, players, IID_DamageReceiver, cmpRangeManager.GetEntityFlagMask("normal"));
|
||||
cmpRangeManager.EnableActiveQuery(this.enemyUnitsQuery);
|
||||
}
|
||||
};
|
||||
|
|
@ -133,7 +134,7 @@ BuildingAI.prototype.SetupGaiaRangeQuery = function()
|
|||
var range = cmpAttack.GetRange("Ranged");
|
||||
|
||||
// This query is only interested in Gaia entities that can attack.
|
||||
this.gaiaUnitsQuery = rangeMan.CreateActiveQuery(this.entity, range.min, range.max, [0], IID_Attack, rangeMan.GetEntityFlagMask("normal"));
|
||||
this.gaiaUnitsQuery = rangeMan.CreateActiveParabolicQuery(this.entity, range.min, range.max, range.elevationBonus, [0], IID_Attack, rangeMan.GetEntityFlagMask("normal"));
|
||||
rangeMan.EnableActiveQuery(this.gaiaUnitsQuery);
|
||||
}
|
||||
};
|
||||
|
|
@ -214,6 +215,7 @@ BuildingAI.prototype.FireArrows = function()
|
|||
var cmpAttack = Engine.QueryInterface(this.entity, IID_Attack);
|
||||
if (cmpAttack)
|
||||
{
|
||||
|
||||
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||
this.timer = cmpTimer.SetTimeout(this.entity, IID_BuildingAI, "FireArrows", timerInterval, {});
|
||||
var arrowsToFire = 0;
|
||||
|
|
@ -239,12 +241,33 @@ BuildingAI.prototype.FireArrows = function()
|
|||
//Fire N arrows, 0 <= N <= Number of arrows left
|
||||
arrowsToFire = Math.floor(Math.random() * this.arrowsLeft);
|
||||
}
|
||||
|
||||
if (this.targetUnits.length > 0)
|
||||
{
|
||||
var clonedTargets = this.targetUnits.slice();
|
||||
for (var i = 0;i < arrowsToFire;i++)
|
||||
{
|
||||
cmpAttack.PerformAttack("Ranged", this.targetUnits[Math.floor(Math.random() * this.targetUnits.length)]);
|
||||
PlaySound("arrowfly", this.entity);
|
||||
var target = clonedTargets[Math.floor(Math.random() * this.targetUnits.length)];
|
||||
if (
|
||||
target &&
|
||||
this.CheckTargetVisible(target)
|
||||
)
|
||||
{
|
||||
cmpAttack.PerformAttack("Ranged", target);
|
||||
PlaySound("arrowfly", this.entity);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
clonedTargets.splice(clonedTargets.indexOf(target),1);
|
||||
i--; // one extra arrow left to fire
|
||||
if(clonedTargets.length < 1)
|
||||
{
|
||||
this.arrowsLeft += arrowsToFire;
|
||||
// no targets found in this round, save arrows and go to next round
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.arrowsLeft -= arrowsToFire;
|
||||
}
|
||||
|
|
@ -252,4 +275,22 @@ BuildingAI.prototype.FireArrows = function()
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if the target entity is visible through the FoW/SoD.
|
||||
*/
|
||||
BuildingAI.prototype.CheckTargetVisible = function(target)
|
||||
{
|
||||
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
|
||||
if (!cmpOwnership)
|
||||
return false;
|
||||
|
||||
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
|
||||
|
||||
if (cmpRangeManager.GetLosVisibility(target, cmpOwnership.GetOwner(), false) == "hidden")
|
||||
return false;
|
||||
|
||||
// Either visible directly, or visible in fog
|
||||
return true;
|
||||
};
|
||||
|
||||
Engine.RegisterComponentType(IID_BuildingAI, "BuildingAI", BuildingAI);
|
||||
|
|
|
|||
|
|
@ -174,6 +174,7 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
|
|||
if (cmpPosition && cmpPosition.IsInWorld())
|
||||
{
|
||||
ret.position = cmpPosition.GetPosition();
|
||||
ret.rotation = cmpPosition.GetRotation();
|
||||
}
|
||||
|
||||
var cmpHealth = Engine.QueryInterface(ent, IID_Health);
|
||||
|
|
@ -185,7 +186,10 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
|
|||
ret.needsHeal = !cmpHealth.IsUnhealable();
|
||||
}
|
||||
|
||||
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
|
||||
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
|
||||
var cmpAttack = Engine.QueryInterface(ent, IID_Attack);
|
||||
|
||||
if (cmpAttack)
|
||||
{
|
||||
var type = cmpAttack.GetBestAttack(); // TODO: how should we decide which attack to show? show all?
|
||||
|
|
@ -194,6 +198,32 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
|
|||
ret.attack.type = type;
|
||||
ret.attack.minRange = range.min;
|
||||
ret.attack.maxRange = range.max;
|
||||
if (type == "Ranged")
|
||||
{
|
||||
ret.attack.elevationBonus = range.elevationBonus;
|
||||
if (cmpUnitAI && cmpPosition && cmpPosition.IsInWorld())
|
||||
{
|
||||
// For units, take the rage in front of it, no spread. So angle = 0
|
||||
ret.attack.elevationAdaptedRange = cmpRangeManager.GetElevationAdaptedRange(ret.position, ret.rotation, range.max, range.elevationBonus, 0);
|
||||
}
|
||||
else if(cmpPosition && cmpPosition.IsInWorld())
|
||||
{
|
||||
// For buildings, take the average elevation around it. So angle = 2*pi
|
||||
ret.attack.elevationAdaptedRange = cmpRangeManager.GetElevationAdaptedRange(ret.position, ret.rotation, range.max, range.elevationBonus, 2*Math.PI);
|
||||
}
|
||||
else
|
||||
{
|
||||
// not in world, set a default?
|
||||
ret.attack.elevationAdaptedRange = ret.attack.maxRange;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// not a ranged attack, set some defaults
|
||||
ret.attack.elevationBonus = 0;
|
||||
ret.attack.elevationAdaptedRange = ret.attack.maxRange;
|
||||
}
|
||||
}
|
||||
|
||||
var cmpArmour = Engine.QueryInterface(ent, IID_DamageReceiver);
|
||||
|
|
@ -313,8 +343,7 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
|
|||
"req": cmpPromotion.GetRequiredXp()
|
||||
};
|
||||
}
|
||||
|
||||
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI);
|
||||
|
||||
if (cmpUnitAI)
|
||||
{
|
||||
ret.unitAI = {
|
||||
|
|
@ -325,7 +354,7 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
|
|||
if (cmpUnitAI.isGarrisoned && ret.player)
|
||||
ret.template = "p" + ret.player + "&" + ret.template;
|
||||
}
|
||||
|
||||
|
||||
var cmpGate = Engine.QueryInterface(ent, IID_Gate);
|
||||
if (cmpGate)
|
||||
{
|
||||
|
|
@ -349,12 +378,26 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
|
|||
};
|
||||
}
|
||||
|
||||
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
|
||||
ret.visibility = cmpRangeManager.GetLosVisibility(ent, player, false);
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
GuiInterface.prototype.GetAverageRangeForBuildings = function(player, cmd)
|
||||
{
|
||||
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
|
||||
var cmpTerrain = Engine.QueryInterface(SYSTEM_ENTITY, IID_Terrain);
|
||||
var rot = {x:0, y:0, z:0};
|
||||
var pos = {x:cmd.x,z:cmd.z};
|
||||
pos.y = cmpTerrain.GetGroundLevel(cmd.x, cmd.z);
|
||||
var elevationBonus = cmd.elevationBonus || 0;
|
||||
var range = cmd.range;
|
||||
|
||||
return cmpRangeManager.GetElevationAdaptedRange(pos, rot, range, elevationBonus, 2*Math.PI);
|
||||
};
|
||||
|
||||
|
||||
|
||||
GuiInterface.prototype.GetTemplateData = function(player, extendedName)
|
||||
{
|
||||
var name = extendedName;
|
||||
|
|
@ -393,6 +436,7 @@ GuiInterface.prototype.GetTemplateData = function(player, extendedName)
|
|||
"crush": GetTechModifiedProperty(techMods, template, "Attack/"+type+"/Crush", +(template.Attack[type].Crush || 0)),
|
||||
"minRange": GetTechModifiedProperty(techMods, template, "Attack/"+type+"/MinRange", +(template.Attack[type].MinRange || 0)),
|
||||
"maxRange": GetTechModifiedProperty(techMods, template, "Attack/"+type+"/MaxRange", +template.Attack[type].MaxRange),
|
||||
"elevationBonus": GetTechModifiedProperty(techMods, template, "Attack/"+type+"/ElevationBonus", +(template.Attack[type].ElevationBonus || 0)),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1729,6 +1773,7 @@ var exposedFunctions = {
|
|||
"GetRenamedEntities": 1,
|
||||
"ClearRenamedEntities": 1,
|
||||
"GetEntityState": 1,
|
||||
"GetAverageRangeForBuildings": 1,
|
||||
"GetTemplateData": 1,
|
||||
"GetTechnologyData": 1,
|
||||
"IsTechnologyResearched": 1,
|
||||
|
|
|
|||
|
|
@ -367,7 +367,7 @@ var UnitFsmSpec = {
|
|||
this.order.data.attackType = type;
|
||||
|
||||
// If we are already at the target, try attacking it from here
|
||||
if (this.CheckTargetRange(this.order.data.target, IID_Attack, this.order.data.attackType))
|
||||
if (this.CheckTargetAttackRange(this.order.data.target, IID_Attack, this.order.data.attackType))
|
||||
{
|
||||
this.StopMoving();
|
||||
// For packable units within attack range:
|
||||
|
|
@ -428,7 +428,7 @@ var UnitFsmSpec = {
|
|||
}
|
||||
|
||||
// Try to move within attack range
|
||||
if (this.MoveToTargetRange(this.order.data.target, IID_Attack, this.order.data.attackType))
|
||||
if (this.MoveToTargetAttackRange(this.order.data.target, IID_Attack, this.order.data.attackType,0.5))
|
||||
{
|
||||
// We've started walking to the given point
|
||||
if (this.IsAnimal())
|
||||
|
|
@ -1315,11 +1315,27 @@ var UnitFsmSpec = {
|
|||
},
|
||||
|
||||
"MoveCompleted": function() {
|
||||
// If the unit needs to unpack, do so
|
||||
if (this.CanUnpack())
|
||||
this.SetNextState("UNPACKING");
|
||||
else
|
||||
this.SetNextState("ATTACKING");
|
||||
|
||||
if (this.CheckTargetAttackRange(this.order.data.target, IID_Attack , this.order.data.attackType))
|
||||
{
|
||||
// If the unit needs to unpack, do so
|
||||
if (this.CanUnpack())
|
||||
this.SetNextState("UNPACKING");
|
||||
else
|
||||
this.SetNextState("ATTACKING");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this.MoveToTargetAttackRange(this.order.data.target, IID_Attack, this.order.data.attackType,0))
|
||||
{
|
||||
this.SetNextState("APPROACHING");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Give up
|
||||
this.FinishOrder();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"Attacked": function(msg) {
|
||||
|
|
@ -1335,9 +1351,9 @@ var UnitFsmSpec = {
|
|||
"UNPACKING": {
|
||||
"enter": function() {
|
||||
// If we're not in range yet (maybe we stopped moving), move to target again
|
||||
if (!this.CheckTargetRange(this.order.data.target, IID_Attack, this.order.data.attackType))
|
||||
if (!this.CheckTargetAttackRange(this.order.data.target, IID_Attack, this.order.data.attackType))
|
||||
{
|
||||
if (this.MoveToTargetRange(this.order.data.target, IID_Attack, this.order.data.attackType))
|
||||
if (this.MoveToTargetAttackRange(this.order.data.target, IID_Attack, this.order.data.attackType,0.5))
|
||||
this.SetNextState("APPROACHING");
|
||||
else
|
||||
{
|
||||
|
|
@ -1403,7 +1419,7 @@ var UnitFsmSpec = {
|
|||
if (this.TargetIsAlive(target) && this.CanAttack(target, this.order.data.forceResponse || null))
|
||||
{
|
||||
// Check we can still reach the target
|
||||
if (this.CheckTargetRange(target, IID_Attack, this.order.data.attackType))
|
||||
if (this.CheckTargetAttackRange(target, IID_Attack, this.order.data.attackType))
|
||||
{
|
||||
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||
this.lastAttacked = cmpTimer.GetTime() - msg.lateness;
|
||||
|
|
@ -3289,6 +3305,53 @@ UnitAI.prototype.MoveToTargetRange = function(target, iid, type)
|
|||
return cmpUnitMotion.MoveToTargetRange(target, range.min, range.max);
|
||||
};
|
||||
|
||||
/**
|
||||
* Move unit so we hope the target is in the attack range
|
||||
* for melee attacks, this goes straight to the default range checks
|
||||
* for ranged attacks, the parabolic range is used, so we can't know exactly at what horizontal range the target can be reached
|
||||
* That's why a guess is needed
|
||||
* a guess of 1 will take the maximum of the possible ranges, and stay far away
|
||||
* a guess of 0 will take the minimum of the possible ranges and, in most cases, will have the target in range.
|
||||
* every guess inbetween is a linear interpollation
|
||||
*/
|
||||
UnitAI.prototype.MoveToTargetAttackRange = function(target, iid, type,guess)
|
||||
{
|
||||
|
||||
if(type!= "Ranged")
|
||||
return this.MoveToTargetRange(target, iid, type);
|
||||
|
||||
if (!this.CheckTargetVisible(target))
|
||||
return false;
|
||||
|
||||
var cmpRanged = Engine.QueryInterface(this.entity, iid);
|
||||
var range = cmpRanged.GetRange(type);
|
||||
|
||||
var thisCmpPosition = Engine.QueryInterface(this.entity, IID_Position);
|
||||
var s = thisCmpPosition.GetPosition();
|
||||
|
||||
var targetCmpPosition = Engine.QueryInterface(target, IID_Position);
|
||||
if(!targetCmpPosition.IsInWorld())
|
||||
return false;
|
||||
|
||||
var t = targetCmpPosition.GetPosition();
|
||||
// h is positive when I'm higher than the target
|
||||
var h = s.y-t.y+range.elevationBonus;
|
||||
|
||||
// No negative roots please
|
||||
if(h>-range.max/2)
|
||||
var parabolicMaxRange = Math.sqrt(range.max*range.max+2*range.max*h);
|
||||
else
|
||||
// return false? Or hope you come close enough?
|
||||
var parabolicMaxRange = 0;
|
||||
//return false;
|
||||
|
||||
// the parabole changes while walking, take something in the middle
|
||||
var guessedMaxRange = Math.max(range.max, parabolicMaxRange)*guess+Math.min(range.max, parabolicMaxRange)*(1-guess) ;
|
||||
|
||||
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
|
||||
return cmpUnitMotion.MoveToTargetRange(target, range.min, guessedMaxRange);
|
||||
};
|
||||
|
||||
UnitAI.prototype.MoveToTargetRangeExplicit = function(target, min, max)
|
||||
{
|
||||
if (!this.CheckTargetVisible(target))
|
||||
|
|
@ -3313,6 +3376,43 @@ UnitAI.prototype.CheckTargetRange = function(target, iid, type)
|
|||
return cmpUnitMotion.IsInTargetRange(target, range.min, range.max);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the target is inside the attack range
|
||||
* For melee attacks, this goes straigt to the regular range calculation
|
||||
* For ranged attacks, the parabolic formula is used to accout for bigger ranges
|
||||
* when the target is lower, and smaller ranges when the target is higher
|
||||
*/
|
||||
UnitAI.prototype.CheckTargetAttackRange = function(target, iid, type)
|
||||
{
|
||||
|
||||
if (type != "Ranged")
|
||||
return this.CheckTargetRange(target,iid,type);
|
||||
|
||||
var targetCmpPosition = Engine.QueryInterface(target, IID_Position);
|
||||
if (!targetCmpPosition || !targetCmpPosition.IsInWorld())
|
||||
return false;
|
||||
|
||||
var cmpRanged = Engine.QueryInterface(this.entity, iid);
|
||||
var range = cmpRanged.GetRange(type);
|
||||
|
||||
var thisCmpPosition = Engine.QueryInterface(this.entity, IID_Position);
|
||||
var s = thisCmpPosition.GetPosition();
|
||||
|
||||
var t = targetCmpPosition.GetPosition();
|
||||
|
||||
var h = s.y-t.y+range.elevationBonus;
|
||||
var maxRangeSq = 2*range.max*(h + range.max/2);
|
||||
|
||||
if (maxRangeSq < 0)
|
||||
return false;
|
||||
|
||||
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
|
||||
return cmpUnitMotion.IsInTargetRange(target, range.min, Math.sqrt(maxRangeSq));
|
||||
|
||||
return maxRangeSq >= distanceSq && range.min*range.min <= distanceSq;
|
||||
|
||||
};
|
||||
|
||||
UnitAI.prototype.CheckTargetRangeExplicit = function(target, min, max)
|
||||
{
|
||||
var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion);
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Entity parent="template_structure_defense">
|
||||
<Attack>
|
||||
<Ranged>
|
||||
<Hack>0.0</Hack>
|
||||
<Pierce>20.0</Pierce>
|
||||
<Crush>0.0</Crush>
|
||||
<MaxRange>70.0</MaxRange>
|
||||
<MinRange>16.0</MinRange>
|
||||
<ProjectileSpeed>75.0</ProjectileSpeed>
|
||||
<PrepareTime>1200</PrepareTime>
|
||||
<RepeatTime>2000</RepeatTime>
|
||||
<Spread>1.5</Spread>
|
||||
</Ranged>
|
||||
</Attack>
|
||||
<Entity parent="template_structure_defense">
|
||||
<Attack>
|
||||
<Ranged>
|
||||
<Hack>0.0</Hack>
|
||||
<Pierce>20.0</Pierce>
|
||||
<Crush>0.0</Crush>
|
||||
<MaxRange>57.0</MaxRange>
|
||||
<MinRange>16.0</MinRange>
|
||||
<ElevationBonus>15</ElevationBonus>
|
||||
<ProjectileSpeed>75.0</ProjectileSpeed>
|
||||
<PrepareTime>1200</PrepareTime>
|
||||
<RepeatTime>2000</RepeatTime>
|
||||
<Spread>1.5</Spread>
|
||||
</Ranged>
|
||||
</Attack>
|
||||
<BuildingAI>
|
||||
<DefaultArrowCount>1</DefaultArrowCount>
|
||||
<GarrisonArrowMultiplier>1</GarrisonArrowMultiplier>
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ class AutoGCRooter;
|
|||
// Set the maximum number of function arguments that can be handled
|
||||
// (This should be as small as possible (for compiler efficiency),
|
||||
// but as large as necessary for all wrapped functions)
|
||||
#define SCRIPT_INTERFACE_MAX_ARGS 7
|
||||
#define SCRIPT_INTERFACE_MAX_ARGS 8
|
||||
|
||||
// TODO: what's a good default?
|
||||
#define DEFAULT_RUNTIME_SIZE 16 * 1024 * 1024
|
||||
|
|
|
|||
|
|
@ -20,10 +20,12 @@
|
|||
#include "simulation2/system/Component.h"
|
||||
#include "ICmpRangeManager.h"
|
||||
|
||||
#include "ICmpTerrain.h"
|
||||
#include "simulation2/MessageTypes.h"
|
||||
#include "simulation2/components/ICmpPosition.h"
|
||||
#include "simulation2/components/ICmpTerritoryManager.h"
|
||||
#include "simulation2/components/ICmpVision.h"
|
||||
#include "simulation2/components/ICmpWaterManager.h"
|
||||
#include "simulation2/helpers/Render.h"
|
||||
#include "simulation2/helpers/Spatial.h"
|
||||
|
||||
|
|
@ -44,9 +46,11 @@
|
|||
struct Query
|
||||
{
|
||||
bool enabled;
|
||||
bool parabolic;
|
||||
entity_id_t source;
|
||||
entity_pos_t minRange;
|
||||
entity_pos_t maxRange;
|
||||
entity_pos_t elevationBonus;
|
||||
u32 ownersMask;
|
||||
i32 interface;
|
||||
std::vector<entity_id_t> lastMatch;
|
||||
|
|
@ -87,6 +91,44 @@ static u32 CalcSharedLosMask(std::vector<player_id_t> players)
|
|||
return playerMask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether v is in a parabolic range of (0,0,0)
|
||||
* The highest point of the paraboloid is (0,range/2,0)
|
||||
* and the circle of distance 'range' around (0,0,0) on height y=0 is part of the paraboloid
|
||||
*
|
||||
* Avoids sqrting and overflowing.
|
||||
*/
|
||||
static bool InParabolicRange(CFixedVector3D v, fixed range)
|
||||
{
|
||||
i64 x = (i64)v.X.GetInternalValue(); // abs(x) <= 2^31
|
||||
i64 z = (i64)v.Z.GetInternalValue();
|
||||
i64 xx = (x * x); // xx <= 2^62
|
||||
i64 zz = (z * z);
|
||||
i64 d2 = (xx + zz) >> 1; // d2 <= 2^62 (no overflow)
|
||||
|
||||
i64 y = (i64)v.Y.GetInternalValue();
|
||||
|
||||
i64 c = (i64)range.GetInternalValue();
|
||||
i64 c_2 = c >> 1;
|
||||
|
||||
i64 c2 = (c_2-y)*c;
|
||||
|
||||
if (d2 <= c2)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
struct EntityParabolicRangeOutline
|
||||
{
|
||||
entity_id_t source;
|
||||
CFixedVector3D position;
|
||||
entity_pos_t range;
|
||||
std::vector<entity_pos_t> outline;
|
||||
};
|
||||
|
||||
static std::map<entity_id_t, EntityParabolicRangeOutline> ParabolicRangesOutlines;
|
||||
|
||||
/**
|
||||
* Representation of an entity, with the data needed for queries.
|
||||
*/
|
||||
|
|
@ -113,9 +155,11 @@ struct SerializeQuery
|
|||
void operator()(S& serialize, const char* UNUSED(name), Query& value)
|
||||
{
|
||||
serialize.Bool("enabled", value.enabled);
|
||||
serialize.Bool("parabolic",value.parabolic);
|
||||
serialize.NumberU32_Unbounded("source", value.source);
|
||||
serialize.NumberFixed_Unbounded("min range", value.minRange);
|
||||
serialize.NumberFixed_Unbounded("max range", value.maxRange);
|
||||
serialize.NumberFixed_Unbounded("elevation bonus", value.elevationBonus);
|
||||
serialize.NumberU32_Unbounded("owners mask", value.ownersMask);
|
||||
serialize.NumberI32_Unbounded("interface", value.interface);
|
||||
SerializeVector<SerializeU32_Unbounded>()(serialize, "last match", value.lastMatch);
|
||||
|
|
@ -619,6 +663,16 @@ public:
|
|||
return id;
|
||||
}
|
||||
|
||||
virtual tag_t CreateActiveParabolicQuery(entity_id_t source,
|
||||
entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t elevationBonus,
|
||||
std::vector<int> owners, int requiredInterface, u8 flags)
|
||||
{
|
||||
tag_t id = m_QueryNext++;
|
||||
m_Queries[id] = ConstructParabolicQuery(source, minRange, maxRange, elevationBonus, owners, requiredInterface, flags);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
virtual void DestroyActiveQuery(tag_t tag)
|
||||
{
|
||||
if (m_Queries.find(tag) == m_Queries.end())
|
||||
|
|
@ -842,7 +896,48 @@ public:
|
|||
r.push_back(it->first);
|
||||
}
|
||||
}
|
||||
else
|
||||
// Not the entire world, so check a parabolic range, or a regular range
|
||||
else if (q.parabolic)
|
||||
{
|
||||
// elevationBonus is part of the 3D position, as the source is really that much heigher
|
||||
CFixedVector3D pos3d = cmpSourcePosition->GetPosition()+
|
||||
CFixedVector3D(entity_pos_t::Zero(), q.elevationBonus, entity_pos_t::Zero()) ;
|
||||
// Get a quick list of entities that are potentially in range, with a cutoff of 2*maxRange
|
||||
std::vector<entity_id_t> ents = m_Subdivision.GetNear(pos, q.maxRange*2);
|
||||
|
||||
for (size_t i = 0; i < ents.size(); ++i)
|
||||
{
|
||||
std::map<entity_id_t, EntityData>::const_iterator it = m_EntityData.find(ents[i]);
|
||||
ENSURE(it != m_EntityData.end());
|
||||
|
||||
if (!TestEntityQuery(q, it->first, it->second))
|
||||
continue;
|
||||
|
||||
CmpPtr<ICmpPosition> cmpSecondPosition(GetSimContext(), ents[i]);
|
||||
if (!cmpSecondPosition || !cmpSecondPosition->IsInWorld())
|
||||
continue;
|
||||
CFixedVector3D secondPosition = cmpSecondPosition->GetPosition();
|
||||
|
||||
// Restrict based on precise distance
|
||||
if (!InParabolicRange(
|
||||
CFixedVector3D(it->second.x, secondPosition.Y, it->second.z)
|
||||
- pos3d,
|
||||
q.maxRange))
|
||||
continue;
|
||||
|
||||
if (!q.minRange.IsZero())
|
||||
{
|
||||
int distVsMin = (CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.minRange);
|
||||
if (distVsMin < 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
r.push_back(it->first);
|
||||
|
||||
}
|
||||
}
|
||||
// check a regular range (i.e. not the entire world, and not parabolic)
|
||||
else
|
||||
{
|
||||
// Get a quick list of entities that are potentially in range
|
||||
std::vector<entity_id_t> ents = m_Subdivision.GetNear(pos, q.maxRange);
|
||||
|
|
@ -868,10 +963,116 @@ public:
|
|||
}
|
||||
|
||||
r.push_back(it->first);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
virtual entity_pos_t GetElevationAdaptedRange(CFixedVector3D pos, CFixedVector3D rot, entity_pos_t range, entity_pos_t elevationBonus, entity_pos_t angle)
|
||||
{
|
||||
entity_pos_t r = entity_pos_t::Zero() ;
|
||||
|
||||
pos.Y += elevationBonus;
|
||||
entity_pos_t orientation = rot.Y;
|
||||
|
||||
entity_pos_t maxAngle = orientation + angle/2;
|
||||
entity_pos_t minAngle = orientation - angle/2;
|
||||
|
||||
int numberOfSteps = 16;
|
||||
|
||||
if (angle == entity_pos_t::Zero())
|
||||
numberOfSteps = 1;
|
||||
|
||||
std::vector<entity_pos_t> coords = getParabolicRangeForm(pos, range, range*2, minAngle, maxAngle, numberOfSteps);
|
||||
|
||||
entity_pos_t part = entity_pos_t::FromInt(numberOfSteps);
|
||||
|
||||
for (int i = 0; i < numberOfSteps; i++)
|
||||
{
|
||||
r = r + CFixedVector2D(coords[2*i],coords[2*i+1]).Length() / part;
|
||||
}
|
||||
|
||||
return r;
|
||||
|
||||
}
|
||||
|
||||
virtual std::vector<entity_pos_t> getParabolicRangeForm(CFixedVector3D pos, entity_pos_t maxRange, entity_pos_t cutoff, entity_pos_t minAngle, entity_pos_t maxAngle, int numberOfSteps)
|
||||
{
|
||||
|
||||
// angle = 0 goes in the positive Z direction
|
||||
entity_pos_t precision = entity_pos_t::FromInt((int)TERRAIN_TILE_SIZE)/8;
|
||||
|
||||
std::vector<entity_pos_t> r;
|
||||
|
||||
|
||||
CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
|
||||
CmpPtr<ICmpWaterManager> cmpWaterManager(GetSimContext(), SYSTEM_ENTITY);
|
||||
entity_pos_t waterLevel = cmpWaterManager->GetWaterLevel(pos.X,pos.Z);
|
||||
entity_pos_t thisHeight = pos.Y > waterLevel ? pos.Y : waterLevel;
|
||||
|
||||
if (cmpTerrain)
|
||||
{
|
||||
for (int i = 0; i < numberOfSteps; i++)
|
||||
{
|
||||
entity_pos_t angle = minAngle + (maxAngle - minAngle) / numberOfSteps * i;
|
||||
entity_pos_t sin;
|
||||
entity_pos_t cos;
|
||||
entity_pos_t minDistance = entity_pos_t::Zero();
|
||||
entity_pos_t maxDistance = cutoff;
|
||||
sincos_approx(angle,sin,cos);
|
||||
|
||||
CFixedVector2D minVector = CFixedVector2D(entity_pos_t::Zero(),entity_pos_t::Zero());
|
||||
CFixedVector2D maxVector = CFixedVector2D(sin,cos).Multiply(cutoff);
|
||||
entity_pos_t targetHeight = cmpTerrain->GetGroundLevel(pos.X+maxVector.X,pos.Z+maxVector.Y);
|
||||
// use water level to display range on water
|
||||
targetHeight = targetHeight > waterLevel ? targetHeight : waterLevel;
|
||||
|
||||
if (InParabolicRange(CFixedVector3D(maxVector.X,targetHeight-thisHeight,maxVector.Y),maxRange))
|
||||
{
|
||||
r.push_back(maxVector.X);
|
||||
r.push_back(maxVector.Y);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Loop until vectors come close enough
|
||||
while ((maxVector - minVector).CompareLength(precision) > 0)
|
||||
{
|
||||
// difference still bigger than precision, bisect to get smaller difference
|
||||
entity_pos_t newDistance = (minDistance+maxDistance)/entity_pos_t::FromInt(2);
|
||||
|
||||
CFixedVector2D newVector = CFixedVector2D(sin,cos).Multiply(newDistance);
|
||||
|
||||
// get the height of the ground
|
||||
targetHeight = cmpTerrain->GetGroundLevel(pos.X+newVector.X,pos.Z+newVector.Y);
|
||||
targetHeight = targetHeight > waterLevel ? targetHeight : waterLevel;
|
||||
|
||||
if (InParabolicRange(CFixedVector3D(newVector.X,targetHeight-thisHeight,newVector.Y),maxRange))
|
||||
{
|
||||
// new vector is in parabolic range, so this is a new minVector
|
||||
minVector = newVector;
|
||||
minDistance = newDistance;
|
||||
}
|
||||
else
|
||||
{
|
||||
// new vector is out parabolic range, so this is a new maxVector
|
||||
maxVector = newVector;
|
||||
maxDistance = newDistance;
|
||||
}
|
||||
|
||||
}
|
||||
r.push_back(maxVector.X);
|
||||
r.push_back(maxVector.Y);
|
||||
|
||||
}
|
||||
r.push_back(r[0]);
|
||||
r.push_back(r[1]);
|
||||
|
||||
}
|
||||
return r;
|
||||
|
||||
}
|
||||
|
||||
Query ConstructQuery(entity_id_t source,
|
||||
entity_pos_t minRange, entity_pos_t maxRange,
|
||||
const std::vector<int>& owners, int requiredInterface, u8 flagsMask)
|
||||
|
|
@ -886,9 +1087,11 @@ public:
|
|||
|
||||
Query q;
|
||||
q.enabled = false;
|
||||
q.parabolic = false;
|
||||
q.source = source;
|
||||
q.minRange = minRange;
|
||||
q.maxRange = maxRange;
|
||||
q.elevationBonus = entity_pos_t::Zero();
|
||||
|
||||
q.ownersMask = 0;
|
||||
for (size_t i = 0; i < owners.size(); ++i)
|
||||
|
|
@ -900,11 +1103,21 @@ public:
|
|||
return q;
|
||||
}
|
||||
|
||||
Query ConstructParabolicQuery(entity_id_t source,
|
||||
entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t elevationBonus,
|
||||
const std::vector<int>& owners, int requiredInterface, u8 flagsMask)
|
||||
{
|
||||
Query q = ConstructQuery(source,minRange,maxRange,owners,requiredInterface,flagsMask);
|
||||
q.parabolic = true;
|
||||
q.elevationBonus = elevationBonus;
|
||||
return q;
|
||||
}
|
||||
|
||||
|
||||
void RenderSubmit(SceneCollector& collector)
|
||||
{
|
||||
if (!m_DebugOverlayEnabled)
|
||||
return;
|
||||
|
||||
CColor enabledRingColour(0, 1, 0, 1);
|
||||
CColor disabledRingColour(1, 0, 0, 1);
|
||||
CColor rayColour(1, 1, 0, 0.2f);
|
||||
|
|
@ -923,15 +1136,74 @@ public:
|
|||
CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
|
||||
|
||||
// Draw the max range circle
|
||||
m_DebugOverlayLines.push_back(SOverlayLine());
|
||||
m_DebugOverlayLines.back().m_Color = (q.enabled ? enabledRingColour : disabledRingColour);
|
||||
SimRender::ConstructCircleOnGround(GetSimContext(), pos.X.ToFloat(), pos.Y.ToFloat(), q.maxRange.ToFloat(), m_DebugOverlayLines.back(), true);
|
||||
if (!q.parabolic)
|
||||
{
|
||||
m_DebugOverlayLines.push_back(SOverlayLine());
|
||||
m_DebugOverlayLines.back().m_Color = (q.enabled ? enabledRingColour : disabledRingColour);
|
||||
SimRender::ConstructCircleOnGround(GetSimContext(), pos.X.ToFloat(), pos.Y.ToFloat(), q.maxRange.ToFloat(), m_DebugOverlayLines.back(), true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// elevation bonus is part of the 3D position. As if the unit is really that much higher
|
||||
CFixedVector3D pos = cmpSourcePosition->GetPosition();
|
||||
pos.Y += q.elevationBonus;
|
||||
|
||||
std::vector<entity_pos_t> coords;
|
||||
|
||||
// Get the outline from cache if possible
|
||||
if (ParabolicRangesOutlines.find(q.source) != ParabolicRangesOutlines.end())
|
||||
{
|
||||
EntityParabolicRangeOutline e = ParabolicRangesOutlines[q.source];
|
||||
if (e.position == pos && e.range == q.maxRange)
|
||||
{
|
||||
// outline is cached correctly, use it
|
||||
coords = e.outline;
|
||||
}
|
||||
else
|
||||
{
|
||||
// outline was cached, but important parameters changed
|
||||
// (position, elevation, range)
|
||||
// update it
|
||||
coords = getParabolicRangeForm(pos,q.maxRange,q.maxRange*2, entity_pos_t::Zero(), entity_pos_t::FromFloat(2.0f*3.14f),70);
|
||||
e.outline = coords;
|
||||
e.range = q.maxRange;
|
||||
e.position = pos;
|
||||
ParabolicRangesOutlines[q.source] = e;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// outline wasn't cached (first time you enable the range overlay
|
||||
// or you created a new entiy)
|
||||
// cache a new outline
|
||||
coords = getParabolicRangeForm(pos,q.maxRange,q.maxRange*2, entity_pos_t::Zero(), entity_pos_t::FromFloat(2.0f*3.14f),70);
|
||||
EntityParabolicRangeOutline e;
|
||||
e.source = q.source;
|
||||
e.range = q.maxRange;
|
||||
e.position = pos;
|
||||
e.outline = coords;
|
||||
ParabolicRangesOutlines[q.source] = e;
|
||||
}
|
||||
|
||||
CColor thiscolor = q.enabled ? enabledRingColour : disabledRingColour;
|
||||
|
||||
// draw the outline (piece by piece)
|
||||
for (size_t i = 3; i < coords.size(); i += 2)
|
||||
{
|
||||
std::vector<float> c;
|
||||
c.push_back((coords[i-3]+pos.X).ToFloat());
|
||||
c.push_back((coords[i-2]+pos.Z).ToFloat());
|
||||
c.push_back((coords[i-1]+pos.X).ToFloat());
|
||||
c.push_back((coords[i]+pos.Z).ToFloat());
|
||||
m_DebugOverlayLines.push_back(SOverlayLine());
|
||||
m_DebugOverlayLines.back().m_Color = thiscolor;
|
||||
SimRender::ConstructLineOnGround(GetSimContext(), c, m_DebugOverlayLines.back(), true);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the min range circle
|
||||
if (!q.minRange.IsZero())
|
||||
{
|
||||
m_DebugOverlayLines.push_back(SOverlayLine());
|
||||
m_DebugOverlayLines.back().m_Color = (q.enabled ? enabledRingColour : disabledRingColour);
|
||||
SimRender::ConstructCircleOnGround(GetSimContext(), pos.X.ToFloat(), pos.Y.ToFloat(), q.minRange.ToFloat(), m_DebugOverlayLines.back(), true);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ std::string ICmpRangeManager::GetLosVisibility_wrapper(entity_id_t ent, int play
|
|||
BEGIN_INTERFACE_WRAPPER(RangeManager)
|
||||
DEFINE_INTERFACE_METHOD_5("ExecuteQuery", std::vector<entity_id_t>, ICmpRangeManager, ExecuteQuery, entity_id_t, entity_pos_t, entity_pos_t, std::vector<int>, int)
|
||||
DEFINE_INTERFACE_METHOD_6("CreateActiveQuery", ICmpRangeManager::tag_t, ICmpRangeManager, CreateActiveQuery, entity_id_t, entity_pos_t, entity_pos_t, std::vector<int>, int, u8)
|
||||
DEFINE_INTERFACE_METHOD_7("CreateActiveParabolicQuery", ICmpRangeManager::tag_t, ICmpRangeManager, CreateActiveParabolicQuery, entity_id_t, entity_pos_t, entity_pos_t, entity_pos_t, std::vector<int>, int, u8)
|
||||
DEFINE_INTERFACE_METHOD_1("DestroyActiveQuery", void, ICmpRangeManager, DestroyActiveQuery, ICmpRangeManager::tag_t)
|
||||
DEFINE_INTERFACE_METHOD_1("EnableActiveQuery", void, ICmpRangeManager, EnableActiveQuery, ICmpRangeManager::tag_t)
|
||||
DEFINE_INTERFACE_METHOD_1("DisableActiveQuery", void, ICmpRangeManager, DisableActiveQuery, ICmpRangeManager::tag_t)
|
||||
|
|
@ -45,6 +46,7 @@ DEFINE_INTERFACE_METHOD_1("GetEntityFlagMask", u8, ICmpRangeManager, GetEntityFl
|
|||
DEFINE_INTERFACE_METHOD_1("GetEntitiesByPlayer", std::vector<entity_id_t>, ICmpRangeManager, GetEntitiesByPlayer, player_id_t)
|
||||
DEFINE_INTERFACE_METHOD_1("SetDebugOverlay", void, ICmpRangeManager, SetDebugOverlay, bool)
|
||||
DEFINE_INTERFACE_METHOD_2("SetLosRevealAll", void, ICmpRangeManager, SetLosRevealAll, player_id_t, bool)
|
||||
DEFINE_INTERFACE_METHOD_5("GetElevationAdaptedRange", entity_pos_t, ICmpRangeManager, GetElevationAdaptedRange, CFixedVector3D, CFixedVector3D, entity_pos_t, entity_pos_t, entity_pos_t)
|
||||
DEFINE_INTERFACE_METHOD_3("GetLosVisibility", std::string, ICmpRangeManager, GetLosVisibility_wrapper, entity_id_t, player_id_t, bool)
|
||||
DEFINE_INTERFACE_METHOD_1("SetLosCircular", void, ICmpRangeManager, SetLosCircular, bool)
|
||||
DEFINE_INTERFACE_METHOD_0("GetLosCircular", bool, ICmpRangeManager, GetLosCircular)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@
|
|||
#ifndef INCLUDED_ICMPRANGEMANAGER
|
||||
#define INCLUDED_ICMPRANGEMANAGER
|
||||
|
||||
#include "maths/FixedVector3D.h"
|
||||
|
||||
#include "simulation2/system/Interface.h"
|
||||
#include "simulation2/helpers/Position.h"
|
||||
#include "simulation2/helpers/Player.h"
|
||||
|
|
@ -102,6 +104,32 @@ public:
|
|||
virtual tag_t CreateActiveQuery(entity_id_t source,
|
||||
entity_pos_t minRange, entity_pos_t maxRange, std::vector<int> owners, int requiredInterface, u8 flags) = 0;
|
||||
|
||||
/**
|
||||
* Construct an active query of a paraboloic form around the unit.
|
||||
* The query will be disabled by default.
|
||||
* @param source the entity around which the range will be computed.
|
||||
* @param minRange non-negative minimum horizontal distance in metres (inclusive). MinRange doesn't do parabolic checks.
|
||||
* @param maxRange non-negative maximum distance in metres (inclusive) for units on the same elevation;
|
||||
* or -1.0 to ignore distance.
|
||||
* For units on a different elevation, a physical correct paraboloid with height=maxRange/2 above the unit is used to query them
|
||||
* @param elevationBonus extra bonus so the source can be placed higher and shoot further
|
||||
* @param owners list of player IDs that matching entities may have; -1 matches entities with no owner.
|
||||
* @param requiredInterface if non-zero, an interface ID that matching entities must implement.
|
||||
* @param flags if a entity in range has one of the flags set it will show up.
|
||||
* @return unique non-zero identifier of query.
|
||||
*/
|
||||
virtual tag_t CreateActiveParabolicQuery(entity_id_t source,
|
||||
entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t elevationBonus, std::vector<int> owners, int requiredInterface, u8 flags) = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Get the average elevation over 8 points on distance range around the entity
|
||||
* @param id the entity id to look around
|
||||
* @param range the distance to compare terrain height with
|
||||
* @return a fixed number representing the average difference. It's positive when the entity is on average higher than the terrain surrounding it.
|
||||
*/
|
||||
virtual entity_pos_t GetElevationAdaptedRange(CFixedVector3D pos, CFixedVector3D rot, entity_pos_t range, entity_pos_t elevationBonus, entity_pos_t angle) = 0;
|
||||
|
||||
/**
|
||||
* Destroy a query and clean up resources. This must be called when an entity no longer needs its
|
||||
* query (e.g. when the entity is destroyed).
|
||||
|
|
|
|||
|
|
@ -85,4 +85,10 @@
|
|||
6, \
|
||||
JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT },
|
||||
|
||||
#define DEFINE_INTERFACE_METHOD_7(scriptname, rettype, classname, methodname, arg1, arg2, arg3, arg4, arg5, arg6, arg7) \
|
||||
{ scriptname, \
|
||||
ScriptInterface::callMethod<rettype, arg1, arg2, arg3, arg4, arg5, arg6, arg7, &class_##classname, classname, &classname::methodname>, \
|
||||
7, \
|
||||
JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT },
|
||||
|
||||
#endif // INCLUDED_INTERFACE_SCRIPTED
|
||||
|
|
|
|||
Loading…
Reference in a new issue