Implement subterranean units.

This commit is contained in:
Paul Chote
2017-01-08 20:10:54 +00:00
parent bb5f25e0dc
commit 572c1cb89f
14 changed files with 228 additions and 9 deletions

View File

@@ -771,7 +771,7 @@ namespace OpenRA.Mods.Common.AI
var path = pathfinder.FindPath(
PathSearch.Search(World, mobileInfo, harvester, true,
loc => domainIndex.IsPassable(harvester.Location, loc, passable) && harvester.CanHarvestAt(loc, resLayer, harvInfo, territory))
loc => domainIndex.IsPassable(harvester.Location, loc, mobileInfo, passable) && harvester.CanHarvestAt(loc, resLayer, harvInfo, territory))
.WithCustomCost(loc => World.FindActorsInCircle(World.Map.CenterOfCell(loc), Info.HarvesterEnemyAvoidanceRadius)
.Where(u => !u.IsDead && harvester.Owner.Stances[u.Owner] == Stance.Enemy)
.Sum(u => Math.Max(WDist.Zero.Length, Info.HarvesterEnemyAvoidanceRadius.Length - (World.Map.CenterOfCell(loc) - u.CenterPosition).Length)))

View File

@@ -127,7 +127,7 @@ namespace OpenRA.Mods.Common.Activities
var passable = (uint)mobileInfo.GetMovementClass(self.World.Map.Rules.TileSet);
List<CPos> path;
using (var search = PathSearch.Search(self.World, mobileInfo, self, true,
loc => domainIndex.IsPassable(self.Location, loc, passable) && self.CanHarvestAt(loc, resLayer, harvInfo, territory))
loc => domainIndex.IsPassable(self.Location, loc, mobileInfo, passable) && self.CanHarvestAt(loc, resLayer, harvInfo, territory))
.WithCustomCost(loc =>
{
if ((avoidCell.HasValue && loc == avoidCell.Value) ||

View File

@@ -142,7 +142,7 @@ namespace OpenRA.Mods.Common.Activities
var loc = self.Location;
foreach (var cell in targetCells)
if (domainIndex.IsPassable(loc, cell, movementClass) && Mobile.CanEnterCell(cell))
if (domainIndex.IsPassable(loc, cell, Mobile.Info, movementClass) && Mobile.CanEnterCell(cell))
searchCells.Add(cell);
if (!searchCells.Any())

View File

@@ -796,6 +796,7 @@
<Compile Include="Traits\World\ActorMap.cs" />
<Compile Include="Traits\World\TerrainTunnelLayer.cs" />
<Compile Include="Traits\World\TerrainTunnel.cs" />
<Compile Include="Traits\World\SubterraneanActorLayer.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="AfterBuild">

View File

@@ -15,6 +15,7 @@ using System.Drawing;
using System.Linq;
using OpenRA.Activities;
using OpenRA.Mods.Common.Activities;
using OpenRA.Mods.Common.Effects;
using OpenRA.Primitives;
using OpenRA.Traits;
@@ -41,6 +42,7 @@ namespace OpenRA.Mods.Common.Traits
public static class CustomMovementLayerType
{
public const byte Tunnel = 1;
public const byte Subterranean = 2;
}
[Desc("Unit is able to move.")]
@@ -82,6 +84,37 @@ namespace OpenRA.Mods.Common.Traits
[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;
public override object Create(ActorInitializer init) { return new Mobile(init, this); }
static object LoadSpeeds(MiniYaml y)
@@ -344,6 +377,7 @@ namespace OpenRA.Mods.Common.Traits
CPos fromCell, toCell;
public SubCell FromSubCell, ToSubCell;
int tunnelToken = ConditionManager.InvalidConditionToken;
int subterraneanToken = ConditionManager.InvalidConditionToken;
ConditionManager conditionManager;
[Sync] public int Facing
@@ -373,11 +407,23 @@ namespace OpenRA.Mods.Common.Traits
ToSubCell = toSub;
AddInfluence();
// Tunnel condition is added/removed when starting the transition between layers
if (toCell.Layer == CustomMovementLayerType.Tunnel && conditionManager != null &&
!string.IsNullOrEmpty(Info.TunnelCondition) && tunnelToken == ConditionManager.InvalidConditionToken)
tunnelToken = conditionManager.GrantCondition(self, Info.TunnelCondition);
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);
}
}
public Mobile(ActorInitializer init, MobileInfo info)
@@ -460,6 +506,32 @@ namespace OpenRA.Mods.Common.Traits
{
CenterPosition = pos;
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
// at the right times to detect this
if (toCell.Layer == CustomMovementLayerType.Subterranean)
{
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
if (!string.IsNullOrEmpty(Info.SubterraneanTransitionSound))
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)));
}
}
}
public void AddedToWorld(Actor self)

