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
{
Dictionary<string, IniSection> sections = new Dictionary<string, IniSection>();
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 '[': 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)
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 );
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<IniSection> 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;

View File

@@ -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 );
}

View File

@@ -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<string, ISoundSource> sounds;
ISoundSource LoadSound(string filename)

View File

@@ -12,15 +12,15 @@ namespace OpenRa.Game.GameRules
{
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 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<KeyValuePair<string, T>> GetEnumerator()
{
return infos.GetEnumerator();
}
}
}

View File

@@ -9,29 +9,35 @@ namespace OpenRa.Game
{
static class Rules
{
public static IniFile AllRules;
public static InfoLoader<UnitInfo> UnitInfo;
public static InfoLoader<WeaponInfo> WeaponInfo;
public static InfoLoader<WarheadInfo> WarheadInfo;
public static InfoLoader<ProjectileInfo> 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<UnitInfo>(rulesIni,
Pair.New<string,Func<string,UnitInfo>>( "buildings.txt", s => new UnitInfo.BuildingInfo(s)),
Pair.New<string, Func<string,UnitInfo>>("infantry.txt", s => new UnitInfo.InfantryInfo(s)),
Pair.New<string,Func<string,UnitInfo>>( "vehicles.txt", s => new UnitInfo.VehicleInfo(s)));
UnitInfo = new InfoLoader<UnitInfo>(
Pair.New<string,Func<string,UnitInfo>>( "BuildingTypes", s => new UnitInfo.BuildingInfo(s)),
Pair.New<string,Func<string,UnitInfo>>( "InfantryTypes", s => new UnitInfo.InfantryInfo(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,
Pair.New<string,Func<string,WeaponInfo>>("weapons.txt", _ => new WeaponInfo()));
WarheadInfo = new InfoLoader<WarheadInfo>(rulesIni,
Pair.New<string,Func<string,WarheadInfo>>("warheads.txt", _ => new WarheadInfo()));
WeaponInfo = new InfoLoader<WeaponInfo>(
Pair.New<string,Func<string,WeaponInfo>>("WeaponTypes", _ => new WeaponInfo()));
WarheadInfo = new InfoLoader<WarheadInfo>(
Pair.New<string,Func<string,WarheadInfo>>("WarheadTypes", _ => new WarheadInfo()));
ProjectileInfo = new InfoLoader<ProjectileInfo>(rulesIni,
Pair.New<string, Func<string, ProjectileInfo>>("projectiles.txt", _ => new ProjectileInfo()));
ProjectileInfo = new InfoLoader<ProjectileInfo>(
Pair.New<string, Func<string, ProjectileInfo>>("ProjectileTypes", _ => new ProjectileInfo()));
Footprint = new Footprint(FileSystem.Open("footprint.txt"));
}

View File

@@ -72,6 +72,8 @@ 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);
if( Rules.UnitInfo[ key ].TechLevel != -1 )
sprites.Add( key, SpriteSheetBuilder.LoadSprite( key + "icon", ".shp" ) );
itemGroups.Add(key, group);
}

View File

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

View File

@@ -48,13 +48,10 @@ namespace OpenRa.TechTree
void LoadRules()
{
IEnumerable<Tuple<string, string, bool>> definitions =
Lines("buildings.txt", true)
.Concat( Lines( "vehicles.txt", false ) )
.Concat( Lines( "infantry.txt", false ) );
var allBuildings = Rules.AllRules.GetSection( "BuildingTypes" ).Select( x => x.Key.ToLowerInvariant() ).ToList();
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));
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)

View File

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

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>
<!-- 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 -->
<unit name="fact">
<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