Split Locomotor trait from Mobile

Add GrantConditionOn*Layer traits

This allows to
- drop some booleans from Locomotor
- drop a good part of the subterranean- and jumpjet-specific code/hacks from Mobile
- grant more than 1 condition per layer type (via multiple traits)
- easily add more traits of this kind for other layers
This commit is contained in:
reaperrr
2018-03-14 20:51:16 +01:00
committed by abcdefg30
parent f453d9c148
commit 81343926b6
29 changed files with 813 additions and 475 deletions

View File

@@ -45,10 +45,10 @@ namespace OpenRA.Mods.Cnc.Traits
return; return;
var mobile = crusher.TraitOrDefault<Mobile>(); var mobile = crusher.TraitOrDefault<Mobile>();
if (mobile != null && !info.DetonateClasses.Overlaps(mobile.Info.Crushes)) if (mobile != null && !info.DetonateClasses.Overlaps(mobile.Info.LocomotorInfo.Crushes))
return; return;
self.Kill(crusher, mobile != null ? mobile.Info.CrushDamageTypes : new HashSet<string>()); self.Kill(crusher, mobile != null ? mobile.Info.LocomotorInfo.CrushDamageTypes : new HashSet<string>());
} }
bool ICrushable.CrushableBy(Actor self, Actor crusher, HashSet<string> crushClasses) bool ICrushable.CrushableBy(Actor self, Actor crusher, HashSet<string> crushClasses)

View File

