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:
70
OpenRA.Game/Traits/World/DebugVisualizations.cs
Normal file
70
OpenRA.Game/Traits/World/DebugVisualizations.cs
Normal file
@@ -0,0 +1,70 @@
|
||||
#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
|
||||
|
||||
namespace OpenRA.Traits
|
||||
{
|
||||
[TraitLocation(SystemActors.World | SystemActors.EditorWorld)]
|
||||
[Desc("Enables visualization commands. Attach this to the world actor.")]
|
||||
public class DebugVisualizationsInfo : TraitInfo<DebugVisualizations> { }
|
||||
|
||||
public class DebugVisualizations
|
||||
{
|
||||
public bool CombatGeometry;
|
||||
public bool RenderGeometry;
|
||||
public bool ScreenMap;
|
||||
public bool ActorTags;
|
||||
|
||||
// The depth buffer may have been left enabled by the previous world
|
||||
// Initializing this as dirty forces us to reset the default rendering before the first render
|
||||
bool depthBufferDirty = true;
|
||||
bool depthBuffer;
|
||||
public bool DepthBuffer
|
||||
{
|
||||
get => depthBuffer;
|
||||
set
|
||||
{
|
||||
depthBuffer = value;
|
||||
depthBufferDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
float depthBufferContrast = 1f;
|
||||
public float DepthBufferContrast
|
||||
{
|
||||
get => depthBufferContrast;
|
||||
set
|
||||
{
|
||||
depthBufferContrast = value;
|
||||
depthBufferDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
float depthBufferOffset;
|
||||
public float DepthBufferOffset
|
||||
{
|
||||
get => depthBufferOffset;
|
||||
set
|
||||
{
|
||||
depthBufferOffset = value;
|
||||
depthBufferDirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateDepthBuffer()
|
||||
{
|
||||
if (depthBufferDirty)
|
||||
{
|
||||
Game.Renderer.WorldSpriteRenderer.SetDepthPreview(DepthBuffer, DepthBufferContrast, DepthBufferOffset);
|
||||
depthBufferDirty = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
41
OpenRA.Game/Traits/World/Faction.cs
Normal file
41
OpenRA.Game/Traits/World/Faction.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
#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.Frozen;
|
||||
|
||||
namespace OpenRA.Traits
|
||||
{
|
||||
[Desc("Attach this to the `World` actor.")]
|
||||
[TraitLocation(SystemActors.World | SystemActors.EditorWorld)]
|
||||
public class FactionInfo : TraitInfo<Faction>
|
||||
{
|
||||
[FluentReference]
|
||||
[Desc("This is the name exposed to the players.")]
|
||||
public readonly string Name = null;
|
||||
|
||||
[Desc("This is the internal name for owner checks.")]
|
||||
public readonly string InternalName = null;
|
||||
|
||||
[Desc("Pick a random faction as the player's faction out of this list.")]
|
||||
public readonly FrozenSet<string> RandomFactionMembers = FrozenSet<string>.Empty;
|
||||
|
||||
[Desc("The side that the faction belongs to. For example, England belongs to the 'Allies' side.")]
|
||||
public readonly string Side = null;
|
||||
|
||||
[FluentReference(optional: true)]
|
||||
[Desc("This is shown in the lobby as a tooltip.")]
|
||||
public readonly string Description = null;
|
||||
|
||||
public readonly bool Selectable = true;
|
||||
}
|
||||
|
||||
public class Faction { /* we're only interested in the Info */ }
|
||||
}
|
||||
287
OpenRA.Game/Traits/World/ScreenMap.cs
Normal file
287
OpenRA.Game/Traits/World/ScreenMap.cs
Normal file
@@ -0,0 +1,287 @@
|
||||
#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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Effects;
|
||||
using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
|
||||
namespace OpenRA.Traits
|
||||
{
|
||||
public readonly struct ActorBoundsPair(Actor actor, Polygon bounds)
|
||||
{
|
||||
public readonly Actor Actor = actor;
|
||||
public readonly Polygon Bounds = bounds;
|
||||
|
||||
public override int GetHashCode() { return Actor.GetHashCode() ^ Bounds.GetHashCode(); }
|
||||
|
||||
public override string ToString() { return $"{Actor.Info.Name}->{Bounds.GetType().Name}"; }
|
||||
}
|
||||
|
||||
[TraitLocation(SystemActors.World | SystemActors.EditorWorld)]
|
||||
public class ScreenMapInfo : TraitInfo
|
||||
{
|
||||
[Desc("Size of partition bins (world pixels)")]
|
||||
public readonly int BinSize = 250;
|
||||
|
||||
public override object Create(ActorInitializer init) { return new ScreenMap(init.World, this); }
|
||||
}
|
||||
|
||||
public class ScreenMap : IWorldLoaded
|
||||
{
|
||||
static readonly IEnumerable<FrozenActor> NoFrozenActors = [];
|
||||
readonly Func<FrozenActor, bool> frozenActorIsValid = fa => fa.IsValid;
|
||||
readonly Func<Actor, bool> actorIsInWorld = a => a.IsInWorld;
|
||||
readonly Func<Actor, ActorBoundsPair> selectActorAndBounds;
|
||||
readonly Cache<Player, SpatiallyPartitioned<FrozenActor>> partitionedMouseFrozenActors;
|
||||
readonly SpatiallyPartitioned<Actor> partitionedMouseActors;
|
||||
readonly Dictionary<Actor, ActorBoundsPair> partitionedMouseActorBounds = [];
|
||||
|
||||
readonly Cache<Player, SpatiallyPartitioned<FrozenActor>> partitionedRenderableFrozenActors;
|
||||
readonly SpatiallyPartitioned<Actor> partitionedRenderableActors;
|
||||
readonly SpatiallyPartitioned<IEffect> partitionedRenderableEffects;
|
||||
|
||||
// Updates are done in one pass to ensure all bound changes have been applied
|
||||
readonly HashSet<Actor> addOrUpdateActors = [];
|
||||
readonly HashSet<Actor> removeActors = [];
|
||||
readonly Cache<Player, HashSet<FrozenActor>> addOrUpdateFrozenActors;
|
||||
readonly Cache<Player, HashSet<FrozenActor>> removeFrozenActors;
|
||||
|
||||
WorldRenderer worldRenderer;
|
||||
|
||||
public ScreenMap(World world, ScreenMapInfo info)
|
||||
{
|
||||
var size = world.Map.Rules.TerrainInfo.TileSize;
|
||||
var width = world.Map.MapSize.Width * size.Width;
|
||||
var height = world.Map.MapSize.Height * size.Height;
|
||||
|
||||
partitionedMouseFrozenActors = new Cache<Player, SpatiallyPartitioned<FrozenActor>>(
|
||||
_ => new SpatiallyPartitioned<FrozenActor>(width, height, info.BinSize));
|
||||
partitionedMouseActors = new SpatiallyPartitioned<Actor>(width, height, info.BinSize);
|
||||
selectActorAndBounds = a => partitionedMouseActorBounds[a];
|
||||
|
||||
partitionedRenderableFrozenActors = new Cache<Player, SpatiallyPartitioned<FrozenActor>>(
|
||||
_ => new SpatiallyPartitioned<FrozenActor>(width, height, info.BinSize));
|
||||
partitionedRenderableActors = new SpatiallyPartitioned<Actor>(width, height, info.BinSize);
|
||||
partitionedRenderableEffects = new SpatiallyPartitioned<IEffect>(width, height, info.BinSize);
|
||||
|
||||
addOrUpdateFrozenActors = new Cache<Player, HashSet<FrozenActor>>(_ => []);
|
||||
removeFrozenActors = new Cache<Player, HashSet<FrozenActor>>(_ => []);
|
||||
}
|
||||
|
||||
public void WorldLoaded(World w, WorldRenderer wr) { worldRenderer = wr; }
|
||||
|
||||
public void AddOrUpdate(Player viewer, FrozenActor fa)
|
||||
{
|
||||
removeFrozenActors[viewer].Remove(fa);
|
||||
|
||||
addOrUpdateFrozenActors[viewer].Add(fa);
|
||||
}
|
||||
|
||||
public void Remove(Player viewer, FrozenActor fa)
|
||||
{
|
||||
removeFrozenActors[viewer].Add(fa);
|
||||
}
|
||||
|
||||
public void AddOrUpdate(Actor a)
|
||||
{
|
||||
removeActors.Remove(a);
|
||||
|
||||
addOrUpdateActors.Add(a);
|
||||
}
|
||||
|
||||
public void Remove(Actor a)
|
||||
{
|
||||
removeActors.Add(a);
|
||||
}
|
||||
|
||||
public void Add(IEffect effect, WPos position, Size size)
|
||||
{
|
||||
var screenPos = worldRenderer.ScreenPxPosition(position);
|
||||
var screenWidth = Math.Abs(size.Width);
|
||||
var screenHeight = Math.Abs(size.Height);
|
||||
var screenBounds = new Rectangle(screenPos.X - screenWidth / 2, screenPos.Y - screenHeight / 2, screenWidth, screenHeight);
|
||||
if (ValidBounds(screenBounds))
|
||||
partitionedRenderableEffects.Add(effect, screenBounds);
|
||||
}
|
||||
|
||||
public void Add(IEffect effect, WPos position, Sprite sprite)
|
||||
{
|
||||
var size = new Size((int)sprite.Size.X, (int)sprite.Size.Y);
|
||||
Add(effect, position, size);
|
||||
}
|
||||
|
||||
public void Update(IEffect effect, WPos position, Size size)
|
||||
{
|
||||
Remove(effect);
|
||||
Add(effect, position, size);
|
||||
}
|
||||
|
||||
public void Update(IEffect effect, WPos position, Sprite sprite)
|
||||
{
|
||||
var size = new Size((int)sprite.Size.X, (int)sprite.Size.Y);
|
||||
Update(effect, position, size);
|
||||
}
|
||||
|
||||
public void Remove(IEffect effect)
|
||||
{
|
||||
partitionedRenderableEffects.Remove(effect);
|
||||
}
|
||||
|
||||
static bool ValidBounds(Rectangle bounds)
|
||||
{
|
||||
return bounds.Width > 0 && bounds.Height > 0;
|
||||
}
|
||||
|
||||
public IEnumerable<FrozenActor> FrozenActorsAtMouse(Player viewer, int2 worldPx)
|
||||
{
|
||||
if (viewer == null)
|
||||
return NoFrozenActors;
|
||||
|
||||
return partitionedMouseFrozenActors[viewer]
|
||||
.At(worldPx)
|
||||
.Where(frozenActorIsValid)
|
||||
.Where(x => x.MouseBounds.Contains(worldPx));
|
||||
}
|
||||
|
||||
public IEnumerable<FrozenActor> FrozenActorsAtMouse(Player viewer, MouseInput mi)
|
||||
{
|
||||
return FrozenActorsAtMouse(viewer, worldRenderer.Viewport.ViewToWorldPx(mi.Location));
|
||||
}
|
||||
|
||||
public IEnumerable<ActorBoundsPair> ActorsAtMouse(int2 worldPx)
|
||||
{
|
||||
return partitionedMouseActors.At(worldPx)
|
||||
.Where(actorIsInWorld)
|
||||
.Select(selectActorAndBounds)
|
||||
.Where(x => x.Bounds.Contains(worldPx));
|
||||
}
|
||||
|
||||
public IEnumerable<ActorBoundsPair> ActorsAtMouse(MouseInput mi)
|
||||
{
|
||||
return ActorsAtMouse(worldRenderer.Viewport.ViewToWorldPx(mi.Location));
|
||||
}
|
||||
|
||||
static Rectangle RectWithCorners(int2 a, int2 b)
|
||||
{
|
||||
return Rectangle.FromLTRB(Math.Min(a.X, b.X), Math.Min(a.Y, b.Y), Math.Max(a.X, b.X), Math.Max(a.Y, b.Y));
|
||||
}
|
||||
|
||||
public IEnumerable<ActorBoundsPair> ActorsInMouseBox(int2 a, int2 b)
|
||||
{
|
||||
return ActorsInMouseBox(RectWithCorners(a, b));
|
||||
}
|
||||
|
||||
public IEnumerable<ActorBoundsPair> ActorsInMouseBox(Rectangle r)
|
||||
{
|
||||
return partitionedMouseActors.InBox(r)
|
||||
.Where(actorIsInWorld)
|
||||
.Select(selectActorAndBounds)
|
||||
.Where(x => x.Bounds.IntersectsWith(r));
|
||||
}
|
||||
|
||||
public IEnumerable<Actor> RenderableActorsInBox(int2 a, int2 b)
|
||||
{
|
||||
return partitionedRenderableActors.InBox(RectWithCorners(a, b)).Where(actorIsInWorld);
|
||||
}
|
||||
|
||||
public IEnumerable<IEffect> RenderableEffectsInBox(int2 a, int2 b)
|
||||
{
|
||||
return partitionedRenderableEffects.InBox(RectWithCorners(a, b));
|
||||
}
|
||||
|
||||
public IEnumerable<FrozenActor> RenderableFrozenActorsInBox(Player p, int2 a, int2 b)
|
||||
{
|
||||
if (p == null)
|
||||
return NoFrozenActors;
|
||||
|
||||
return partitionedRenderableFrozenActors[p].InBox(RectWithCorners(a, b)).Where(frozenActorIsValid);
|
||||
}
|
||||
|
||||
public void TickRender()
|
||||
{
|
||||
foreach (var a in addOrUpdateActors)
|
||||
{
|
||||
var mouseBounds = a.MouseBounds(worldRenderer);
|
||||
if (!mouseBounds.IsEmpty)
|
||||
{
|
||||
partitionedMouseActors[a] = mouseBounds.BoundingRect;
|
||||
partitionedMouseActorBounds[a] = new ActorBoundsPair(a, mouseBounds);
|
||||
}
|
||||
else
|
||||
partitionedMouseActors.Remove(a);
|
||||
|
||||
var screenBounds = a.ScreenBounds(worldRenderer).Union();
|
||||
if (!screenBounds.Size.IsEmpty)
|
||||
partitionedRenderableActors[a] = screenBounds;
|
||||
else
|
||||
partitionedRenderableActors.Remove(a);
|
||||
}
|
||||
|
||||
foreach (var a in removeActors)
|
||||
{
|
||||
partitionedMouseActors.Remove(a);
|
||||
partitionedMouseActorBounds.Remove(a);
|
||||
partitionedRenderableActors.Remove(a);
|
||||
}
|
||||
|
||||
addOrUpdateActors.Clear();
|
||||
removeActors.Clear();
|
||||
|
||||
foreach (var kv in addOrUpdateFrozenActors)
|
||||
{
|
||||
foreach (var fa in kv.Value)
|
||||
{
|
||||
var mouseBounds = fa.MouseBounds;
|
||||
if (!mouseBounds.IsEmpty)
|
||||
partitionedMouseFrozenActors[kv.Key][fa] = mouseBounds.BoundingRect;
|
||||
else
|
||||
partitionedMouseFrozenActors[kv.Key].Remove(fa);
|
||||
|
||||
var screenBounds = fa.ScreenBounds.Union();
|
||||
if (!screenBounds.Size.IsEmpty)
|
||||
partitionedRenderableFrozenActors[kv.Key][fa] = screenBounds;
|
||||
else
|
||||
partitionedRenderableFrozenActors[kv.Key].Remove(fa);
|
||||
}
|
||||
|
||||
kv.Value.Clear();
|
||||
}
|
||||
|
||||
foreach (var kv in removeFrozenActors)
|
||||
{
|
||||
foreach (var fa in kv.Value)
|
||||
{
|
||||
partitionedMouseFrozenActors[kv.Key].Remove(fa);
|
||||
partitionedRenderableFrozenActors[kv.Key].Remove(fa);
|
||||
}
|
||||
|
||||
kv.Value.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<Rectangle> RenderBounds(Player viewer)
|
||||
{
|
||||
var bounds = partitionedRenderableActors.Values
|
||||
.Concat(partitionedRenderableEffects.Values);
|
||||
|
||||
return viewer != null ? bounds.Concat(partitionedRenderableFrozenActors[viewer].Values) : bounds;
|
||||
}
|
||||
|
||||
public IEnumerable<Polygon> MouseBounds(Player viewer)
|
||||
{
|
||||
var bounds = partitionedMouseActorBounds.Values.Select(a => a.Bounds);
|
||||
return viewer != null ? bounds.Concat(partitionedMouseFrozenActors[viewer].Keys.Select(fa => fa.MouseBounds)) : bounds;
|
||||
}
|
||||
}
|
||||
}
|
||||
93
OpenRA.Game/Traits/World/ScreenShaker.cs
Normal file
93
OpenRA.Game/Traits/World/ScreenShaker.cs
Normal file
@@ -0,0 +1,93 @@
|
||||
#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;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using OpenRA.Graphics;
|
||||
|
||||
namespace OpenRA.Traits
|
||||
{
|
||||
[TraitLocation(SystemActors.World)]
|
||||
public class ScreenShakerInfo : TraitInfo
|
||||
{
|
||||
public readonly float2 MinMultiplier = new(-3, -3);
|
||||
public readonly float2 MaxMultiplier = new(3, 3);
|
||||
|
||||
public override object Create(ActorInitializer init) { return new ScreenShaker(this); }
|
||||
}
|
||||
|
||||
public class ScreenShaker : ITick, IWorldLoaded
|
||||
{
|
||||
readonly ScreenShakerInfo info;
|
||||
WorldRenderer worldRenderer;
|
||||
readonly List<ShakeEffect> shakeEffects = [];
|
||||
int ticks = 0;
|
||||
|
||||
public ScreenShaker(ScreenShakerInfo info)
|
||||
{
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
void IWorldLoaded.WorldLoaded(World w, WorldRenderer wr) { worldRenderer = wr; }
|
||||
|
||||
void ITick.Tick(Actor self)
|
||||
{
|
||||
if (shakeEffects.Count > 0)
|
||||
{
|
||||
worldRenderer.Viewport.Scroll(GetScrollOffset(), true);
|
||||
shakeEffects.RemoveAll(t => t.ExpiryTime == ticks);
|
||||
}
|
||||
|
||||
ticks++;
|
||||
}
|
||||
|
||||
public void AddEffect(int time, WPos position, int intensity)
|
||||
{
|
||||
AddEffect(time, position, intensity, new float2(1, 1));
|
||||
}
|
||||
|
||||
public void AddEffect(int time, WPos position, int intensity, float2 multiplier)
|
||||
{
|
||||
shakeEffects.Add(new ShakeEffect { ExpiryTime = ticks + time, Position = position, Intensity = intensity, Multiplier = multiplier });
|
||||
}
|
||||
|
||||
float2 GetScrollOffset()
|
||||
{
|
||||
return GetMultiplier() * GetIntensity() * new float2(
|
||||
(float)Math.Sin(ticks * 2 * Math.PI / 4),
|
||||
(float)Math.Cos(ticks * 2 * Math.PI / 5));
|
||||
}
|
||||
|
||||
float2 GetMultiplier()
|
||||
{
|
||||
return shakeEffects.Aggregate(float2.Zero, (sum, next) => sum + next.Multiplier)
|
||||
.Constrain(info.MinMultiplier, info.MaxMultiplier);
|
||||
}
|
||||
|
||||
float GetIntensity()
|
||||
{
|
||||
var cp = worldRenderer.Viewport.CenterPosition;
|
||||
var intensity = 100 * 1024 * 1024 * shakeEffects.Sum(
|
||||
e => (float)e.Intensity / (e.Position - cp).LengthSquared);
|
||||
|
||||
return Math.Min(intensity, 10);
|
||||
}
|
||||
}
|
||||
|
||||
struct ShakeEffect
|
||||
{
|
||||
public int ExpiryTime;
|
||||
public WPos Position;
|
||||
public int Intensity;
|
||||
public float2 Multiplier;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user