From 2a8efde8e675e68ad303cb41b73bb11ae67e4512 Mon Sep 17 00:00:00 2001 From: ScottNZ Date: Wed, 19 Feb 2014 21:43:54 +1300 Subject: [PATCH] Add map beacons and radar pings --- OpenRA.Game/GameRules/Settings.cs | 2 + OpenRA.Mods.RA/Effects/Beacon.cs | 64 +++++++++++ OpenRA.Mods.RA/OpenRA.Mods.RA.csproj | 3 + OpenRA.Mods.RA/Player/PlaceBeacon.cs | 73 ++++++++++++ OpenRA.Mods.RA/Widgets/Logic/SettingsLogic.cs | 2 + OpenRA.Mods.RA/Widgets/RadarWidget.cs | 41 +++++-- OpenRA.Mods.RA/Widgets/WorldCommandWidget.cs | 12 ++ OpenRA.Mods.RA/World/RadarPings.cs | 106 ++++++++++++++++++ mods/cnc/notifications.yaml | 3 +- mods/cnc/rules/system-player.yaml | 1 + mods/cnc/rules/system-world.yaml | 2 +- mods/cnc/sequences/misc.yaml | 8 ++ mods/d2k/notifications.yaml | 3 +- mods/d2k/rules/system-player.yaml | 2 +- mods/d2k/rules/system-world.yaml | 2 +- mods/d2k/sequences/misc.yaml | 8 ++ mods/ra/notifications.yaml | 3 +- mods/ra/rules/system-player.yaml | 1 + mods/ra/rules/system-world.yaml | 2 +- mods/ra/sequences/misc.yaml | 8 ++ mods/ts/notifications.yaml | 3 +- mods/ts/rules/system-player.yaml | 2 +- mods/ts/rules/system-world.yaml | 2 +- mods/ts/sequences/misc.yaml | 9 ++ 24 files changed, 345 insertions(+), 17 deletions(-) create mode 100644 OpenRA.Mods.RA/Effects/Beacon.cs create mode 100644 OpenRA.Mods.RA/Player/PlaceBeacon.cs create mode 100644 OpenRA.Mods.RA/World/RadarPings.cs diff --git a/OpenRA.Game/GameRules/Settings.cs b/OpenRA.Game/GameRules/Settings.cs index c30a0db04c..c7f67567df 100644 --- a/OpenRA.Game/GameRules/Settings.cs +++ b/OpenRA.Game/GameRules/Settings.cs @@ -150,6 +150,8 @@ namespace OpenRA.GameRules public Hotkey SelectAllUnitsKey = new Hotkey(Keycode.A, Modifiers.Ctrl); public Hotkey SelectUnitsByTypeKey = new Hotkey(Keycode.T, Modifiers.Ctrl); + public Hotkey PlaceBeaconKey = new Hotkey(Keycode.B, Modifiers.Ctrl); + public Hotkey PauseKey = new Hotkey(Keycode.F9, Modifiers.None); public Hotkey SellKey = new Hotkey(Keycode.F10, Modifiers.None); public Hotkey PowerDownKey = new Hotkey(Keycode.F11, Modifiers.None); diff --git a/OpenRA.Mods.RA/Effects/Beacon.cs b/OpenRA.Mods.RA/Effects/Beacon.cs new file mode 100644 index 0000000000..8ae5a4a4e9 --- /dev/null +++ b/OpenRA.Mods.RA/Effects/Beacon.cs @@ -0,0 +1,64 @@ +#region Copyright & License Information +/* + * Copyright 2007-2014 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. For more information, + * see COPYING. + */ +#endregion + +using System.Collections.Generic; +using System.Linq; +using OpenRA.Effects; +using OpenRA.Graphics; + +namespace OpenRA.Mods.RA.Effects +{ + public class Beacon : IEffect + { + readonly Player owner; + readonly WPos position; + readonly string palettePrefix; + readonly Animation arrow = new Animation("beacon"); + readonly Animation circles = new Animation("beacon"); + static readonly int maxArrowHeight = 512; + int arrowHeight = maxArrowHeight; + int arrowSpeed = 50; + + public Beacon(Player owner, WPos position, int duration, string palettePrefix) + { + this.owner = owner; + this.position = position; + this.palettePrefix = palettePrefix; + + arrow.Play("arrow"); + circles.Play("circles"); + + owner.World.Add(new DelayedAction(duration, () => owner.World.Remove(this))); + } + + public void Tick(World world) + { + arrowHeight += arrowSpeed; + var clamped = arrowHeight.Clamp(0, maxArrowHeight); + if (arrowHeight != clamped) + { + arrowHeight = clamped; + arrowSpeed *= -1; + } + + arrow.Tick(); + circles.Tick(); + } + + public IEnumerable Render(WorldRenderer r) + { + if (!owner.IsAlliedWith(owner.World.RenderPlayer)) + return SpriteRenderable.None; + + var palette = r.Palette(palettePrefix + owner.InternalName); + return circles.Render(position, palette).Concat(arrow.Render(position + new WVec(0, 0, arrowHeight), palette)); + } + } +} diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index e76aa1ef5c..8496ab0806 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -123,6 +123,8 @@ + + @@ -306,6 +308,7 @@ + diff --git a/OpenRA.Mods.RA/Player/PlaceBeacon.cs b/OpenRA.Mods.RA/Player/PlaceBeacon.cs new file mode 100644 index 0000000000..8e7464744f --- /dev/null +++ b/OpenRA.Mods.RA/Player/PlaceBeacon.cs @@ -0,0 +1,73 @@ +#region Copyright & License Information +/* + * Copyright 2007-2014 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. For more information, + * see COPYING. + */ +#endregion + +using OpenRA.Mods.RA.Effects; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA +{ + public class PlaceBeaconInfo : ITraitInfo + { + public readonly int Duration = 30 * 25; + public readonly string NotificationType = "Sounds"; + public readonly string Notification = "Beacon"; + public readonly string PalettePrefix = "player"; + + public object Create(ActorInitializer init) { return new PlaceBeacon(init.self, this); } + } + + public class PlaceBeacon : IResolveOrder + { + readonly PlaceBeaconInfo info; + readonly RadarPings radarPings; + + Beacon playerBeacon; + RadarPing playerRadarPing; + + public PlaceBeacon(Actor self, PlaceBeaconInfo info) + { + radarPings = self.World.WorldActor.TraitOrDefault(); + this.info = info; + } + + public void ResolveOrder(Actor self, Order order) + { + if (order.OrderString != "PlaceBeacon") + return; + + var pos = order.TargetLocation.CenterPosition; + + self.World.AddFrameEndTask(w => + { + if (playerBeacon != null) + self.World.Remove(playerBeacon); + + playerBeacon = new Beacon(self.Owner, pos, info.Duration, info.PalettePrefix); + self.World.Add(playerBeacon); + + if (self.Owner.IsAlliedWith(self.World.RenderPlayer)) + Sound.PlayNotification(null, info.NotificationType, info.Notification, + self.World.RenderPlayer != null ? self.World.RenderPlayer.Country.Race : null); + + if (radarPings != null) + { + if (playerRadarPing != null) + radarPings.Remove(playerRadarPing); + + playerRadarPing = radarPings.Add( + () => self.Owner.IsAlliedWith(self.World.RenderPlayer), + pos, + self.Owner.Color.RGB, + info.Duration); + } + }); + } + } +} diff --git a/OpenRA.Mods.RA/Widgets/Logic/SettingsLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/SettingsLogic.cs index 5a39b2584e..d59cc7a376 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/SettingsLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/SettingsLogic.cs @@ -255,6 +255,8 @@ namespace OpenRA.Mods.RA.Widgets.Logic { "SelectAllUnitsKey", "Select all units on screen" }, { "SelectUnitsByTypeKey", "Select units by type" }, + { "PlaceBeaconKey", "Place beacon" }, + { "PauseKey", "Pause / Unpause" }, { "SellKey", "Sell mode" }, { "PowerDownKey", "Power-down mode" }, diff --git a/OpenRA.Mods.RA/Widgets/RadarWidget.cs b/OpenRA.Mods.RA/Widgets/RadarWidget.cs index f5c18abd30..7e5da024ad 100755 --- a/OpenRA.Mods.RA/Widgets/RadarWidget.cs +++ b/OpenRA.Mods.RA/Widgets/RadarWidget.cs @@ -10,6 +10,7 @@ using System; using System.Drawing; +using System.Linq; using OpenRA.Graphics; using OpenRA.Widgets; @@ -22,8 +23,8 @@ namespace OpenRA.Mods.RA.Widgets public string RadarOnlineSound = null; public string RadarOfflineSound = null; public Func IsEnabled = () => true; - public Action AfterOpen = () => {}; - public Action AfterClose = () => {}; + public Action AfterOpen = () => { }; + public Action AfterClose = () => { }; float radarMinimapHeight; int frame; @@ -43,11 +44,14 @@ namespace OpenRA.Mods.RA.Widgets readonly World world; readonly WorldRenderer worldRenderer; + readonly RadarPings radarPings; + [ObjectCreator.UseCtor] public RadarWidget(World world, WorldRenderer worldRenderer) { this.world = world; this.worldRenderer = worldRenderer; + radarPings = world.WorldActor.TraitOrDefault(); } public override void Initialize(WidgetArgs args) @@ -60,8 +64,8 @@ namespace OpenRA.Mods.RA.Widgets var rb = RenderBounds; previewScale = Math.Min(rb.Width * 1f / width, rb.Height * 1f / height); - previewOrigin = new int2((int)(previewScale*(size - width)/2), (int)(previewScale*(size - height)/2)); - mapRect = new Rectangle(previewOrigin.X, previewOrigin.Y, (int)(previewScale*width), (int)(previewScale*height)); + previewOrigin = new int2((int)(previewScale * (size - width) / 2), (int)(previewScale * (size - height) / 2)); + mapRect = new Rectangle(previewOrigin.X, previewOrigin.Y, (int)(previewScale * width), (int)(previewScale * height)); // Only needs to be done once var terrainBitmap = Minimap.TerrainBitmap(world.Map); @@ -95,7 +99,7 @@ namespace OpenRA.Mods.RA.Widgets if (cursor == null) return "default"; - return CursorProvider.HasCursorSequence(cursor+"-minimap") ? cursor+"-minimap" : cursor; + return CursorProvider.HasCursorSequence(cursor + "-minimap") ? cursor + "-minimap" : cursor; } public override bool HandleMouseInput(MouseInput mi) @@ -140,8 +144,8 @@ namespace OpenRA.Mods.RA.Widgets if (world == null) return; - var o = new float2(mapRect.Location.X, mapRect.Location.Y + world.Map.Bounds.Height * previewScale * (1 - radarMinimapHeight)/2); - var s = new float2(mapRect.Size.Width, mapRect.Size.Height*radarMinimapHeight); + var o = new float2(mapRect.Location.X, mapRect.Location.Y + world.Map.Bounds.Height * previewScale * (1 - radarMinimapHeight) / 2); + var s = new float2(mapRect.Size.Width, mapRect.Size.Height * radarMinimapHeight); var rsr = Game.Renderer.RgbaSpriteRenderer; rsr.DrawSprite(terrainSprite, o, s); @@ -156,11 +160,34 @@ namespace OpenRA.Mods.RA.Widgets var br = CellToMinimapPixel(worldRenderer.Position(worldRenderer.Viewport.BottomRight).ToCPos()); Game.Renderer.EnableScissor(mapRect); + DrawRadarPings(); Game.Renderer.LineRenderer.DrawRect(tl, br, Color.White); Game.Renderer.DisableScissor(); } } + void DrawRadarPings() + { + if (radarPings == null) + return; + + var lr = Game.Renderer.LineRenderer; + var oldWidth = lr.LineWidth; + lr.LineWidth = 2; + + foreach (var radarPing in radarPings.Pings.Where(e => e.IsVisible())) + { + var c = radarPing.Color; + var points = radarPing.Points(CellToMinimapPixel(radarPing.Position.ToCPos())).ToArray(); + + lr.DrawLine(points[0], points[1], c, c); + lr.DrawLine(points[1], points[2], c, c); + lr.DrawLine(points[2], points[0], c, c); + } + + lr.LineWidth = oldWidth; + } + public override void Tick() { // Update the radar animation even when its closed diff --git a/OpenRA.Mods.RA/Widgets/WorldCommandWidget.cs b/OpenRA.Mods.RA/Widgets/WorldCommandWidget.cs index ca05bf27ed..bdc4837c39 100644 --- a/OpenRA.Mods.RA/Widgets/WorldCommandWidget.cs +++ b/OpenRA.Mods.RA/Widgets/WorldCommandWidget.cs @@ -63,6 +63,9 @@ namespace OpenRA.Mods.RA.Widgets if (key == ks.ToggleStatusBarsKey) return ToggleStatusBars(); + if (key == ks.PlaceBeaconKey) + return PerformPlaceBeacon(); + // Put all functions that aren't unit-specific before this line! if (!world.Selection.Actors.Any()) return false; @@ -180,6 +183,15 @@ namespace OpenRA.Mods.RA.Widgets return true; } + bool PerformPlaceBeacon() + { + if (world.LocalPlayer == null) + return true; + + world.OrderGenerator = new GenericSelectTarget(world.LocalPlayer.PlayerActor, "PlaceBeacon", "ability"); + return true; + } + bool CycleBases() { var bases = world.ActorsWithTrait() diff --git a/OpenRA.Mods.RA/World/RadarPings.cs b/OpenRA.Mods.RA/World/RadarPings.cs new file mode 100644 index 0000000000..a9677f5f18 --- /dev/null +++ b/OpenRA.Mods.RA/World/RadarPings.cs @@ -0,0 +1,106 @@ +#region Copyright & License Information +/* + * Copyright 2007-2014 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. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Drawing; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA +{ + public class RadarPingsInfo : ITraitInfo + { + public readonly int FromRadius = 200; + public readonly int ToRadius = 15; + public readonly int ShrinkSpeed = 4; + public readonly float RotationSpeed = 0.12f; + + public object Create(ActorInitializer init) { return new RadarPings(this); } + } + + public class RadarPings : ITick + { + public readonly List Pings = new List(); + readonly RadarPingsInfo info; + + public RadarPings(RadarPingsInfo info) + { + this.info = info; + } + + public void Tick(Actor self) + { + foreach (var ping in Pings.ToArray()) + if (!ping.Tick()) + Pings.Remove(ping); + } + + public RadarPing Add(Func isVisible, WPos position, Color color, int duration) + { + var ping = new RadarPing(isVisible, position, color, duration, + info.FromRadius, info.ToRadius, info.ShrinkSpeed, info.RotationSpeed); + Pings.Add(ping); + return ping; + } + + public void Remove(RadarPing ping) + { + Pings.Remove(ping); + } + } + + public class RadarPing + { + public Func IsVisible; + public WPos Position; + public Color Color; + public int Duration; + public int FromRadius; + public int ToRadius; + public int ShrinkSpeed; + public float RotationSpeed; + + int radius; + float angle; + int tick; + + public RadarPing(Func isVisible, WPos position, Color color, int duration, + int fromRadius, int toRadius, int shrinkSpeed, float rotationSpeed) + { + IsVisible = isVisible; + Position = position; + Color = color; + Duration = duration; + FromRadius = fromRadius; + ToRadius = toRadius; + ShrinkSpeed = shrinkSpeed; + RotationSpeed = rotationSpeed; + + radius = fromRadius; + } + + public bool Tick() + { + if (++tick == Duration) + return false; + + radius = Math.Max(radius - ShrinkSpeed, ToRadius); + angle -= RotationSpeed; + return true; + } + + public IEnumerable Points(float2 center) + { + yield return center + radius * float2.FromAngle(angle); + yield return center + radius * float2.FromAngle((float)(angle + 2 * Math.PI / 3)); + yield return center + radius * float2.FromAngle((float)(angle + 4 * Math.PI / 3)); + } + } +} diff --git a/mods/cnc/notifications.yaml b/mods/cnc/notifications.yaml index 332fb91e32..0c77532935 100644 --- a/mods/cnc/notifications.yaml +++ b/mods/cnc/notifications.yaml @@ -41,4 +41,5 @@ Sounds: ChatLine: scold1 TabClick: button ClickSound: button - ClickDisabledSound: scold2 \ No newline at end of file + ClickDisabledSound: scold2 + Beacon: bleep2 \ No newline at end of file diff --git a/mods/cnc/rules/system-player.yaml b/mods/cnc/rules/system-player.yaml index 35137a0f78..e069fb86b8 100644 --- a/mods/cnc/rules/system-player.yaml +++ b/mods/cnc/rules/system-player.yaml @@ -16,4 +16,5 @@ Player: Shroud: PlayerStatistics: FrozenActorLayer: + PlaceBeacon: diff --git a/mods/cnc/rules/system-world.yaml b/mods/cnc/rules/system-world.yaml index 45b5f8cc7d..c4e96ebc5e 100644 --- a/mods/cnc/rules/system-world.yaml +++ b/mods/cnc/rules/system-world.yaml @@ -175,4 +175,4 @@ World: DebugPauseState: ConquestObjectivesPanel: ObjectivesPanel: CONQUEST_OBJECTIVES - + RadarPings: diff --git a/mods/cnc/sequences/misc.yaml b/mods/cnc/sequences/misc.yaml index e8416aa33a..cdf291b8af 100644 --- a/mods/cnc/sequences/misc.yaml +++ b/mods/cnc/sequences/misc.yaml @@ -152,6 +152,14 @@ rallypoint: Start: 0 Length: * +beacon: + arrow: mouse2 + Start: 5 + Offset: 1,-12 + circles: fpls + Start: 0 + Length: * + select: repair: Start: 2 diff --git a/mods/d2k/notifications.yaml b/mods/d2k/notifications.yaml index b5f75abadb..56c3dffc63 100644 --- a/mods/d2k/notifications.yaml +++ b/mods/d2k/notifications.yaml @@ -47,4 +47,5 @@ Sounds: BuildPaletteClose: BUTTON1 TabClick: SIDEBAR1 ClickSound: BUTTON1 - ClickDisabledSound: ENDLIST1 \ No newline at end of file + ClickDisabledSound: ENDLIST1 + Beacon: CHAT1 \ No newline at end of file diff --git a/mods/d2k/rules/system-player.yaml b/mods/d2k/rules/system-player.yaml index 1c8a8ba9e7..f77b6564f4 100644 --- a/mods/d2k/rules/system-player.yaml +++ b/mods/d2k/rules/system-player.yaml @@ -51,4 +51,4 @@ Player: FrozenActorLayer: HarvesterAttackNotifier: PlayerStatistics: - + PlaceBeacon: diff --git a/mods/d2k/rules/system-world.yaml b/mods/d2k/rules/system-world.yaml index f406e4b2ae..620fdb5b6a 100644 --- a/mods/d2k/rules/system-world.yaml +++ b/mods/d2k/rules/system-world.yaml @@ -126,4 +126,4 @@ World: PathFinder: ValidateOrder: DebugPauseState: - + RadarPings: diff --git a/mods/d2k/sequences/misc.yaml b/mods/d2k/sequences/misc.yaml index fc9610a36d..c8f3a9c45d 100644 --- a/mods/d2k/sequences/misc.yaml +++ b/mods/d2k/sequences/misc.yaml @@ -130,6 +130,14 @@ rallypoint: Start: 0 Length: * +beacon: + arrow: mouse + Start: 148 + Offset: -24,-24 + circles: fpls + Start: 0 + Length: * + rpg: idle: DATA Start: 3015 diff --git a/mods/ra/notifications.yaml b/mods/ra/notifications.yaml index e906c504a4..7c8a43af9e 100644 --- a/mods/ra/notifications.yaml +++ b/mods/ra/notifications.yaml @@ -42,4 +42,5 @@ Sounds: BuildPaletteClose: bleep13 TabClick: ramenu1 ClickSound: - ClickDisabledSound: \ No newline at end of file + ClickDisabledSound: + Beacon: beepslct \ No newline at end of file diff --git a/mods/ra/rules/system-player.yaml b/mods/ra/rules/system-player.yaml index 79cf0050d8..b58265e0de 100644 --- a/mods/ra/rules/system-player.yaml +++ b/mods/ra/rules/system-player.yaml @@ -56,4 +56,5 @@ Player: FrozenActorLayer: BaseAttackNotifier: PlayerStatistics: + PlaceBeacon: diff --git a/mods/ra/rules/system-world.yaml b/mods/ra/rules/system-world.yaml index 2838fd5d42..16b08f4b59 100644 --- a/mods/ra/rules/system-world.yaml +++ b/mods/ra/rules/system-world.yaml @@ -164,4 +164,4 @@ World: PathFinder: ValidateOrder: DebugPauseState: - + RadarPings: diff --git a/mods/ra/sequences/misc.yaml b/mods/ra/sequences/misc.yaml index 7cd65490c0..245a6fce3a 100644 --- a/mods/ra/sequences/misc.yaml +++ b/mods/ra/sequences/misc.yaml @@ -114,6 +114,14 @@ rallypoint: Start: 0 Length: * +beacon: + arrow: mouse + Start: 5 + Offset: 1,-12 + circles: fpls + Start: 0 + Length: * + smoke_m: idle: Start: 0 diff --git a/mods/ts/notifications.yaml b/mods/ts/notifications.yaml index 8b0e113afd..f6e5c00a4e 100644 --- a/mods/ts/notifications.yaml +++ b/mods/ts/notifications.yaml @@ -39,4 +39,5 @@ Sounds: BuildPaletteClose: TabClick: ClickSound:clicky1 - ClickDisabledSound:wrong1 \ No newline at end of file + ClickDisabledSound:wrong1 + Beacon: message1 \ No newline at end of file diff --git a/mods/ts/rules/system-player.yaml b/mods/ts/rules/system-player.yaml index addcb00bf6..eb6ac74e48 100644 --- a/mods/ts/rules/system-player.yaml +++ b/mods/ts/rules/system-player.yaml @@ -40,4 +40,4 @@ Player: Shroud: BaseAttackNotifier: PlayerStatistics: - + PlaceBeacon: diff --git a/mods/ts/rules/system-world.yaml b/mods/ts/rules/system-world.yaml index 20b439841f..a5e1818a42 100644 --- a/mods/ts/rules/system-world.yaml +++ b/mods/ts/rules/system-world.yaml @@ -96,4 +96,4 @@ World: ValidateOrder: DebugPauseState: ScreenShaker: - + RadarPings: \ No newline at end of file diff --git a/mods/ts/sequences/misc.yaml b/mods/ts/sequences/misc.yaml index d0b62f2f01..bcba708efa 100644 --- a/mods/ts/sequences/misc.yaml +++ b/mods/ts/sequences/misc.yaml @@ -44,6 +44,15 @@ rallypoint: Length: 12 BlendMode: Additive +beacon: + arrow: mouse + Start: 6 + Offset: 1,-12 + circles: ring + Start: 0 + Length: 12 + BlendMode: Additive + rank: # TODO: backfall to RA asset rank: Start: 0