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>
This commit is contained in:
198
OpenRA.Mods.Common/Traits/World/Selection.cs
Normal file
198
OpenRA.Mods.Common/Traits/World/Selection.cs
Normal file
@@ -0,0 +1,198 @@
|
||||
#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.Traits;
|
||||
|
||||
namespace OpenRA.Mods.Common.Traits
|
||||
{
|
||||
public class SelectionInfo : TraitInfo
|
||||
{
|
||||
public override object Create(ActorInitializer init) { return new Selection(); }
|
||||
}
|
||||
|
||||
[TraitLocation(SystemActors.World | SystemActors.EditorWorld)]
|
||||
public class Selection : ISelection, INotifyCreated, INotifyOwnerChanged, ITick, IGameSaveTraitData
|
||||
{
|
||||
public int Hash { get; private set; }
|
||||
public IReadOnlyCollection<Actor> Actors => actors;
|
||||
|
||||
readonly HashSet<Actor> actors = [];
|
||||
readonly List<Actor> rolloverActors = [];
|
||||
World world;
|
||||
|
||||
INotifySelection[] worldNotifySelection;
|
||||
|
||||
void INotifyCreated.Created(Actor self)
|
||||
{
|
||||
worldNotifySelection = self.TraitsImplementing<INotifySelection>().ToArray();
|
||||
world = self.World;
|
||||
}
|
||||
|
||||
void UpdateHash()
|
||||
{
|
||||
// Not a real hash, but things checking this only care about checking when the selection has changed
|
||||
// For this purpose, having a false positive (forcing a refresh when nothing changed) is much better
|
||||
// than a false negative (selection state mismatch)
|
||||
Hash++;
|
||||
}
|
||||
|
||||
public virtual void Add(Actor a)
|
||||
{
|
||||
actors.Add(a);
|
||||
UpdateHash();
|
||||
|
||||
foreach (var sel in a.TraitsImplementing<INotifySelected>())
|
||||
sel.Selected(a);
|
||||
|
||||
Sync.RunUnsynced(world, () => world.OrderGenerator.SelectionChanged(world, actors));
|
||||
foreach (var ns in worldNotifySelection)
|
||||
ns.SelectionChanged();
|
||||
}
|
||||
|
||||
public virtual void Remove(Actor a)
|
||||
{
|
||||
if (actors.Remove(a))
|
||||
{
|
||||
UpdateHash();
|
||||
Sync.RunUnsynced(world, () => world.OrderGenerator.SelectionChanged(world, actors));
|
||||
foreach (var ns in worldNotifySelection)
|
||||
ns.SelectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void INotifyOwnerChanged.OnOwnerChanged(Actor a, Player oldOwner, Player newOwner)
|
||||
{
|
||||
if (!actors.Contains(a))
|
||||
return;
|
||||
|
||||
// Remove the actor from the original owners selection
|
||||
// Call UpdateHash directly for everyone else so watchers can account for the owner change if needed
|
||||
if (oldOwner == world.LocalPlayer)
|
||||
Remove(a);
|
||||
else
|
||||
UpdateHash();
|
||||
}
|
||||
|
||||
public bool Contains(Actor a)
|
||||
{
|
||||
return actors.Contains(a);
|
||||
}
|
||||
|
||||
public virtual void Combine(World world, IEnumerable<Actor> newSelection, bool isCombine, bool isClick)
|
||||
{
|
||||
var newSelectionCollection = newSelection as IReadOnlyCollection<Actor>;
|
||||
newSelectionCollection ??= newSelection.ToList();
|
||||
|
||||
if (isClick)
|
||||
{
|
||||
// TODO: select BEST, not FIRST
|
||||
var adjNewSelection = newSelectionCollection.Take(1);
|
||||
if (isCombine)
|
||||
actors.SymmetricExceptWith(adjNewSelection);
|
||||
else
|
||||
{
|
||||
actors.Clear();
|
||||
actors.UnionWith(adjNewSelection);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isCombine)
|
||||
actors.UnionWith(newSelectionCollection);
|
||||
else
|
||||
{
|
||||
actors.Clear();
|
||||
actors.UnionWith(newSelectionCollection);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateHash();
|
||||
|
||||
foreach (var a in newSelectionCollection)
|
||||
foreach (var sel in a.TraitsImplementing<INotifySelected>())
|
||||
sel.Selected(a);
|
||||
|
||||
Sync.RunUnsynced(world, () => world.OrderGenerator.SelectionChanged(world, actors));
|
||||
foreach (var ns in worldNotifySelection)
|
||||
ns.SelectionChanged();
|
||||
|
||||
if (world.IsGameOver)
|
||||
return;
|
||||
|
||||
// Play the selection voice from one of the selected actors
|
||||
foreach (var actor in actors.Intersect(newSelectionCollection))
|
||||
{
|
||||
if (actor.Owner != world.LocalPlayer || !actor.IsInWorld)
|
||||
continue;
|
||||
|
||||
var selectable = actor.Info.TraitInfoOrDefault<ISelectableInfo>();
|
||||
if (selectable == null || !actor.HasVoice(selectable.Voice))
|
||||
continue;
|
||||
|
||||
actor.PlayVoice(selectable.Voice);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
actors.Clear();
|
||||
UpdateHash();
|
||||
Sync.RunUnsynced(world, () => world.OrderGenerator.SelectionChanged(world, actors));
|
||||
foreach (var ns in worldNotifySelection)
|
||||
ns.SelectionChanged();
|
||||
}
|
||||
|
||||
public void SetRollover(IEnumerable<Actor> rollover)
|
||||
{
|
||||
rolloverActors.Clear();
|
||||
rolloverActors.AddRange(rollover);
|
||||
}
|
||||
|
||||
public bool RolloverContains(Actor a)
|
||||
{
|
||||
return rolloverActors.Contains(a);
|
||||
}
|
||||
|
||||
void ITick.Tick(Actor self)
|
||||
{
|
||||
var removed = actors.RemoveWhere(a => !a.IsInWorld || (!a.Owner.IsAlliedWith(world.RenderPlayer) && world.FogObscures(a)));
|
||||
if (removed > 0)
|
||||
{
|
||||
UpdateHash();
|
||||
Sync.RunUnsynced(world, () => world.OrderGenerator.SelectionChanged(world, actors));
|
||||
foreach (var ns in worldNotifySelection)
|
||||
ns.SelectionChanged();
|
||||
}
|
||||
}
|
||||
|
||||
List<MiniYamlNode> IGameSaveTraitData.IssueTraitData(Actor self)
|
||||
{
|
||||
return
|
||||
[
|
||||
new("Selection", FieldSaver.FormatValue(Actors.Select(a => a.ActorID).ToArray()))
|
||||
];
|
||||
}
|
||||
|
||||
void IGameSaveTraitData.ResolveTraitData(Actor self, MiniYaml data)
|
||||
{
|
||||
var selectionNode = data.NodeWithKeyOrDefault("Selection");
|
||||
if (selectionNode != null)
|
||||
{
|
||||
var selected = FieldLoader.GetValue<uint[]>("Selection", selectionNode.Value.Value)
|
||||
.Select(self.World.GetActorById).Where(a => a != null);
|
||||
Combine(self.World, selected, false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user