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
This commit is contained in:
Bob
2009-10-20 17:16:45 +13:00
parent a59265a661
commit 6dec94d00e
12 changed files with 564 additions and 156 deletions

View File

@@ -10,65 +10,94 @@ namespace OpenRa.FileFormats
public class IniFile public class IniFile
{ {
Dictionary<string, IniSection> sections = new Dictionary<string, IniSection>(); Dictionary<string, IniSection> sections = new Dictionary<string, IniSection>();
IniSection currentSection;
public IniFile( Stream s ) 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 ); StreamReader reader = new StreamReader( s );
IniSection currentSection = null;
while( !reader.EndOfStream ) while( !reader.EndOfStream )
{ {
string line = reader.ReadLine(); string line = reader.ReadLine();
if (line.Length == 0) continue; if( line.Length == 0 ) continue;
switch (line[0]) switch( line[ 0 ] )
{ {
case ';': break; case ';': break;
case '[': ProcessSection(line); break; case '[': currentSection = ProcessSection( line ); break;
default: ProcessEntry(line); break; default: ProcessEntry( line, currentSection ); break;
} }
} }
} }
Regex sectionPattern = new Regex( @"^\[([^]]*)\]" ); Regex sectionPattern = new Regex( @"^\[([^]]*)\]" );
Regex entryPattern = new Regex( @"([^=;]+)=([^;]*)" );
bool ProcessSection( string line ) IniSection ProcessSection( string line )
{ {
Match m = sectionPattern.Match( line ); Match m = sectionPattern.Match( line );
if( m == null || !m.Success ) if( m == null || !m.Success )
return false; return null;
string sectionName = m.Groups[ 1 ].Value.ToLowerInvariant();
string sectionName = m.Groups[ 1 ].Value; IniSection ret;
currentSection = new IniSection( sectionName ); if( !sections.TryGetValue( sectionName, out ret ) )
sections.Add( sectionName, currentSection ); sections.Add( sectionName, ret = new IniSection( sectionName ) );
return ret;
return true;
} }
bool ProcessEntry( string line ) bool ProcessEntry( string line, IniSection currentSection )
{ {
int comment = line.IndexOf(';'); int comment = line.IndexOf( ';' );
if (comment >= 0) if( comment >= 0 )
line = line.Substring(0, comment); line = line.Substring( 0, comment );
int eq = line.IndexOf('='); line = line.Trim();
if (eq < 0) if( line.Length == 0 )
return false; return false;
if (currentSection == null) var key = line;
throw new InvalidOperationException("No current INI section"); 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), if( currentSection == null )
line.Substring(eq + 1, line.Length - eq - 1)); throw new InvalidOperationException( "No current INI section" );
if( !currentSection.Contains( key ) )
currentSection.Add( key, value );
return true; return true;
} }
public IniSection GetSection( string s ) public IniSection GetSection( string s )
{
return GetSection( s, false );
}
public IniSection GetSection( string s, bool allowFail )
{ {
IniSection section; IniSection section;
sections.TryGetValue( s, out section ); if( sections.TryGetValue( s.ToLowerInvariant(), out section ) )
return section; return section;
if( allowFail )
return new IniSection( s );
throw new InvalidOperationException( "Section does not exist in map or rules: " + s );
} }
public IEnumerable<IniSection> Sections { get { return sections.Values; } } public IEnumerable<IniSection> Sections { get { return sections.Values; } }
@@ -89,6 +118,11 @@ namespace OpenRa.FileFormats
values[key] = value; values[key] = value;
} }
public bool Contains( string key )
{
return values.ContainsKey( key );
}
public string GetValue( string key, string defaultValue ) public string GetValue( string key, string defaultValue )
{ {
string s; string s;

View File

@@ -117,6 +117,10 @@ namespace OpenRa.Game
traits.Add(new Traits.RenderBuildingOre(this)); traits.Add(new Traits.RenderBuildingOre(this));
break; break;
case "fcom":
traits.Add( new Traits.Building( this ) );
traits.Add( new Traits.RenderBuilding( this ) );
break;
default: default:
throw new NotImplementedException( "Actor traits for " + name ); throw new NotImplementedException( "Actor traits for " + name );
} }

