From 6dec94d00e842f81e6bbb03872c501e48651626a Mon Sep 17 00:00:00 2001 From: Bob Date: Tue, 20 Oct 2009 17:16:45 +1300 Subject: [PATCH] added units.ini (and campaignUnits.ini), and IniFile got support for load/merging multiple files. - Rules can now handle map-specific rules changes - units.ini replaces {buildings,units,infantry}.txt (or will replace; sidebar still uses them) - Added support for loading map-placed units/structures - try scg11eb.ini - added FCOM --- OpenRa.FileFormats/IniFile.cs | 88 +++++--- OpenRa.Game/Actor.cs | 4 + OpenRa.Game/Game.cs | 30 ++- OpenRa.Game/GameRules/InfoLoader.cs | 17 +- OpenRa.Game/GameRules/Rules.cs | 32 +-- OpenRa.Game/Sidebar.cs | 4 +- OpenRa.Game/TechTree/Item.cs | 1 - OpenRa.Game/TechTree/TechTree.cs | 209 +++++++++--------- buildings.txt | 2 + campaignUnits.ini | 6 + sequences.xml | 7 + units.ini | 320 ++++++++++++++++++++++++++++ 12 files changed, 564 insertions(+), 156 deletions(-) create mode 100755 campaignUnits.ini create mode 100755 units.ini diff --git a/OpenRa.FileFormats/IniFile.cs b/OpenRa.FileFormats/IniFile.cs index 6a9592ec88..cd77862583 100644 --- a/OpenRa.FileFormats/IniFile.cs +++ b/OpenRa.FileFormats/IniFile.cs @@ -10,65 +10,94 @@ namespace OpenRa.FileFormats public class IniFile { Dictionary sections = new Dictionary(); - IniSection currentSection; public IniFile( Stream s ) + { + Load( s ); + } + + public IniFile( params Stream[] streams ) + { + foreach( var s in streams ) + Load( s ); + } + + public void Load( Stream s ) { StreamReader reader = new StreamReader( s ); + IniSection currentSection = null; + while( !reader.EndOfStream ) { string line = reader.ReadLine(); - if (line.Length == 0) continue; + if( line.Length == 0 ) continue; - switch (line[0]) + switch( line[ 0 ] ) { - case ';': break; - case '[': ProcessSection(line); break; - default: ProcessEntry(line); break; + case ';': break; + case '[': currentSection = ProcessSection( line ); break; + default: ProcessEntry( line, currentSection ); break; } } } Regex sectionPattern = new Regex( @"^\[([^]]*)\]" ); - Regex entryPattern = new Regex( @"([^=;]+)=([^;]*)" ); - bool ProcessSection( string line ) + IniSection ProcessSection( string line ) { Match m = sectionPattern.Match( line ); if( m == null || !m.Success ) - return false; + return null; + string sectionName = m.Groups[ 1 ].Value.ToLowerInvariant(); - string sectionName = m.Groups[ 1 ].Value; - currentSection = new IniSection( sectionName ); - sections.Add( sectionName, currentSection ); - - return true; + IniSection ret; + if( !sections.TryGetValue( sectionName, out ret ) ) + sections.Add( sectionName, ret = new IniSection( sectionName ) ); + return ret; } - bool ProcessEntry( string line ) + bool ProcessEntry( string line, IniSection currentSection ) { - int comment = line.IndexOf(';'); - if (comment >= 0) - line = line.Substring(0, comment); + int comment = line.IndexOf( ';' ); + if( comment >= 0 ) + line = line.Substring( 0, comment ); - int eq = line.IndexOf('='); - if (eq < 0) - return false; + line = line.Trim(); + if( line.Length == 0 ) + return false; - if (currentSection == null) - throw new InvalidOperationException("No current INI section"); + var key = line; + var value = ""; + int eq = line.IndexOf( '=' ); + if( eq >= 0 ) + { + key = line.Substring( 0, eq ); + value = line.Substring( eq + 1, line.Length - eq - 1 ); + } - currentSection.Add(line.Substring(0, eq), - line.Substring(eq + 1, line.Length - eq - 1)); + if( currentSection == null ) + throw new InvalidOperationException( "No current INI section" ); + + if( !currentSection.Contains( key ) ) + currentSection.Add( key, value ); return true; } public IniSection GetSection( string s ) + { + return GetSection( s, false ); + } + + public IniSection GetSection( string s, bool allowFail ) { IniSection section; - sections.TryGetValue( s, out section ); - return section; + if( sections.TryGetValue( s.ToLowerInvariant(), out section ) ) + return section; + + if( allowFail ) + return new IniSection( s ); + throw new InvalidOperationException( "Section does not exist in map or rules: " + s ); } public IEnumerable Sections { get { return sections.Values; } } @@ -89,6 +118,11 @@ namespace OpenRa.FileFormats values[key] = value; } + public bool Contains( string key ) + { + return values.ContainsKey( key ); + } + public string GetValue( string key, string defaultValue ) { string s; diff --git a/OpenRa.Game/Actor.cs b/OpenRa.Game/Actor.cs index fd0432a6da..fc46fc9bf7 100755 --- a/OpenRa.Game/Actor.cs +++ b/OpenRa.Game/Actor.cs @@ -117,6 +117,10 @@ namespace OpenRa.Game traits.Add(new Traits.RenderBuildingOre(this)); break; + case "fcom": + traits.Add( new Traits.Building( this ) ); + traits.Add( new Traits.RenderBuilding( this ) ); + break; default: throw new NotImplementedException( "Actor traits for " + name ); } diff --git a/OpenRa.Game/Game.cs b/OpenRa.Game/Game.cs index 508aaa73ec..491225c691 100644 --- a/OpenRa.Game/Game.cs +++ b/OpenRa.Game/Game.cs @@ -34,12 +34,13 @@ namespace OpenRa.Game public Game(string mapName, Renderer renderer, int2 clientSize) { - Rules.LoadRules(); + Rules.LoadRules( mapName ); for( int i = 0 ; i < 8 ; i++ ) players.Add(i, new Player(i, string.Format("Multi{0}", i), Race.Soviet)); - map = new Map(new IniFile(FileSystem.Open(mapName))); + var mapFile = new IniFile( FileSystem.Open( mapName ) ); + map = new Map( mapFile ); FileSystem.Mount(new Package(map.Theater + ".mix")); viewport = new Viewport(clientSize, map.Size, renderer); @@ -51,6 +52,9 @@ namespace OpenRa.Game foreach( TreeReference treeReference in map.Trees ) world.Add( new Actor( treeReference, treeCache, map.Offset ) ); + LoadMapBuildings( mapFile ); + LoadMapUnits( mapFile ); + LocalPlayerBuildings = new BuildingInfluenceMap(world, LocalPlayer); pathFinder = new PathFinder(map, terrain.tileSet, LocalPlayerBuildings); @@ -66,6 +70,28 @@ namespace OpenRa.Game PlaySound("intro.aud", false); } + 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 - map.Offset.X, loc / 128-map.Offset.Y ), players[ 0 ] ) ); + } + } + + 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 - map.Offset.X, loc / 128 - map.Offset.Y ), players[ 0 ] ) ); + } + } + readonly Cache sounds; ISoundSource LoadSound(string filename) diff --git a/OpenRa.Game/GameRules/InfoLoader.cs b/OpenRa.Game/GameRules/InfoLoader.cs index 0634447269..7e4cf6931a 100644 --- a/OpenRa.Game/GameRules/InfoLoader.cs +++ b/OpenRa.Game/GameRules/InfoLoader.cs @@ -12,15 +12,15 @@ namespace OpenRa.Game.GameRules { readonly Dictionary infos = new Dictionary(); - public InfoLoader(IniFile rules, params Pair>[] srcs) + public InfoLoader(params Pair>[] srcs) { foreach (var src in srcs) - foreach (var s in Util.ReadAllLines(FileSystem.Open(src.First))) + foreach (var s in Rules.AllRules.GetSection(src.First)) { - var name = s.Split(',')[0]; - var t = src.Second(name.ToLowerInvariant()); - FieldLoader.Load(t, rules.GetSection(name)); - infos[name.ToLowerInvariant()] = t; + var name = s.Key.ToLowerInvariant(); + var t = src.Second(name); + FieldLoader.Load(t, Rules.AllRules.GetSection(name)); + infos[name] = t; } } @@ -28,5 +28,10 @@ namespace OpenRa.Game.GameRules { get { return infos[name.ToLowerInvariant()]; } } + + public IEnumerator> GetEnumerator() + { + return infos.GetEnumerator(); + } } } diff --git a/OpenRa.Game/GameRules/Rules.cs b/OpenRa.Game/GameRules/Rules.cs index b1df97a4c2..fce75ff64c 100755 --- a/OpenRa.Game/GameRules/Rules.cs +++ b/OpenRa.Game/GameRules/Rules.cs @@ -9,29 +9,35 @@ namespace OpenRa.Game { static class Rules { + public static IniFile AllRules; public static InfoLoader UnitInfo; public static InfoLoader WeaponInfo; public static InfoLoader WarheadInfo; public static InfoLoader ProjectileInfo; public static Footprint Footprint; - // TODO: load rules from the map, where appropriate. - public static void LoadRules() + public static void LoadRules( string mapFileName ) { - var rulesIni = new IniFile(FileSystem.Open("rules.ini")); + AllRules = new IniFile( + FileSystem.Open( mapFileName ), + FileSystem.Open( "rules.ini" ), + FileSystem.Open( "units.ini" ), + FileSystem.Open( "campaignUnits.ini" ) ); - UnitInfo = new InfoLoader(rulesIni, - Pair.New>( "buildings.txt", s => new UnitInfo.BuildingInfo(s)), - Pair.New>("infantry.txt", s => new UnitInfo.InfantryInfo(s)), - Pair.New>( "vehicles.txt", s => new UnitInfo.VehicleInfo(s))); + UnitInfo = new InfoLoader( + Pair.New>( "BuildingTypes", s => new UnitInfo.BuildingInfo(s)), + Pair.New>( "InfantryTypes", s => new UnitInfo.InfantryInfo(s)), + Pair.New>( "VehicleTypes", s => new UnitInfo.VehicleInfo(s)), + Pair.New>( "ShipTypes", s => new UnitInfo.VehicleInfo(s)), + Pair.New>( "PlaneTypes", s => new UnitInfo.VehicleInfo(s))); - WeaponInfo = new InfoLoader(rulesIni, - Pair.New>("weapons.txt", _ => new WeaponInfo())); - WarheadInfo = new InfoLoader(rulesIni, - Pair.New>("warheads.txt", _ => new WarheadInfo())); + WeaponInfo = new InfoLoader( + Pair.New>("WeaponTypes", _ => new WeaponInfo())); + WarheadInfo = new InfoLoader( + Pair.New>("WarheadTypes", _ => new WarheadInfo())); - ProjectileInfo = new InfoLoader(rulesIni, - Pair.New>("projectiles.txt", _ => new ProjectileInfo())); + ProjectileInfo = new InfoLoader( + Pair.New>("ProjectileTypes", _ => new ProjectileInfo())); Footprint = new Footprint(FileSystem.Open("footprint.txt")); } diff --git a/OpenRa.Game/Sidebar.cs b/OpenRa.Game/Sidebar.cs index 4c669f3617..a28cb86dcd 100644 --- a/OpenRa.Game/Sidebar.cs +++ b/OpenRa.Game/Sidebar.cs @@ -72,7 +72,9 @@ namespace OpenRa.Game string key = line.Substring(0, line.IndexOf(',')); int secondComma = line.IndexOf(',', line.IndexOf(',') + 1); string group = line.Substring(secondComma + 1, line.Length - secondComma - 1); - sprites.Add( key, SpriteSheetBuilder.LoadSprite( key + "icon", ".shp" ) ); + + if( Rules.UnitInfo[ key ].TechLevel != -1 ) + sprites.Add( key, SpriteSheetBuilder.LoadSprite( key + "icon", ".shp" ) ); itemGroups.Add(key, group); } } diff --git a/OpenRa.Game/TechTree/Item.cs b/OpenRa.Game/TechTree/Item.cs index 10211a8c21..0d17dc2a8a 100755 --- a/OpenRa.Game/TechTree/Item.cs +++ b/OpenRa.Game/TechTree/Item.cs @@ -41,7 +41,6 @@ namespace OpenRa.TechTree static Tuple ParsePrerequisites(string prerequisites, string tag) { - Debug.WriteLine( tag ); List allied = new List(prerequisites.ToLowerInvariant().Split( new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)); diff --git a/OpenRa.Game/TechTree/TechTree.cs b/OpenRa.Game/TechTree/TechTree.cs index d044210212..d83e852650 100644 --- a/OpenRa.Game/TechTree/TechTree.cs +++ b/OpenRa.Game/TechTree/TechTree.cs @@ -1,106 +1,103 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; -using System.Text.RegularExpressions; -using OpenRa.FileFormats; -using System.Linq; -using OpenRa.Game; - -namespace OpenRa.TechTree -{ - public class TechTree - { - Dictionary objects = new Dictionary(); - public ICollection built = new List(); - - Race currentRace = Race.None; - - public Race CurrentRace - { - get { return currentRace; } - set - { - currentRace = value; - CheckAll(); - } - } - - public TechTree() - { - LoadRules(); - CheckAll(); - } - - IEnumerable> Lines(string filename, bool param) - { - Regex pattern = new Regex(@"^(\w+),([\w ]+),(\w+)$"); - foreach (string s in File.ReadAllLines("../../../../" + filename)) - { - Match m = pattern.Match(s); - if (m == null || !m.Success) - continue; - - yield return new Tuple( - m.Groups[1].Value, m.Groups[2].Value, param); - } - } - - void LoadRules() - { - IEnumerable> definitions = - Lines("buildings.txt", true) - .Concat( Lines( "vehicles.txt", false ) ) - .Concat( Lines( "infantry.txt", false ) ); - - foreach (Tuple p in definitions) - objects.Add(p.a.ToLowerInvariant(), new Item(p.a.ToLowerInvariant(), Rules.UnitInfo[p.a.ToLowerInvariant()], p.c)); - } - - public bool Build(string key, bool force) - { - if( string.IsNullOrEmpty( key ) ) return false; - key = key.ToLowerInvariant(); - Item b = objects[ key ]; - if (!force && !b.CanBuild) return false; - built.Add(key); - CheckAll(); - return true; - } - - public bool Build(string key) - { - return Build(key, false); - } - - public bool Unbuild(string key) - { - key = key.ToLowerInvariant(); - Item b = objects[key]; - if (!built.Contains(key)) return false; - built.Remove(key); - CheckAll(); - return true; - } - - void CheckAll() - { - foreach (Item unit in objects.Values) - unit.CheckPrerequisites(built, currentRace); - - BuildableItemsChanged(); - } - - public IEnumerable BuildableItems - { - get - { - foreach (Item b in objects.Values) - if (b.CanBuild) - yield return b; - } - } - - public event Action BuildableItemsChanged = () => { }; - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Text.RegularExpressions; +using OpenRa.FileFormats; +using System.Linq; +using OpenRa.Game; + +namespace OpenRa.TechTree +{ + public class TechTree + { + Dictionary objects = new Dictionary(); + public ICollection built = new List(); + + Race currentRace = Race.None; + + public Race CurrentRace + { + get { return currentRace; } + set + { + currentRace = value; + CheckAll(); + } + } + + public TechTree() + { + LoadRules(); + CheckAll(); + } + + IEnumerable> Lines(string filename, bool param) + { + Regex pattern = new Regex(@"^(\w+),([\w ]+),(\w+)$"); + foreach (string s in File.ReadAllLines("../../../../" + filename)) + { + Match m = pattern.Match(s); + if (m == null || !m.Success) + continue; + + yield return new Tuple( + m.Groups[1].Value, m.Groups[2].Value, param); + } + } + + void LoadRules() + { + var allBuildings = Rules.AllRules.GetSection( "BuildingTypes" ).Select( x => x.Key.ToLowerInvariant() ).ToList(); + + foreach( var unit in Rules.UnitInfo ) + objects.Add( unit.Key, new Item( unit.Key, unit.Value, allBuildings.Contains( unit.Key ) ) ); + } + + public bool Build(string key, bool force) + { + if( string.IsNullOrEmpty( key ) ) return false; + key = key.ToLowerInvariant(); + Item b = objects[ key ]; + if (!force && !b.CanBuild) return false; + built.Add(key); + CheckAll(); + return true; + } + + public bool Build(string key) + { + return Build(key, false); + } + + public bool Unbuild(string key) + { + key = key.ToLowerInvariant(); + Item b = objects[key]; + if (!built.Contains(key)) return false; + built.Remove(key); + CheckAll(); + return true; + } + + void CheckAll() + { + foreach (Item unit in objects.Values) + unit.CheckPrerequisites(built, currentRace); + + BuildableItemsChanged(); + } + + public IEnumerable BuildableItems + { + get + { + foreach (Item b in objects.Values) + if (b.CanBuild) + yield return b; + } + } + + public event Action BuildableItemsChanged = () => { }; + } +} diff --git a/buildings.txt b/buildings.txt index e8bb5d2310..297e7ad124 100644 --- a/buildings.txt +++ b/buildings.txt @@ -34,3 +34,5 @@ WEAF,Fake War Factory,building SYRF,Fake Shipyard,building SPEF,Fake Sub Pen,building DOMF,Fake Radar Dome,building +FCOM,Forward Command Post,building + diff --git a/campaignUnits.ini b/campaignUnits.ini new file mode 100755 index 0000000000..c7f46c8d7d --- /dev/null +++ b/campaignUnits.ini @@ -0,0 +1,6 @@ +[BuildingTypes] +FCOM + +[FCOM] +Description=Forward Command Post + diff --git a/sequences.xml b/sequences.xml index d40748c56f..2c98143aca 100644 --- a/sequences.xml +++ b/sequences.xml @@ -30,6 +30,13 @@ + + + + + + + diff --git a/units.ini b/units.ini new file mode 100755 index 0000000000..f3cd2365e9 --- /dev/null +++ b/units.ini @@ -0,0 +1,320 @@ +[VehicleTypes] +V2RL +1TNK +2TNK +3TNK +4TNK +MRJ +MGG +ARTY +HARV +MCV +JEEP +APC +MNLY + +[V2RL] +Description=V2 Rocket +[1TNK] +Description=Light Tank +[2TNK] +Description=Medium Tank +[3TNK] +Description=Heavy Tank +[4TNK] +Description=Mammoth Tank +[MRJ] +Description=Radar Jammer +[MGG] +Description=Mobile Gap Generator +[ARTY] +Description=Artillery +[HARV] +Description=Ore Truck +[MCV] +Description=Mobile Construction Vehicle +[JEEP] +Description=Ranger +[APC] +Description=Armored Personnel Carrier +[MNLY] +Description=Minelayer + + + + + +[ShipTypes] +SS +DD +CA +LST +PT + +[SS] +Description=Submarine +[DD] +Description=Destroyer +[CA] +Description=Cruiser +[LST] +Description=Transport +[PT] +Description=Gunboat + + + + + +[PlaneTypes] +MIG +YAK +TRAN +HELI +HIND +; TODO: +; U2 (spyplane) +; BADR (paratrooper/paradrop plane) + +[MIG] +Description=Mig Attack Plane +[YAK] +Description=Yak Attack Plane +[TRAN] +Description=Transport Helicopter +[HELI] +Description=Longbow +[HIND] +Description=Hind + + + + + +[BuildingTypes] +IRON +ATEK +PDOX +WEAP +SYRD +SPEN +PBOX +HBOX +TSLA +GUN +AGUN +FTUR +FACT +PROC +SILO +HPAD +DOME +GAP +SAM +MSLO +AFLD +POWR +APWR +STEK +BARR +TENT +KENN +FIX +SBAG +BRIK +FENC +FACF +WEAF +SYRF +SPEF +DOMF +; TODO? : campaign-specific stuff - FCOM, civilian buildings, etc + +[IRON] +Description=Iron Curtain +[ATEK] +Description=Allied Tech Center +[PDOX] +Description=Chronosphere +[WEAP] +Description=War Factory +[SYRD] +Description=Shipyard +[SPEN] +Description=Sub Pen +[PBOX] +Description=Pillbox +[HBOX] +Description=Camo Pillbox +[TSLA] +Description=Tesla Coil +[GUN] +Description=Turret +[AGUN] +Description=AA Gun +[FTUR] +Description=Flame Turret +[FACT] +Description=Construction Yard +[PROC] +Description=Ore Refinery +[SILO] +Description=Silo +[HPAD] +Description=Helipad +[DOME] +Description=Radar Dome +[GAP] +Description=Gap Generator +[SAM] +Description=SAM Site +[MSLO] +Description=Missile Silo +[AFLD] +Description=Airstrip +[POWR] +Description=Power Plant +[APWR] +Description=Advanced Power Plant +[STEK] +Description=Soviet Tech Center +[BARR] +Description=Soviet Barracks +[TENT] +Description=Allied Barracks +[KENN] +Description=Kennel +[FIX] +Description=Service Depot +[SBAG] +Description=Sandbags +[BRIK] +Description=Concrete Wall +[FENC] +Description=Wire Fence +[FACF] +Description=Fake Construction Yard +[WEAF] +Description=Fake War Factory +[SYRF] +Description=Fake Shipyard +[SPEF] +Description=Fake Sub Pen +[DOMF] +Description=Fake Radar Dome + + + + + +[InfantryTypes] +DOG +E1 +E2 +E3 +E4 +E6 +SPY +THF +E7 +MEDI + +[DOG] +Description=Attack Dog +[E1] +Description=Rifle Infantry +[E2] +Description=Grenadier +[E3] +Description=Rocket Soldier +[E4] +Description=Flamethrower +[E6] +Description=Engineer +[SPY] +Description=Spy +[THF] +Description=Thief +[E7] +Description=Tanya +[MEDI] +Description=Medic + + + + + +[WeaponTypes] +Colt45 +ZSU-23 +Vulcan +Maverick +Camera +FireballLauncher +Flamer +Sniper +ChainGun +Pistol +M1Carbine +Dragon +Hellfire +Grenade +75mm +90mm +105mm +120mm +TurretGun +MammothTusk +155mm +M60mg +Napalm +TeslaZap +Nike +RedEye +8Inch +Stinger +TorpTube +2Inch +DepthCharge +ParaBomb +DogJaw +Heal +SCUD + + + + + +[ProjectileTypes] +Invisible +LeapDog +Cannon +Ack +Torpedo +FROG +HeatSeeker +LaserGuided +AAMissile +Lobbed +Catapult +Bomblet +Ballistic +Parachute +GPSSatellite +NukeUp +NukeDown +Fireball + + + + + +[WarheadTypes] +SA +HE +AP +Fire +HollowPoint +Super +Organic +Nuke