Files
OpenRA/OpenRA.Mods.Common/Orders/UnitOrderGenerator.cs
let5sne.win10 9cf6ebb986
Some checks failed
Continuous Integration / Linux (.NET 8.0) (push) Has been cancelled
Continuous Integration / Windows (.NET 8.0) (push) Has been cancelled
Initial commit: OpenRA game engine
Fork from OpenRA/OpenRA with one-click launch script (start-ra.cmd)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 21:46:54 +08:00

225 lines
8.0 KiB
C#

#region Copyright & License Information
/*
* Copyright (c) The OpenRA Developers and Contributors
* 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 System.Linq;
using OpenRA.Graphics;
using OpenRA.Orders;
using OpenRA.Traits;
using OpenRA.Widgets;
namespace OpenRA.Mods.Common.Orders
{
public class UnitOrderGenerator : IOrderGenerator
{
readonly string worldSelectCursor = ChromeMetrics.Get<string>("WorldSelectCursor");
readonly string worldDefaultCursor = ChromeMetrics.Get<string>("WorldDefaultCursor");
readonly GameSettings gameSettings;
protected virtual MouseActionType ActionType => MouseActionType.Contextual;
public MouseButton ActionButton => gameSettings.ResolveActionButton(ActionType);
public MouseButton CancelButton => gameSettings.ResolveCancelButton(ActionType);
public UnitOrderGenerator(World world)
{
gameSettings = Game.Settings.Game;
}
protected static Target TargetForInput(World world, CPos cell, int2 worldPixel, MouseInput mi)
{
var actor = world.ScreenMap.ActorsAtMouse(mi)
.Where(a => !a.Actor.IsDead && a.Actor.Info.HasTraitInfo<ITargetableInfo>() && !world.FogObscures(a.Actor))
.WithHighestSelectionPriority(worldPixel, mi.Modifiers);
if (actor != null)
return Target.FromActor(actor);
var frozen = world.ScreenMap.FrozenActorsAtMouse(world.RenderPlayer, mi)
.Where(a => a.Info.HasTraitInfo<ITargetableInfo>() && a.Visible && a.HasRenderables)
.WithHighestSelectionPriority(worldPixel, mi.Modifiers);
if (frozen != null)
return Target.FromFrozenActor(frozen);
return Target.FromCell(world, cell);
}
public virtual IEnumerable<Order> Order(World world, CPos cell, int2 worldPixel, MouseInput mi)
{
if (mi.Button == ActionButton)
return OrderInner(world, cell, worldPixel, mi);
if (mi.Button == CancelButton)
world.CancelInputMode();
return [];
}
protected virtual IEnumerable<Order> OrderInner(World world, CPos cell, int2 worldPixel, MouseInput mi)
{
var target = TargetForInput(world, cell, worldPixel, mi);
var orders = world.Selection.Actors
.Select(a => OrderForUnit(a, target, cell, mi))
.Where(o => o != null)
.ToList();
var actorsInvolved = orders.Select(o => o.Actor).Distinct().ToArray();
if (actorsInvolved.Length == 0)
yield break;
// HACK: This is required by the hacky player actions-per-minute calculation
// TODO: Reimplement APM properly and then remove this
yield return new Order("CreateGroup", actorsInvolved[0].Owner.PlayerActor, false, actorsInvolved);
foreach (var o in orders)
yield return CheckSameOrder(o.Order, o.Trait.IssueOrder(o.Actor, o.Order, o.Target, mi.Modifiers.HasModifier(Modifiers.Shift)));
}
public virtual void Tick(World world) { }
public virtual IEnumerable<IRenderable> Render(WorldRenderer wr, World world) { yield break; }
public virtual IEnumerable<IRenderable> RenderAboveShroud(WorldRenderer wr, World world) { yield break; }
public virtual IEnumerable<IRenderable> RenderAnnotations(WorldRenderer wr, World world) { yield break; }
public virtual string GetCursor(World world, CPos cell, int2 worldPixel, MouseInput mi)
{
var target = TargetForInput(world, cell, worldPixel, mi);
bool useSelect;
if (gameSettings.MouseControlStyle == MouseControlStyle.Classic && !InputOverridesSelection(world, worldPixel, mi))
useSelect = target.Type == TargetType.Actor && target.Actor.Info.HasTraitInfo<ISelectableInfo>();
else
{
var ordersWithCursor = world.Selection.Actors
.Select(a => OrderForUnit(a, target, cell, mi))
.Where(o => o != null && o.Cursor != null);
var cursorOrder = ordersWithCursor.MaxByOrDefault(o => o.Order.OrderPriority);
useSelect = target.Type == TargetType.Actor && target.Actor.Info.HasTraitInfo<ISelectableInfo>() &&
(cursorOrder == null || world.Selection.Actors.Count == 0 || !InputOverridesSelection(world, worldPixel, mi));
if (!useSelect && cursorOrder != null)
return cursorOrder.Cursor;
}
return useSelect ? worldSelectCursor : worldDefaultCursor;
}
public void Deactivate() { }
bool IOrderGenerator.HandleKeyPress(KeyInput e) { return false; }
// Used for classic mouse orders, determines whether or not action at xy is move or select
public virtual bool InputOverridesSelection(World world, int2 xy, MouseInput mi)
{
var actor = world.ScreenMap.ActorsAtMouse(xy)
.Where(a =>
!a.Actor.IsDead &&
a.Actor.Info.HasTraitInfo<ISelectableInfo>() &&
(a.Actor.Owner.IsAlliedWith(world.RenderPlayer) || !world.FogObscures(a.Actor)))
.WithHighestSelectionPriority(xy, mi.Modifiers);
if (actor == null)
return true;
var target = Target.FromActor(actor);
var cell = world.Map.CellContaining(target.CenterPosition);
var actorsAt = world.ActorMap.GetActorsAt(cell).ToList();
var modifiers = TargetModifiers.None;
if (mi.Modifiers.HasModifier(Modifiers.Ctrl))
modifiers |= TargetModifiers.ForceAttack;
if (mi.Modifiers.HasModifier(Modifiers.Shift))
modifiers |= TargetModifiers.ForceQueue;
if (mi.Modifiers.HasModifier(Modifiers.Alt))
modifiers |= TargetModifiers.ForceMove;
foreach (var a in world.Selection.Actors)
{
var o = OrderForUnit(a, target, cell, mi);
if (o != null && o.Order.TargetOverridesSelection(a, target, actorsAt, cell, modifiers))
return true;
}
return false;
}
public virtual void SelectionChanged(World world, IEnumerable<Actor> selected) { }
/// <summary>
/// Returns the most appropriate order for a given actor and target.
/// First priority is given to orders that interact with the given actors.
/// Second priority is given to actors in the given cell.
/// </summary>
protected UnitOrderResult OrderForUnit(Actor self, Target target, CPos xy, MouseInput mi)
{
if (self.Owner != self.World.LocalPlayer)
return null;
if (self.World.IsGameOver)
return null;
if (self.Disposed || !target.IsValidFor(self))
return null;
var modifiers = TargetModifiers.None;
if (mi.Modifiers.HasModifier(Modifiers.Ctrl))
modifiers |= TargetModifiers.ForceAttack;
if (mi.Modifiers.HasModifier(Modifiers.Shift))
modifiers |= TargetModifiers.ForceQueue;
if (mi.Modifiers.HasModifier(Modifiers.Alt))
modifiers |= TargetModifiers.ForceMove;
var orders = self.TraitsImplementing<IIssueOrder>()
.SelectMany(trait => trait.Orders.Select(x => new { Trait = trait, Order = x }))
.OrderByDescending(x => x.Order.OrderPriority)
.ToList();
for (var i = 0; i < 2; i++)
{
foreach (var o in orders)
{
var localModifiers = modifiers;
string cursor = null;
if (o.Order.CanTarget(self, target, ref localModifiers, ref cursor))
return new UnitOrderResult(self, o.Order, o.Trait, cursor, target);
}
// No valid orders, so check for orders against the cell
target = Target.FromCell(self.World, xy);
}
return null;
}
static Order CheckSameOrder(IOrderTargeter iot, Order order)
{
if (order == null && iot.OrderID != null)
TextNotificationsManager.Debug("BUG: in order targeter - decided on {0} but then didn't order", iot.OrderID);
else if (order != null && iot.OrderID != order.OrderString)
TextNotificationsManager.Debug("BUG: in order targeter - decided on {0} but ordered {1}", iot.OrderID, order.OrderString);
return order;
}
protected sealed class UnitOrderResult(Actor actor, IOrderTargeter order, IIssueOrder trait, string cursor, in Target target)
{
public readonly Actor Actor = actor;
public readonly IOrderTargeter Order = order;
public readonly IIssueOrder Trait = trait;
public readonly string Cursor = cursor;
public ref readonly Target Target => ref target;
readonly Target target = target;
}
public virtual bool ClearSelectionOnLeftClick => true;
}
}