#region Copyright & License Information /* * Copyright 2007-2020 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.Orders; using OpenRA.Primitives; using OpenRA.Support; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { [Desc("This actor can interact with TunnelEntrances to move through TerrainTunnels.")] public class EntersTunnelsInfo : TraitInfo, Requires, IObservesVariablesInfo { [Desc("Cursor to display when able to enter target tunnel.")] public readonly string EnterCursor = "enter"; [Desc("Cursor to display when unable to enter target tunnel.")] public readonly string EnterBlockedCursor = "enter-blocked"; [VoiceReference] public readonly string Voice = "Action"; [ConsumedConditionReference] [Desc("Boolean expression defining the condition under which the regular (non-force) enter cursor is disabled.")] public readonly BooleanExpression RequireForceMoveCondition = null; public override object Create(ActorInitializer init) { return new EntersTunnels(init.Self, this); } } public class EntersTunnels : IIssueOrder, IResolveOrder, IOrderVoice, IObservesVariables { readonly EntersTunnelsInfo info; readonly IMove move; bool requireForceMove; public EntersTunnels(Actor self, EntersTunnelsInfo info) { this.info = info; move = self.Trait(); } public IEnumerable Orders { get { yield return new EnterTunnelOrderTargeter(info.EnterCursor, info.EnterBlockedCursor, CanEnterTunnel, _ => true); } } bool CanEnterTunnel(Actor target, TargetModifiers modifiers) { return !requireForceMove || modifiers.HasModifier(TargetModifiers.ForceMove); } public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued) { if (order.OrderID != "EnterTunnel") return null; return new Order(order.OrderID, self, target, queued) { SuppressVisualFeedback = true }; } public string VoicePhraseForOrder(Actor self, Order order) { return order.OrderString == "EnterTunnel" ? info.Voice : null; } public void ResolveOrder(Actor self, Order order) { if (order.OrderString != "EnterTunnel" || order.Target.Type != TargetType.Actor) return; var tunnel = order.Target.Actor.TraitOrDefault(); if (tunnel == null || !tunnel.Exit.HasValue) return; self.QueueActivity(order.Queued, move.MoveTo(tunnel.Entrance, tunnel.NearEnough, targetLineColor: Color.Green)); self.QueueActivity(move.MoveTo(tunnel.Exit.Value, tunnel.NearEnough, targetLineColor: Color.Green)); self.ShowTargetLines(); } IEnumerable IObservesVariables.GetVariableObservers() { if (info.RequireForceMoveCondition != null) yield return new VariableObserver(RequireForceMoveConditionChanged, info.RequireForceMoveCondition.Variables); } void RequireForceMoveConditionChanged(Actor self, IReadOnlyDictionary conditions) { requireForceMove = info.RequireForceMoveCondition.Evaluate(conditions); } public class EnterTunnelOrderTargeter : UnitOrderTargeter { readonly string enterCursor; readonly string enterBlockedCursor; readonly Func canTarget; readonly Func useEnterCursor; public EnterTunnelOrderTargeter(string enterCursor, string enterBlockedCursor, Func canTarget, Func useEnterCursor) : base("EnterTunnel", 6, enterCursor, true, true) { this.enterCursor = enterCursor; this.enterBlockedCursor = enterBlockedCursor; this.canTarget = canTarget; this.useEnterCursor = useEnterCursor; } public override bool CanTargetActor(Actor self, Actor target, TargetModifiers modifiers, ref string cursor) { if (target == null || target.IsDead || !canTarget(target, modifiers)) return false; var tunnel = target.TraitOrDefault(); if (tunnel == null) return false; // HACK: The engine does not support HiddenUnderFog combined with buildings that use the "_" footprint // We therefore have to use AlwaysVisible and then force-disable interacting with the entrance under shroud var buildingInfo = target.Info.TraitInfoOrDefault(); if (buildingInfo != null) { var footprint = buildingInfo.PathableTiles(target.Location); if (footprint.All(c => self.World.ShroudObscures(c))) return false; } if (!tunnel.Exit.HasValue) { cursor = enterBlockedCursor; return false; } cursor = useEnterCursor(target) ? enterCursor : enterBlockedCursor; return true; } public override bool CanTargetFrozenActor(Actor self, FrozenActor target, TargetModifiers modifiers, ref string cursor) { return CanTargetActor(self, target.Actor, modifiers, ref cursor); } } } }