@@ -41,16 +41,16 @@ namespace OpenRA.Mods.Common.AI
CPos FindNextResource(Actor actor, Harvester harv) CPos FindNextResource(Actor actor, Harvester harv)
{ {
var mobileInfo = actor.Info.TraitInfo<MobileInfo>(); var locomotorInfo = actor.Info.TraitInfo<MobileInfo>().LocomotorInfo;
var passable = (uint)mobileInfo.GetMovementClass(world.Map.Rules.TileSet); var passable = (uint)locomotorInfo.GetMovementClass(World.Map.Rules.TileSet);
Func<CPos, bool> isValidResource = cell => Func<CPos, bool> isValidResource = cell =>
domainIndex.IsPassable(actor.Location, cell, mobileInfo, passable) && domainIndex.IsPassable(actor.Location, cell, locomotorInfo, passable) &&
harv.CanHarvestCell(actor, cell) && harv.CanHarvestCell(actor, cell) &&
claimLayer.CanClaimCell(actor, cell); claimLayer.CanClaimCell(actor, cell);
var path = pathfinder.FindPath( var path = pathfinder.FindPath(
PathSearch.Search(world, mobileInfo, actor, true, isValidResource) PathSearch.Search(world, locomotorInfo, actor, true, isValidResource)
.WithCustomCost(loc => world.FindActorsInCircle(world.Map.CenterOfCell(loc), ai.Info.HarvesterEnemyAvoidanceRadius) .WithCustomCost(loc => world.FindActorsInCircle(world.Map.CenterOfCell(loc), ai.Info.HarvesterEnemyAvoidanceRadius)
.Where(u => !u.IsDead && actor.Owner.Stances[u.Owner] == Stance.Enemy) .Where(u => !u.IsDead && actor.Owner.Stances[u.Owner] == Stance.Enemy)
.Sum(u => Math.Max(WDist.Zero.Length, ai.Info.HarvesterEnemyAvoidanceRadius.Length - (world.Map.CenterOfCell(loc) - u.CenterPosition).Length))) .Sum(u => Math.Max(WDist.Zero.Length, ai.Info.HarvesterEnemyAvoidanceRadius.Length - (world.Map.CenterOfCell(loc) - u.CenterPosition).Length)))

View File

@@ -30,12 +30,12 @@ namespace OpenRA.Mods.Common.AI
// (Way better than finding a nearest target which is likely to be on Ground) // (Way better than finding a nearest target which is likely to be on Ground)
// You might be tempted to move these lookups into Activate() but that causes null reference exception. // You might be tempted to move these lookups into Activate() but that causes null reference exception.
var domainIndex = first.World.WorldActor.Trait<DomainIndex>(); var domainIndex = first.World.WorldActor.Trait<DomainIndex>();
var mobileInfo = first.Info.TraitInfo<MobileInfo>(); var locomotorInfo = first.Info.TraitInfo<MobileInfo>().LocomotorInfo;
var passable = (uint)mobileInfo.GetMovementClass(first.World.Map.Rules.TileSet); var passable = (uint)locomotorInfo.GetMovementClass(first.World.Map.Rules.TileSet);
var navalProductions = owner.World.ActorsHavingTrait<Building>().Where(a var navalProductions = owner.World.ActorsHavingTrait<Building>().Where(a
=> owner.Bot.Info.BuildingCommonNames.NavalProduction.Contains(a.Info.Name) => owner.Bot.Info.BuildingCommonNames.NavalProduction.Contains(a.Info.Name)
&& domainIndex.IsPassable(first.Location, a.Location, mobileInfo, passable) && domainIndex.IsPassable(first.Location, a.Location, locomotorInfo, passable)
&& a.AppearsHostileTo(first)); && a.AppearsHostileTo(first));
if (navalProductions.Any()) if (navalProductions.Any())

View File

@@ -23,7 +23,7 @@ namespace OpenRA.Mods.Common.Activities
readonly Harvester harv; readonly Harvester harv;
readonly HarvesterInfo harvInfo; readonly HarvesterInfo harvInfo;
readonly Mobile mobile; readonly Mobile mobile;
readonly MobileInfo mobileInfo; readonly LocomotorInfo locomotorInfo;
readonly ResourceClaimLayer claimLayer; readonly ResourceClaimLayer claimLayer;
readonly IPathFinder pathFinder; readonly IPathFinder pathFinder;
readonly DomainIndex domainIndex; readonly DomainIndex domainIndex;
@@ -35,7 +35,7 @@ namespace OpenRA.Mods.Common.Activities
harv = self.Trait<Harvester>(); harv = self.Trait<Harvester>();
harvInfo = self.Info.TraitInfo<HarvesterInfo>(); harvInfo = self.Info.TraitInfo<HarvesterInfo>();
mobile = self.Trait<Mobile>(); mobile = self.Trait<Mobile>();
mobileInfo = self.Info.TraitInfo<MobileInfo>(); locomotorInfo = mobile.Info.LocomotorInfo;
claimLayer = self.World.WorldActor.Trait<ResourceClaimLayer>(); claimLayer = self.World.WorldActor.Trait<ResourceClaimLayer>();
pathFinder = self.World.WorldActor.Trait<IPathFinder>(); pathFinder = self.World.WorldActor.Trait<IPathFinder>();
domainIndex = self.World.WorldActor.Trait<DomainIndex>(); domainIndex = self.World.WorldActor.Trait<DomainIndex>();
@@ -126,10 +126,10 @@ namespace OpenRA.Mods.Common.Activities
var searchRadiusSquared = searchRadius * searchRadius; var searchRadiusSquared = searchRadius * searchRadius;
// Find any harvestable resources: // Find any harvestable resources:
var passable = (uint)mobileInfo.GetMovementClass(self.World.Map.Rules.TileSet); var passable = (uint)locomotorInfo.GetMovementClass(self.World.Map.Rules.TileSet);
List<CPos> path; List<CPos> path;
using (var search = PathSearch.Search(self.World, mobileInfo, self, true, loc => using (var search = PathSearch.Search(self.World, locomotorInfo, self, true, loc =>
domainIndex.IsPassable(self.Location, loc, mobileInfo, passable) && harv.CanHarvestCell(self, loc) && claimLayer.CanClaimCell(self, loc)) domainIndex.IsPassable(self.Location, loc, locomotorInfo, passable) && harv.CanHarvestCell(self, loc) && claimLayer.CanClaimCell(self, loc))
.WithCustomCost(loc => .WithCustomCost(loc =>
{ {
if ((avoidCell.HasValue && loc == avoidCell.Value) || if ((avoidCell.HasValue && loc == avoidCell.Value) ||

View File

@@ -51,7 +51,7 @@ namespace OpenRA.Mods.Common.Activities
{ {
List<CPos> path; List<CPos> path;
using (var search = using (var search =
PathSearch.FromPoint(self.World, mobile.Info, self, mobile.ToCell, destination, false) PathSearch.FromPoint(self.World, mobile.Info.LocomotorInfo, self, mobile.ToCell, destination, false)
.WithoutLaneBias()) .WithoutLaneBias())
path = self.World.WorldActor.Trait<IPathFinder>().FindPath(search); path = self.World.WorldActor.Trait<IPathFinder>().FindPath(search);
return path; return path;
@@ -293,7 +293,9 @@ namespace OpenRA.Mods.Common.Activities
// Wait a bit to see if they leave // Wait a bit to see if they leave
if (!hasWaited) if (!hasWaited)
{ {
waitTicksRemaining = mobile.Info.WaitAverage + self.World.SharedRandom.Next(-mobile.Info.WaitSpread, mobile.Info.WaitSpread); waitTicksRemaining = mobile.Info.LocomotorInfo.WaitAverage
+ self.World.SharedRandom.Next(-mobile.Info.LocomotorInfo.WaitSpread, mobile.Info.LocomotorInfo.WaitSpread);
hasWaited = true; hasWaited = true;
} }

View File

@@ -56,7 +56,7 @@ namespace OpenRA.Mods.Common.Activities
Mobile = self.Trait<Mobile>(); Mobile = self.Trait<Mobile>();
pathFinder = self.World.WorldActor.Trait<IPathFinder>(); pathFinder = self.World.WorldActor.Trait<IPathFinder>();
domainIndex = self.World.WorldActor.Trait<DomainIndex>(); domainIndex = self.World.WorldActor.Trait<DomainIndex>();
movementClass = (uint)Mobile.Info.GetMovementClass(self.World.Map.Rules.TileSet); movementClass = (uint)Mobile.Info.LocomotorInfo.GetMovementClass(self.World.Map.Rules.TileSet);
if (target.IsValidFor(self)) if (target.IsValidFor(self))
targetPosition = self.World.Map.CellContaining(target.CenterPosition); targetPosition = self.World.Map.CellContaining(target.CenterPosition);
@@ -142,14 +142,14 @@ namespace OpenRA.Mods.Common.Activities
var loc = self.Location; var loc = self.Location;
foreach (var cell in targetCells) foreach (var cell in targetCells)
if (domainIndex.IsPassable(loc, cell, Mobile.Info, movementClass) && Mobile.CanEnterCell(cell)) if (domainIndex.IsPassable(loc, cell, Mobile.Info.LocomotorInfo, movementClass) && Mobile.CanEnterCell(cell))
searchCells.Add(cell); searchCells.Add(cell);
if (!searchCells.Any()) if (!searchCells.Any())
return NoPath; return NoPath;
using (var fromSrc = PathSearch.FromPoints(self.World, Mobile.Info, self, searchCells, loc, true)) using (var fromSrc = PathSearch.FromPoints(self.World, Mobile.Info.LocomotorInfo, self, searchCells, loc, true))
using (var fromDest = PathSearch.FromPoint(self.World, Mobile.Info, self, loc, targetPosition, true).Reverse()) using (var fromDest = PathSearch.FromPoint(self.World, Mobile.Info.LocomotorInfo, self, loc, targetPosition, true).Reverse())
return pathFinder.FindBidiPath(fromSrc, fromDest); return pathFinder.FindBidiPath(fromSrc, fromDest);
} }

View File

@@ -524,6 +524,10 @@
<Compile Include="Traits\Conditions\GrantConditionOnDamageState.cs" /> <Compile Include="Traits\Conditions\GrantConditionOnDamageState.cs" />
<Compile Include="Traits\Conditions\GrantConditionOnFaction.cs" /> <Compile Include="Traits\Conditions\GrantConditionOnFaction.cs" />
<Compile Include="Traits\Conditions\GrantConditionOnTerrain.cs" /> <Compile Include="Traits\Conditions\GrantConditionOnTerrain.cs" />
<Compile Include="Traits\Conditions\GrantConditionOnLayer.cs" />
<Compile Include="Traits\Conditions\GrantConditionOnTunnelLayer.cs" />
<Compile Include="Traits\Conditions\GrantConditionOnJumpjetLayer.cs" />
<Compile Include="Traits\Conditions\GrantConditionOnSubterraneanLayer.cs" />
<Compile Include="Traits\Conditions\GrantConditionOnMovement.cs" /> <Compile Include="Traits\Conditions\GrantConditionOnMovement.cs" />
<Compile Include="Traits\Conditions\GrantConditionOnPrerequisite.cs" /> <Compile Include="Traits\Conditions\GrantConditionOnPrerequisite.cs" />
<Compile Include="Traits\Conditions\ConditionManager.cs" /> <Compile Include="Traits\Conditions\ConditionManager.cs" />
@@ -534,6 +538,9 @@
<Compile Include="Traits\World\CrateSpawner.cs" /> <Compile Include="Traits\World\CrateSpawner.cs" />
<Compile Include="Traits\World\CreateMPPlayers.cs" /> <Compile Include="Traits\World\CreateMPPlayers.cs" />
<Compile Include="Traits\World\DomainIndex.cs" /> <Compile Include="Traits\World\DomainIndex.cs" />
<Compile Include="Traits\World\Locomotor.cs" />
<Compile Include="Traits\World\JumpjetLocomotor.cs" />
<Compile Include="Traits\World\SubterraneanLocomotor.cs" />
<Compile Include="Traits\World\LoadWidgetAtGameStart.cs" /> <Compile Include="Traits\World\LoadWidgetAtGameStart.cs" />
<Compile Include="Traits\World\MPStartLocations.cs" /> <Compile Include="Traits\World\MPStartLocations.cs" />
<Compile Include="Traits\World\MPStartUnits.cs" /> <Compile Include="Traits\World\MPStartUnits.cs" />

View File

@@ -84,8 +84,8 @@ namespace OpenRA.Mods.Common.Pathfinder
public Actor IgnoreActor { get; set; } public Actor IgnoreActor { get; set; }
readonly CellConditions checkConditions; readonly CellConditions checkConditions;
readonly MobileInfo mobileInfo; readonly LocomotorInfo locomotorInfo;
readonly MobileInfo.WorldMovementInfo worldMovementInfo; readonly LocomotorInfo.WorldMovementInfo worldMovementInfo;
readonly CellInfoLayerPool.PooledCellInfoLayer pooledLayer; readonly CellInfoLayerPool.PooledCellInfoLayer pooledLayer;
readonly bool checkTerrainHeight; readonly bool checkTerrainHeight;
CellLayer<CellInfo> groundInfo; CellLayer<CellInfo> groundInfo;
@@ -93,19 +93,19 @@ namespace OpenRA.Mods.Common.Pathfinder
readonly Dictionary<byte, Pair<ICustomMovementLayer, CellLayer<CellInfo>>> customLayerInfo = readonly Dictionary<byte, Pair<ICustomMovementLayer, CellLayer<CellInfo>>> customLayerInfo =
new Dictionary<byte, Pair<ICustomMovementLayer, CellLayer<CellInfo>>>(); new Dictionary<byte, Pair<ICustomMovementLayer, CellLayer<CellInfo>>>();
public PathGraph(CellInfoLayerPool layerPool, MobileInfo mobileInfo, Actor actor, World world, bool checkForBlocked) public PathGraph(CellInfoLayerPool layerPool, LocomotorInfo li, Actor actor, World world, bool checkForBlocked)
{ {
pooledLayer = layerPool.Get(); pooledLayer = layerPool.Get();
groundInfo = pooledLayer.GetLayer(); groundInfo = pooledLayer.GetLayer();
locomotorInfo = li;
var layers = world.GetCustomMovementLayers().Values var layers = world.GetCustomMovementLayers().Values
.Where(cml => cml.EnabledForActor(actor.Info, mobileInfo)); .Where(cml => cml.EnabledForActor(actor.Info, locomotorInfo));
foreach (var cml in layers) foreach (var cml in layers)
customLayerInfo[cml.Index] = Pair.New(cml, pooledLayer.GetLayer()); customLayerInfo[cml.Index] = Pair.New(cml, pooledLayer.GetLayer());
World = world; World = world;
this.mobileInfo = mobileInfo; worldMovementInfo = locomotorInfo.GetWorldMovementInfo(world);
worldMovementInfo = mobileInfo.GetWorldMovementInfo(world);
Actor = actor; Actor = actor;
LaneBias = 1; LaneBias = 1;
checkConditions = checkForBlocked ? CellConditions.TransientActors : CellConditions.None; checkConditions = checkForBlocked ? CellConditions.TransientActors : CellConditions.None;
@@ -153,7 +153,7 @@ namespace OpenRA.Mods.Common.Pathfinder
foreach (var cli in customLayerInfo.Values) foreach (var cli in customLayerInfo.Values)
{ {
var layerPosition = new CPos(position.X, position.Y, cli.First.Index); var layerPosition = new CPos(position.X, position.Y, cli.First.Index);
var entryCost = cli.First.EntryMovementCost(Actor.Info, mobileInfo, layerPosition); var entryCost = cli.First.EntryMovementCost(Actor.Info, locomotorInfo, layerPosition);
if (entryCost != Constants.InvalidNode) if (entryCost != Constants.InvalidNode)
validNeighbors.Add(new GraphConnection(layerPosition, entryCost)); validNeighbors.Add(new GraphConnection(layerPosition, entryCost));
} }
@@ -161,7 +161,7 @@ namespace OpenRA.Mods.Common.Pathfinder
else else
{ {
var layerPosition = new CPos(position.X, position.Y, 0); var layerPosition = new CPos(position.X, position.Y, 0);
var exitCost = customLayerInfo[position.Layer].First.ExitMovementCost(Actor.Info, mobileInfo, layerPosition); var exitCost = customLayerInfo[position.Layer].First.ExitMovementCost(Actor.Info, locomotorInfo, layerPosition);
if (exitCost != Constants.InvalidNode) if (exitCost != Constants.InvalidNode)
validNeighbors.Add(new GraphConnection(layerPosition, exitCost)); validNeighbors.Add(new GraphConnection(layerPosition, exitCost));
} }
@@ -171,7 +171,7 @@ namespace OpenRA.Mods.Common.Pathfinder
int GetCostToNode(CPos destNode, CVec direction) int GetCostToNode(CPos destNode, CVec direction)
{ {
var movementCost = mobileInfo.MovementCostToEnterCell(worldMovementInfo, Actor, destNode, IgnoreActor, checkConditions); var movementCost = locomotorInfo.MovementCostToEnterCell(worldMovementInfo, Actor, destNode, IgnoreActor, checkConditions);
if (movementCost != int.MaxValue && !(CustomBlock != null && CustomBlock(destNode))) if (movementCost != int.MaxValue && !(CustomBlock != null && CustomBlock(destNode)))
return CalculateCellCost(destNode, direction, movementCost); return CalculateCellCost(destNode, direction, movementCost);

View File

@@ -45,18 +45,18 @@ namespace OpenRA.Mods.Common.Pathfinder
considered = new LinkedList<Pair<CPos, int>>(); considered = new LinkedList<Pair<CPos, int>>();
} }
public static IPathSearch Search(World world, MobileInfo mi, Actor self, bool checkForBlocked, Func<CPos, bool> goalCondition) public static IPathSearch Search(World world, LocomotorInfo li, Actor self, bool checkForBlocked, Func<CPos, bool> goalCondition)
{ {
var graph = new PathGraph(LayerPoolForWorld(world), mi, self, world, checkForBlocked); var graph = new PathGraph(LayerPoolForWorld(world), li, self, world, checkForBlocked);
var search = new PathSearch(graph); var search = new PathSearch(graph);
search.isGoal = goalCondition; search.isGoal = goalCondition;
search.heuristic = loc => 0; search.heuristic = loc => 0;
return search; return search;
} }
public static IPathSearch FromPoint(World world, MobileInfo mi, Actor self, CPos from, CPos target, bool checkForBlocked) public static IPathSearch FromPoint(World world, LocomotorInfo li, Actor self, CPos from, CPos target, bool checkForBlocked)
{ {
var graph = new PathGraph(LayerPoolForWorld(world), mi, self, world, checkForBlocked); var graph = new PathGraph(LayerPoolForWorld(world), li, self, world, checkForBlocked);
var search = new PathSearch(graph) var search = new PathSearch(graph)
{ {
heuristic = DefaultEstimator(target) heuristic = DefaultEstimator(target)
@@ -74,9 +74,9 @@ namespace OpenRA.Mods.Common.Pathfinder
return search; return search;
} }
public static IPathSearch FromPoints(World world, MobileInfo mi, Actor self, IEnumerable<CPos> froms, CPos target, bool checkForBlocked) public static IPathSearch FromPoints(World world, LocomotorInfo li, Actor self, IEnumerable<CPos> froms, CPos target, bool checkForBlocked)
{ {
var graph = new PathGraph(LayerPoolForWorld(world), mi, self, world, checkForBlocked); var graph = new PathGraph(LayerPoolForWorld(world), li, self, world, checkForBlocked);
var search = new PathSearch(graph) var search = new PathSearch(graph)
{ {
heuristic = DefaultEstimator(target) heuristic = DefaultEstimator(target)

View File

@@ -161,12 +161,13 @@ namespace OpenRA.Mods.Common.Scripting
{ {
var mobiles = cargo != null ? cargo.Passengers.Select(a => var mobiles = cargo != null ? cargo.Passengers.Select(a =>
{ {
var mobileInfo = a.Info.TraitInfoOrDefault<MobileInfo>(); var mobile = a.TraitOrDefault<Mobile>();
if (mobileInfo == null) if (mobile == null)
return new Pair<MobileInfo, uint>(null, 0); return new Pair<LocomotorInfo, uint>(null, 0);
return new Pair<MobileInfo, uint>(mobileInfo, (uint)mobileInfo.GetMovementClass(a.World.Map.Rules.TileSet)); var locomotorInfo = mobile.Info.LocomotorInfo;
}) : new Pair<MobileInfo, uint>[0]; return new Pair<LocomotorInfo, uint>(locomotorInfo, (uint)locomotorInfo.GetMovementClass(a.World.Map.Rules.TileSet));
}) : new Pair<LocomotorInfo, uint>[0];
foreach (var c in transport.World.Map.FindTilesInCircle(destination, dropRange)) foreach (var c in transport.World.Map.FindTilesInCircle(destination, dropRange))
{ {

View File

@@ -0,0 +1,59 @@
#region Copyright & License Information
/*
* Copyright 2007-2018 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
public class GrantConditionOnJumpjetLayerInfo : GrantConditionOnLayerInfo
{
public override object Create(ActorInitializer init) { return new GrantConditionOnJumpjetLayer(init.Self, this); }
public override void RulesetLoaded(Ruleset rules, ActorInfo ai)
{
var mobileInfo = ai.TraitInfoOrDefault<MobileInfo>();
if (mobileInfo == null || !(mobileInfo.LocomotorInfo is JumpjetLocomotorInfo))
throw new YamlException("GrantConditionOnJumpjetLayer requires Mobile to be linked to a JumpjetLocomotor!");
base.RulesetLoaded(rules, ai);
}
}
public class GrantConditionOnJumpjetLayer : GrantConditionOnLayer<GrantConditionOnJumpjetLayerInfo>, INotifyFinishedMoving
{
bool jumpjetInAir;
public GrantConditionOnJumpjetLayer(Actor self, GrantConditionOnJumpjetLayerInfo info)
: base(self, info, CustomMovementLayerType.Jumpjet) { }
void INotifyFinishedMoving.FinishedMoving(Actor self, byte oldLayer, byte newLayer)
{
if (jumpjetInAir && oldLayer != ValidLayerType && newLayer != ValidLayerType)
UpdateConditions(self, oldLayer, newLayer);
}
protected override void UpdateConditions(Actor self, byte oldLayer, byte newLayer)
{
if (!jumpjetInAir && newLayer == ValidLayerType && oldLayer != ValidLayerType && conditionToken == ConditionManager.InvalidConditionToken)
{
conditionToken = conditionManager.GrantCondition(self, Info.Condition);
jumpjetInAir = true;
}
// By the time the condition is meant to be revoked, the 'oldLayer' is already no longer the Jumpjet layer, either
if (jumpjetInAir && newLayer != ValidLayerType && oldLayer != ValidLayerType && conditionToken != ConditionManager.InvalidConditionToken)
{
conditionToken = conditionManager.RevokeCondition(self, conditionToken);
jumpjetInAir = false;
}
}
}
}

View File

@@ -0,0 +1,73 @@
#region Copyright & License Information
/*
* Copyright 2007-2018 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System.Linq;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
public abstract class GrantConditionOnLayerInfo : ConditionalTraitInfo
{
[FieldLoader.Require]
[GrantedConditionReference]
[Desc("The condition to grant to self when changing to specific custom layer.")]
public readonly string Condition = null;
}
public abstract class GrantConditionOnLayer<InfoType> : ConditionalTrait<InfoType>, INotifyCustomLayerChanged where InfoType : GrantConditionOnLayerInfo
{
protected readonly byte ValidLayerType;
protected ConditionManager conditionManager;
protected int conditionToken = ConditionManager.InvalidConditionToken;
public GrantConditionOnLayer(Actor self, InfoType info, byte validLayer)
: base(info)
{
ValidLayerType = validLayer;
}
protected override void Created(Actor self)
{
conditionManager = self.TraitOrDefault<ConditionManager>();
base.Created(self);
}
void INotifyCustomLayerChanged.CustomLayerChanged(Actor self, byte oldLayer, byte newLayer)
{
if (conditionManager == null)
return;
UpdateConditions(self, oldLayer, newLayer);
}
protected virtual void UpdateConditions(Actor self, byte oldLayer, byte newLayer)
{
if (newLayer == ValidLayerType && oldLayer != ValidLayerType && conditionToken == ConditionManager.InvalidConditionToken)
conditionToken = conditionManager.GrantCondition(self, Info.Condition);
else if (newLayer != ValidLayerType && oldLayer == ValidLayerType && conditionToken != ConditionManager.InvalidConditionToken)
conditionToken = conditionManager.RevokeCondition(self, conditionToken);
}
protected override void TraitEnabled(Actor self)
{
if (self.Location.Layer == ValidLayerType && conditionToken == ConditionManager.InvalidConditionToken)
conditionToken = conditionManager.GrantCondition(self, Info.Condition);
}
protected override void TraitDisabled(Actor self)
{
if (conditionToken == ConditionManager.InvalidConditionToken)
return;
conditionToken = conditionManager.RevokeCondition(self, conditionToken);
}
}
}

View File

@@ -0,0 +1,94 @@
#region Copyright & License Information
/*
* Copyright 2007-2018 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using OpenRA.Mods.Common.Effects;
using OpenRA.Mods.Common.Traits;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("Grants Condition on subterranean layer. Also plays transition audio-visuals.")]
public class GrantConditionOnSubterraneanLayerInfo : GrantConditionOnLayerInfo
{
[Desc("Dig animation image to play when transitioning.")]
public readonly string SubterraneanTransitionImage = null;
[SequenceReference("SubterraneanTransitionImage")]
[Desc("Dig animation sequence to play when transitioning.")]
public readonly string SubterraneanTransitionSequence = null;
[PaletteReference]
public readonly string SubterraneanTransitionPalette = "effect";
[Desc("Dig sound to play when transitioning.")]
public readonly string SubterraneanTransitionSound = null;
public override object Create(ActorInitializer init) { return new GrantConditionOnSubterraneanLayer(init.Self, this); }
public override void RulesetLoaded(Ruleset rules, ActorInfo ai)
{
var mobileInfo = ai.TraitInfoOrDefault<MobileInfo>();
if (mobileInfo == null || !(mobileInfo.LocomotorInfo is SubterraneanLocomotorInfo))
throw new YamlException("GrantConditionOnSubterraneanLayer requires Mobile to be linked to a SubterraneanLocomotor!");
base.RulesetLoaded(rules, ai);
}
}
public class GrantConditionOnSubterraneanLayer : GrantConditionOnLayer<GrantConditionOnSubterraneanLayerInfo>, INotifyVisualPositionChanged
{
WDist transitionDepth;
public GrantConditionOnSubterraneanLayer(Actor self, GrantConditionOnSubterraneanLayerInfo info)
: base(self, info, CustomMovementLayerType.Subterranean) { }
protected override void Created(Actor self)
{
var mobileInfo = self.Info.TraitInfo<MobileInfo>();
var li = (SubterraneanLocomotorInfo)mobileInfo.LocomotorInfo;
transitionDepth = li.SubterraneanTransitionDepth;
base.Created(self);
}
void PlayTransitionAudioVisuals(Actor self, CPos fromCell)
{
if (!string.IsNullOrEmpty(Info.SubterraneanTransitionSequence))
self.World.AddFrameEndTask(w => w.Add(new SpriteEffect(self.World.Map.CenterOfCell(fromCell), self.World,
Info.SubterraneanTransitionImage,
Info.SubterraneanTransitionSequence, Info.SubterraneanTransitionPalette)));
if (!string.IsNullOrEmpty(Info.SubterraneanTransitionSound))
Game.Sound.Play(SoundType.World, Info.SubterraneanTransitionSound);
}
void INotifyVisualPositionChanged.VisualPositionChanged(Actor self, byte oldLayer, byte newLayer)
{
var depth = self.World.Map.DistanceAboveTerrain(self.CenterPosition);
// Grant condition when new layer is Subterranean and depth is lower than transition depth,
// revoke condition when new layer is not Subterranean and depth is at or higher than transition depth.
if (newLayer == ValidLayerType && depth < transitionDepth && conditionToken == ConditionManager.InvalidConditionToken)
conditionToken = conditionManager.GrantCondition(self, Info.Condition);
else if (newLayer != ValidLayerType && depth > transitionDepth && conditionToken != ConditionManager.InvalidConditionToken)
{
conditionToken = conditionManager.RevokeCondition(self, conditionToken);
PlayTransitionAudioVisuals(self, self.Location);
}
}
protected override void UpdateConditions(Actor self, byte oldLayer, byte newLayer)
{
// Special case, only audio-visuals are played at the time the Layer changes from normal to Subterranean
if (newLayer == ValidLayerType && oldLayer != ValidLayerType)
PlayTransitionAudioVisuals(self, self.Location);
}
}
}

View File

@@ -0,0 +1,26 @@
#region Copyright & License Information
/*
* Copyright 2007-2018 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
public class GrantConditionOnTunnelLayerInfo : GrantConditionOnLayerInfo
{
public override object Create(ActorInitializer init) { return new GrantConditionOnTunnelLayer(init.Self, this); }
}
public class GrantConditionOnTunnelLayer : GrantConditionOnLayer<GrantConditionOnTunnelLayerInfo>
{
public GrantConditionOnTunnelLayer(Actor self, GrantConditionOnTunnelLayerInfo info)
: base(self, info, CustomMovementLayerType.Tunnel) { }
}
}

View File

@@ -121,7 +121,7 @@ namespace OpenRA.Mods.Common.Traits
// Make sure that the actor can collect this crate type // Make sure that the actor can collect this crate type
// Crate can only be crushed if it is not in the air. // Crate can only be crushed if it is not in the air.
return self.IsAtGroundLevel() && mi.Crushes.Contains(info.CrushClass); return self.IsAtGroundLevel() && mi.LocomotorInfo.Crushes.Contains(info.CrushClass);
}); });
// Destroy the crate if none of the units in the cell are valid collectors // Destroy the crate if none of the units in the cell are valid collectors

View File

@@ -58,7 +58,7 @@ namespace OpenRA.Mods.Common.Traits
Game.Sound.Play(SoundType.World, Info.CrushSound, crusher.CenterPosition); Game.Sound.Play(SoundType.World, Info.CrushSound, crusher.CenterPosition);
var crusherMobile = crusher.TraitOrDefault<Mobile>(); var crusherMobile = crusher.TraitOrDefault<Mobile>();
self.Kill(crusher, crusherMobile != null ? crusherMobile.Info.CrushDamageTypes : new HashSet<string>()); self.Kill(crusher, crusherMobile != null ? crusherMobile.Info.LocomotorInfo.CrushDamageTypes : new HashSet<string>());
} }
bool ICrushable.CrushableBy(Actor self, Actor crusher, HashSet<string> crushClasses) bool ICrushable.CrushableBy(Actor self, Actor crusher, HashSet<string> crushClasses)

View File

@@ -185,8 +185,8 @@ namespace OpenRA.Mods.Common.Traits
// Start a search from each refinery's delivery location: // Start a search from each refinery's delivery location:
List<CPos> path; List<CPos> path;
var mi = self.Info.TraitInfo<MobileInfo>(); var li = self.Info.TraitInfo<MobileInfo>().LocomotorInfo;
using (var search = PathSearch.FromPoints(self.World, mi, self, refs.Values.Select(r => r.Location), self.Location, false) using (var search = PathSearch.FromPoints(self.World, li, self, refs.Values.Select(r => r.Location), self.Location, false)
.WithCustomCost(loc => .WithCustomCost(loc =>
{ {
if (!refs.ContainsKey(loc)) if (!refs.ContainsKey(loc))

View File

@@ -22,49 +22,13 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits namespace OpenRA.Mods.Common.Traits
{ {
[Flags]
public enum CellConditions
{
None = 0,
TransientActors,
BlockedByMovers,
All = TransientActors | BlockedByMovers
}
public static class CellConditionsExts
{
public static bool HasCellCondition(this CellConditions c, CellConditions cellCondition)
{
// PERF: Enum.HasFlag is slower and requires allocations.
return (c & cellCondition) == cellCondition;
}
}
public static class CustomMovementLayerType
{
public const byte Tunnel = 1;
public const byte Subterranean = 2;
public const byte Jumpjet = 3;
public const byte ElevatedBridge = 4;
}
[Desc("Unit is able to move.")] [Desc("Unit is able to move.")]
public class MobileInfo : ConditionalTraitInfo, IMoveInfo, IPositionableInfo, IFacingInfo, public class MobileInfo : ConditionalTraitInfo, IMoveInfo, IPositionableInfo, IFacingInfo,
UsesInit<FacingInit>, UsesInit<LocationInit>, UsesInit<SubCellInit>, IActorPreviewInitInfo UsesInit<FacingInit>, UsesInit<LocationInit>, UsesInit<SubCellInit>, IActorPreviewInitInfo
{ {
[FieldLoader.LoadUsing("LoadSpeeds", true)] [Desc("Which Locomotor does this trait use. Must be defined on the World actor.")]
[Desc("Set Water: 0 for ground units and lower the value on rough terrain.")] [FieldLoader.Require]
public readonly Dictionary<string, TerrainInfo> TerrainSpeeds; public readonly string Locomotor = null;
[Desc("e.g. crate, wall, infantry")]
public readonly HashSet<string> Crushes = new HashSet<string>();
[Desc("Types of damage that are caused while crushing. Leave empty for no damage types.")]
public readonly HashSet<string> CrushDamageTypes = new HashSet<string>();
public readonly int WaitAverage = 5;
public readonly int WaitSpread = 2;
public readonly int InitialFacing = 0; public readonly int InitialFacing = 0;
@@ -73,68 +37,11 @@ namespace OpenRA.Mods.Common.Traits
public readonly int Speed = 1; public readonly int Speed = 1;
[Desc("Allow multiple (infantry) units in one cell.")]
public readonly bool SharesCell = false;
[Desc("Can the actor be ordered to move in to shroud?")]
public readonly bool MoveIntoShroud = true;
public readonly string Cursor = "move"; public readonly string Cursor = "move";
public readonly string BlockedCursor = "move-blocked"; public readonly string BlockedCursor = "move-blocked";
[VoiceReference] public readonly string Voice = "Action"; [VoiceReference] public readonly string Voice = "Action";
[GrantedConditionReference]
[Desc("The condition to grant to self while inside a tunnel.")]
public readonly string TunnelCondition = null;
[Desc("Can this unit move underground?")]
public readonly bool Subterranean = false;
[GrantedConditionReference]
[Desc("The condition to grant to self while underground.")]
public readonly string SubterraneanCondition = null;
[Desc("Pathfinding cost for submerging or reemerging.")]
public readonly int SubterraneanTransitionCost = 0;
[Desc("The terrain types that this actor can transition on. Leave empty to allow any.")]
public readonly HashSet<string> SubterraneanTransitionTerrainTypes = new HashSet<string>();
[Desc("Can this actor transition on slopes?")]
public readonly bool SubterraneanTransitionOnRamps = false;
[Desc("Depth at which the subterranian condition is applied.")]
public readonly WDist SubterraneanTransitionDepth = new WDist(-1024);
[Desc("Dig animation image to play when transitioning.")]
public readonly string SubterraneanTransitionImage = null;
[SequenceReference("SubterraneanTransitionImage")]
[Desc("Dig animation image to play when transitioning.")]
public readonly string SubterraneanTransitionSequence = null;
[PaletteReference]
public readonly string SubterraneanTransitionPalette = "effect";
public readonly string SubterraneanTransitionSound = null;
[Desc("Can this unit fly over obstacles?")]
public readonly bool Jumpjet = false;
[GrantedConditionReference]
[Desc("The condition to grant to self while flying.")]
public readonly string JumpjetCondition = null;
[Desc("Pathfinding cost for taking off or landing.")]
public readonly int JumpjetTransitionCost = 0;
[Desc("The terrain types that this actor can transition on. Leave empty to allow any.")]
public readonly HashSet<string> JumpjetTransitionTerrainTypes = new HashSet<string>();
[Desc("Can this actor transition on slopes?")]
public readonly bool JumpjetTransitionOnRamps = true;
[Desc("Facing to use for actor previews (map editor, color picker, etc)")] [Desc("Facing to use for actor previews (map editor, color picker, etc)")]
public readonly int PreviewFacing = 92; public readonly int PreviewFacing = 92;
@@ -145,240 +52,37 @@ namespace OpenRA.Mods.Common.Traits
public override object Create(ActorInitializer init) { return new Mobile(init, this); } public override object Create(ActorInitializer init) { return new Mobile(init, this); }
static object LoadSpeeds(MiniYaml y) public LocomotorInfo LocomotorInfo { get; private set; }
public override void RulesetLoaded(Ruleset rules, ActorInfo ai)
{ {
var ret = new Dictionary<string, TerrainInfo>(); var locomotorInfos = rules.Actors["world"].TraitInfos<LocomotorInfo>();
foreach (var t in y.ToDictionary()["TerrainSpeeds"].Nodes) LocomotorInfo = locomotorInfos.FirstOrDefault(li => li.Name == Locomotor);
{ if (LocomotorInfo == null)
var speed = FieldLoader.GetValue<int>("speed", t.Value.Value); throw new YamlException("A locomotor named '{0}' doesn't exist.".F(Locomotor));
var nodesDict = t.Value.ToDictionary(); else if (locomotorInfos.Count(li => li.Name == Locomotor) > 1)
var cost = nodesDict.ContainsKey("PathingCost") throw new YamlException("There is more than one locomotor named '{0}'.".F(Locomotor));
? FieldLoader.GetValue<int>("cost", nodesDict["PathingCost"].Value)
: 10000 / speed;
ret.Add(t.Key, new TerrainInfo(speed, cost));
}
return ret; base.RulesetLoaded(rules, ai);
}
TerrainInfo[] LoadTilesetSpeeds(TileSet tileSet)
{
var info = new TerrainInfo[tileSet.TerrainInfo.Length];
for (var i = 0; i < info.Length; i++)
info[i] = TerrainInfo.Impassable;
foreach (var kvp in TerrainSpeeds)
{
byte index;
if (tileSet.TryGetTerrainIndex(kvp.Key, out index))
info[index] = kvp.Value;
}
return info;
}
public class TerrainInfo
{
public static readonly TerrainInfo Impassable = new TerrainInfo();
public readonly int Cost;
public readonly int Speed;
public TerrainInfo()
{
Cost = int.MaxValue;
Speed = 0;
}
public TerrainInfo(int speed, int cost)
{
Speed = speed;
Cost = cost;
}
}
public struct WorldMovementInfo
{
internal readonly World World;
internal readonly TerrainInfo[] TerrainInfos;
internal WorldMovementInfo(World world, MobileInfo info)
{
// PERF: This struct allows us to cache the terrain info for the tileset used by the world.
// This allows us to speed up some performance-sensitive pathfinding calculations.
World = world;
TerrainInfos = info.TilesetTerrainInfo[world.Map.Rules.TileSet];
}
}
public readonly Cache<TileSet, TerrainInfo[]> TilesetTerrainInfo;
public readonly Cache<TileSet, int> TilesetMovementClass;
public MobileInfo()
{
TilesetTerrainInfo = new Cache<TileSet, TerrainInfo[]>(LoadTilesetSpeeds);
TilesetMovementClass = new Cache<TileSet, int>(CalculateTilesetMovementClass);
}
public int MovementCostForCell(World world, CPos cell)
{
return MovementCostForCell(world, TilesetTerrainInfo[world.Map.Rules.TileSet], cell);
}
int MovementCostForCell(World world, TerrainInfo[] terrainInfos, CPos cell)
{
if (!world.Map.Contains(cell))
return int.MaxValue;
var index = cell.Layer == 0 ? world.Map.GetTerrainIndex(cell) :
world.GetCustomMovementLayers()[cell.Layer].GetTerrainIndex(cell);
if (index == byte.MaxValue)
return int.MaxValue;
return terrainInfos[index].Cost;
}
public int CalculateTilesetMovementClass(TileSet tileset)
{
// collect our ability to cross *all* terraintypes, in a bitvector
return TilesetTerrainInfo[tileset].Select(ti => ti.Cost < int.MaxValue).ToBits();
}
public int GetMovementClass(TileSet tileset)
{
return TilesetMovementClass[tileset];
}
static bool IsMovingInMyDirection(Actor self, Actor other)
{
var otherMobile = other.TraitOrDefault<Mobile>();
if (otherMobile == null || !otherMobile.IsMoving)
return false;
var selfMobile = self.TraitOrDefault<Mobile>();
if (selfMobile == null)
return false;
// Moving in the same direction if the facing delta is between +/- 90 degrees
var delta = Util.NormalizeFacing(otherMobile.Facing - selfMobile.Facing);
return delta < 64 || delta > 192;
}
public int TileSetMovementHash(TileSet tileSet)
{
var terrainInfos = TilesetTerrainInfo[tileSet];
// Compute and return the hash using aggregate
return terrainInfos.Aggregate(terrainInfos.Length,
(current, terrainInfo) => unchecked(current * 31 + terrainInfo.Cost));
}
public bool CanEnterCell(World world, Actor self, CPos cell, Actor ignoreActor = null, bool checkTransientActors = true)
{
if (MovementCostForCell(world, cell) == int.MaxValue)
return false;
var check = checkTransientActors ? CellConditions.All : CellConditions.BlockedByMovers;
return CanMoveFreelyInto(world, self, cell, ignoreActor, check);
}
// Determines whether the actor is blocked by other Actors
public bool CanMoveFreelyInto(World world, Actor self, CPos cell, Actor ignoreActor, CellConditions check)
{
if (!check.HasCellCondition(CellConditions.TransientActors))
return true;
if (SharesCell && world.ActorMap.HasFreeSubCell(cell))
return true;
// PERF: Avoid LINQ.
foreach (var otherActor in world.ActorMap.GetActorsAt(cell))
if (IsBlockedBy(self, otherActor, ignoreActor, check))
return false;
return true;
}
bool IsBlockedBy(Actor self, Actor otherActor, Actor ignoreActor, CellConditions check)
{
// We are not blocked by the actor we are ignoring.
if (otherActor == ignoreActor)
return false;
// If self is null, we don't have a real actor - we're just checking what would happen theoretically.
// In such a scenario - we'll just assume any other actor in the cell will block us by default.
// If we have a real actor, we can then perform the extra checks that allow us to avoid being blocked.
if (self == null)
return true;
// If the check allows: we are not blocked by allied units moving in our direction.
if (!check.HasCellCondition(CellConditions.BlockedByMovers) &&
self.Owner.Stances[otherActor.Owner] == Stance.Ally &&
IsMovingInMyDirection(self, otherActor))
return false;
// If there is a temporary blocker in our path, but we can remove it, we are not blocked.
var temporaryBlocker = otherActor.TraitOrDefault<ITemporaryBlocker>();
if (temporaryBlocker != null && temporaryBlocker.CanRemoveBlockage(otherActor, self))
return false;
// If we cannot crush the other actor in our way, we are blocked.
if (Crushes == null || Crushes.Count == 0)
return true;
// If the other actor in our way cannot be crushed, we are blocked.
// PERF: Avoid LINQ.
var crushables = otherActor.TraitsImplementing<ICrushable>();
foreach (var crushable in crushables)
if (crushable.CrushableBy(otherActor, self, Crushes))
return false;
return true;
}
public WorldMovementInfo GetWorldMovementInfo(World world)
{
return new WorldMovementInfo(world, this);
}
public int MovementCostToEnterCell(WorldMovementInfo worldMovementInfo, Actor self, CPos cell, Actor ignoreActor = null, CellConditions check = CellConditions.All)
{
var cost = MovementCostForCell(worldMovementInfo.World, worldMovementInfo.TerrainInfos, cell);
if (cost == int.MaxValue || !CanMoveFreelyInto(worldMovementInfo.World, self, cell, ignoreActor, check))
return int.MaxValue;
return cost;
}
public SubCell GetAvailableSubCell(
World world, Actor self, CPos cell, SubCell preferredSubCell = SubCell.Any, Actor ignoreActor = null, CellConditions check = CellConditions.All)
{
if (MovementCostForCell(world, cell) == int.MaxValue)
return SubCell.Invalid;
if (check.HasCellCondition(CellConditions.TransientActors))
{
Func<Actor, bool> checkTransient = otherActor => IsBlockedBy(self, otherActor, ignoreActor, check);
if (!SharesCell)
return world.ActorMap.AnyActorsAt(cell, SubCell.FullCell, checkTransient) ? SubCell.Invalid : SubCell.FullCell;
return world.ActorMap.FreeSubCell(cell, preferredSubCell, checkTransient);
}
if (!SharesCell)
return world.ActorMap.AnyActorsAt(cell, SubCell.FullCell) ? SubCell.Invalid : SubCell.FullCell;
return world.ActorMap.FreeSubCell(cell, preferredSubCell);
} }
public int GetInitialFacing() { return InitialFacing; } public int GetInitialFacing() { return InitialFacing; }
public bool CanEnterCell(World world, Actor self, CPos cell, Actor ignoreActor = null, bool checkTransientActors = true)
{
if (LocomotorInfo.MovementCostForCell(world, cell) == int.MaxValue)
return false;
var check = checkTransientActors ? CellConditions.All : CellConditions.BlockedByMovers;
return LocomotorInfo.CanMoveFreelyInto(world, self, cell, ignoreActor, check);
}
public IReadOnlyDictionary<CPos, SubCell> OccupiedCells(ActorInfo info, CPos location, SubCell subCell = SubCell.Any) public IReadOnlyDictionary<CPos, SubCell> OccupiedCells(ActorInfo info, CPos location, SubCell subCell = SubCell.Any)
{ {
return new ReadOnlyDictionary<CPos, SubCell>(new Dictionary<CPos, SubCell>() { { location, subCell } }); return new ReadOnlyDictionary<CPos, SubCell>(new Dictionary<CPos, SubCell>() { { location, subCell } });
} }
bool IOccupySpaceInfo.SharesCell { get { return SharesCell; } } bool IOccupySpaceInfo.SharesCell { get { return LocomotorInfo.SharesCell; } }
} }
public class Mobile : ConditionalTrait<MobileInfo>, INotifyCreated, IIssueOrder, IResolveOrder, IOrderVoice, IPositionable, IMove, public class Mobile : ConditionalTrait<MobileInfo>, INotifyCreated, IIssueOrder, IResolveOrder, IOrderVoice, IPositionable, IMove,
@@ -396,10 +100,9 @@ namespace OpenRA.Mods.Common.Traits
int facing; int facing;
CPos fromCell, toCell; CPos fromCell, toCell;
public SubCell FromSubCell, ToSubCell; public SubCell FromSubCell, ToSubCell;
int tunnelToken = ConditionManager.InvalidConditionToken; INotifyCustomLayerChanged[] notifyCustomLayerChanged;
int subterraneanToken = ConditionManager.InvalidConditionToken; INotifyVisualPositionChanged[] notifyVisualPositionChanged;
int jumpjetToken = ConditionManager.InvalidConditionToken; INotifyFinishedMoving[] notifyFinishedMoving;
ConditionManager conditionManager;
[Sync] public int Facing [Sync] public int Facing
{ {
@@ -428,29 +131,10 @@ namespace OpenRA.Mods.Common.Traits
ToSubCell = toSub; ToSubCell = toSub;
AddInfluence(); AddInfluence();
// Tunnel condition is added/removed when starting the transition between layers // Most custom layer conditions are added/removed when starting the transition between layers.
if (toCell.Layer == CustomMovementLayerType.Tunnel && conditionManager != null && if (toCell.Layer != fromCell.Layer)
!string.IsNullOrEmpty(Info.TunnelCondition) && tunnelToken == ConditionManager.InvalidConditionToken) foreach (var n in notifyCustomLayerChanged)
tunnelToken = conditionManager.GrantCondition(self, Info.TunnelCondition); n.CustomLayerChanged(self, fromCell.Layer, toCell.Layer);
else if (toCell.Layer != CustomMovementLayerType.Tunnel && tunnelToken != ConditionManager.InvalidConditionToken)
tunnelToken = conditionManager.RevokeCondition(self, tunnelToken);
// Play submerging animation as soon as it starts to submerge (before applying the condition)
if (toCell.Layer == CustomMovementLayerType.Subterranean && fromCell.Layer != CustomMovementLayerType.Subterranean)
{
if (!string.IsNullOrEmpty(Info.SubterraneanTransitionSequence))
self.World.AddFrameEndTask(w => w.Add(new SpriteEffect(self.World.Map.CenterOfCell(fromCell), self.World, Info.SubterraneanTransitionImage,
Info.SubterraneanTransitionSequence, Info.SubterraneanTransitionPalette)));
if (!string.IsNullOrEmpty(Info.SubterraneanTransitionSound))
Game.Sound.Play(SoundType.World, Info.SubterraneanTransitionSound);
}
// Grant the jumpjet condition as soon as the actor starts leaving the ground layer
// The condition is revoked from FinishedMoving
if (toCell.Layer == CustomMovementLayerType.Jumpjet && conditionManager != null &&
!string.IsNullOrEmpty(Info.JumpjetCondition) && jumpjetToken == ConditionManager.InvalidConditionToken)
jumpjetToken = conditionManager.GrantCondition(self, Info.JumpjetCondition);
} }
public Mobile(ActorInitializer init, MobileInfo info) public Mobile(ActorInitializer init, MobileInfo info)
@@ -460,7 +144,7 @@ namespace OpenRA.Mods.Common.Traits
speedModifiers = Exts.Lazy(() => self.TraitsImplementing<ISpeedModifier>().ToArray().Select(x => x.GetSpeedModifier())); speedModifiers = Exts.Lazy(() => self.TraitsImplementing<ISpeedModifier>().ToArray().Select(x => x.GetSpeedModifier()));
ToSubCell = FromSubCell = info.SharesCell ? init.World.Map.Grid.DefaultSubCell : SubCell.FullCell; ToSubCell = FromSubCell = info.LocomotorInfo.SharesCell ? init.World.Map.Grid.DefaultSubCell : SubCell.FullCell;
if (init.Contains<SubCellInit>()) if (init.Contains<SubCellInit>())
FromSubCell = ToSubCell = init.Get<SubCellInit, SubCell>(); FromSubCell = ToSubCell = init.Get<SubCellInit, SubCell>();
@@ -480,7 +164,9 @@ namespace OpenRA.Mods.Common.Traits
protected override void Created(Actor self) protected override void Created(Actor self)
{ {
conditionManager = self.TraitOrDefault<ConditionManager>(); notifyCustomLayerChanged = self.TraitsImplementing<INotifyCustomLayerChanged>().ToArray();
notifyVisualPositionChanged = self.TraitsImplementing<INotifyVisualPositionChanged>().ToArray();
notifyFinishedMoving = self.TraitsImplementing<INotifyFinishedMoving>().ToArray();
base.Created(self); base.Created(self);
} }
@@ -493,7 +179,7 @@ namespace OpenRA.Mods.Common.Traits
preferred = FromSubCell; preferred = FromSubCell;
// Fix sub-cell assignment // Fix sub-cell assignment
if (Info.SharesCell) if (Info.LocomotorInfo.SharesCell)
{ {
if (preferred <= SubCell.FullCell) if (preferred <= SubCell.FullCell)
return self.World.Map.Grid.DefaultSubCell; return self.World.Map.Grid.DefaultSubCell;
@@ -536,31 +222,12 @@ namespace OpenRA.Mods.Common.Traits
CenterPosition = pos; CenterPosition = pos;
self.World.UpdateMaps(self, this); self.World.UpdateMaps(self, this);
// HACK: The submerging conditions must be applied part way through a move, and this is the only method that gets called // The first time SetVisualPosition is called is in the constructor before creation, so we need a null check here as well
// at the right times to detect this if (notifyVisualPositionChanged == null)
if (toCell.Layer == CustomMovementLayerType.Subterranean) return;
{
var depth = self.World.Map.DistanceAboveTerrain(self.CenterPosition);
if (subterraneanToken == ConditionManager.InvalidConditionToken && depth < Info.SubterraneanTransitionDepth && conditionManager != null
&& !string.IsNullOrEmpty(Info.SubterraneanCondition))
subterraneanToken = conditionManager.GrantCondition(self, Info.SubterraneanCondition);
}
else if (subterraneanToken != ConditionManager.InvalidConditionToken)
{
var depth = self.World.Map.DistanceAboveTerrain(self.CenterPosition);
if (depth > Info.SubterraneanTransitionDepth)
{
subterraneanToken = conditionManager.RevokeCondition(self, subterraneanToken);
// HACK: the submerging animation and sound won't play if a condition isn't defined foreach (var n in notifyVisualPositionChanged)
if (!string.IsNullOrEmpty(Info.SubterraneanTransitionSound)) n.VisualPositionChanged(self, fromCell.Layer, toCell.Layer);
Game.Sound.Play(SoundType.World, Info.SubterraneanTransitionSound);
if (!string.IsNullOrEmpty(Info.SubterraneanTransitionSequence))
self.World.AddFrameEndTask(w => w.Add(new SpriteEffect(self.World.Map.CenterOfCell(fromCell), self.World, Info.SubterraneanTransitionImage,
Info.SubterraneanTransitionSequence, Info.SubterraneanTransitionPalette)));
}
}
} }
void INotifyAddedToWorld.AddedToWorld(Actor self) void INotifyAddedToWorld.AddedToWorld(Actor self)
@@ -630,7 +297,7 @@ namespace OpenRA.Mods.Common.Traits
{ {
var loc = self.World.Map.Clamp(order.TargetLocation); var loc = self.World.Map.Clamp(order.TargetLocation);
if (!Info.MoveIntoShroud && !self.Owner.Shroud.IsExplored(loc)) if (!Info.LocomotorInfo.MoveIntoShroud && !self.Owner.Shroud.IsExplored(loc))
return; return;
if (!order.Queued) if (!order.Queued)
@@ -651,7 +318,7 @@ namespace OpenRA.Mods.Common.Traits
public string VoicePhraseForOrder(Actor self, Order order) public string VoicePhraseForOrder(Actor self, Order order)
{ {
if (!Info.MoveIntoShroud && !self.Owner.Shroud.IsExplored(order.TargetLocation)) if (!Info.LocomotorInfo.MoveIntoShroud && !self.Owner.Shroud.IsExplored(order.TargetLocation))
return null; return null;
switch (order.OrderString) switch (order.OrderString)
@@ -673,6 +340,7 @@ namespace OpenRA.Mods.Common.Traits
return new[] { Pair.New(FromCell, FromSubCell) }; return new[] { Pair.New(FromCell, FromSubCell) };
if (CanEnterCell(ToCell)) if (CanEnterCell(ToCell))
return new[] { Pair.New(ToCell, ToSubCell) }; return new[] { Pair.New(ToCell, ToSubCell) };
return new[] { Pair.New(FromCell, FromSubCell), Pair.New(ToCell, ToSubCell) }; return new[] { Pair.New(FromCell, FromSubCell), Pair.New(ToCell, ToSubCell) };
} }
@@ -684,12 +352,13 @@ namespace OpenRA.Mods.Common.Traits
public SubCell GetAvailableSubCell(CPos a, SubCell preferredSubCell = SubCell.Any, Actor ignoreActor = null, bool checkTransientActors = true) public SubCell GetAvailableSubCell(CPos a, SubCell preferredSubCell = SubCell.Any, Actor ignoreActor = null, bool checkTransientActors = true)
{ {
return Info.GetAvailableSubCell(self.World, self, a, preferredSubCell, ignoreActor, checkTransientActors ? CellConditions.All : CellConditions.None); var cellConditions = checkTransientActors ? CellConditions.All : CellConditions.None;
return Info.LocomotorInfo.GetAvailableSubCell(self.World, self, a, preferredSubCell, ignoreActor, cellConditions);
} }
public bool CanExistInCell(CPos cell) public bool CanExistInCell(CPos cell)
{ {
return Info.MovementCostForCell(self.World, cell) != int.MaxValue; return Info.LocomotorInfo.MovementCostForCell(self.World, cell) != int.MaxValue;
} }
public bool CanEnterCell(CPos cell, Actor ignoreActor = null, bool checkTransientActors = true) public bool CanEnterCell(CPos cell, Actor ignoreActor = null, bool checkTransientActors = true)
@@ -699,7 +368,7 @@ namespace OpenRA.Mods.Common.Traits
public bool CanMoveFreelyInto(CPos cell, Actor ignoreActor = null, bool checkTransientActors = true) public bool CanMoveFreelyInto(CPos cell, Actor ignoreActor = null, bool checkTransientActors = true)
{ {
return Info.CanMoveFreelyInto(self.World, self, cell, ignoreActor, checkTransientActors ? CellConditions.All : CellConditions.BlockedByMovers); return Info.LocomotorInfo.CanMoveFreelyInto(self.World, self, cell, ignoreActor, checkTransientActors ? CellConditions.All : CellConditions.BlockedByMovers);
} }
public void EnteringCell(Actor self) public void EnteringCell(Actor self)
@@ -714,15 +383,15 @@ namespace OpenRA.Mods.Common.Traits
var notifiers = actors.SelectMany(a => a.TraitsImplementing<INotifyCrushed>().Select(t => new TraitPair<INotifyCrushed>(a, t))); var notifiers = actors.SelectMany(a => a.TraitsImplementing<INotifyCrushed>().Select(t => new TraitPair<INotifyCrushed>(a, t)));
foreach (var notifyCrushed in notifiers) foreach (var notifyCrushed in notifiers)
notifyCrushed.Trait.WarnCrush(notifyCrushed.Actor, self, Info.Crushes); notifyCrushed.Trait.WarnCrush(notifyCrushed.Actor, self, Info.LocomotorInfo.Crushes);
} }
public void FinishedMoving(Actor self) public void FinishedMoving(Actor self)
{ {
// Need to check both fromCell and toCell because FinishedMoving is called multiple times during the move // Need to check both fromCell and toCell because FinishedMoving is called multiple times during the move
// and that condition guarantees that this only runs when the unit has finished landing. if (fromCell.Layer == toCell.Layer)
if (fromCell.Layer != CustomMovementLayerType.Jumpjet && toCell.Layer != CustomMovementLayerType.Jumpjet && jumpjetToken != ConditionManager.InvalidConditionToken) foreach (var n in notifyFinishedMoving)
jumpjetToken = conditionManager.RevokeCondition(self, jumpjetToken); n.FinishedMoving(self, fromCell.Layer, toCell.Layer);
// Only make actor crush if it is on the ground // Only make actor crush if it is on the ground
if (!self.IsAtGroundLevel()) if (!self.IsAtGroundLevel())
@@ -734,7 +403,7 @@ namespace OpenRA.Mods.Common.Traits
var notifiers = actors.SelectMany(a => a.TraitsImplementing<INotifyCrushed>().Select(t => new TraitPair<INotifyCrushed>(a, t))); var notifiers = actors.SelectMany(a => a.TraitsImplementing<INotifyCrushed>().Select(t => new TraitPair<INotifyCrushed>(a, t)));
foreach (var notifyCrushed in notifiers) foreach (var notifyCrushed in notifiers)
notifyCrushed.Trait.OnCrush(notifyCrushed.Actor, self, Info.Crushes); notifyCrushed.Trait.OnCrush(notifyCrushed.Actor, self, Info.LocomotorInfo.Crushes);
} }
bool AnyCrushables(List<Actor> actors) bool AnyCrushables(List<Actor> actors)
@@ -744,7 +413,7 @@ namespace OpenRA.Mods.Common.Traits
return false; return false;
foreach (var crushes in crushables) foreach (var crushes in crushables)
if (crushes.Trait.CrushableBy(crushes.Actor, self, Info.Crushes)) if (crushes.Trait.CrushableBy(crushes.Actor, self, Info.LocomotorInfo.Crushes))
return true; return true;
return false; return false;
@@ -758,7 +427,7 @@ namespace OpenRA.Mods.Common.Traits
if (index == byte.MaxValue) if (index == byte.MaxValue)
return 0; return 0;
var terrainSpeed = Info.TilesetTerrainInfo[self.World.Map.Rules.TileSet][index].Speed; var terrainSpeed = Info.LocomotorInfo.TilesetTerrainInfo[self.World.Map.Rules.TileSet][index].Speed;
if (terrainSpeed == 0) if (terrainSpeed == 0)
return 0; return 0;
@@ -868,6 +537,7 @@ namespace OpenRA.Mods.Common.Traits
class MoveOrderTargeter : IOrderTargeter class MoveOrderTargeter : IOrderTargeter
{ {
readonly Mobile mobile; readonly Mobile mobile;
readonly LocomotorInfo locomotorInfo;
readonly bool rejectMove; readonly bool rejectMove;
public bool TargetOverridesSelection(TargetModifiers modifiers) public bool TargetOverridesSelection(TargetModifiers modifiers)
{ {
@@ -877,6 +547,7 @@ namespace OpenRA.Mods.Common.Traits
public MoveOrderTargeter(Actor self, Mobile unit) public MoveOrderTargeter(Actor self, Mobile unit)
{ {
mobile = unit; mobile = unit;
locomotorInfo = mobile.Info.LocomotorInfo;
rejectMove = !self.AcceptsOrder("Move"); rejectMove = !self.AcceptsOrder("Move");
} }
@@ -897,8 +568,8 @@ namespace OpenRA.Mods.Common.Traits
(self.World.Map.GetTerrainInfo(location).CustomCursor ?? mobile.Info.Cursor) : mobile.Info.BlockedCursor; (self.World.Map.GetTerrainInfo(location).CustomCursor ?? mobile.Info.Cursor) : mobile.Info.BlockedCursor;
if (mobile.IsTraitDisabled if (mobile.IsTraitDisabled
|| (!explored && !mobile.Info.MoveIntoShroud) || (!explored && !locomotorInfo.MoveIntoShroud)
|| (explored && mobile.Info.MovementCostForCell(self.World, location) == int.MaxValue)) || (explored && locomotorInfo.MovementCostForCell(self.World, location) == int.MaxValue))
cursor = mobile.Info.BlockedCursor; cursor = mobile.Info.BlockedCursor;
return true; return true;
@@ -924,7 +595,7 @@ namespace OpenRA.Mods.Common.Traits
var pos = self.CenterPosition; var pos = self.CenterPosition;
if (subCell == SubCell.Any) if (subCell == SubCell.Any)
subCell = Info.SharesCell ? self.World.ActorMap.FreeSubCell(cell, subCell) : SubCell.FullCell; subCell = Info.LocomotorInfo.SharesCell ? self.World.ActorMap.FreeSubCell(cell, subCell) : SubCell.FullCell;
// TODO: solve/reduce cell is full problem // TODO: solve/reduce cell is full problem
if (subCell == SubCell.Invalid) if (subCell == SubCell.Invalid)
@@ -990,7 +661,7 @@ namespace OpenRA.Mods.Common.Traits
var pathFinder = self.World.WorldActor.Trait<IPathFinder>(); var pathFinder = self.World.WorldActor.Trait<IPathFinder>();
List<CPos> path; List<CPos> path;
using (var search = PathSearch.Search(self.World, Info, self, true, using (var search = PathSearch.Search(self.World, Info.LocomotorInfo, self, true,
loc => loc.Layer == 0 && CanEnterCell(loc)) loc => loc.Layer == 0 && CanEnterCell(loc))
.FromPoint(self.Location)) .FromPoint(self.Location))
path = pathFinder.FindPath(search); path = pathFinder.FindPath(search);

View File

@@ -10,6 +10,7 @@
#endregion #endregion
using System.Drawing; using System.Drawing;
using System.Linq;
using OpenRA.Primitives; using OpenRA.Primitives;
using OpenRA.Traits; using OpenRA.Traits;
@@ -47,8 +48,9 @@ namespace OpenRA.Mods.Common.Traits
var aircraftInfo = producee.TraitInfoOrDefault<AircraftInfo>(); var aircraftInfo = producee.TraitInfoOrDefault<AircraftInfo>();
var mobileInfo = producee.TraitInfoOrDefault<MobileInfo>(); var mobileInfo = producee.TraitInfoOrDefault<MobileInfo>();
var locomotorInfo = mobileInfo.LocomotorInfo;
var passable = mobileInfo != null ? (uint)mobileInfo.GetMovementClass(self.World.Map.Rules.TileSet) : 0; var passable = mobileInfo != null ? (uint)locomotorInfo.GetMovementClass(self.World.Map.Rules.TileSet) : 0;
var destination = rp != null ? rp.Location : self.Location; var destination = rp != null ? rp.Location : self.Location;
var location = spawnLocation; var location = spawnLocation;
@@ -59,7 +61,7 @@ namespace OpenRA.Mods.Common.Traits
if (mobileInfo != null) if (mobileInfo != null)
location = self.World.Map.ChooseClosestMatchingEdgeCell(self.Location, location = self.World.Map.ChooseClosestMatchingEdgeCell(self.Location,
c => mobileInfo.CanEnterCell(self.World, null, c) && domainIndex.IsPassable(c, destination, mobileInfo, passable)); c => mobileInfo.CanEnterCell(self.World, null, c) && domainIndex.IsPassable(c, destination, locomotorInfo, passable));
} }
// No suitable spawn location could be found, so production has failed. // No suitable spawn location could be found, so production has failed.

View File

@@ -29,15 +29,14 @@ namespace OpenRA.Mods.Common.Traits
{ {
domainIndexes = new Dictionary<uint, MovementClassDomainIndex>(); domainIndexes = new Dictionary<uint, MovementClassDomainIndex>();
var tileSet = world.Map.Rules.TileSet; var tileSet = world.Map.Rules.TileSet;
var movementClasses = var locomotors = world.WorldActor.TraitsImplementing<Locomotor>().Where(l => !string.IsNullOrEmpty(l.Info.Name));
world.Map.Rules.Actors.Where(ai => ai.Value.HasTraitInfo<MobileInfo>()) var movementClasses = locomotors.Select(t => (uint)t.Info.GetMovementClass(tileSet)).Distinct();
.Select(ai => (uint)ai.Value.TraitInfo<MobileInfo>().GetMovementClass(tileSet)).Distinct();
foreach (var mc in movementClasses) foreach (var mc in movementClasses)
domainIndexes[mc] = new MovementClassDomainIndex(world, mc); domainIndexes[mc] = new MovementClassDomainIndex(world, mc);
} }
public bool IsPassable(CPos p1, CPos p2, MobileInfo mi, uint movementClass) public bool IsPassable(CPos p1, CPos p2, LocomotorInfo loco, uint movementClass)
{ {
// HACK: Work around units in other movement layers from being blocked // HACK: Work around units in other movement layers from being blocked
// when the point in the main layer is not pathable // when the point in the main layer is not pathable
@@ -45,7 +44,7 @@ namespace OpenRA.Mods.Common.Traits
return true; return true;
// HACK: Workaround until we can generalize movement classes // HACK: Workaround until we can generalize movement classes
if (mi.Subterranean || mi.Jumpjet) if (loco is SubterraneanLocomotorInfo || loco is JumpjetLocomotorInfo)
return true; return true;
return domainIndexes[movementClass].IsPassable(p1, p2); return domainIndexes[movementClass].IsPassable(p1, p2);

View File

@@ -69,7 +69,7 @@ namespace OpenRA.Mods.Common.Traits
} }
} }
bool ICustomMovementLayer.EnabledForActor(ActorInfo a, MobileInfo mi) { return enabled; } bool ICustomMovementLayer.EnabledForActor(ActorInfo a, LocomotorInfo li) { return enabled; }
byte ICustomMovementLayer.Index { get { return CustomMovementLayerType.ElevatedBridge; } } byte ICustomMovementLayer.Index { get { return CustomMovementLayerType.ElevatedBridge; } }
bool ICustomMovementLayer.InteractsWithDefaultLayer { get { return true; } } bool ICustomMovementLayer.InteractsWithDefaultLayer { get { return true; } }
@@ -78,12 +78,12 @@ namespace OpenRA.Mods.Common.Traits
return cellCenters[cell]; return cellCenters[cell];
} }
int ICustomMovementLayer.EntryMovementCost(ActorInfo a, MobileInfo mi, CPos cell) int ICustomMovementLayer.EntryMovementCost(ActorInfo a, LocomotorInfo li, CPos cell)
{ {
return ends.Contains(cell) ? 0 : int.MaxValue; return ends.Contains(cell) ? 0 : int.MaxValue;
} }
int ICustomMovementLayer.ExitMovementCost(ActorInfo a, MobileInfo mi, CPos cell) int ICustomMovementLayer.ExitMovementCost(ActorInfo a, LocomotorInfo li, CPos cell)
{ {
return ends.Contains(cell) ? 0 : int.MaxValue; return ends.Contains(cell) ? 0 : int.MaxValue;
} }

View File

@@ -62,7 +62,7 @@ namespace OpenRA.Mods.Common.Traits
} }
} }
bool ICustomMovementLayer.EnabledForActor(ActorInfo a, MobileInfo mi) { return mi.Jumpjet; } bool ICustomMovementLayer.EnabledForActor(ActorInfo a, LocomotorInfo li) { return li is JumpjetLocomotorInfo; }
byte ICustomMovementLayer.Index { get { return CustomMovementLayerType.Jumpjet; } } byte ICustomMovementLayer.Index { get { return CustomMovementLayerType.Jumpjet; } }
bool ICustomMovementLayer.InteractsWithDefaultLayer { get { return true; } } bool ICustomMovementLayer.InteractsWithDefaultLayer { get { return true; } }
@@ -72,13 +72,14 @@ namespace OpenRA.Mods.Common.Traits
return pos + new WVec(0, 0, height[cell] - pos.Z); return pos + new WVec(0, 0, height[cell] - pos.Z);
} }
bool ValidTransitionCell(CPos cell, MobileInfo mi) bool ValidTransitionCell(CPos cell, LocomotorInfo li)
{ {
var terrainType = map.GetTerrainInfo(cell).Type; var terrainType = map.GetTerrainInfo(cell).Type;
if (!mi.JumpjetTransitionTerrainTypes.Contains(terrainType) && mi.JumpjetTransitionTerrainTypes.Any()) var jli = (JumpjetLocomotorInfo)li;
if (!jli.JumpjetTransitionTerrainTypes.Contains(terrainType) && jli.JumpjetTransitionTerrainTypes.Any())
return false; return false;
if (mi.JumpjetTransitionOnRamps) if (jli.JumpjetTransitionOnRamps)
return true; return true;
var tile = map.Tiles[cell]; var tile = map.Tiles[cell];
@@ -86,14 +87,16 @@ namespace OpenRA.Mods.Common.Traits
return ti == null || ti.RampType == 0; return ti == null || ti.RampType == 0;
} }
int ICustomMovementLayer.EntryMovementCost(ActorInfo a, MobileInfo mi, CPos cell) int ICustomMovementLayer.EntryMovementCost(ActorInfo a, LocomotorInfo li, CPos cell)
{ {
return ValidTransitionCell(cell, mi) ? mi.JumpjetTransitionCost : int.MaxValue; var jli = (JumpjetLocomotorInfo)li;
return ValidTransitionCell(cell, jli) ? jli.JumpjetTransitionCost : int.MaxValue;
} }
int ICustomMovementLayer.ExitMovementCost(ActorInfo a, MobileInfo mi, CPos cell) int ICustomMovementLayer.ExitMovementCost(ActorInfo a, LocomotorInfo li, CPos cell)
{ {
return ValidTransitionCell(cell, mi) ? mi.JumpjetTransitionCost : int.MaxValue; var jli = (JumpjetLocomotorInfo)li;
return ValidTransitionCell(cell, jli) ? jli.JumpjetTransitionCost : int.MaxValue;
} }
byte ICustomMovementLayer.GetTerrainIndex(CPos cell) byte ICustomMovementLayer.GetTerrainIndex(CPos cell)

View File

@@ -0,0 +1,36 @@
#region Copyright & License Information
/*
* Copyright 2007-2018 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System.Collections.Generic;
namespace OpenRA.Mods.Common.Traits
{
[Desc("Used by Mobile. Required for jumpjet actors. Attach these to the world actor. You can have multiple variants by adding @suffixes.")]
public class JumpjetLocomotorInfo : LocomotorInfo
{
[Desc("Pathfinding cost for taking off or landing.")]
public readonly int JumpjetTransitionCost = 0;
[Desc("The terrain types that this actor can transition on. Leave empty to allow any.")]
public readonly HashSet<string> JumpjetTransitionTerrainTypes = new HashSet<string>();
[Desc("Can this actor transition on slopes?")]
public readonly bool JumpjetTransitionOnRamps = true;
public override object Create(ActorInitializer init) { return new JumpjetLocomotor(init.Self, this); }
}
public class JumpjetLocomotor : Locomotor
{
public JumpjetLocomotor(Actor self, JumpjetLocomotorInfo info)
: base(self, info) { }
}
}

View File

@@ -0,0 +1,302 @@
#region Copyright & License Information
/*
* Copyright 2007-2018 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using OpenRA.Mods.Common.Pathfinder;
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Flags]
public enum CellConditions
{
None = 0,
TransientActors,
BlockedByMovers,
All = TransientActors | BlockedByMovers
}
public static class CellConditionsExts
{
public static bool HasCellCondition(this CellConditions c, CellConditions cellCondition)
{
// PERF: Enum.HasFlag is slower and requires allocations.
return (c & cellCondition) == cellCondition;
}
}
public static class CustomMovementLayerType
{
public const byte Tunnel = 1;
public const byte Subterranean = 2;
public const byte Jumpjet = 3;
public const byte ElevatedBridge = 4;
}
[Desc("Used by Mobile. Attach these to the world actor. You can have multiple variants by adding @suffixes.")]
public class LocomotorInfo : ITraitInfo
{
[Desc("Locomotor ID.")]
public readonly string Name = "default";
public readonly int WaitAverage = 5;
public readonly int WaitSpread = 2;
[Desc("Allow multiple (infantry) units in one cell.")]
public readonly bool SharesCell = false;
[Desc("Can the actor be ordered to move in to shroud?")]
public readonly bool MoveIntoShroud = true;
[Desc("e.g. crate, wall, infantry")]
public readonly HashSet<string> Crushes = new HashSet<string>();
[Desc("Types of damage that are caused while crushing. Leave empty for no damage types.")]
public readonly HashSet<string> CrushDamageTypes = new HashSet<string>();
[FieldLoader.LoadUsing("LoadSpeeds", true)]
[Desc("Set Water: 0 for ground units and lower the value on rough terrain.")]
public readonly Dictionary<string, TerrainInfo> TerrainSpeeds;
protected static object LoadSpeeds(MiniYaml y)
{
var ret = new Dictionary<string, TerrainInfo>();
foreach (var t in y.ToDictionary()["TerrainSpeeds"].Nodes)
{
var speed = FieldLoader.GetValue<int>("speed", t.Value.Value);
var nodesDict = t.Value.ToDictionary();
var cost = nodesDict.ContainsKey("PathingCost")
? FieldLoader.GetValue<int>("cost", nodesDict["PathingCost"].Value)
: 10000 / speed;
ret.Add(t.Key, new TerrainInfo(speed, cost));
}
return ret;
}
TerrainInfo[] LoadTilesetSpeeds(TileSet tileSet)
{
var info = new TerrainInfo[tileSet.TerrainInfo.Length];
for (var i = 0; i < info.Length; i++)
info[i] = TerrainInfo.Impassable;
foreach (var kvp in TerrainSpeeds)
{
byte index;
if (tileSet.TryGetTerrainIndex(kvp.Key, out index))
info[index] = kvp.Value;
}
return info;
}
public class TerrainInfo
{
public static readonly TerrainInfo Impassable = new TerrainInfo();
public readonly int Cost;
public readonly int Speed;
public TerrainInfo()
{
Cost = int.MaxValue;
Speed = 0;
}
public TerrainInfo(int speed, int cost)
{
Speed = speed;
Cost = cost;
}
}
public struct WorldMovementInfo
{
internal readonly World World;
internal readonly TerrainInfo[] TerrainInfos;
internal WorldMovementInfo(World world, LocomotorInfo info)
{
// PERF: This struct allows us to cache the terrain info for the tileset used by the world.
// This allows us to speed up some performance-sensitive pathfinding calculations.
World = world;
TerrainInfos = info.TilesetTerrainInfo[world.Map.Rules.TileSet];
}
}
public readonly Cache<TileSet, TerrainInfo[]> TilesetTerrainInfo;
public readonly Cache<TileSet, int> TilesetMovementClass;
public LocomotorInfo()
{
TilesetTerrainInfo = new Cache<TileSet, TerrainInfo[]>(LoadTilesetSpeeds);
TilesetMovementClass = new Cache<TileSet, int>(CalculateTilesetMovementClass);
}
public int MovementCostForCell(World world, CPos cell)
{
return MovementCostForCell(world, TilesetTerrainInfo[world.Map.Rules.TileSet], cell);
}
int MovementCostForCell(World world, TerrainInfo[] terrainInfos, CPos cell)
{
if (!world.Map.Contains(cell))
return int.MaxValue;
var index = cell.Layer == 0 ? world.Map.GetTerrainIndex(cell) :
world.GetCustomMovementLayers()[cell.Layer].GetTerrainIndex(cell);
if (index == byte.MaxValue)
return int.MaxValue;
return terrainInfos[index].Cost;
}
public int CalculateTilesetMovementClass(TileSet tileset)
{
// collect our ability to cross *all* terraintypes, in a bitvector
return TilesetTerrainInfo[tileset].Select(ti => ti.Cost < int.MaxValue).ToBits();
}
public int GetMovementClass(TileSet tileset)
{
return TilesetMovementClass[tileset];
}
public int TileSetMovementHash(TileSet tileSet)
{
var terrainInfos = TilesetTerrainInfo[tileSet];
// Compute and return the hash using aggregate
return terrainInfos.Aggregate(terrainInfos.Length,
(current, terrainInfo) => unchecked(current * 31 + terrainInfo.Cost));
}
public WorldMovementInfo GetWorldMovementInfo(World world)
{
return new WorldMovementInfo(world, this);
}
public int MovementCostToEnterCell(WorldMovementInfo worldMovementInfo, Actor self, CPos cell, Actor ignoreActor = null, CellConditions check = CellConditions.All)
{
var cost = MovementCostForCell(worldMovementInfo.World, worldMovementInfo.TerrainInfos, cell);
if (cost == int.MaxValue || !CanMoveFreelyInto(worldMovementInfo.World, self, cell, ignoreActor, check))
return int.MaxValue;
return cost;
}
public SubCell GetAvailableSubCell(
World world, Actor self, CPos cell, SubCell preferredSubCell = SubCell.Any, Actor ignoreActor = null, CellConditions check = CellConditions.All)
{
if (MovementCostForCell(world, cell) == int.MaxValue)
return SubCell.Invalid;
if (check.HasCellCondition(CellConditions.TransientActors))
{
Func<Actor, bool> checkTransient = otherActor => IsBlockedBy(self, otherActor, ignoreActor, check);
if (!SharesCell)
return world.ActorMap.AnyActorsAt(cell, SubCell.FullCell, checkTransient) ? SubCell.Invalid : SubCell.FullCell;
return world.ActorMap.FreeSubCell(cell, preferredSubCell, checkTransient);
}
if (!SharesCell)
return world.ActorMap.AnyActorsAt(cell, SubCell.FullCell) ? SubCell.Invalid : SubCell.FullCell;
return world.ActorMap.FreeSubCell(cell, preferredSubCell);
}
static bool IsMovingInMyDirection(Actor self, Actor other)
{
var otherMobile = other.TraitOrDefault<Mobile>();
if (otherMobile == null || !otherMobile.IsMoving)
return false;
var selfMobile = self.TraitOrDefault<Mobile>();
if (selfMobile == null)
return false;
// Moving in the same direction if the facing delta is between +/- 90 degrees
var delta = Util.NormalizeFacing(otherMobile.Facing - selfMobile.Facing);
return delta < 64 || delta > 192;
}
// Determines whether the actor is blocked by other Actors
public bool CanMoveFreelyInto(World world, Actor self, CPos cell, Actor ignoreActor, CellConditions check)
{
if (!check.HasCellCondition(CellConditions.TransientActors))
return true;
if (SharesCell && world.ActorMap.HasFreeSubCell(cell))
return true;
// PERF: Avoid LINQ.
foreach (var otherActor in world.ActorMap.GetActorsAt(cell))
if (IsBlockedBy(self, otherActor, ignoreActor, check))
return false;
return true;
}
bool IsBlockedBy(Actor self, Actor otherActor, Actor ignoreActor, CellConditions check)
{
// We are not blocked by the actor we are ignoring.
if (otherActor == ignoreActor)
return false;
// If self is null, we don't have a real actor - we're just checking what would happen theoretically.
// In such a scenario - we'll just assume any other actor in the cell will block us by default.
// If we have a real actor, we can then perform the extra checks that allow us to avoid being blocked.
if (self == null)
return true;
// If the check allows: we are not blocked by allied units moving in our direction.
if (!check.HasCellCondition(CellConditions.BlockedByMovers) &&
self.Owner.Stances[otherActor.Owner] == Stance.Ally &&
IsMovingInMyDirection(self, otherActor))
return false;
// If there is a temporary blocker in our path, but we can remove it, we are not blocked.
var temporaryBlocker = otherActor.TraitOrDefault<ITemporaryBlocker>();
if (temporaryBlocker != null && temporaryBlocker.CanRemoveBlockage(otherActor, self))
return false;
// If we cannot crush the other actor in our way, we are blocked.
if (Crushes == null || Crushes.Count == 0)
return true;
// If the other actor in our way cannot be crushed, we are blocked.
// PERF: Avoid LINQ.
var crushables = otherActor.TraitsImplementing<ICrushable>();
foreach (var crushable in crushables)
if (crushable.CrushableBy(otherActor, self, Crushes))
return false;
return true;
}
public virtual object Create(ActorInitializer init) { return new Locomotor(init.Self, this); }
}
public class Locomotor
{
public readonly LocomotorInfo Info;
public Locomotor(Actor self, LocomotorInfo info)
{
Info = info;
}
}
}

View File

@@ -13,13 +13,14 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using OpenRA.Graphics;
using OpenRA.Mods.Common.Pathfinder; using OpenRA.Mods.Common.Pathfinder;
using OpenRA.Traits; using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits namespace OpenRA.Mods.Common.Traits
{ {
[Desc("Calculates routes for mobile units based on the A* search algorithm.", " Attach this to the world actor.")] [Desc("Calculates routes for mobile units based on the A* search algorithm.", " Attach this to the world actor.")]
public class PathFinderInfo : ITraitInfo public class PathFinderInfo : ITraitInfo, Requires<LocomotorInfo>
{ {
public object Create(ActorInitializer init) public object Create(ActorInitializer init)
{ {
@@ -62,20 +63,20 @@ namespace OpenRA.Mods.Common.Traits
public List<CPos> FindUnitPath(CPos source, CPos target, Actor self, Actor ignoreActor) public List<CPos> FindUnitPath(CPos source, CPos target, Actor self, Actor ignoreActor)
{ {
var mi = self.Info.TraitInfo<MobileInfo>(); var li = self.Info.TraitInfo<MobileInfo>().LocomotorInfo;
// If a water-land transition is required, bail early // If a water-land transition is required, bail early
var domainIndex = world.WorldActor.TraitOrDefault<DomainIndex>(); var domainIndex = world.WorldActor.TraitOrDefault<DomainIndex>();
if (domainIndex != null) if (domainIndex != null)
{ {
var passable = mi.GetMovementClass(world.Map.Rules.TileSet); var passable = li.GetMovementClass(world.Map.Rules.TileSet);
if (!domainIndex.IsPassable(source, target, mi, (uint)passable)) if (!domainIndex.IsPassable(source, target, li, (uint)passable))
return EmptyPath; return EmptyPath;
} }
List<CPos> pb; List<CPos> pb;
using (var fromSrc = PathSearch.FromPoint(world, mi, self, target, source, true).WithIgnoredActor(ignoreActor)) using (var fromSrc = PathSearch.FromPoint(world, li, self, target, source, true).WithIgnoredActor(ignoreActor))
using (var fromDest = PathSearch.FromPoint(world, mi, self, source, target, true).WithIgnoredActor(ignoreActor).Reverse()) using (var fromDest = PathSearch.FromPoint(world, li, self, source, target, true).WithIgnoredActor(ignoreActor).Reverse())
pb = FindBidiPath(fromSrc, fromDest); pb = FindBidiPath(fromSrc, fromDest);
CheckSanePath2(pb, source, target); CheckSanePath2(pb, source, target);
@@ -86,6 +87,7 @@ namespace OpenRA.Mods.Common.Traits
public List<CPos> FindUnitPathToRange(CPos source, SubCell srcSub, WPos target, WDist range, Actor self) public List<CPos> FindUnitPathToRange(CPos source, SubCell srcSub, WPos target, WDist range, Actor self)
{ {
var mi = self.Info.TraitInfo<MobileInfo>(); var mi = self.Info.TraitInfo<MobileInfo>();
var li = mi.LocomotorInfo;
var targetCell = world.Map.CellContaining(target); var targetCell = world.Map.CellContaining(target);
// Correct for SubCell offset // Correct for SubCell offset
@@ -102,14 +104,14 @@ namespace OpenRA.Mods.Common.Traits
var domainIndex = world.WorldActor.TraitOrDefault<DomainIndex>(); var domainIndex = world.WorldActor.TraitOrDefault<DomainIndex>();
if (domainIndex != null) if (domainIndex != null)
{ {
var passable = mi.GetMovementClass(world.Map.Rules.TileSet); var passable = li.GetMovementClass(world.Map.Rules.TileSet);
tilesInRange = new List<CPos>(tilesInRange.Where(t => domainIndex.IsPassable(source, t, mi, (uint)passable))); tilesInRange = new List<CPos>(tilesInRange.Where(t => domainIndex.IsPassable(source, t, li, (uint)passable)));
if (!tilesInRange.Any()) if (!tilesInRange.Any())
return EmptyPath; return EmptyPath;
} }
using (var fromSrc = PathSearch.FromPoints(world, mi, self, tilesInRange, source, true)) using (var fromSrc = PathSearch.FromPoints(world, li, self, tilesInRange, source, true))
using (var fromDest = PathSearch.FromPoint(world, mi, self, source, targetCell, true).Reverse()) using (var fromDest = PathSearch.FromPoint(world, li, self, source, targetCell, true).Reverse())
return FindBidiPath(fromSrc, fromDest); return FindBidiPath(fromSrc, fromDest);
} }

View File

@@ -61,7 +61,7 @@ namespace OpenRA.Mods.Common.Traits
} }
} }
bool ICustomMovementLayer.EnabledForActor(ActorInfo a, MobileInfo mi) { return mi.Subterranean; } bool ICustomMovementLayer.EnabledForActor(ActorInfo a, LocomotorInfo li) { return li is SubterraneanLocomotorInfo; }
byte ICustomMovementLayer.Index { get { return CustomMovementLayerType.Subterranean; } } byte ICustomMovementLayer.Index { get { return CustomMovementLayerType.Subterranean; } }
bool ICustomMovementLayer.InteractsWithDefaultLayer { get { return false; } } bool ICustomMovementLayer.InteractsWithDefaultLayer { get { return false; } }
@@ -71,13 +71,14 @@ namespace OpenRA.Mods.Common.Traits
return pos + new WVec(0, 0, height[cell] - pos.Z); return pos + new WVec(0, 0, height[cell] - pos.Z);
} }
bool ValidTransitionCell(CPos cell, MobileInfo mi) bool ValidTransitionCell(CPos cell, LocomotorInfo li)
{ {
var terrainType = map.GetTerrainInfo(cell).Type; var terrainType = map.GetTerrainInfo(cell).Type;
if (!mi.SubterraneanTransitionTerrainTypes.Contains(terrainType) && mi.SubterraneanTransitionTerrainTypes.Any()) var sli = (SubterraneanLocomotorInfo)li;
if (!sli.SubterraneanTransitionTerrainTypes.Contains(terrainType) && sli.SubterraneanTransitionTerrainTypes.Any())
return false; return false;
if (mi.SubterraneanTransitionOnRamps) if (sli.SubterraneanTransitionOnRamps)
return true; return true;
var tile = map.Tiles[cell]; var tile = map.Tiles[cell];
@@ -85,14 +86,16 @@ namespace OpenRA.Mods.Common.Traits
return ti == null || ti.RampType == 0; return ti == null || ti.RampType == 0;
} }
int ICustomMovementLayer.EntryMovementCost(ActorInfo a, MobileInfo mi, CPos cell) int ICustomMovementLayer.EntryMovementCost(ActorInfo a, LocomotorInfo li, CPos cell)
{ {
return ValidTransitionCell(cell, mi) ? mi.SubterraneanTransitionCost : int.MaxValue; var sli = (SubterraneanLocomotorInfo)li;
return ValidTransitionCell(cell, sli) ? sli.SubterraneanTransitionCost : int.MaxValue;
} }
int ICustomMovementLayer.ExitMovementCost(ActorInfo a, MobileInfo mi, CPos cell) int ICustomMovementLayer.ExitMovementCost(ActorInfo a, LocomotorInfo li, CPos cell)
{ {
return ValidTransitionCell(cell, mi) ? mi.SubterraneanTransitionCost : int.MaxValue; var sli = (SubterraneanLocomotorInfo)li;
return ValidTransitionCell(cell, sli) ? sli.SubterraneanTransitionCost : int.MaxValue;
} }
byte ICustomMovementLayer.GetTerrainIndex(CPos cell) byte ICustomMovementLayer.GetTerrainIndex(CPos cell)

View File

@@ -0,0 +1,40 @@
#region Copyright & License Information
/*
* Copyright 2007-2018 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System.Collections.Generic;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("Used by Mobile. Required for subterranean actors. Attach these to the world actor. You can have multiple variants by adding @suffixes.")]
public class SubterraneanLocomotorInfo : LocomotorInfo
{
[Desc("Pathfinding cost for submerging or reemerging.")]
public readonly int SubterraneanTransitionCost = 0;
[Desc("The terrain types that this actor can transition on. Leave empty to allow any.")]
public readonly HashSet<string> SubterraneanTransitionTerrainTypes = new HashSet<string>();
[Desc("Can this actor transition on slopes?")]
public readonly bool SubterraneanTransitionOnRamps = false;
[Desc("Depth at which the subterranian condition is applied.")]
public readonly WDist SubterraneanTransitionDepth = new WDist(-1024);
public override object Create(ActorInitializer init) { return new SubterraneanLocomotor(init.Self, this); }
}
public class SubterraneanLocomotor : Locomotor
{
public SubterraneanLocomotor(Actor self, SubterraneanLocomotorInfo info)
: base(self, info) { }
}
}

View File

@@ -68,7 +68,7 @@ namespace OpenRA.Mods.Common.Traits
} }
} }
bool ICustomMovementLayer.EnabledForActor(ActorInfo a, MobileInfo mi) { return enabled; } bool ICustomMovementLayer.EnabledForActor(ActorInfo a, LocomotorInfo li) { return enabled; }
byte ICustomMovementLayer.Index { get { return CustomMovementLayerType.Tunnel; } } byte ICustomMovementLayer.Index { get { return CustomMovementLayerType.Tunnel; } }
bool ICustomMovementLayer.InteractsWithDefaultLayer { get { return false; } } bool ICustomMovementLayer.InteractsWithDefaultLayer { get { return false; } }
@@ -77,12 +77,12 @@ namespace OpenRA.Mods.Common.Traits
return cellCenters[cell]; return cellCenters[cell];
} }
int ICustomMovementLayer.EntryMovementCost(ActorInfo a, MobileInfo mi, CPos cell) int ICustomMovementLayer.EntryMovementCost(ActorInfo a, LocomotorInfo li, CPos cell)
{ {
return portals.Contains(cell) ? 0 : int.MaxValue; return portals.Contains(cell) ? 0 : int.MaxValue;
} }
int ICustomMovementLayer.ExitMovementCost(ActorInfo a, MobileInfo mi, CPos cell) int ICustomMovementLayer.ExitMovementCost(ActorInfo a, LocomotorInfo li, CPos cell)
{ {
return portals.Contains(cell) ? 0 : int.MaxValue; return portals.Contains(cell) ? 0 : int.MaxValue;
} }

View File

@@ -50,6 +50,24 @@ namespace OpenRA.Mods.Common.Traits
void Sold(Actor self); void Sold(Actor self);
} }
[RequireExplicitImplementation]
public interface INotifyCustomLayerChanged
{
void CustomLayerChanged(Actor self, byte oldLayer, byte newLayer);
}
[RequireExplicitImplementation]
public interface INotifyVisualPositionChanged
{
void VisualPositionChanged(Actor self, byte oldLayer, byte newLayer);
}
[RequireExplicitImplementation]
public interface INotifyFinishedMoving
{
void FinishedMoving(Actor self, byte oldLayer, byte newLayer);
}
public interface IDemolishableInfo : ITraitInfoInterface { bool IsValidTarget(ActorInfo actorInfo, Actor saboteur); } public interface IDemolishableInfo : ITraitInfoInterface { bool IsValidTarget(ActorInfo actorInfo, Actor saboteur); }
public interface IDemolishable public interface IDemolishable
{ {
@@ -313,9 +331,9 @@ namespace OpenRA.Mods.Common.Traits
byte Index { get; } byte Index { get; }
bool InteractsWithDefaultLayer { get; } bool InteractsWithDefaultLayer { get; }
bool EnabledForActor(ActorInfo a, MobileInfo mi); bool EnabledForActor(ActorInfo a, LocomotorInfo li);
int EntryMovementCost(ActorInfo a, MobileInfo mi, CPos cell); int EntryMovementCost(ActorInfo a, LocomotorInfo li, CPos cell);
int ExitMovementCost(ActorInfo a, MobileInfo mi, CPos cell); int ExitMovementCost(ActorInfo a, LocomotorInfo li, CPos cell);
byte GetTerrainIndex(CPos cell); byte GetTerrainIndex(CPos cell);
WPos CenterOfCell(CPos cell); WPos CenterOfCell(CPos cell);