View File

@@ -34,12 +34,13 @@ namespace OpenRa.Game
public Game(string mapName, Renderer renderer, int2 clientSize) public Game(string mapName, Renderer renderer, int2 clientSize)
{ {
Rules.LoadRules(); Rules.LoadRules( mapName );
for( int i = 0 ; i < 8 ; i++ ) for( int i = 0 ; i < 8 ; i++ )
players.Add(i, new Player(i, string.Format("Multi{0}", i), Race.Soviet)); 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")); FileSystem.Mount(new Package(map.Theater + ".mix"));
viewport = new Viewport(clientSize, map.Size, renderer); viewport = new Viewport(clientSize, map.Size, renderer);
@@ -51,6 +52,9 @@ namespace OpenRa.Game
foreach( TreeReference treeReference in map.Trees ) foreach( TreeReference treeReference in map.Trees )
world.Add( new Actor( treeReference, treeCache, map.Offset ) ); world.Add( new Actor( treeReference, treeCache, map.Offset ) );
LoadMapBuildings( mapFile );
LoadMapUnits( mapFile );
LocalPlayerBuildings = new BuildingInfluenceMap(world, LocalPlayer); LocalPlayerBuildings = new BuildingInfluenceMap(world, LocalPlayer);
pathFinder = new PathFinder(map, terrain.tileSet, LocalPlayerBuildings); pathFinder = new PathFinder(map, terrain.tileSet, LocalPlayerBuildings);
@@ -66,6 +70,28 @@ namespace OpenRa.Game
PlaySound("intro.aud", false); 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<string, ISoundSource> sounds; readonly Cache<string, ISoundSource> sounds;
ISoundSource LoadSound(string filename) ISoundSource LoadSound(string filename)

View File

@@ -12,15 +12,15 @@ namespace OpenRa.Game.GameRules
{ {
readonly Dictionary<string, T> infos = new Dictionary<string, T>(); readonly Dictionary<string, T> infos = new Dictionary<string, T>();
public InfoLoader(IniFile rules, params Pair<string, Func<string,T>>[] srcs) public InfoLoader(params Pair<string, Func<string,T>>[] srcs)
{ {
foreach (var src in 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 name = s.Key.ToLowerInvariant();
var t = src.Second(name.ToLowerInvariant()); var t = src.Second(name);
FieldLoader.Load(t, rules.GetSection(name)); FieldLoader.Load(t, Rules.AllRules.GetSection(name));
infos[name.ToLowerInvariant()] = t; infos[name] = t;
} }
} }
@@ -28,5 +28,10 @@ namespace OpenRa.Game.GameRules
{ {
get { return infos[name.ToLowerInvariant()]; } get { return infos[name.ToLowerInvariant()]; }
} }
public IEnumerator<KeyValuePair<string, T>> GetEnumerator()
{
return infos.GetEnumerator();
}
} }
} }

View File