View File

@@ -57,7 +57,7 @@ namespace OpenRA.Mods.Common.Traits
if (mobileInfo != null)
location = self.World.Map.ChooseClosestMatchingEdgeCell(self.Location,
c => mobileInfo.CanEnterCell(self.World, null, c) && domainIndex.IsPassable(c, destination, passable));
c => mobileInfo.CanEnterCell(self.World, null, c) && domainIndex.IsPassable(c, destination, mobileInfo, passable));
}
// No suitable spawn location could be found, so production has failed.

View File

@@ -37,13 +37,17 @@ namespace OpenRA.Mods.Common.Traits
domainIndexes[mc] = new MovementClassDomainIndex(world, mc);
}
public bool IsPassable(CPos p1, CPos p2, uint movementClass)
public bool IsPassable(CPos p1, CPos p2, MobileInfo mi, uint movementClass)
{
// HACK: Work around units in other movement layers from being blocked
// when the point in the main layer is not pathable
if (p1.Layer != 0 || p2.Layer != 0)
return true;
// HACK: Workaround until we can generalize movement classes
if (mi.Subterranean)
return true;
return domainIndexes[movementClass].IsPassable(p1, p2);
}

View File

@@ -69,7 +69,7 @@ namespace OpenRA.Mods.Common.Traits
if (domainIndex != null)
{
var passable = mi.GetMovementClass(world.Map.Rules.TileSet);
if (!domainIndex.IsPassable(source, target, (uint)passable))
if (!domainIndex.IsPassable(source, target, mi, (uint)passable))
return EmptyPath;
}
@@ -103,7 +103,7 @@ namespace OpenRA.Mods.Common.Traits
if (domainIndex != null)
{
var passable = mi.GetMovementClass(world.Map.Rules.TileSet);
tilesInRange = new List<CPos>(tilesInRange.Where(t => domainIndex.IsPassable(source, t, (uint)passable)));
tilesInRange = new List<CPos>(tilesInRange.Where(t => domainIndex.IsPassable(source, t, mi, (uint)passable)));
if (!tilesInRange.Any())
return EmptyPath;
}

View File

