diff --git a/OpenRa.Game/Controller.cs b/OpenRa.Game/Controller.cs index 1bc3d1a695..086526860c 100644 --- a/OpenRa.Game/Controller.cs +++ b/OpenRa.Game/Controller.cs @@ -1,13 +1,13 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Windows.Forms; -using IjwFramework.Types; -using System.Drawing; -using OpenRa.Game.Traits; - -namespace OpenRa.Game +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Forms; +using IjwFramework.Types; +using System.Drawing; +using OpenRa.Game.Traits; + +namespace OpenRa.Game { class Controller { @@ -76,7 +76,7 @@ namespace OpenRa.Game if (mi.Button == MouseButtons.None && mi.Event == MouseInputEvent.Move) { - /* update the cursor to reflect the thing under us - note this + /* update the cursor to reflect the thing under us - note this * needs to also happen when the *thing* changes, so per-frame hook */ dragStart = dragEnd = xy; } @@ -101,31 +101,6 @@ namespace OpenRa.Game .FirstOrDefault(a => a != null) : null; return c ?? (Game.SelectUnitOrBuilding(Game.CellSize * dragEnd).Any() ? Cursor.Select : Cursor.Default); - - //foreach( var o in - //var uog = orderGenerator as UnitOrderGenerator; - - //if (uog != null) - // uog.selection.RemoveAll(a => a.IsDead); - - //if (uog != null && uog.selection.Count > 0 - // && uog.selection.Any(a => a.traits.Contains()) - // && uog.selection.All(a => a.Owner == Game.LocalPlayer)) - //{ - // var umts = uog.selection.Select(a => a.traits.GetOrDefault()) - // .Where(m => m != null) - // .Select(m => m.GetMovementType()) - // .Distinct(); - - // if (!umts.Any(umt => Game.IsCellBuildable(dragEnd.ToInt2(), umt))) - // return Cursor.MoveBlocked; - // return Cursor.Move; - //} - - //if (Game.SelectUnitOrBuilding(Game.CellSize * dragEnd).Any()) - // return Cursor.Select; - - //return Cursor.Default; } - } -} + } +} diff --git a/OpenRa.Game/Game.cs b/OpenRa.Game/Game.cs index 4588f35b56..656ef59c17 100644 --- a/OpenRa.Game/Game.cs +++ b/OpenRa.Game/Game.cs @@ -1,260 +1,262 @@ -using System.Collections.Generic; -using OpenRa.FileFormats; -using OpenRa.Game.Graphics; -using OpenRa.TechTree; -using System.Drawing; -using System.Linq; -using IrrKlang; -using IjwFramework.Collections; -using System; -using IjwFramework.Types; -using OpenRa.Game.Traits; -using OpenRa.Game.GameRules; - -namespace OpenRa.Game -{ - static class Game - { - public static readonly int CellSize = 24; - - public static World world; - public static Map map; - static TreeCache treeCache; - public static Viewport viewport; - public static PathFinder PathFinder; - public static WorldRenderer worldRenderer; - public static Controller controller; - - public static OrderManager orderManager; - - static int localPlayerIndex; - - public static Dictionary players = new Dictionary(); - - public static Player LocalPlayer { get { return players[localPlayerIndex]; } } - public static BuildingInfluenceMap BuildingInfluence; - public static UnitInfluenceMap UnitInfluence; - - static ISoundEngine soundEngine; - - public static string Replay; - - public static void Initialize(string mapName, Renderer renderer, int2 clientSize, int localPlayer) - { - Rules.LoadRules(mapName); - - for (int i = 0; i < 8; i++) - players.Add(i, new Player(i, string.Format("Multi{0}", i), Race.Soviet)); - - localPlayerIndex = localPlayer; - - var mapFile = new IniFile(FileSystem.Open(mapName)); - map = new Map(mapFile); - FileSystem.Mount(new Package(map.Theater + ".mix")); - - viewport = new Viewport( clientSize, map.Offset, map.Offset + map.Size, renderer ); - - world = new World(); - treeCache = new TreeCache(map); - - foreach (TreeReference treeReference in map.Trees) - world.Add(new Actor(treeReference, treeCache)); - - BuildingInfluence = new BuildingInfluenceMap(8); - UnitInfluence = new UnitInfluenceMap(); - - LoadMapBuildings(mapFile); - LoadMapUnits(mapFile); - - controller = new Controller(); - worldRenderer = new WorldRenderer(renderer); - - PathFinder = new PathFinder( map, worldRenderer.terrainRenderer.tileSet ); - - soundEngine = new ISoundEngine(); - sounds = new Cache(LoadSound); - - orderManager = (Replay == "") - ? new OrderManager(new[] { new LocalOrderSource() }, "replay.rep") - : new OrderManager(new[] { new ReplayOrderSource(Replay) }); - - PlaySound("intro.aud", false); - } - - static void LoadMapBuildings( IniFile mapfile ) - { - foreach( var s in mapfile.GetSection( "STRUCTURES", true ) ) - { - //num=owner,type,health,location,facing,trigger,unknown,shouldRepair - var parts = s.Value.ToLowerInvariant().Split( ',' ); - var loc = int.Parse( parts[ 3 ] ); - world.Add( new Actor( parts[ 1 ], new int2( loc % 128, loc / 128 ), players[ 0 ] ) ); - } - } - - static void LoadMapUnits( IniFile mapfile ) - { - foreach( var s in mapfile.GetSection( "UNITS", true ) ) - { - //num=owner,type,health,location,facing,action,trigger - var parts = s.Value.ToLowerInvariant().Split( ',' ); - var loc = int.Parse( parts[ 3 ] ); - world.Add( new Actor( parts[ 1 ], new int2( loc % 128, loc / 128 ), players[ 0 ] ) ); - } - } - - static Cache sounds; - - static ISoundSource LoadSound(string filename) - { - var data = AudLoader.LoadSound(FileSystem.Open(filename)); - return soundEngine.AddSoundSourceFromPCMData(data, filename, - new AudioFormat() - { - ChannelCount = 1, - FrameCount = data.Length / 2, - Format = SampleFormat.Signed16Bit, - SampleRate = 22050 - }); - } - - public static void PlaySound(string name, bool loop) - { - var sound = sounds[name]; - // todo: positioning - soundEngine.Play2D(sound, loop, false, false); - } - - public static void Tick() - { - world.Update(); - UnitInfluence.Tick(); - - viewport.DrawRegions(); - - orderManager.Tick(); - } - - public static bool IsCellBuildable(int2 a, UnitMovementType umt) - { - if (BuildingInfluence.GetBuildingAt(a) != null) return false; - if (UnitInfluence.GetUnitAt(a) != null) return false; - - return map.IsInMap(a.X, a.Y) && - TerrainCosts.Cost(umt, - worldRenderer.terrainRenderer.tileSet.GetWalkability( map.MapTiles[ a.X, a.Y ] ) ) < double.PositiveInfinity; - } - - static IEnumerable FindUnits(float2 a, float2 b) - { - var min = float2.Min(a, b); - var max = float2.Max(a, b); - - var rect = new RectangleF(min.X, min.Y, max.X - min.X, max.Y - min.Y); - - return world.Actors - .Where(x => x.Bounds.IntersectsWith(rect)); - } - - public static IEnumerable FindUnitsInCircle(float2 a, float r) - { - return FindUnits(a - new float2(r, r), a + new float2(r, r)) - .Where(x => (x.CenterLocation - a).LengthSquared < r * r); - } - - public static IEnumerable FindTilesInCircle( int2 a, int r ) - { - var min = a - new int2(r, r); - var max = a + new int2(r, r); - if( min.X < 0 ) min.X = 0; - if( min.Y < 0 ) min.Y = 0; - if( max.X > 127 ) max.X = 127; - if( max.Y > 127 ) max.Y = 127; - - for (var j = min.Y; j <= max.Y; j++) - for (var i = min.X; i <= max.X; i++) - if (r * r >= (new int2(i, j) - a).LengthSquared) - yield return new int2(i, j); - } - - public static IEnumerable SelectUnitsInBox(float2 a, float2 b) - { - return FindUnits(a, b).Where(x => x.Owner == LocalPlayer && x.traits.Contains()); - } - - public static IEnumerable SelectUnitOrBuilding(float2 a) - { - var q = FindUnits(a, a); - return q.Where(x => x.traits.Contains()).Concat(q).Take(1); - } - - public static int GetDistanceToBase(int2 b, Player p) - { - var building = BuildingInfluence.GetNearestBuilding(b); - if (building == null || building.Owner != p) - return int.MaxValue; - - return BuildingInfluence.GetDistanceToBuilding(b); - } - - public static Random SharedRandom = new Random(); /* for things that require sync */ - public static Random CosmeticRandom = new Random(); /* for things that are just fluff */ - - public static readonly Pair SovietVoices = - Pair.New( - new VoicePool("ackno", "affirm1", "noprob", "overout", "ritaway", "roger", "ugotit"), - new VoicePool("await1", "ready", "report1", "yessir1")); - - static int2? FindAdjacentTile(Actor a, UnitMovementType umt) - { - var tiles = Footprint.Tiles(a); - var min = tiles.Aggregate(int2.Min) - new int2(1, 1); - var max = tiles.Aggregate(int2.Max) + new int2(1, 1); - - for (var j = min.Y; j <= max.Y; j++) - for (var i = min.X; i <= max.X; i++) - if (IsCellBuildable(new int2(i, j), umt)) - return new int2(i, j); - - return null; - } - - public static void BuildUnit(Player player, string name) - { - var producerTypes = Rules.UnitInfo[name].BuiltAt; - var producer = world.Actors - .FirstOrDefault(a => a.unitInfo != null - && producerTypes.Contains(a.unitInfo.Name) && a.Owner == player); - - if (producer == null) - throw new InvalidOperationException("BuildUnit without suitable production structure!"); - - Actor unit; - - if (producerTypes.Contains("spen") || producerTypes.Contains("syrd")) - { - var space = FindAdjacentTile(producer, Rules.UnitInfo[name].WaterBound ? - UnitMovementType.Float : UnitMovementType.Wheel ); /* hackety hack */ - - if (space == null) - throw new NotImplementedException("Nowhere to place this unit."); - - unit = new Actor(name, space.Value, player); - var mobile = unit.traits.Get(); - mobile.facing = SharedRandom.Next(256); - } - else - { - unit = new Actor(name, (1 / 24f * producer.CenterLocation).ToInt2(), player); - var mobile = unit.traits.Get(); - mobile.facing = 128; - mobile.QueueActivity(new Mobile.MoveTo(unit.Location + new int2(0, 3))); - } - - world.Add(unit); - - if (producer.traits.Contains()) - producer.traits.Get().EjectUnit(); - } - } -} +using System.Collections.Generic; +using OpenRa.FileFormats; +using OpenRa.Game.Graphics; +using OpenRa.TechTree; +using System.Drawing; +using System.Linq; +using IrrKlang; +using IjwFramework.Collections; +using System; +using IjwFramework.Types; +using OpenRa.Game.Traits; +using OpenRa.Game.GameRules; + +namespace OpenRa.Game +{ + static class Game + { + public static readonly int CellSize = 24; + + public static World world; + public static Map map; + static TreeCache treeCache; + public static Viewport viewport; + public static PathFinder PathFinder; + public static WorldRenderer worldRenderer; + public static Controller controller; + + public static OrderManager orderManager; + + static int localPlayerIndex; + + public static Dictionary players = new Dictionary(); + + public static Player LocalPlayer { get { return players[localPlayerIndex]; } } + public static BuildingInfluenceMap BuildingInfluence; + public static UnitInfluenceMap UnitInfluence; + + static ISoundEngine soundEngine; + + public static string Replay; + + public static void Initialize(string mapName, Renderer renderer, int2 clientSize, int localPlayer) + { + Rules.LoadRules(mapName); + + for (int i = 0; i < 8; i++) + players.Add(i, new Player(i, string.Format("Multi{0}", i), Race.Soviet)); + + localPlayerIndex = localPlayer; + + var mapFile = new IniFile(FileSystem.Open(mapName)); + map = new Map(mapFile); + FileSystem.Mount(new Package(map.Theater + ".mix")); + + viewport = new Viewport( clientSize, map.Offset, map.Offset + map.Size, renderer ); + + world = new World(); + treeCache = new TreeCache(map); + + foreach (TreeReference treeReference in map.Trees) + world.Add(new Actor(treeReference, treeCache)); + + BuildingInfluence = new BuildingInfluenceMap(8); + UnitInfluence = new UnitInfluenceMap(); + + LoadMapBuildings(mapFile); + LoadMapUnits(mapFile); + + controller = new Controller(); + worldRenderer = new WorldRenderer(renderer); + + PathFinder = new PathFinder( map, worldRenderer.terrainRenderer.tileSet ); + + soundEngine = new ISoundEngine(); + sounds = new Cache(LoadSound); + + orderManager = (Replay == "") + ? new OrderManager(new[] { new LocalOrderSource() }, "replay.rep") + : new OrderManager(new[] { new ReplayOrderSource(Replay) }); + + PlaySound("intro.aud", false); + } + + static void LoadMapBuildings( IniFile mapfile ) + { + foreach( var s in mapfile.GetSection( "STRUCTURES", true ) ) + { + //num=owner,type,health,location,facing,trigger,unknown,shouldRepair + var parts = s.Value.ToLowerInvariant().Split( ',' ); + var loc = int.Parse( parts[ 3 ] ); + world.Add( new Actor( parts[ 1 ], new int2( loc % 128, loc / 128 ), players[ 0 ] ) ); + } + } + + static void LoadMapUnits( IniFile mapfile ) + { + foreach( var s in mapfile.GetSection( "UNITS", true ) ) + { + //num=owner,type,health,location,facing,action,trigger + var parts = s.Value.ToLowerInvariant().Split( ',' ); + var loc = int.Parse( parts[ 3 ] ); + world.Add( new Actor( parts[ 1 ], new int2( loc % 128, loc / 128 ), players[ 0 ] ) ); + } + } + + static Cache sounds; + + static ISoundSource LoadSound(string filename) + { + var data = AudLoader.LoadSound(FileSystem.Open(filename)); + return soundEngine.AddSoundSourceFromPCMData(data, filename, + new AudioFormat() + { + ChannelCount = 1, + FrameCount = data.Length / 2, + Format = SampleFormat.Signed16Bit, + SampleRate = 22050 + }); + } + + public static void PlaySound(string name, bool loop) + { + var sound = sounds[name]; + // todo: positioning + soundEngine.Play2D(sound, loop, false, false); + } + + public static void Tick() + { + world.Update(); + UnitInfluence.Tick(); + foreach( var player in players.Values ) + player.Tick(); + + viewport.DrawRegions(); + + orderManager.Tick(); + } + + public static bool IsCellBuildable(int2 a, UnitMovementType umt) + { + if (BuildingInfluence.GetBuildingAt(a) != null) return false; + if (UnitInfluence.GetUnitAt(a) != null) return false; + + return map.IsInMap(a.X, a.Y) && + TerrainCosts.Cost(umt, + worldRenderer.terrainRenderer.tileSet.GetWalkability( map.MapTiles[ a.X, a.Y ] ) ) < double.PositiveInfinity; + } + + static IEnumerable FindUnits(float2 a, float2 b) + { + var min = float2.Min(a, b); + var max = float2.Max(a, b); + + var rect = new RectangleF(min.X, min.Y, max.X - min.X, max.Y - min.Y); + + return world.Actors + .Where(x => x.Bounds.IntersectsWith(rect)); + } + + public static IEnumerable FindUnitsInCircle(float2 a, float r) + { + return FindUnits(a - new float2(r, r), a + new float2(r, r)) + .Where(x => (x.CenterLocation - a).LengthSquared < r * r); + } + + public static IEnumerable FindTilesInCircle( int2 a, int r ) + { + var min = a - new int2(r, r); + var max = a + new int2(r, r); + if( min.X < 0 ) min.X = 0; + if( min.Y < 0 ) min.Y = 0; + if( max.X > 127 ) max.X = 127; + if( max.Y > 127 ) max.Y = 127; + + for (var j = min.Y; j <= max.Y; j++) + for (var i = min.X; i <= max.X; i++) + if (r * r >= (new int2(i, j) - a).LengthSquared) + yield return new int2(i, j); + } + + public static IEnumerable SelectUnitsInBox(float2 a, float2 b) + { + return FindUnits(a, b).Where(x => x.Owner == LocalPlayer && x.traits.Contains()); + } + + public static IEnumerable SelectUnitOrBuilding(float2 a) + { + var q = FindUnits(a, a); + return q.Where(x => x.traits.Contains()).Concat(q).Take(1); + } + + public static int GetDistanceToBase(int2 b, Player p) + { + var building = BuildingInfluence.GetNearestBuilding(b); + if (building == null || building.Owner != p) + return int.MaxValue; + + return BuildingInfluence.GetDistanceToBuilding(b); + } + + public static Random SharedRandom = new Random(); /* for things that require sync */ + public static Random CosmeticRandom = new Random(); /* for things that are just fluff */ + + public static readonly Pair SovietVoices = + Pair.New( + new VoicePool("ackno", "affirm1", "noprob", "overout", "ritaway", "roger", "ugotit"), + new VoicePool("await1", "ready", "report1", "yessir1")); + + static int2? FindAdjacentTile(Actor a, UnitMovementType umt) + { + var tiles = Footprint.Tiles(a); + var min = tiles.Aggregate(int2.Min) - new int2(1, 1); + var max = tiles.Aggregate(int2.Max) + new int2(1, 1); + + for (var j = min.Y; j <= max.Y; j++) + for (var i = min.X; i <= max.X; i++) + if (IsCellBuildable(new int2(i, j), umt)) + return new int2(i, j); + + return null; + } + + public static void BuildUnit(Player player, string name) + { + var producerTypes = Rules.UnitInfo[name].BuiltAt; + var producer = world.Actors + .FirstOrDefault(a => a.unitInfo != null + && producerTypes.Contains(a.unitInfo.Name) && a.Owner == player); + + if (producer == null) + throw new InvalidOperationException("BuildUnit without suitable production structure!"); + + Actor unit; + + if (producerTypes.Contains("spen") || producerTypes.Contains("syrd")) + { + var space = FindAdjacentTile(producer, Rules.UnitInfo[name].WaterBound ? + UnitMovementType.Float : UnitMovementType.Wheel ); /* hackety hack */ + + if (space == null) + throw new NotImplementedException("Nowhere to place this unit."); + + unit = new Actor(name, space.Value, player); + var mobile = unit.traits.Get(); + mobile.facing = SharedRandom.Next(256); + } + else + { + unit = new Actor(name, (1 / 24f * producer.CenterLocation).ToInt2(), player); + var mobile = unit.traits.Get(); + mobile.facing = 128; + mobile.QueueActivity(new Mobile.MoveTo(unit.Location + new int2(0, 3))); + } + + world.Add(unit); + + if (producer.traits.Contains()) + producer.traits.Get().EjectUnit(); + } + } +} diff --git a/OpenRa.Game/Graphics/Sequence.cs b/OpenRa.Game/Graphics/Sequence.cs index ecce1b57b5..66eaeef597 100644 --- a/OpenRa.Game/Graphics/Sequence.cs +++ b/OpenRa.Game/Graphics/Sequence.cs @@ -21,11 +21,11 @@ namespace OpenRa.Game.Graphics start = src.Start + int.Parse(e.GetAttribute("start")); if (e.GetAttribute("length") == "*" || e.GetAttribute("end") == "*") - length = src.End - start; + length = src.End - start + 1; else if (e.HasAttribute("length")) length = int.Parse(e.GetAttribute("length")); else if (e.HasAttribute("end")) - length = int.Parse(e.GetAttribute("end")) - start; + length = int.Parse(e.GetAttribute("end")) - int.Parse(e.GetAttribute("start")); else length = 1; } diff --git a/OpenRa.Game/Order.cs b/OpenRa.Game/Order.cs index 05301ac383..112998a518 100644 --- a/OpenRa.Game/Order.cs +++ b/OpenRa.Game/Order.cs @@ -1,7 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Linq; +using System; +using System.Collections.Generic; +using System.Text; +using System.Linq; using OpenRa.Game.Traits; using System.IO; @@ -43,7 +43,7 @@ namespace OpenRa.Game var ret = new MemoryStream(); var w = new BinaryWriter(ret); w.Write((uint)Player.Palette | 0x80000000u); - w.Write((byte)0xFF); // + w.Write((byte)0xFF); w.Write(OrderString); w.Write(Subject == null ? 0xFFFFFFFF : Subject.ActorID); w.Write(TargetActor == null ? 0xFFFFFFFF : TargetActor.ActorID); @@ -112,4 +112,4 @@ namespace OpenRa.Game return new Order(subject, "BuildUnit", null, null, int2.Zero, unitName, Cursor.Default); } } -} +} diff --git a/OpenRa.Game/Player.cs b/OpenRa.Game/Player.cs index bd92e08ed4..55a022e4a3 100644 --- a/OpenRa.Game/Player.cs +++ b/OpenRa.Game/Player.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Generic; namespace OpenRa.Game { @@ -19,5 +21,87 @@ namespace OpenRa.Game { return 0.5f; /* todo: work this out the same way as RA */ } + + public void GiveCash( int num ) + { + // TODO: increase cash + } + + public bool TakeCash( int num ) + { + // TODO: decrease cash. + // returns: if enough cash was available, true + return true; + } + + public void Tick() + { + foreach( var p in production ) + { + if( p.Value != null ) + p.Value.Tick( this ); + } + } + + // Key: Production category. Categories are: building, infantry, vehicle, boat, plane (and one per super, if they're done in here) + readonly Dictionary production = new Dictionary(); + + public void ProductionInit( string category ) + { + production.Add( category, null ); + } + + public ProductionItem Producing( string category ) + { + return production[ category ]; + } + + public void CancelProduction( string category ) + { + var item = production[ category ]; + if( item == null ) return; + GiveCash( item.TotalCost - item.RemainingCost ); // refund what's been paid so far. + production[ category ] = null; + } + + public void BeginProduction( string group, ProductionItem item ) + { + if( production[ group ] != null ) return; + production[ group ] = item; + } + } + + class ProductionItem + { + public readonly string Item; + + public readonly int TotalTime; + public readonly int TotalCost; + public int RemainingTime { get; private set; } + public int RemainingCost { get; private set; } + + public bool Paused = false, Done = false; + + public ProductionItem( string item, int time, int cost ) + { + Item = item; + RemainingTime = TotalTime = time; + RemainingCost = TotalCost = cost; + } + + public void Tick( Player player ) + { + if( Paused || Done ) return; + + var costThisFrame = RemainingCost / RemainingTime; + if( costThisFrame == 0 && !player.TakeCash( costThisFrame ) ) return; + + RemainingCost -= costThisFrame; + RemainingTime -= 1; + if( RemainingTime > 0 ) return; + + // item finished; do whatever needs done. + Done = true; + } } } diff --git a/OpenRa.Game/Sidebar.cs b/OpenRa.Game/Sidebar.cs index 81df8c4f4f..a4189b75dd 100644 --- a/OpenRa.Game/Sidebar.cs +++ b/OpenRa.Game/Sidebar.cs @@ -1,180 +1,189 @@ -using System; -using System.Collections.Generic; -using System.Drawing; -using System.Windows.Forms; -using OpenRa.FileFormats; -using OpenRa.Game.Graphics; -using OpenRa.TechTree; -using System.Linq; - -namespace OpenRa.Game -{ - using GRegion = OpenRa.Game.Graphics.Region; - - class Sidebar - { - TechTree.TechTree techTree; - - SpriteRenderer spriteRenderer, clockRenderer; - Sprite blank; - readonly GRegion region; - - public GRegion Region { get { return region; } } - public float Width { get { return spriteWidth * 2; } } - - Dictionary sprites = new Dictionary(); - const int spriteWidth = 64, spriteHeight = 48; - - static string[] groups = new string[] { "building", "vehicle", "boat", "infantry", "plane" }; - - Dictionary itemGroups = new Dictionary(); //item->group - Dictionary clockAnimations = new Dictionary(); //group->clockAnimation - Dictionary selectedItems = new Dictionary(); //group->selectedItem - - List items = new List(); - - public Sidebar( Renderer renderer ) - { - this.techTree = Game.LocalPlayer.TechTree; - this.techTree.BuildableItemsChanged += PopulateItemList; - region = GRegion.Create(Game.viewport, DockStyle.Right, 128, Paint, MouseHandler); - Game.viewport.AddRegion( region ); - spriteRenderer = new SpriteRenderer(renderer, false); - clockRenderer = new SpriteRenderer(renderer, true); - - LoadSprites( "BuildingTypes", "building" ); - LoadSprites( "VehicleTypes", "vehicle" ); - LoadSprites( "InfantryTypes", "infantry" ); - LoadSprites( "ShipTypes", "boat" ); - LoadSprites( "PlaneTypes", "plane" ); - - foreach (string s in groups) - { - clockAnimations.Add(s, new Animation("clock")); - clockAnimations[s].PlayRepeating("idle"); - selectedItems.Add(s, null); - } - - blank = SheetBuilder.Add(new Size((int)spriteWidth, (int)spriteHeight), 16); - } - - public void Build(SidebarItem item) +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms; +using OpenRa.FileFormats; +using OpenRa.Game.Graphics; +using OpenRa.TechTree; +using System.Linq; + +namespace OpenRa.Game +{ + using GRegion = OpenRa.Game.Graphics.Region; + + class Sidebar + { + TechTree.TechTree techTree; + + SpriteRenderer spriteRenderer, clockRenderer; + Sprite blank; + readonly GRegion region; + + public GRegion Region { get { return region; } } + public float Width { get { return spriteWidth * 2; } } + + Dictionary sprites = new Dictionary(); + const int spriteWidth = 64, spriteHeight = 48; + + static string[] groups = new string[] { "building", "vehicle", "boat", "infantry", "plane" }; + + Dictionary itemGroups = new Dictionary(); //item->group + Dictionary clockAnimations = new Dictionary(); //group->clockAnimation + + List items = new List(); + + public Sidebar( Renderer renderer ) { - if (item == null) return; - + this.techTree = Game.LocalPlayer.TechTree; + this.techTree.BuildableItemsChanged += PopulateItemList; + region = GRegion.Create(Game.viewport, DockStyle.Right, 128, Paint, MouseHandler); + Game.viewport.AddRegion( region ); + spriteRenderer = new SpriteRenderer(renderer, false); + clockRenderer = new SpriteRenderer(renderer, true); + + LoadSprites( "BuildingTypes", "building" ); + LoadSprites( "VehicleTypes", "vehicle" ); + LoadSprites( "InfantryTypes", "infantry" ); + LoadSprites( "ShipTypes", "boat" ); + LoadSprites( "PlaneTypes", "plane" ); + + foreach (string group in groups) + { + Game.LocalPlayer.ProductionInit( group ); + clockAnimations.Add( group, new Animation( "clock" ) ); + clockAnimations[ group ].PlayFetchIndex( "idle", ClockAnimFrame( group ) ); + } + + blank = SheetBuilder.Add(new Size((int)spriteWidth, (int)spriteHeight), 16); + } + + const int NumClockFrames = 54; + Func ClockAnimFrame( string group ) + { + return () => + { + var producing = Game.LocalPlayer.Producing( group ); + if( producing == null ) return 0; + return ( producing.TotalTime - producing.RemainingTime ) * NumClockFrames / producing.TotalTime; + }; + } + + public void Build(SidebarItem item) + { + if (item == null) return; + if (item.techTreeItem.IsStructure) Game.controller.orderGenerator = new PlaceBuilding(Game.LocalPlayer, item.techTreeItem.tag.ToLowerInvariant()); else - Game.controller.AddOrder(Order.BuildUnit(Game.LocalPlayer, item.techTreeItem.tag.ToLowerInvariant())); - } - - void LoadSprites( string category, string group ) - { - foreach( var u in Rules.AllRules.GetSection( category ) ) - { - var unit = Rules.UnitInfo[ u.Key ]; - - if( unit.TechLevel != -1 ) - sprites.Add( unit.Name, SpriteSheetBuilder.LoadSprite( unit.Name + "icon", ".shp" ) ); - itemGroups.Add( unit.Name, group ); - } - } - - void DrawSprite(Sprite s, ref float2 p) - { - spriteRenderer.DrawSprite(s, p, 0); - p.Y += spriteHeight; - } - - void Fill(float height, float2 p) - { - while (p.Y < height) - DrawSprite(blank, ref p); - } - - int buildPos = 0; - int unitPos = 0; - - void PopulateItemList() - { - buildPos = 0; unitPos = 0; - - items.Clear(); - - foreach (Item i in techTree.BuildableItems) - { - Sprite sprite; - if (!sprites.TryGetValue(i.tag, out sprite)) continue; - - items.Add(new SidebarItem(sprite, i, i.IsStructure ? buildPos : unitPos)); - - if (i.IsStructure) - buildPos += spriteHeight; - else - unitPos += spriteHeight; - } - - foreach (string g in groups) selectedItems[g] = null; - } - - void Paint() - { - foreach (SidebarItem i in items) - i.Paint(spriteRenderer, region.Location); - - Fill(region.Size.Y + region.Location.Y, new float2(region.Location.X, buildPos + region.Location.Y)); - Fill(region.Size.Y + region.Location.Y, new float2(region.Location.X + spriteWidth, unitPos + region.Location.Y)); - - spriteRenderer.Flush(); - - foreach (var kvp in selectedItems) - { - if (kvp.Value != null) - { - clockRenderer.DrawSprite(clockAnimations[kvp.Key].Image, region.Location.ToFloat2() + kvp.Value.location, 0); - clockAnimations[kvp.Key].Tick(1); - } - } - - clockRenderer.Flush(); - } - - public SidebarItem GetItem(float2 point) - { - foreach (SidebarItem i in items) - if (i.Clicked(point)) - return i; - - return null; - } - - void MouseHandler(MouseInput mi) - { - if (mi.Button == MouseButtons.Left && mi.Event == MouseInputEvent.Down) - { - var point = mi.Location.ToFloat2(); - var item = GetItem(point); - if (item != null) - { - string group = itemGroups[item.techTreeItem.tag]; - if (selectedItems[group] == null) - { - selectedItems[group] = item; - Build(item); - } - } - } - else if( mi.Button == MouseButtons.Right && mi.Event == MouseInputEvent.Down ) - { - var point = mi.Location.ToFloat2(); - var item = GetItem(point); - if( item != null ) - { - string group = itemGroups[ item.techTreeItem.tag ]; - selectedItems[ group ] = null; - } - } - } - } -} + Game.controller.AddOrder(Order.BuildUnit(Game.LocalPlayer, item.techTreeItem.tag.ToLowerInvariant())); + } + + void LoadSprites( string category, string group ) + { + foreach( var u in Rules.AllRules.GetSection( category ) ) + { + var unit = Rules.UnitInfo[ u.Key ]; + + if( unit.TechLevel != -1 ) + sprites.Add( unit.Name, SpriteSheetBuilder.LoadSprite( unit.Name + "icon", ".shp" ) ); + itemGroups.Add( unit.Name, group ); + } + } + + void DrawSprite(Sprite s, ref float2 p) + { + spriteRenderer.DrawSprite(s, p, 0); + p.Y += spriteHeight; + } + + void Fill(float height, float2 p) + { + while (p.Y < height) + DrawSprite(blank, ref p); + } + + int buildPos = 0; + int unitPos = 0; + + void PopulateItemList() + { + buildPos = 0; unitPos = 0; + + items.Clear(); + + foreach (Item i in techTree.BuildableItems) + { + Sprite sprite; + if (!sprites.TryGetValue(i.tag, out sprite)) continue; + + items.Add(new SidebarItem(sprite, i, i.IsStructure ? buildPos : unitPos)); + + if (i.IsStructure) + buildPos += spriteHeight; + else + unitPos += spriteHeight; + } + + foreach( string g in groups ) Game.LocalPlayer.CancelProduction( g ); + } + + void Paint() + { + foreach( SidebarItem i in items ) + { + var group = itemGroups[ i.techTreeItem.tag ]; + var producing = Game.LocalPlayer.Producing( group ); + if( producing != null && producing.Item == i.techTreeItem.tag ) + { + clockAnimations[ group ].Tick(); + clockRenderer.DrawSprite( clockAnimations[ group ].Image, region.Location.ToFloat2() + i.location, 0 ); + } + i.Paint( spriteRenderer, region.Location ); + } + + Fill(region.Size.Y + region.Location.Y, new float2(region.Location.X, buildPos + region.Location.Y)); + Fill(region.Size.Y + region.Location.Y, new float2(region.Location.X + spriteWidth, unitPos + region.Location.Y)); + + spriteRenderer.Flush(); + clockRenderer.Flush(); + } + + public SidebarItem GetItem(float2 point) + { + foreach (SidebarItem i in items) + if (i.Clicked(point)) + return i; + + return null; + } + + void MouseHandler(MouseInput mi) + { + if (mi.Button == MouseButtons.Left && mi.Event == MouseInputEvent.Down) + { + var point = mi.Location.ToFloat2(); + var item = GetItem(point); + if (item != null) + { + string group = itemGroups[item.techTreeItem.tag]; + if (Game.LocalPlayer.Producing(group) == null) + { + Game.LocalPlayer.BeginProduction( group, new ProductionItem( item.techTreeItem.tag, 25, 0 ) ); + Build(item); + } + } + } + else if( mi.Button == MouseButtons.Right && mi.Event == MouseInputEvent.Down ) + { + var point = mi.Location.ToFloat2(); + var item = GetItem(point); + if( item != null ) + { + string group = itemGroups[ item.techTreeItem.tag ]; + Game.LocalPlayer.CancelProduction( group ); + } + } + } + } +}