@@ -9,29 +9,35 @@ namespace OpenRa.Game
{ {
static class Rules static class Rules
{ {
public static IniFile AllRules;
public static InfoLoader<UnitInfo> UnitInfo; public static InfoLoader<UnitInfo> UnitInfo;
public static InfoLoader<WeaponInfo> WeaponInfo; public static InfoLoader<WeaponInfo> WeaponInfo;
public static InfoLoader<WarheadInfo> WarheadInfo; public static InfoLoader<WarheadInfo> WarheadInfo;
public static InfoLoader<ProjectileInfo> ProjectileInfo; public static InfoLoader<ProjectileInfo> ProjectileInfo;
public static Footprint Footprint; public static Footprint Footprint;
// TODO: load rules from the map, where appropriate. public static void LoadRules( string mapFileName )
public static void LoadRules()
{ {
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<UnitInfo>(rulesIni, UnitInfo = new InfoLoader<UnitInfo>(
Pair.New<string,Func<string,UnitInfo>>( "buildings.txt", s => new UnitInfo.BuildingInfo(s)), Pair.New<string,Func<string,UnitInfo>>( "BuildingTypes", s => new UnitInfo.BuildingInfo(s)),
Pair.New<string, Func<string,UnitInfo>>("infantry.txt", s => new UnitInfo.InfantryInfo(s)), Pair.New<string,Func<string,UnitInfo>>( "InfantryTypes", s => new UnitInfo.InfantryInfo(s)),
Pair.New<string,Func<string,UnitInfo>>( "vehicles.txt", s => new UnitInfo.VehicleInfo(s))); Pair.New<string,Func<string,UnitInfo>>( "VehicleTypes", s => new UnitInfo.VehicleInfo(s)),
Pair.New<string,Func<string,UnitInfo>>( "ShipTypes", s => new UnitInfo.VehicleInfo(s)),
Pair.New<string,Func<string,UnitInfo>>( "PlaneTypes", s => new UnitInfo.VehicleInfo(s)));
WeaponInfo = new InfoLoader<WeaponInfo>(rulesIni, WeaponInfo = new InfoLoader<WeaponInfo>(
Pair.New<string,Func<string,WeaponInfo>>("weapons.txt", _ => new WeaponInfo())); Pair.New<string,Func<string,WeaponInfo>>("WeaponTypes", _ => new WeaponInfo()));
WarheadInfo = new InfoLoader<WarheadInfo>(rulesIni, WarheadInfo = new InfoLoader<WarheadInfo>(
Pair.New<string,Func<string,WarheadInfo>>("warheads.txt", _ => new WarheadInfo())); Pair.New<string,Func<string,WarheadInfo>>("WarheadTypes", _ => new WarheadInfo()));
ProjectileInfo = new InfoLoader<ProjectileInfo>(rulesIni, ProjectileInfo = new InfoLoader<ProjectileInfo>(
Pair.New<string, Func<string, ProjectileInfo>>("projectiles.txt", _ => new ProjectileInfo())); Pair.New<string, Func<string, ProjectileInfo>>("ProjectileTypes", _ => new ProjectileInfo()));
Footprint = new Footprint(FileSystem.Open("footprint.txt")); Footprint = new Footprint(FileSystem.Open("footprint.txt"));
} }

View File

@@ -72,7 +72,9 @@ namespace OpenRa.Game
string key = line.Substring(0, line.IndexOf(',')); string key = line.Substring(0, line.IndexOf(','));
int secondComma = line.IndexOf(',', line.IndexOf(',') + 1); int secondComma = line.IndexOf(',', line.IndexOf(',') + 1);
string group = line.Substring(secondComma + 1, line.Length - secondComma - 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); itemGroups.Add(key, group);
} }
} }

View File

