123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773 |
- //
- // Copyright 2020 Electronic Arts Inc.
- //
- // The Command & Conquer Map Editor and corresponding source code is free
- // software: you can redistribute it and/or modify it under the terms of
- // the GNU General Public License as published by the Free Software Foundation,
- // either version 3 of the License, or (at your option) any later version.
- // The Command & Conquer Map Editor and corresponding source code is distributed
- // in the hope that it will be useful, but with permitted additional restrictions
- // under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
- // distributed with this program. You should have received a copy of the
- // GNU General Public License along with permitted additional restrictions
- // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
- using MobiusEditor.Interface;
- using MobiusEditor.Model;
- using MobiusEditor.Utility;
- using Newtonsoft.Json;
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Drawing;
- using System.IO;
- using System.Linq;
- using System.Text;
- using System.Windows.Forms;
- namespace MobiusEditor.RedAlert
- {
- class GamePlugin : IGamePlugin
- {
- private static readonly IEnumerable<string> movieTypes = new string[]
- {
- "x",
- "AAGUN",
- "AFTRMATH",
- "AIRFIELD",
- "ALLIEND",
- "ALLY1",
- "ALLY10",
- "ALLY10B",
- "ALLY11",
- "ALLY12",
- "ALLY14",
- "ALLY2",
- "ALLY4",
- "ALLY5",
- "ALLY6",
- "ALLY8",
- "ALLY9",
- "ALLYMORF",
- "ANTEND",
- "ANTINTRO",
- "APCESCPE",
- "ASSESS",
- "AVERTED",
- "BATTLE",
- "BEACHEAD",
- "BINOC",
- "BMAP",
- "BOMBRUN",
- "BRDGTILT",
- "COUNTDWN",
- "CRONFAIL",
- "CRONTEST",
- "DESTROYR",
- "DOUBLE",
- "DPTHCHRG",
- "DUD",
- "ELEVATOR",
- "ENGLISH",
- "EXECUTE",
- "FLARE",
- "FROZEN",
- "GRVESTNE",
- "LANDING",
- "MASASSLT",
- "MCV",
- "MCVBRDGE",
- "MCV_LAND",
- "MIG",
- "MONTPASS",
- "MOVINGIN",
- "MTNKFACT",
- "NUKESTOK",
- "OILDRUM",
- "ONTHPRWL",
- "OVERRUN",
- "PERISCOP",
- "PROLOG",
- "RADRRAID",
- "REDINTRO",
- "RETALIATION_ALLIED1",
- "RETALIATION_ALLIED10",
- "RETALIATION_ALLIED2",
- "RETALIATION_ALLIED3",
- "RETALIATION_ALLIED4",
- "RETALIATION_ALLIED5",
- "RETALIATION_ALLIED6",
- "RETALIATION_ALLIED7",
- "RETALIATION_ALLIED8",
- "RETALIATION_ALLIED9",
- "RETALIATION_ANTS",
- "RETALIATION_SOVIET1",
- "RETALIATION_SOVIET10",
- "RETALIATION_SOVIET2",
- "RETALIATION_SOVIET3",
- "RETALIATION_SOVIET4",
- "RETALIATION_SOVIET5",
- "RETALIATION_SOVIET6",
- "RETALIATION_SOVIET7",
- "RETALIATION_SOVIET8",
- "RETALIATION_SOVIET9",
- "RETALIATION_WINA",
- "RETALIATION_WINS",
- "SEARCH",
- "SFROZEN",
- "SHIPSINK",
- "SHIPYARD", // MISSING
- "SHORBOM1",
- "SHORBOM2",
- "SHORBOMB",
- "SITDUCK",
- "SIZZLE", //MISSING
- "SIZZLE2", //MISSING
- "SLNTSRVC",
- "SNOWBASE",
- "SNOWBOMB",
- "SNSTRAFE",
- "SOVBATL",
- "SOVCEMET",
- "SOVFINAL",
- "SOVIET1",
- "SOVIET10",
- "SOVIET11",
- "SOVIET12",
- "SOVIET13",
- "SOVIET14",
- "SOVIET2",
- "SOVIET3",
- "SOVIET4",
- "SOVIET5",
- "SOVIET6",
- "SOVIET7",
- "SOVIET8",
- "SOVIET9",
- "SOVMCV",
- "SOVTSTAR",
- "SPOTTER",
- "SPY",
- "STRAFE",
- "TAKE_OFF",
- "TANYA1",
- "TANYA2",
- "TESLA",
- "TOOFAR",
- "TRINITY",
- "V2ROCKET",
- };
- private static readonly IEnumerable<ITechnoType> technoTypes;
- public GameType GameType => GameType.RedAlert;
- public Map Map { get; }
- public Image MapImage { get; private set; }
- public bool Dirty { get; set; }
- private INISectionCollection extraSections;
- static GamePlugin()
- {
- technoTypes = InfantryTypes.GetTypes().Cast<ITechnoType>().Concat(UnitTypes.GetTypes().Cast<ITechnoType>());
- }
- public GamePlugin(bool mapImage)
- {
- var playerWaypoints = Enumerable.Range(0, 8).Select(i => new Waypoint(string.Format("P{0}", i), WaypointFlag.PlayerStart));
- var generalWaypoints = Enumerable.Range(8, 90).Select(i => new Waypoint(i.ToString()));
- var specialWaypoints = new Waypoint[] { new Waypoint("Home"), new Waypoint("Reinf."), new Waypoint("Special") };
- var waypoints = playerWaypoints.Concat(generalWaypoints).Concat(specialWaypoints);
- var basicSection = new BasicSection();
- basicSection.SetDefault();
- var houseTypes = HouseTypes.GetTypes();
- basicSection.Player = houseTypes.First().Name;
- Map = new Map(basicSection, null, Constants.MaxSize, typeof(House),
- houseTypes, TheaterTypes.GetTypes(), TemplateTypes.GetTypes(), TerrainTypes.GetTypes(),
- OverlayTypes.GetTypes(), SmudgeTypes.GetTypes(), EventTypes.GetTypes(), ActionTypes.GetTypes(),
- MissionTypes.GetTypes(), DirectionTypes.GetTypes(), InfantryTypes.GetTypes(), UnitTypes.GetTypes(),
- BuildingTypes.GetTypes(), TeamMissionTypes.GetTypes(), waypoints, movieTypes)
- {
- TiberiumOrGoldValue = 35,
- GemValue = 110
- };
- Map.BasicSection.PropertyChanged += BasicSection_PropertyChanged;
- Map.MapSection.PropertyChanged += MapSection_PropertyChanged;
- if (mapImage)
- {
- MapImage = new Bitmap(Map.Metrics.Width * Globals.TileWidth, Map.Metrics.Height * Globals.TileHeight);
- }
- }
- public GamePlugin()
- : this(true)
- {
- }
- public void New(string theater)
- {
- Map.Theater = Map.TheaterTypes.Where(t => t.Equals(theater)).FirstOrDefault() ?? TheaterTypes.Temperate;
- Map.TopLeft = new Point(1, 1);
- Map.Size = Map.Metrics.Size - new Size(2, 2);
- UpdateBasePlayerHouse();
- Dirty = true;
- }
- public IEnumerable<string> Load(string path, FileType fileType)
- {
- var errors = new List<string>();
- switch (fileType)
- {
- case FileType.INI:
- case FileType.BIN:
- {
- var ini = new INI();
- using (var reader = new StreamReader(path))
- {
- ini.Parse(reader);
- }
- errors.AddRange(LoadINI(ini));
- }
- break;
- case FileType.MEG:
- case FileType.PGM:
- {
- using (var megafile = new Megafile(path))
- {
- var mprFile = megafile.Where(p => Path.GetExtension(p).ToLower() == ".mpr").FirstOrDefault();
- if (mprFile != null)
- {
- var ini = new INI();
- using (var reader = new StreamReader(megafile.Open(mprFile)))
- {
- ini.Parse(reader);
- }
- errors.AddRange(LoadINI(ini));
- }
- }
- }
- break;
- default:
- throw new NotSupportedException();
- }
- return errors;
- }
- private IEnumerable<string> LoadINI(INI ini)
- {
- var errors = new List<string>();
- Map.BeginUpdate();
- var basicSection = ini.Sections.Extract("Basic");
- if (basicSection != null)
- {
- INI.ParseSection(new MapContext(Map, true), basicSection, Map.BasicSection);
- }
- Map.BasicSection.Player = Map.HouseTypes.Where(t => t.Equals(Map.BasicSection.Player)).FirstOrDefault()?.Name ?? Map.HouseTypes.First().Name;
- Map.BasicSection.BasePlayer = HouseTypes.GetBasePlayer(Map.BasicSection.Player);
- var mapSection = ini.Sections.Extract("Map");
- if (mapSection != null)
- {
- INI.ParseSection(new MapContext(Map, true), mapSection, Map.MapSection);
- }
- var steamSection = ini.Sections.Extract("Steam");
- if (steamSection != null)
- {
- INI.ParseSection(new MapContext(Map, true), steamSection, Map.SteamSection);
- }
- string indexToType(IList<string> list, string index)
- {
- return (int.TryParse(index, out int result) && (result >= 0) && (result < list.Count)) ? list[result] : list.First();
- }
- var teamTypesSection = ini.Sections.Extract("TeamTypes");
- if (teamTypesSection != null)
- {
- foreach (var (Key, Value) in teamTypesSection)
- {
- try
- {
- var teamType = new TeamType { Name = Key };
- var tokens = Value.Split(',').ToList();
- teamType.House = Map.HouseTypes.Where(t => t.Equals(sbyte.Parse(tokens[0]))).FirstOrDefault(); tokens.RemoveAt(0);
- var flags = int.Parse(tokens[0]); tokens.RemoveAt(0);
- teamType.IsRoundAbout = (flags & 0x01) != 0;
- teamType.IsSuicide = (flags & 0x02) != 0;
- teamType.IsAutocreate = (flags & 0x04) != 0;
- teamType.IsPrebuilt = (flags & 0x08) != 0;
- teamType.IsReinforcable = (flags & 0x10) != 0;
- teamType.RecruitPriority = int.Parse(tokens[0]); tokens.RemoveAt(0);
- teamType.InitNum = byte.Parse(tokens[0]); tokens.RemoveAt(0);
- teamType.MaxAllowed = byte.Parse(tokens[0]); tokens.RemoveAt(0);
- teamType.Origin = int.Parse(tokens[0]) + 1; tokens.RemoveAt(0);
- teamType.Trigger = tokens[0]; tokens.RemoveAt(0);
- var numClasses = int.Parse(tokens[0]); tokens.RemoveAt(0);
- for (int i = 0; i < Math.Min(Globals.MaxTeamClasses, numClasses); ++i)
- {
- var classTokens = tokens[0].Split(':'); tokens.RemoveAt(0);
- if (classTokens.Length == 2)
- {
- var type = technoTypes.Where(t => t.Equals(classTokens[0])).FirstOrDefault();
- var count = byte.Parse(classTokens[1]);
- if (type != null)
- {
- teamType.Classes.Add(new TeamTypeClass { Type = type, Count = count });
- }
- else
- {
- errors.Add(string.Format("Team '{0}' references unknown class '{1}'", Key, classTokens[0]));
- }
- }
- else
- {
- errors.Add(string.Format("Team '{0}' has wrong number of tokens for class index {1} (expecting 2)", Key, i));
- }
- }
- var numMissions = int.Parse(tokens[0]); tokens.RemoveAt(0);
- for (int i = 0; i < Math.Min(Globals.MaxTeamMissions, numMissions); ++i)
- {
- var missionTokens = tokens[0].Split(':'); tokens.RemoveAt(0);
- if (missionTokens.Length == 2)
- {
- teamType.Missions.Add(new TeamTypeMission { Mission = indexToType(Map.TeamMissionTypes, missionTokens[0]), Argument = int.Parse(missionTokens[1]) });
- }
- else
- {
- errors.Add(string.Format("Team '{0}' has wrong number of tokens for mission index {1} (expecting 2)", Key, i));
- }
- }
- Map.TeamTypes.Add(teamType);
- }
- catch (ArgumentOutOfRangeException) { }
- }
- }
- var triggersSection = ini.Sections.Extract("Trigs");
- if (triggersSection != null)
- {
- foreach (var (Key, Value) in triggersSection)
- {
- var tokens = Value.Split(',');
- if (tokens.Length == 18)
- {
- var trigger = new Trigger { Name = Key };
- trigger.PersistantType = (TriggerPersistantType)int.Parse(tokens[0]);
- trigger.House = Map.HouseTypes.Where(t => t.Equals(sbyte.Parse(tokens[1]))).FirstOrDefault()?.Name ?? "None";
- trigger.EventControl = (TriggerMultiStyleType)int.Parse(tokens[2]);
- trigger.Event1.EventType = indexToType(Map.EventTypes, tokens[4]);
- trigger.Event1.Team = tokens[5];
- trigger.Event1.Data = long.Parse(tokens[6]);
- trigger.Event2.EventType = indexToType(Map.EventTypes, tokens[7]);
- trigger.Event2.Team = tokens[8];
- trigger.Event2.Data = long.Parse(tokens[9]);
- trigger.Action1.ActionType = indexToType(Map.ActionTypes, tokens[10]);
- trigger.Action1.Team = tokens[11];
- trigger.Action1.Trigger = tokens[12];
- trigger.Action1.Data = long.Parse(tokens[13]);
- trigger.Action2.ActionType = indexToType(Map.ActionTypes, tokens[14]);
- trigger.Action2.Team = tokens[15];
- trigger.Action2.Trigger = tokens[16];
- trigger.Action2.Data = long.Parse(tokens[17]);
- // Fix up data caused by union usage in the legacy game
- Action<TriggerEvent> fixEvent = (TriggerEvent e) =>
- {
- switch (e.EventType)
- {
- case EventTypes.TEVENT_THIEVED:
- case EventTypes.TEVENT_PLAYER_ENTERED:
- case EventTypes.TEVENT_CROSS_HORIZONTAL:
- case EventTypes.TEVENT_CROSS_VERTICAL:
- case EventTypes.TEVENT_ENTERS_ZONE:
- case EventTypes.TEVENT_HOUSE_DISCOVERED:
- case EventTypes.TEVENT_BUILDINGS_DESTROYED:
- case EventTypes.TEVENT_UNITS_DESTROYED:
- case EventTypes.TEVENT_ALL_DESTROYED:
- case EventTypes.TEVENT_LOW_POWER:
- case EventTypes.TEVENT_BUILDING_EXISTS:
- case EventTypes.TEVENT_BUILD:
- case EventTypes.TEVENT_BUILD_UNIT:
- case EventTypes.TEVENT_BUILD_INFANTRY:
- case EventTypes.TEVENT_BUILD_AIRCRAFT:
- e.Data &= 0xFF;
- break;
- default:
- break;
- }
- };
- Action<TriggerAction> fixAction = (TriggerAction a) =>
- {
- switch (a.ActionType)
- {
- case ActionTypes.TACTION_1_SPECIAL:
- case ActionTypes.TACTION_FULL_SPECIAL:
- case ActionTypes.TACTION_FIRE_SALE:
- case ActionTypes.TACTION_WIN:
- case ActionTypes.TACTION_LOSE:
- case ActionTypes.TACTION_ALL_HUNT:
- case ActionTypes.TACTION_BEGIN_PRODUCTION:
- case ActionTypes.TACTION_AUTOCREATE:
- case ActionTypes.TACTION_BASE_BUILDING:
- case ActionTypes.TACTION_CREATE_TEAM:
- case ActionTypes.TACTION_DESTROY_TEAM:
- case ActionTypes.TACTION_REINFORCEMENTS:
- case ActionTypes.TACTION_FORCE_TRIGGER:
- case ActionTypes.TACTION_DESTROY_TRIGGER:
- case ActionTypes.TACTION_DZ:
- case ActionTypes.TACTION_REVEAL_SOME:
- case ActionTypes.TACTION_REVEAL_ZONE:
- case ActionTypes.TACTION_PLAY_MUSIC:
- case ActionTypes.TACTION_PLAY_MOVIE:
- case ActionTypes.TACTION_PLAY_SOUND:
- case ActionTypes.TACTION_PLAY_SPEECH:
- case ActionTypes.TACTION_PREFERRED_TARGET:
- a.Data &= 0xFF;
- break;
- case ActionTypes.TACTION_TEXT_TRIGGER:
- a.Data = Math.Max(1, Math.Min(209, a.Data));
- break;
- default:
- break;
- }
- };
- fixEvent(trigger.Event1);
- fixEvent(trigger.Event2);
- fixAction(trigger.Action1);
- fixAction(trigger.Action2);
- Map.Triggers.Add(trigger);
- }
- else
- {
- errors.Add(string.Format("Trigger '{0}' has too few tokens (expecting 18)", Key));
- }
- }
- }
- var mapPackSection = ini.Sections.Extract("MapPack");
- if (mapPackSection != null)
- {
- Map.Templates.Clear();
- var data = DecompressLCWSection(mapPackSection, 3);
- using (var reader = new BinaryReader(new MemoryStream(data)))
- {
- for (var y = 0; y < Map.Metrics.Height; ++y)
- {
- for (var x = 0; x < Map.Metrics.Width; ++x)
- {
- var typeValue = reader.ReadUInt16();
- var templateType = Map.TemplateTypes.Where(t => t.Equals(typeValue)).FirstOrDefault();
- if ((templateType != null) && !templateType.Theaters.Contains(Map.Theater))
- {
- templateType = null;
- }
- Map.Templates[x, y] = (templateType != null) ? new Template { Type = templateType } : null;
- }
- }
- for (var y = 0; y < Map.Metrics.Height; ++y)
- {
- for (var x = 0; x < Map.Metrics.Width; ++x)
- {
- var iconValue = reader.ReadByte();
- var template = Map.Templates[x, y];
- if (template != null)
- {
- if ((template.Type != TemplateTypes.Clear) && (iconValue >= template.Type.NumIcons))
- {
- Map.Templates[x, y] = null;
- }
- else
- {
- template.Icon = iconValue;
- }
- }
- }
- }
- }
- }
- var terrainSection = ini.Sections.Extract("Terrain");
- if (terrainSection != null)
- {
- foreach (var (Key, Value) in terrainSection)
- {
- var cell = int.Parse(Key);
- var name = Value.Split(',')[0];
- var terrainType = Map.TerrainTypes.Where(t => t.Equals(name)).FirstOrDefault();
- if (terrainType != null)
- {
- if (!Map.Technos.Add(cell, new Terrain
- {
- Type = terrainType,
- Icon = terrainType.IsTransformable ? 22 : 0,
- Trigger = Trigger.None
- }))
- {
- var techno = Map.Technos[cell];
- if (techno is Building building)
- {
- errors.Add(string.Format("Terrain '{0}' overlaps structure '{1}' in cell {2}, skipping", name, building.Type.Name, cell));
- }
- else if (techno is Overlay overlay)
- {
- errors.Add(string.Format("Terrain '{0}' overlaps overlay '{1}' in cell {2}, skipping", name, overlay.Type.Name, cell));
- }
- else if (techno is Terrain terrain)
- {
- errors.Add(string.Format("Terrain '{0}' overlaps terrain '{1}' in cell {2}, skipping", name, terrain.Type.Name, cell));
- }
- else if (techno is InfantryGroup infantry)
- {
- errors.Add(string.Format("Terrain '{0}' overlaps infantry in cell {1}, skipping", name, cell));
- }
- else if (techno is Unit unit)
- {
- errors.Add(string.Format("Terrain '{0}' overlaps unit '{1}' in cell {2}, skipping", name, unit.Type.Name, cell));
- }
- else
- {
- errors.Add(string.Format("Terrain '{0}' overlaps unknown techno in cell {1}, skipping", name, cell));
- }
- }
- }
- else
- {
- errors.Add(string.Format("Terrain '{0}' references unknown terrain", name));
- }
- }
- }
- var overlayPackSection = ini.Sections.Extract("OverlayPack");
- if (overlayPackSection != null)
- {
- Map.Overlay.Clear();
- var data = DecompressLCWSection(overlayPackSection, 1);
- using (var reader = new BinaryReader(new MemoryStream(data)))
- {
- for (var i = 0; i < Map.Metrics.Length; ++i)
- {
- var overlayId = reader.ReadSByte();
- if (overlayId >= 0)
- {
- var overlayType = Map.OverlayTypes.Where(t => t.Equals(overlayId)).FirstOrDefault();
- if (overlayType != null)
- {
- Map.Overlay[i] = new Overlay { Type = overlayType, Icon = 0 };
- }
- else
- {
- errors.Add(string.Format("Overlay ID {0} references unknown overlay", overlayId));
- }
- }
- }
- }
- }
- var smudgeSection = ini.Sections.Extract("Smudge");
- if (smudgeSection != null)
- {
- foreach (var (Key, Value) in smudgeSection)
- {
- var cell = int.Parse(Key);
- var tokens = Value.Split(',');
- if (tokens.Length == 3)
- {
- var smudgeType = Map.SmudgeTypes.Where(t => t.Equals(tokens[0])).FirstOrDefault();
- if (smudgeType != null)
- {
- if ((smudgeType.Flag & SmudgeTypeFlag.Bib) == SmudgeTypeFlag.None)
- {
- Map.Smudge[cell] = new Smudge
- {
- Type = smudgeType,
- Icon = 0,
- Data = int.Parse(tokens[2])
- };
- }
- else
- {
- errors.Add(string.Format("Smudge '{0}' is a bib, skipped", tokens[0]));
- }
- }
- else
- {
- errors.Add(string.Format("Smudge '{0}' references unknown smudge", tokens[0]));
- }
- }
- }
- }
- var unitsSection = ini.Sections.Extract("Units");
- if (unitsSection != null)
- {
- foreach (var (_, Value) in unitsSection)
- {
- var tokens = Value.Split(',');
- if (tokens.Length == 7)
- {
- var unitType = Map.UnitTypes.Where(t => t.IsUnit && t.Equals(tokens[1])).FirstOrDefault();
- if (unitType != null)
- {
- var direction = (byte)((int.Parse(tokens[4]) + 0x08) & ~0x0F);
- var cell = int.Parse(tokens[3]);
- if (!Map.Technos.Add(cell, new Unit()
- {
- Type = unitType,
- House = Map.HouseTypes.Where(t => t.Equals(tokens[0])).FirstOrDefault(),
- Strength = int.Parse(tokens[2]),
- Direction = Map.DirectionTypes.Where(d => d.Equals(direction)).FirstOrDefault(),
- Mission = tokens[5],
- Trigger = tokens[6]
- }))
- {
- var techno = Map.Technos[cell];
- if (techno is Building building)
- {
- errors.Add(string.Format("Unit '{0}' overlaps structure '{1}' in cell {2}, skipping", tokens[1], building.Type.Name, cell));
- }
- else if (techno is Overlay overlay)
- {
- errors.Add(string.Format("Unit '{0}' overlaps overlay '{1}' in cell {2}, skipping", tokens[1], overlay.Type.Name, cell));
- }
- else if (techno is Terrain terrain)
- {
- errors.Add(string.Format("Unit '{0}' overlaps terrain '{1}' in cell {2}, skipping", tokens[1], terrain.Type.Name, cell));
- }
- else if (techno is InfantryGroup infantry)
- {
- errors.Add(string.Format("Unit '{0}' overlaps infantry in cell {1}, skipping", tokens[1], cell));
- }
- else if (techno is Unit unit)
- {
- errors.Add(string.Format("Unit '{0}' overlaps unit '{1}' in cell {2}, skipping", tokens[1], unit.Type.Name, cell));
- }
- else
- {
- errors.Add(string.Format("Unit '{0}' overlaps unknown techno in cell {1}, skipping", tokens[1], cell));
- }
- }
- }
- else
- {
- errors.Add(string.Format("Unit '{0}' references unknown unit", tokens[1]));
- }
- }
- else
- {
- errors.Add(string.Format("Unit '{0}' has wrong number of tokens (expecting 7)", tokens[1]));
- }
- }
- }
- var aircraftSections = ini.Sections.Extract("Aircraft");
- if (aircraftSections != null)
- {
- foreach (var (_, Value) in aircraftSections)
- {
- var tokens = Value.Split(',');
- if (tokens.Length == 6)
- {
- var aircraftType = Map.UnitTypes.Where(t => t.IsAircraft && t.Equals(tokens[1])).FirstOrDefault();
- if (aircraftType != null)
- {
- var direction = (byte)((int.Parse(tokens[4]) + 0x08) & ~0x0F);
- var cell = int.Parse(tokens[3]);
- if (!Map.Technos.Add(cell, new Unit()
- {
- Type = aircraftType,
- House = Map.HouseTypes.Where(t => t.Equals(tokens[0])).FirstOrDefault(),
- Strength = int.Parse(tokens[2]),
- Direction = Map.DirectionTypes.Where(d => d.Equals(direction)).FirstOrDefault(),
- Mission = tokens[5]
- }))
- {
- var techno = Map.Technos[cell];
- if (techno is Building building)
- {
- errors.Add(string.Format("Aircraft '{0}' overlaps structure '{1}' in cell {2}, skipping", tokens[1], building.Type.Name, cell));
- }
- else if (techno is Overlay overlay)
- {
- errors.Add(string.Format("Aircraft '{0}' overlaps overlay '{1}' in cell {2}, skipping", tokens[1], overlay.Type.Name, cell));
- }
- else if (techno is Terrain terrain)
- {
- errors.Add(string.Format("Aircraft '{0}' overlaps terrain '{1}' in cell {2}, skipping", tokens[1], terrain.Type.Name, cell));
- }
- else if (techno is InfantryGroup infantry)
- {
- errors.Add(string.Format("Aircraft '{0}' overlaps infantry in cell {1}, skipping", tokens[1], cell));
- }
- else if (techno is Unit unit)
- {
- errors.Add(string.Format("Aircraft '{0}' overlaps unit '{1}' in cell {2}, skipping", tokens[1], unit.Type.Name, cell));
- }
- else
- {
- errors.Add(string.Format("Aircraft '{0}' overlaps unknown techno in cell {1}, skipping", tokens[1], cell));
- }
- }
- }
- else
- {
- errors.Add(string.Format("Aircraft '{0}' references unknown aircraft", tokens[1]));
- }
- }
- else
- {
- errors.Add(string.Format("Aircraft '{0}' has wrong number of tokens (expecting 6)", tokens[1]));
- }
- }
- }
- var shipsSection = ini.Sections.Extract("Ships");
- if (shipsSection != null)
- {
- foreach (var (_, Value) in shipsSection)
- {
- var tokens = Value.Split(',');
- if (tokens.Length == 7)
- {
- var vesselType = Map.UnitTypes.Where(t => t.IsVessel && t.Equals(tokens[1])).FirstOrDefault();
- if (vesselType != null)
- {
- var direction = (byte)((int.Parse(tokens[4]) + 0x08) & ~0x0F);
- var cell = int.Parse(tokens[3]);
- if (!Map.Technos.Add(cell, new Unit()
- {
- Type = vesselType,
- House = Map.HouseTypes.Where(t => t.Equals(tokens[0])).FirstOrDefault(),
- Strength = int.Parse(tokens[2]),
- Direction = Map.DirectionTypes.Where(d => d.Equals(direction)).FirstOrDefault(),
- Mission = Map.MissionTypes.Where(t => t.Equals(tokens[5])).FirstOrDefault(),
- Trigger = tokens[6]
- }))
- {
- var techno = Map.Technos[cell];
- if (techno is Building building)
- {
- errors.Add(string.Format("Ship '{0}' overlaps structure '{1}' in cell {2}, skipping", tokens[1], building.Type.Name, cell));
- }
- else if (techno is Overlay overlay)
- {
- errors.Add(string.Format("Ship '{0}' overlaps overlay '{1}' in cell {2}, skipping", tokens[1], overlay.Type.Name, cell));
- }
- else if (techno is Terrain terrain)
- {
- errors.Add(string.Format("Ship '{0}' overlaps terrain '{1}' in cell {2}, skipping", tokens[1], terrain.Type.Name, cell));
- }
- else if (techno is InfantryGroup infantry)
- {
- errors.Add(string.Format("Ship '{0}' overlaps infantry in cell {1}, skipping", tokens[1], cell));
- }
- else if (techno is Unit unit)
- {
- errors.Add(string.Format("Ship '{0}' overlaps unit '{1}' in cell {2}, skipping", tokens[1], unit.Type.Name, cell));
- }
- else
- {
- errors.Add(string.Format("Ship '{0}' overlaps unknown techno in cell {1}, skipping", tokens[1], cell));
- }
- }
- }
- else
- {
- errors.Add(string.Format("Ship '{0}' references unknown ship", tokens[1]));
- }
- }
- else
- {
- errors.Add(string.Format("Ship '{0}' has wrong number of tokens (expecting 7)", tokens[1]));
- }
- }
- }
- var infantrySections = ini.Sections.Extract("Infantry");
- if (infantrySections != null)
- {
- foreach (var (_, Value) in infantrySections)
- {
- var tokens = Value.Split(',');
- if (tokens.Length == 8)
- {
- var infantryType = Map.InfantryTypes.Where(t => t.Equals(tokens[1])).FirstOrDefault();
- if (infantryType != null)
- {
- var cell = int.Parse(tokens[3]);
- var infantryGroup = Map.Technos[cell] as InfantryGroup;
- if ((infantryGroup == null) && (Map.Technos[cell] == null))
- {
- infantryGroup = new InfantryGroup();
- Map.Technos.Add(cell, infantryGroup);
- }
- if (infantryGroup != null)
- {
- var stoppingPos = int.Parse(tokens[4]);
- if (stoppingPos < Globals.NumInfantryStops)
- {
- var direction = (byte)((int.Parse(tokens[6]) + 0x08) & ~0x0F);
- if (infantryGroup.Infantry[stoppingPos] == null)
- {
- infantryGroup.Infantry[stoppingPos] = new Infantry(infantryGroup)
- {
- Type = infantryType,
- House = Map.HouseTypes.Where(t => t.Equals(tokens[0])).FirstOrDefault(),
- Strength = int.Parse(tokens[2]),
- Direction = Map.DirectionTypes.Where(d => d.Equals(direction)).FirstOrDefault(),
- Mission = Map.MissionTypes.Where(t => t.Equals(tokens[5])).FirstOrDefault(),
- Trigger = tokens[7]
- };
- }
- else
- {
- errors.Add(string.Format("Infantry '{0}' overlaps another infantry at position {1} in cell {2}, skipping", tokens[1], stoppingPos, cell));
- }
- }
- else
- {
- errors.Add(string.Format("Infantry '{0}' has invalid position {1} in cell {2}, skipping", tokens[1], stoppingPos, cell));
- }
- }
- else
- {
- var techno = Map.Technos[cell];
- if (techno is Building building)
- {
- errors.Add(string.Format("Infantry '{0}' overlaps structure '{1}' in cell {2}, skipping", tokens[1], building.Type.Name, cell));
- }
- else if (techno is Overlay overlay)
- {
- errors.Add(string.Format("Infantry '{0}' overlaps overlay '{1}' in cell {2}, skipping", tokens[1], overlay.Type.Name, cell));
- }
- else if (techno is Terrain terrain)
- {
- errors.Add(string.Format("Infantry '{0}' overlaps terrain '{1}' in cell {2}, skipping", tokens[1], terrain.Type.Name, cell));
- }
- else if (techno is Unit unit)
- {
- errors.Add(string.Format("Infantry '{0}' overlaps unit '{1}' in cell {2}, skipping", tokens[1], unit.Type.Name, cell));
- }
- else
- {
- errors.Add(string.Format("Infantry '{0}' overlaps unknown techno in cell {1}, skipping", tokens[1], cell));
- }
- }
- }
- else
- {
- errors.Add(string.Format("Infantry '{0}' references unknown infantry", tokens[1]));
- }
- }
- else
- {
- errors.Add(string.Format("Infantry '{0}' has wrong number of tokens (expecting 8)", tokens[1]));
- }
- }
- }
- var structuresSection = ini.Sections.Extract("Structures");
- if (structuresSection != null)
- {
- foreach (var (_, Value) in structuresSection)
- {
- var tokens = Value.Split(',');
- if (tokens.Length >= 6)
- {
- var buildingType = Map.BuildingTypes.Where(t => t.Equals(tokens[1])).FirstOrDefault();
- if (buildingType != null)
- {
- var direction = (byte)((int.Parse(tokens[4]) + 0x08) & ~0x0F);
- bool sellable = (tokens.Length >= 7) ? (int.Parse(tokens[6]) != 0) : false;
- bool rebuild = (tokens.Length >= 8) ? (int.Parse(tokens[7]) != 0) : false;
- var cell = int.Parse(tokens[3]);
- if (!Map.Buildings.Add(cell, new Building()
- {
- Type = buildingType,
- House = Map.HouseTypes.Where(t => t.Equals(tokens[0])).FirstOrDefault(),
- Strength = int.Parse(tokens[2]),
- Direction = Map.DirectionTypes.Where(d => d.Equals(direction)).FirstOrDefault(),
- Trigger = tokens[5],
- Sellable = sellable,
- Rebuild = rebuild
- }))
- {
- var techno = Map.Technos[cell];
- if (techno is Building building)
- {
- errors.Add(string.Format("Structure '{0}' overlaps structure '{1}' in cell {2}, skipping", tokens[1], building.Type.Name, cell));
- }
- else if (techno is Overlay overlay)
- {
- errors.Add(string.Format("Structure '{0}' overlaps overlay '{1}' in cell {2}, skipping", tokens[1], overlay.Type.Name, cell));
- }
- else if (techno is Terrain terrain)
- {
- errors.Add(string.Format("Structure '{0}' overlaps terrain '{1}' in cell {2}, skipping", tokens[1], terrain.Type.Name, cell));
- }
- else if (techno is InfantryGroup infantry)
- {
- errors.Add(string.Format("Structure '{0}' overlaps infantry in cell {1}, skipping", tokens[1], cell));
- }
- else if (techno is Unit unit)
- {
- errors.Add(string.Format("Structure '{0}' overlaps unit '{1}' in cell {2}, skipping", tokens[1], unit.Type.Name, cell));
- }
- else
- {
- errors.Add(string.Format("Structure '{0}' overlaps unknown techno in cell {1}, skipping", tokens[1], cell));
- }
- }
- }
- else
- {
- errors.Add(string.Format("Structure '{0}' references unknown structure", tokens[1]));
- }
- }
- else
- {
- errors.Add(string.Format("Structure '{0}' has wrong number of tokens (expecting 6)", tokens[1]));
- }
- }
- }
- var baseSection = ini.Sections.Extract("Base");
- if (baseSection != null)
- {
- foreach (var (Key, Value) in baseSection)
- {
- if (Key.Equals("Player", StringComparison.OrdinalIgnoreCase))
- {
- Map.BasicSection.BasePlayer = Map.HouseTypes.Where(t => t.Equals(Value)).FirstOrDefault()?.Name ?? Map.HouseTypes.First().Name;
- }
- else if (int.TryParse(Key, out int priority))
- {
- var tokens = Value.Split(',');
- if (tokens.Length == 2)
- {
- var buildingType = Map.BuildingTypes.Where(t => t.Equals(tokens[0])).FirstOrDefault();
- if (buildingType != null)
- {
- var cell = int.Parse(tokens[1]);
- Map.Metrics.GetLocation(cell, out Point location);
- if (Map.Buildings.OfType<Building>().Where(x => x.Location == location).FirstOrDefault().Occupier is Building building)
- {
- building.BasePriority = priority;
- }
- else
- {
- Map.Buildings.Add(cell, new Building()
- {
- Type = buildingType,
- Strength = 256,
- Direction = DirectionTypes.North,
- BasePriority = priority,
- IsPrebuilt = false
- });
- }
- }
- else
- {
- errors.Add(string.Format("Base priority {0} references unknown structure '{1}'", priority, tokens[0]));
- }
- }
- else
- {
- errors.Add(string.Format("Base priority {0} has wrong number of tokens (expecting 2)", priority));
- }
- }
- else if (!Key.Equals("Count", StringComparison.CurrentCultureIgnoreCase))
- {
- errors.Add(string.Format("Invalid base priority '{0}' (expecting integer)", Key));
- }
- }
- }
- var waypointsSection = ini.Sections.Extract("Waypoints");
- if (waypointsSection != null)
- {
- foreach (var (Key, Value) in waypointsSection)
- {
- if (int.TryParse(Key, out int waypoint))
- {
- if (int.TryParse(Value, out int cell))
- {
- if ((waypoint >= 0) && (waypoint < Map.Waypoints.Length))
- {
- if (Map.Metrics.Contains(cell))
- {
- Map.Waypoints[waypoint].Cell = cell;
- }
- else
- {
- Map.Waypoints[waypoint].Cell = null;
- if (cell != -1)
- {
- errors.Add(string.Format("Waypoint {0} cell value {1} out of range (expecting between {2} and {3})", waypoint, cell, 0, Map.Metrics.Length - 1));
- }
- }
- }
- else if (cell != -1)
- {
- errors.Add(string.Format("Waypoint {0} out of range (expecting between {1} and {2})", waypoint, 0, Map.Waypoints.Length - 1));
- }
- }
- else
- {
- errors.Add(string.Format("Waypoint {0} has invalid cell '{1}' (expecting integer)", waypoint, Value));
- }
- }
- else
- {
- errors.Add(string.Format("Invalid waypoint '{0}' (expecting integer)", Key));
- }
- }
- }
- var cellTriggersSection = ini.Sections.Extract("CellTriggers");
- if (cellTriggersSection != null)
- {
- foreach (var (Key, Value) in cellTriggersSection)
- {
- if (int.TryParse(Key, out int cell))
- {
- if (Map.Metrics.Contains(cell))
- {
- Map.CellTriggers[cell] = new CellTrigger
- {
- Trigger = Value
- };
- }
- else
- {
- errors.Add(string.Format("Cell trigger {0} outside map bounds", cell));
- }
- }
- else
- {
- errors.Add(string.Format("Invalid cell trigger '{0}' (expecting integer)", Key));
- }
- }
- }
- var briefingSection = ini.Sections.Extract("Briefing");
- if (briefingSection != null)
- {
- if (briefingSection.Keys.Contains("Text"))
- {
- Map.BriefingSection.Briefing = briefingSection["Text"].Replace("@", Environment.NewLine);
- }
- else
- {
- Map.BriefingSection.Briefing = string.Join(" ", briefingSection.Keys.Select(k => k.Value)).Replace("@", Environment.NewLine);
- }
- }
- foreach (var house in Map.Houses)
- {
- if (house.Type.ID < 0)
- {
- continue;
- }
- var houseSection = ini.Sections.Extract(house.Type.Name);
- if (houseSection != null)
- {
- INI.ParseSection(new MapContext(Map, true), houseSection, house);
- house.Enabled = true;
- }
- else
- {
- house.Enabled = false;
- }
- }
- string indexToName<T>(IList<T> list, string index, string defaultValue) where T : INamedType
- {
- return (int.TryParse(index, out int result) && (result >= 0) && (result < list.Count)) ? list[result].Name : defaultValue;
- }
- foreach (var teamType in Map.TeamTypes)
- {
- teamType.Trigger = indexToName(Map.Triggers, teamType.Trigger, Trigger.None);
- }
- foreach (var trigger in Map.Triggers)
- {
- trigger.Event1.Team = indexToName(Map.TeamTypes, trigger.Event1.Team, TeamType.None);
- trigger.Event2.Team = indexToName(Map.TeamTypes, trigger.Event2.Team, TeamType.None);
- trigger.Action1.Team = indexToName(Map.TeamTypes, trigger.Action1.Team, TeamType.None);
- trigger.Action1.Trigger = indexToName(Map.Triggers, trigger.Action1.Trigger, Trigger.None);
- trigger.Action2.Team = indexToName(Map.TeamTypes, trigger.Action2.Team, TeamType.None);
- trigger.Action2.Trigger = indexToName(Map.Triggers, trigger.Action2.Trigger, Trigger.None);
- }
- UpdateBasePlayerHouse();
- extraSections = ini.Sections;
- Map.EndUpdate();
- return errors;
- }
- public bool Save(string path, FileType fileType)
- {
- if (!Validate())
- {
- return false;
- }
- switch (fileType)
- {
- case FileType.INI:
- case FileType.BIN:
- {
- var mprPath = Path.ChangeExtension(path, ".mpr");
- var tgaPath = Path.ChangeExtension(path, ".tga");
- var jsonPath = Path.ChangeExtension(path, ".json");
- var ini = new INI();
- using (var mprWriter = new StreamWriter(mprPath))
- using (var tgaStream = new FileStream(tgaPath, FileMode.Create))
- using (var jsonStream = new FileStream(jsonPath, FileMode.Create))
- using (var jsonWriter = new JsonTextWriter(new StreamWriter(jsonStream)))
- {
- SaveINI(ini, fileType);
- mprWriter.Write(ini.ToString());
- SaveMapPreview(tgaStream);
- SaveJSON(jsonWriter);
- }
- }
- break;
- case FileType.MEG:
- case FileType.PGM:
- {
- using (var iniStream = new MemoryStream())
- using (var tgaStream = new MemoryStream())
- using (var jsonStream = new MemoryStream())
- using (var jsonWriter = new JsonTextWriter(new StreamWriter(jsonStream)))
- using (var megafileBuilder = new MegafileBuilder(@"", path))
- {
- var ini = new INI();
- SaveINI(ini, fileType);
- var iniWriter = new StreamWriter(iniStream);
- iniWriter.Write(ini.ToString());
- iniWriter.Flush();
- iniStream.Position = 0;
- SaveMapPreview(tgaStream);
- tgaStream.Position = 0;
- SaveJSON(jsonWriter);
- jsonWriter.Flush();
- jsonStream.Position = 0;
- var mprFile = Path.ChangeExtension(Path.GetFileName(path), ".mpr").ToUpper();
- var tgaFile = Path.ChangeExtension(Path.GetFileName(path), ".tga").ToUpper();
- var jsonFile = Path.ChangeExtension(Path.GetFileName(path), ".json").ToUpper();
- megafileBuilder.AddFile(mprFile, iniStream);
- megafileBuilder.AddFile(tgaFile, tgaStream);
- megafileBuilder.AddFile(jsonFile, jsonStream);
- megafileBuilder.Write();
- }
- }
- break;
- default:
- throw new NotSupportedException();
- }
- return true;
- }
- private void SaveINI(INI ini, FileType fileType)
- {
- if (extraSections != null)
- {
- ini.Sections.AddRange(extraSections);
- }
- INI.WriteSection(new MapContext(Map, false), ini.Sections.Add("Basic"), Map.BasicSection);
- INI.WriteSection(new MapContext(Map, false), ini.Sections.Add("Map"), Map.MapSection);
- if (fileType != FileType.PGM)
- {
- INI.WriteSection(new MapContext(Map, false), ini.Sections.Add("Steam"), Map.SteamSection);
- }
- ini["Basic"]["NewINIFormat"] = "3";
- var smudgeSection = ini.Sections.Add("SMUDGE");
- foreach (var (cell, smudge) in Map.Smudge.Where(item => (item.Value.Type.Flag & SmudgeTypeFlag.Bib) == SmudgeTypeFlag.None))
- {
- smudgeSection[cell.ToString()] = string.Format("{0},{1},{2}", smudge.Type.Name.ToUpper(), cell, smudge.Data);
- }
- var terrainSection = ini.Sections.Add("TERRAIN");
- foreach (var (location, terrain) in Map.Technos.OfType<Terrain>())
- {
- Map.Metrics.GetCell(location, out int cell);
- terrainSection[cell.ToString()] = terrain.Type.Name.ToUpper();
- }
- var cellTriggersSection = ini.Sections.Add("CellTriggers");
- foreach (var (cell, cellTrigger) in Map.CellTriggers)
- {
- cellTriggersSection[cell.ToString()] = cellTrigger.Trigger;
- }
- int nameToIndex<T>(IList<T> list, string name)
- {
- var index = list.TakeWhile(x => !x.Equals(name)).Count();
- return (index < list.Count) ? index : -1;
- }
- string nameToIndexString<T>(IList<T> list, string name) => nameToIndex(list, name).ToString();
- var teamTypesSection = ini.Sections.Add("TeamTypes");
- foreach (var teamType in Map.TeamTypes)
- {
- var classes = teamType.Classes
- .Select(c => string.Format("{0}:{1}", c.Type.Name.ToUpper(), c.Count))
- .ToArray();
- var missions = teamType.Missions
- .Select(m => string.Format("{0}:{1}", nameToIndexString(Map.TeamMissionTypes, m.Mission), m.Argument))
- .ToArray();
- int flags = 0;
- if (teamType.IsRoundAbout) flags |= 0x01;
- if (teamType.IsSuicide) flags |= 0x02;
- if (teamType.IsAutocreate) flags |= 0x04;
- if (teamType.IsPrebuilt) flags |= 0x08;
- if (teamType.IsReinforcable) flags |= 0x10;
- var tokens = new List<string>
- {
- teamType.House.ID.ToString(),
- flags.ToString(),
- teamType.RecruitPriority.ToString(),
- teamType.InitNum.ToString(),
- teamType.MaxAllowed.ToString(),
- (teamType.Origin - 1).ToString(),
- nameToIndexString(Map.Triggers, teamType.Trigger),
- classes.Length.ToString(),
- string.Join(",", classes),
- missions.Length.ToString(),
- string.Join(",", missions)
- };
- teamTypesSection[teamType.Name] = string.Join(",", tokens.Where(t => !string.IsNullOrEmpty(t)));
- }
- var infantrySection = ini.Sections.Add("INFANTRY");
- var infantryIndex = 0;
- foreach (var (location, infantryGroup) in Map.Technos.OfType<InfantryGroup>())
- {
- for (var i = 0; i < infantryGroup.Infantry.Length; ++i)
- {
- var infantry = infantryGroup.Infantry[i];
- if (infantry == null)
- {
- continue;
- }
- var key = infantryIndex.ToString("D3");
- infantryIndex++;
- Map.Metrics.GetCell(location, out int cell);
- infantrySection[key] = string.Format("{0},{1},{2},{3},{4},{5},{6},{7}",
- infantry.House.Name,
- infantry.Type.Name,
- infantry.Strength,
- cell,
- i,
- infantry.Mission,
- infantry.Direction.ID,
- infantry.Trigger
- );
- }
- }
- var structuresSection = ini.Sections.Add("STRUCTURES");
- var structureIndex = 0;
- foreach (var (location, building) in Map.Buildings.OfType<Building>().Where(x => x.Occupier.IsPrebuilt))
- {
- var key = structureIndex.ToString("D3");
- structureIndex++;
- Map.Metrics.GetCell(location, out int cell);
- structuresSection[key] = string.Format("{0},{1},{2},{3},{4},{5},{6},{7}",
- building.House.Name,
- building.Type.Name,
- building.Strength,
- cell,
- building.Direction.ID,
- building.Trigger,
- building.Sellable ? 1 : 0,
- building.Rebuild ? 1 : 0
- );
- }
- var baseSection = ini.Sections.Add("Base");
- var baseBuildings = Map.Buildings.OfType<Building>().Where(x => x.Occupier.BasePriority >= 0).OrderBy(x => x.Occupier.BasePriority).ToArray();
- baseSection["Player"] = Map.BasicSection.BasePlayer;
- baseSection["Count"] = baseBuildings.Length.ToString();
- var baseIndex = 0;
- foreach (var (location, building) in baseBuildings)
- {
- var key = baseIndex.ToString("D3");
- baseIndex++;
- Map.Metrics.GetCell(location, out int cell);
- baseSection[key] = string.Format("{0},{1}",
- building.Type.Name.ToUpper(),
- cell
- );
- }
- var unitsSection = ini.Sections.Add("UNITS");
- var unitIndex = 0;
- foreach (var (location, unit) in Map.Technos.OfType<Unit>().Where(u => u.Occupier.Type.IsUnit))
- {
- var key = unitIndex.ToString("D3");
- unitIndex++;
- Map.Metrics.GetCell(location, out int cell);
- unitsSection[key] = string.Format("{0},{1},{2},{3},{4},{5},{6}",
- unit.House.Name,
- unit.Type.Name,
- unit.Strength,
- cell,
- unit.Direction.ID,
- unit.Mission,
- unit.Trigger
- );
- }
- var aircraftSection = ini.Sections.Add("AIRCRAFT");
- var aircraftIndex = 0;
- foreach (var (location, aircraft) in Map.Technos.OfType<Unit>().Where(u => u.Occupier.Type.IsAircraft))
- {
- var key = aircraftIndex.ToString("D3");
- aircraftIndex++;
- Map.Metrics.GetCell(location, out int cell);
- aircraftSection[key] = string.Format("{0},{1},{2},{3},{4},{5}",
- aircraft.House.Name,
- aircraft.Type.Name,
- aircraft.Strength,
- cell,
- aircraft.Direction.ID,
- aircraft.Mission
- );
- }
- var shipsSection = ini.Sections.Add("SHIPS");
- var shipsIndex = 0;
- foreach (var (location, ship) in Map.Technos.OfType<Unit>().Where(u => u.Occupier.Type.IsVessel))
- {
- var key = shipsIndex.ToString("D3");
- shipsIndex++;
- Map.Metrics.GetCell(location, out int cell);
- shipsSection[key] = string.Format("{0},{1},{2},{3},{4},{5},{6}",
- ship.House.Name,
- ship.Type.Name,
- ship.Strength,
- cell,
- ship.Direction.ID,
- ship.Mission,
- ship.Trigger
- );
- }
- var triggersSection = ini.Sections.Add("Trigs");
- foreach (var trigger in Map.Triggers)
- {
- if (string.IsNullOrEmpty(trigger.Name))
- {
- continue;
- }
- var action2TypeIndex = nameToIndex(Map.ActionTypes, trigger.Action2.ActionType);
- var actionControl = (action2TypeIndex > 0) ? TriggerMultiStyleType.And : TriggerMultiStyleType.Only;
- var tokens = new List<string>
- {
- ((int)trigger.PersistantType).ToString(),
- !string.IsNullOrEmpty(trigger.House) ? (Map.HouseTypes.Where(h => h.Equals(trigger.House)).FirstOrDefault()?.ID.ToString() ?? "-1") : "-1",
- ((int)trigger.EventControl).ToString(),
- ((int)actionControl).ToString(),
- nameToIndexString(Map.EventTypes, trigger.Event1.EventType),
- nameToIndexString(Map.TeamTypes, trigger.Event1.Team),
- trigger.Event1.Data.ToString(),
- nameToIndexString(Map.EventTypes, trigger.Event2.EventType),
- nameToIndexString(Map.TeamTypes, trigger.Event2.Team),
- trigger.Event2.Data.ToString(),
- nameToIndexString(Map.ActionTypes, trigger.Action1.ActionType),
- nameToIndexString(Map.TeamTypes, trigger.Action1.Team),
- nameToIndexString(Map.Triggers, trigger.Action1.Trigger),
- trigger.Action1.Data.ToString(),
- action2TypeIndex.ToString(),
- nameToIndexString(Map.TeamTypes, trigger.Action2.Team),
- nameToIndexString(Map.Triggers, trigger.Action2.Trigger),
- trigger.Action2.Data.ToString()
- };
- triggersSection[trigger.Name] = string.Join(",", tokens);
- }
- var waypointsSection = ini.Sections.Add("Waypoints");
- for (var i = 0; i < Map.Waypoints.Length; ++i)
- {
- var waypoint = Map.Waypoints[i];
- if (waypoint.Cell.HasValue)
- {
- waypointsSection[i.ToString()] = waypoint.Cell.Value.ToString();
- }
- }
- foreach (var house in Map.Houses)
- {
- if ((house.Type.ID < 0) || !house.Enabled)
- {
- continue;
- }
- INI.WriteSection(new MapContext(Map, true), ini.Sections.Add(house.Type.Name), house);
- }
- ini.Sections.Remove("Briefing");
- if (!string.IsNullOrEmpty(Map.BriefingSection.Briefing))
- {
- var briefingSection = ini.Sections.Add("Briefing");
- briefingSection["Text"] = Map.BriefingSection.Briefing.Replace(Environment.NewLine, "@");
- }
- using (var stream = new MemoryStream())
- {
- using (var writer = new BinaryWriter(stream))
- {
- for (var y = 0; y < Map.Metrics.Height; ++y)
- {
- for (var x = 0; x < Map.Metrics.Width; ++x)
- {
- var template = Map.Templates[x, y];
- if (template != null)
- {
- writer.Write(template.Type.ID);
- }
- else
- {
- writer.Write(ushort.MaxValue);
- }
- }
- }
- for (var y = 0; y < Map.Metrics.Height; ++y)
- {
- for (var x = 0; x < Map.Metrics.Width; ++x)
- {
- var template = Map.Templates[x, y];
- if (template != null)
- {
- writer.Write((byte)template.Icon);
- }
- else
- {
- writer.Write(byte.MaxValue);
- }
- }
- }
- }
- ini.Sections.Remove("MapPack");
- CompressLCWSection(ini.Sections.Add("MapPack"), stream.ToArray());
- }
- using (var stream = new MemoryStream())
- {
- using (var writer = new BinaryWriter(stream))
- {
- for (var i = 0; i < Map.Metrics.Length; ++i)
- {
- var overlay = Map.Overlay[i];
- if (overlay != null)
- {
- writer.Write(overlay.Type.ID);
- }
- else
- {
- writer.Write((sbyte)-1);
- }
- }
- }
- ini.Sections.Remove("OverlayPack");
- CompressLCWSection(ini.Sections.Add("OverlayPack"), stream.ToArray());
- }
- }
- private void SaveMapPreview(Stream stream)
- {
- Map.GenerateMapPreview().Save(stream);
- }
- private void SaveJSON(JsonTextWriter writer)
- {
- writer.WriteStartObject();
- writer.WritePropertyName("MapTileX");
- writer.WriteValue(Map.MapSection.X);
- writer.WritePropertyName("MapTileY");
- writer.WriteValue(Map.MapSection.Y);
- writer.WritePropertyName("MapTileWidth");
- writer.WriteValue(Map.MapSection.Width);
- writer.WritePropertyName("MapTileHeight");
- writer.WriteValue(Map.MapSection.Height);
- writer.WritePropertyName("Theater");
- writer.WriteValue(Map.MapSection.Theater.Name.ToUpper());
- writer.WritePropertyName("Waypoints");
- writer.WriteStartArray();
- foreach (var waypoint in Map.Waypoints.Where(w => (w.Flag == WaypointFlag.PlayerStart) && w.Cell.HasValue))
- {
- writer.WriteValue(waypoint.Cell.Value);
- }
- writer.WriteEndArray();
- writer.WriteEndObject();
- }
- private bool Validate()
- {
- StringBuilder sb = new StringBuilder("Error(s) during map validation:");
- bool ok = true;
- int numAircraft = Map.Technos.OfType<Unit>().Where(u => u.Occupier.Type.IsAircraft).Count();
- int numBuildings = Map.Buildings.OfType<Building>().Where(x => x.Occupier.IsPrebuilt).Count();
- int numInfantry = Map.Technos.OfType<InfantryGroup>().Sum(item => item.Occupier.Infantry.Count(i => i != null));
- int numTerrain = Map.Technos.OfType<Terrain>().Count();
- int numUnits = Map.Technos.OfType<Unit>().Where(u => u.Occupier.Type.IsUnit).Count();
- int numVessels = Map.Technos.OfType<Unit>().Where(u => u.Occupier.Type.IsVessel).Count();
- int numWaypoints = Map.Waypoints.Count(w => w.Cell.HasValue);
- if (numAircraft > Constants.MaxAircraft)
- {
- sb.Append(Environment.NewLine + string.Format("Maximum number of aircraft exceeded ({0} > {1})", numAircraft, Constants.MaxAircraft));
- ok = false;
- }
- if (numBuildings > Constants.MaxBuildings)
- {
- sb.Append(Environment.NewLine + string.Format("Maximum number of structures exceeded ({0} > {1})", numBuildings, Constants.MaxBuildings));
- ok = false;
- }
- if (numInfantry > Constants.MaxInfantry)
- {
- sb.Append(Environment.NewLine + string.Format("Maximum number of infantry exceeded ({0} > {1})", numInfantry, Constants.MaxInfantry));
- ok = false;
- }
- if (numTerrain > Constants.MaxTerrain)
- {
- sb.Append(Environment.NewLine + string.Format("Maximum number of terrain objects exceeded ({0} > {1})", numTerrain, Constants.MaxTerrain));
- ok = false;
- }
- if (numUnits > Constants.MaxUnits)
- {
- sb.Append(Environment.NewLine + string.Format("Maximum number of units exceeded ({0} > {1})", numUnits, Constants.MaxUnits));
- ok = false;
- }
- if (numVessels > Constants.MaxVessels)
- {
- sb.Append(Environment.NewLine + string.Format("Maximum number of ships exceeded ({0} > {1})", numVessels, Constants.MaxVessels));
- ok = false;
- }
- if (Map.TeamTypes.Count > Constants.MaxTeams)
- {
- sb.Append(Environment.NewLine + string.Format("Maximum number of team types exceeded ({0} > {1})", Map.TeamTypes.Count, Constants.MaxTeams));
- ok = false;
- }
- if (Map.Triggers.Count > Constants.MaxTriggers)
- {
- sb.Append(Environment.NewLine + string.Format("Maximum number of triggers exceeded ({0} > {1})", Map.Triggers.Count, Constants.MaxTriggers));
- ok = false;
- }
- if (!Map.BasicSection.SoloMission && (numWaypoints < 2))
- {
- sb.Append(Environment.NewLine + "Skirmish/Multiplayer maps need at least 2 waypoints for player starting locations.");
- ok = false;
- }
- var homeWaypoint = Map.Waypoints.Where(w => w.Equals("Home")).FirstOrDefault();
- if (Map.BasicSection.SoloMission && !homeWaypoint.Cell.HasValue)
- {
- sb.Append(Environment.NewLine + string.Format("Single-player maps need the Home waypoint to be placed.", Map.Triggers.Count, Constants.MaxTriggers));
- ok = false;
- }
- if (!ok)
- {
- MessageBox.Show(sb.ToString(), "Validation Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
- }
- return ok;
- }
- private void BasicSection_PropertyChanged(object sender, PropertyChangedEventArgs e)
- {
- switch (e.PropertyName)
- {
- case "BasePlayer":
- {
- UpdateBasePlayerHouse();
- }
- break;
- }
- }
- private void MapSection_PropertyChanged(object sender, PropertyChangedEventArgs e)
- {
- switch (e.PropertyName)
- {
- case "Theater":
- {
- Map.InitTheater(GameType);
- }
- break;
- }
- }
- private void UpdateBasePlayerHouse()
- {
- var basePlayer = Map.HouseTypes.Where(h => h.Equals(Map.BasicSection.BasePlayer)).FirstOrDefault() ?? Map.HouseTypes.First();
- foreach (var (_, building) in Map.Buildings.OfType<Building>())
- {
- if (!building.IsPrebuilt)
- {
- building.House = basePlayer;
- }
- }
- }
- private void CompressLCWSection(INISection section, byte[] decompressedBytes)
- {
- using (var stream = new MemoryStream())
- using (var writer = new BinaryWriter(stream))
- {
- foreach (var decompressedChunk in decompressedBytes.Split(8192))
- {
- var compressedChunk = WWCompression.LcwCompress(decompressedChunk);
- writer.Write((ushort)compressedChunk.Length);
- writer.Write((ushort)decompressedChunk.Length);
- writer.Write(compressedChunk);
- }
- writer.Flush();
- stream.Position = 0;
- var values = Convert.ToBase64String(stream.ToArray()).Split(70).ToArray();
- for (var i = 0; i < values.Length; ++i)
- {
- section[(i + 1).ToString()] = values[i];
- }
- }
- }
- private byte[] DecompressLCWSection(INISection section, int bytesPerCell)
- {
- var sb = new StringBuilder();
- foreach (var (key, value) in section)
- {
- sb.Append(value);
- }
- var compressedBytes = Convert.FromBase64String(sb.ToString());
- int readPtr = 0;
- int writePtr = 0;
- var decompressedBytes = new byte[Map.Metrics.Width * Map.Metrics.Height * bytesPerCell];
- while ((readPtr + 4) <= compressedBytes.Length)
- {
- uint uLength;
- using (var reader = new BinaryReader(new MemoryStream(compressedBytes, readPtr, 4)))
- {
- uLength = reader.ReadUInt32();
- }
- var length = (int)(uLength & 0x0000FFFF);
- readPtr += 4;
- var dest = new byte[8192];
- var readPtr2 = readPtr;
- var decompressed = WWCompression.LcwDecompress(compressedBytes, ref readPtr2, dest, 0);
- Array.Copy(dest, 0, decompressedBytes, writePtr, decompressed);
- readPtr += length;
- writePtr += decompressed;
- }
- return decompressedBytes;
- }
- #region IDisposable Support
- private bool disposedValue = false;
- protected virtual void Dispose(bool disposing)
- {
- if (!disposedValue)
- {
- if (disposing)
- {
- MapImage?.Dispose();
- }
- disposedValue = true;
- }
- }
- public void Dispose()
- {
- Dispose(true);
- }
- #endregion
- }
- }
|