@@ -0,0 +1,103 @@
#region Copyright & License Information
/*
* Copyright 2007-2017 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 class SubterraneanActorLayerInfo : ITraitInfo
{
[Desc("Terrain type of the underground layer.")]
public readonly string TerrainType = "Subterranean";
[Desc("Height offset relative to the smoothed terrain for movement.")]
public readonly WDist HeightOffset = -new WDist(2048);
[Desc("Cell radius for smoothing adjacent cell heights.")]
public readonly int SmoothingRadius = 2;
public object Create(ActorInitializer init) { return new SubterraneanActorLayer(init.Self, this); }
}
public class SubterraneanActorLayer : ICustomMovementLayer
{
readonly Map map;
readonly byte terrainIndex;
readonly CellLayer<int> height;
public SubterraneanActorLayer(Actor self, SubterraneanActorLayerInfo info)
{
map = self.World.Map;
terrainIndex = self.World.Map.Rules.TileSet.GetTerrainIndex(info.TerrainType);
height = new CellLayer<int>(map);
foreach (var c in map.AllCells)
{
var neighbourCount = 0;
var neighbourHeight = 0;
for (var dy = -info.SmoothingRadius; dy <= info.SmoothingRadius; dy++)
{
for (var dx = -info.SmoothingRadius; dx <= info.SmoothingRadius; dx++)
{
var neighbour = c + new CVec(dx, dy);
if (!map.AllCells.Contains(neighbour))
continue;
neighbourCount++;
neighbourHeight += map.Height[neighbour];
}
}
height[c] = info.HeightOffset.Length + neighbourHeight * 512 / neighbourCount;
}
}
bool ICustomMovementLayer.EnabledForActor(ActorInfo a, MobileInfo mi) { return mi.Subterranean; }
byte ICustomMovementLayer.Index { get { return CustomMovementLayerType.Subterranean; } }
bool ICustomMovementLayer.InteractsWithDefaultLayer { get { return false; } }
WPos ICustomMovementLayer.CenterOfCell(CPos cell)
{
var pos = map.CenterOfCell(cell);
return pos + new WVec(0, 0, height[cell] - pos.Z);
}
bool ValidTransitionCell(CPos cell, MobileInfo mi)
{
var terrainType = map.GetTerrainInfo(cell).Type;
if (!mi.SubterraneanTransitionTerrainTypes.Contains(terrainType) && mi.SubterraneanTransitionTerrainTypes.Any())
return false;
if (mi.SubterraneanTransitionOnRamps)
return true;
var tile = map.Tiles[cell];
var ti = map.Rules.TileSet.GetTileInfo(tile);
return ti == null || ti.RampType == 0;
}
int ICustomMovementLayer.EntryMovementCost(ActorInfo a, MobileInfo mi, CPos cell)
{
return ValidTransitionCell(cell, mi) ? mi.SubterraneanTransitionCost : int.MaxValue;
}
int ICustomMovementLayer.ExitMovementCost(ActorInfo a, MobileInfo mi, CPos cell)
{
return ValidTransitionCell(cell, mi) ? mi.SubterraneanTransitionCost : int.MaxValue;
}
byte ICustomMovementLayer.GetTerrainIndex(CPos cell)
{
return terrainIndex;
}
}
}

View File

@@ -291,12 +291,21 @@ SAPC:
TurnSpeed: 5
Speed: 71
RequiresCondition: !empdisable && !loading
Subterranean: true
SubterraneanCondition: submerged
SubterraneanTransitionTerrainTypes: Clear, Rough
SubterraneanTransitionCost: 120
SubterraneanTransitionSound: subdril1.aud
SubterraneanTransitionImage: dig
SubterraneanTransitionSequence: idle
TerrainSpeeds:
Subterranean: 120
Health:
HP: 175
Armor:
Type: Heavy
RevealsShroud:
RequiresCondition: !inside-tunnel
RequiresCondition: !inside-tunnel && !submerged
Range: 5c0
MaxHeightDelta: 3
Cargo:
@@ -306,6 +315,10 @@ SAPC:
UnloadVoice: Unload
LoadingCondition: loading
EjectOnDeath: true
WithVoxelBody:
RequiresCondition: !submerged
Targetable:
RequiresCondition: !inside-tunnel && !submerged
SUBTANK:
Inherits: ^Tank
@@ -324,12 +337,21 @@ SUBTANK:
TurnSpeed: 6
Speed: 71
Crushes: wall, crate, infantry
Subterranean: true
SubterraneanCondition: submerged
SubterraneanTransitionTerrainTypes: Clear, Rough
SubterraneanTransitionCost: 120
SubterraneanTransitionSound: subdril1.aud
SubterraneanTransitionImage: dig
SubterraneanTransitionSequence: idle
TerrainSpeeds:
Subterranean: 120
Health:
HP: 300
Armor:
Type: Light
RevealsShroud:
RequiresCondition: !inside-tunnel
RequiresCondition: !inside-tunnel && !submerged
Range: 5c0
MaxHeightDelta: 3
Armament:
@@ -337,6 +359,10 @@ SUBTANK:
AttackFrontal:
Voice: Attack
AutoTarget:
WithVoxelBody:
RequiresCondition: !submerged
Targetable:
RequiresCondition: !inside-tunnel && !submerged
STNK:
Inherits: ^Tank

View File

@@ -60,6 +60,7 @@
TerrainGeometryOverlay:
ExitsDebugOverlayManager:
CliffBackImpassabilityLayer:
SubterraneanActorLayer:
World:
Inherits: ^BaseWorld

View File

@@ -631,3 +631,9 @@ tuntop03:
tuntop04:
Inherits: ^tuntop
dig:
idle:
Length: *
ZOffset: 511
Offset: 0, 0, 24

View File

@@ -72,6 +72,9 @@ Terrain:
Type: Rock
TargetTypes: Ground
Color: 44443C
TerrainType@Subterranean:
Type: Subterranean
Color: C7C9FA
# Automatically generated. DO NOT EDIT!
Templates:

View File

@@ -72,6 +72,9 @@ Terrain:
Type: Rock
TargetTypes: Ground
Color: 44443C
TerrainType@Subterranean:
Type: Subterranean
Color: 745537
# Automatically generated. DO NOT EDIT!
Templates: