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:
@@ -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)
|
||||||
|
|||||||
@@ -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)))
|
||||||
|
|||||||
@@ -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())
|
||||||
|
|||||||
@@ -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) ||
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
36
OpenRA.Mods.Common/Traits/World/JumpjetLocomotor.cs
Normal file
36
OpenRA.Mods.Common/Traits/World/JumpjetLocomotor.cs
Normal 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) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
302
OpenRA.Mods.Common/Traits/World/Locomotor.cs
Normal file
302
OpenRA.Mods.Common/Traits/World/Locomotor.cs
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
40
OpenRA.Mods.Common/Traits/World/SubterraneanLocomotor.cs
Normal file
40
OpenRA.Mods.Common/Traits/World/SubterraneanLocomotor.cs
Normal 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) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user