@@ -41,7 +41,6 @@ namespace OpenRa.TechTree
static Tuple<string[],string[]> ParsePrerequisites(string prerequisites, string tag) static Tuple<string[],string[]> ParsePrerequisites(string prerequisites, string tag)
{ {
Debug.WriteLine( tag );
List<string> allied = new List<string>(prerequisites.ToLowerInvariant().Split( List<string> allied = new List<string>(prerequisites.ToLowerInvariant().Split(
new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)); new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries));

View File

@@ -1,106 +1,103 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using OpenRa.FileFormats; using OpenRa.FileFormats;
using System.Linq; using System.Linq;
using OpenRa.Game; using OpenRa.Game;
namespace OpenRa.TechTree namespace OpenRa.TechTree
{ {
public class TechTree public class TechTree
{ {
Dictionary<string, Item> objects = new Dictionary<string, Item>(); Dictionary<string, Item> objects = new Dictionary<string, Item>();
public ICollection<string> built = new List<string>(); public ICollection<string> built = new List<string>();
Race currentRace = Race.None; Race currentRace = Race.None;
public Race CurrentRace public Race CurrentRace
{ {
get { return currentRace; } get { return currentRace; }
set set
{ {
currentRace = value; currentRace = value;
CheckAll(); CheckAll();
} }
} }
public TechTree() public TechTree()
{ {
LoadRules(); LoadRules();
CheckAll(); CheckAll();
} }
IEnumerable<Tuple<string, string, bool>> Lines(string filename, bool param) IEnumerable<Tuple<string, string, bool>> Lines(string filename, bool param)
{ {
Regex pattern = new Regex(@"^(\w+),([\w ]+),(\w+)$"); Regex pattern = new Regex(@"^(\w+),([\w ]+),(\w+)$");
foreach (string s in File.ReadAllLines("../../../../" + filename)) foreach (string s in File.ReadAllLines("../../../../" + filename))
{ {
Match m = pattern.Match(s); Match m = pattern.Match(s);
if (m == null || !m.Success) if (m == null || !m.Success)
continue; continue;
yield return new Tuple<string, string, bool>( yield return new Tuple<string, string, bool>(
m.Groups[1].Value, m.Groups[2].Value, param); m.Groups[1].Value, m.Groups[2].Value, param);
} }
} }
void LoadRules() void LoadRules()
{ {
IEnumerable<Tuple<string, string, bool>> definitions = var allBuildings = Rules.AllRules.GetSection( "BuildingTypes" ).Select( x => x.Key.ToLowerInvariant() ).ToList();
Lines("buildings.txt", true)
.Concat( Lines( "vehicles.txt", false ) ) foreach( var unit in Rules.UnitInfo )
.Concat( Lines( "infantry.txt", false ) ); objects.Add( unit.Key, new Item( unit.Key, unit.Value, allBuildings.Contains( unit.Key ) ) );
}
foreach (Tuple<string, string, bool> 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;
public bool Build(string key, bool force) key = key.ToLowerInvariant();
{ Item b = objects[ key ];
if( string.IsNullOrEmpty( key ) ) return false; if (!force && !b.CanBuild) return false;
key = key.ToLowerInvariant(); built.Add(key);
Item b = objects[ key ]; CheckAll();
if (!force && !b.CanBuild) return false; return true;
built.Add(key); }
CheckAll();
return true; public bool Build(string key)
} {
return Build(key, false);
public bool Build(string key) }
{
return Build(key, false); public bool Unbuild(string key)
} {
key = key.ToLowerInvariant();
public bool Unbuild(string key) Item b = objects[key];
{ if (!built.Contains(key)) return false;
key = key.ToLowerInvariant(); built.Remove(key);
Item b = objects[key]; CheckAll();
if (!built.Contains(key)) return false; return true;
built.Remove(key); }
CheckAll();
return true; void CheckAll()
} {
foreach (Item unit in objects.Values)
void CheckAll() unit.CheckPrerequisites(built, currentRace);
{
foreach (Item unit in objects.Values) BuildableItemsChanged();
unit.CheckPrerequisites(built, currentRace); }
BuildableItemsChanged(); public IEnumerable<Item> BuildableItems
} {
get
public IEnumerable<Item> BuildableItems {
{ foreach (Item b in objects.Values)
get if (b.CanBuild)
{ yield return b;
foreach (Item b in objects.Values) }
if (b.CanBuild) }
yield return b;
} public event Action BuildableItemsChanged = () => { };
} }
}
public event Action BuildableItemsChanged = () => { };
}
}

View File

@@ -34,3 +34,5 @@ WEAF,Fake War Factory,building
SYRF,Fake Shipyard,building SYRF,Fake Shipyard,building
SPEF,Fake Sub Pen,building SPEF,Fake Sub Pen,building
DOMF,Fake Radar Dome,building DOMF,Fake Radar Dome,building
FCOM,Forward Command Post,building

6
campaignUnits.ini Executable file
View File

@@ -0,0 +1,6 @@
[BuildingTypes]
FCOM
[FCOM]
Description=Forward Command Post

View File

@@ -30,6 +30,13 @@
<sequences> <sequences>
<!-- forward command post (campaign specific building) -->
<unit name="fcom">
<sequence name="idle" start="0"/>
<sequence name="damaged-idle" start="1"/>
<sequence name="make" start="0"/>
</unit>
<!-- construction yard --> <!-- construction yard -->
<unit name="fact"> <unit name="fact">
<sequence name="idle" start="0"/> <sequence name="idle" start="0"/>

320
units.ini Executable file
View File

@@ -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