GamePlugin.cs (61345B)
1 // 2 // Copyright 2020 Electronic Arts Inc. 3 // 4 // The Command & Conquer Map Editor and corresponding source code is free 5 // software: you can redistribute it and/or modify it under the terms of 6 // the GNU General Public License as published by the Free Software Foundation, 7 // either version 3 of the License, or (at your option) any later version. 8 9 // The Command & Conquer Map Editor and corresponding source code is distributed 10 // in the hope that it will be useful, but with permitted additional restrictions 11 // under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT 12 // distributed with this program. You should have received a copy of the 13 // GNU General Public License along with permitted additional restrictions 14 // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection 15 using MobiusEditor.Interface; 16 using MobiusEditor.Model; 17 using MobiusEditor.Utility; 18 using Newtonsoft.Json; 19 using System; 20 using System.Collections.Generic; 21 using System.ComponentModel; 22 using System.Drawing; 23 using System.IO; 24 using System.Linq; 25 using System.Text; 26 using System.Text.RegularExpressions; 27 using System.Windows.Forms; 28 29 namespace MobiusEditor.TiberianDawn 30 { 31 public class GamePlugin : IGamePlugin 32 { 33 private static readonly Regex MovieRegex = new Regex(@"^(.*?\\)*(.*?)\.BK2$", RegexOptions.IgnoreCase | RegexOptions.Compiled); 34 35 private static readonly IEnumerable<ITechnoType> technoTypes; 36 37 private readonly IEnumerable<string> movieTypes; 38 39 public GameType GameType => GameType.TiberianDawn; 40 41 public Map Map { get; } 42 43 public Image MapImage { get; private set; } 44 45 public bool Dirty { get; set; } 46 47 private INISectionCollection extraSections; 48 49 static GamePlugin() 50 { 51 technoTypes = InfantryTypes.GetTypes().Cast<ITechnoType>().Concat(UnitTypes.GetTypes().Cast<ITechnoType>()); 52 } 53 54 public GamePlugin(bool mapImage) 55 { 56 var playerWaypoints = Enumerable.Range(0, 8).Select(i => new Waypoint(string.Format("P{0}", i), WaypointFlag.PlayerStart)); 57 var generalWaypoints = Enumerable.Range(8, 17).Select(i => new Waypoint(i.ToString())); 58 var specialWaypoints = new Waypoint[] { new Waypoint("Flare"), new Waypoint("Home"), new Waypoint("Reinf.") }; 59 var waypoints = playerWaypoints.Concat(generalWaypoints).Concat(specialWaypoints); 60 61 var movies = new List<string>(new string[] { "x" }); 62 using (var megafile = new Megafile(Path.Combine(Globals.MegafilePath, "MOVIES_TD.MEG"))) 63 { 64 foreach (var filename in megafile) 65 { 66 var m = MovieRegex.Match(filename); 67 if (m.Success) 68 { 69 movies.Add(m.Groups[m.Groups.Count - 1].ToString()); 70 } 71 } 72 } 73 movieTypes = movies.ToArray(); 74 75 var basicSection = new BasicSection(); 76 basicSection.SetDefault(); 77 78 var houseTypes = HouseTypes.GetTypes(); 79 basicSection.Player = houseTypes.First().Name; 80 81 Map = new Map(basicSection, null, Constants.MaxSize, typeof(House), 82 houseTypes, TheaterTypes.GetTypes(), TemplateTypes.GetTypes(), TerrainTypes.GetTypes(), 83 OverlayTypes.GetTypes(), SmudgeTypes.GetTypes(), EventTypes.GetTypes(), ActionTypes.GetTypes(), 84 MissionTypes.GetTypes(), DirectionTypes.GetTypes(), InfantryTypes.GetTypes(), UnitTypes.GetTypes(), 85 BuildingTypes.GetTypes(), TeamMissionTypes.GetTypes(), waypoints, movieTypes) 86 { 87 TiberiumOrGoldValue = 25 88 }; 89 90 Map.BasicSection.PropertyChanged += BasicSection_PropertyChanged; 91 Map.MapSection.PropertyChanged += MapSection_PropertyChanged; 92 93 if (mapImage) 94 { 95 MapImage = new Bitmap(Map.Metrics.Width * Globals.TileWidth, Map.Metrics.Height * Globals.TileHeight); 96 } 97 } 98 99 public GamePlugin() 100 : this(true) 101 { 102 } 103 104 public void New(string theater) 105 { 106 Map.Theater = Map.TheaterTypes.Where(t => t.Equals(theater)).FirstOrDefault() ?? TheaterTypes.Temperate; 107 Map.TopLeft = new Point(1, 1); 108 Map.Size = Map.Metrics.Size - new Size(2, 2); 109 110 UpdateBasePlayerHouse(); 111 112 Dirty = true; 113 } 114 115 public IEnumerable<string> Load(string path, FileType fileType) 116 { 117 var errors = new List<string>(); 118 switch (fileType) 119 { 120 case FileType.INI: 121 case FileType.BIN: 122 { 123 var iniPath = Path.ChangeExtension(path, ".ini"); 124 var binPath = Path.ChangeExtension(path, ".bin"); 125 var ini = new INI(); 126 using (var iniReader = new StreamReader(iniPath)) 127 using (var binReader = new BinaryReader(new FileStream(binPath, FileMode.Open, FileAccess.Read))) 128 { 129 ini.Parse(iniReader); 130 errors.AddRange(LoadINI(ini)); 131 LoadBinary(binReader); 132 } 133 } 134 break; 135 case FileType.MEG: 136 case FileType.PGM: 137 { 138 using (var megafile = new Megafile(path)) 139 { 140 var iniFile = megafile.Where(p => Path.GetExtension(p).ToLower() == ".ini").FirstOrDefault(); 141 var binFile = megafile.Where(p => Path.GetExtension(p).ToLower() == ".bin").FirstOrDefault(); 142 if ((iniFile != null) && (binFile != null)) 143 { 144 var ini = new INI(); 145 using (var iniReader = new StreamReader(megafile.Open(iniFile))) 146 using (var binReader = new BinaryReader(megafile.Open(binFile))) 147 { 148 ini.Parse(iniReader); 149 errors.AddRange(LoadINI(ini)); 150 LoadBinary(binReader); 151 } 152 } 153 } 154 } 155 break; 156 default: 157 throw new NotSupportedException(); 158 } 159 return errors; 160 } 161 162 private IEnumerable<string> LoadINI(INI ini) 163 { 164 var errors = new List<string>(); 165 166 Map.BeginUpdate(); 167 168 var basicSection = ini.Sections.Extract("Basic"); 169 if (basicSection != null) 170 { 171 INI.ParseSection(new MapContext(Map, false), basicSection, Map.BasicSection); 172 } 173 174 Map.BasicSection.Player = Map.HouseTypes.Where(t => t.Equals(Map.BasicSection.Player)).FirstOrDefault()?.Name ?? Map.HouseTypes.First().Name; 175 176 var mapSection = ini.Sections.Extract("Map"); 177 if (mapSection != null) 178 { 179 INI.ParseSection(new MapContext(Map, false), mapSection, Map.MapSection); 180 } 181 182 var briefingSection = ini.Sections.Extract("Briefing"); 183 if (briefingSection != null) 184 { 185 if (briefingSection.Keys.Contains("Text")) 186 { 187 Map.BriefingSection.Briefing = briefingSection["Text"].Replace("@", Environment.NewLine); 188 } 189 else 190 { 191 Map.BriefingSection.Briefing = string.Join(" ", briefingSection.Keys.Select(k => k.Value)).Replace("@", Environment.NewLine); 192 } 193 } 194 195 var steamSection = ini.Sections.Extract("Steam"); 196 if (steamSection != null) 197 { 198 INI.ParseSection(new MapContext(Map, false), steamSection, Map.SteamSection); 199 } 200 201 var teamTypesSection = ini.Sections.Extract("TeamTypes"); 202 if (teamTypesSection != null) 203 { 204 foreach (var (Key, Value) in teamTypesSection) 205 { 206 try 207 { 208 var teamType = new TeamType { Name = Key }; 209 210 var tokens = Value.Split(',').ToList(); 211 teamType.House = Map.HouseTypes.Where(t => t.Equals(tokens[0])).FirstOrDefault(); tokens.RemoveAt(0); 212 teamType.IsRoundAbout = int.Parse(tokens[0]) != 0; tokens.RemoveAt(0); 213 teamType.IsLearning = int.Parse(tokens[0]) != 0; tokens.RemoveAt(0); 214 teamType.IsSuicide = int.Parse(tokens[0]) != 0; tokens.RemoveAt(0); 215 teamType.IsAutocreate = int.Parse(tokens[0]) != 0; tokens.RemoveAt(0); 216 teamType.IsMercenary = int.Parse(tokens[0]) != 0; tokens.RemoveAt(0); 217 teamType.RecruitPriority = int.Parse(tokens[0]); tokens.RemoveAt(0); 218 teamType.MaxAllowed = byte.Parse(tokens[0]); tokens.RemoveAt(0); 219 teamType.InitNum = byte.Parse(tokens[0]); tokens.RemoveAt(0); 220 teamType.Fear = byte.Parse(tokens[0]); tokens.RemoveAt(0); 221 222 var numClasses = int.Parse(tokens[0]); tokens.RemoveAt(0); 223 for (int i = 0; i < Math.Min(Globals.MaxTeamClasses, numClasses); ++i) 224 { 225 var classTokens = tokens[0].Split(':'); tokens.RemoveAt(0); 226 if (classTokens.Length == 2) 227 { 228 var type = technoTypes.Where(t => t.Equals(classTokens[0])).FirstOrDefault(); 229 var count = byte.Parse(classTokens[1]); 230 if (type != null) 231 { 232 teamType.Classes.Add(new TeamTypeClass { Type = type, Count = count }); 233 } 234 else 235 { 236 errors.Add(string.Format("Team '{0}' references unknown class '{1}'", Key, classTokens[0])); 237 } 238 } 239 else 240 { 241 errors.Add(string.Format("Team '{0}' has wrong number of tokens for class index {1} (expecting 2)", Key, i)); 242 } 243 } 244 245 var numMissions = int.Parse(tokens[0]); tokens.RemoveAt(0); 246 for (int i = 0; i < Math.Min(Globals.MaxTeamMissions, numMissions); ++i) 247 { 248 var missionTokens = tokens[0].Split(':'); tokens.RemoveAt(0); 249 if (missionTokens.Length == 2) 250 { 251 teamType.Missions.Add(new TeamTypeMission { Mission = missionTokens[0], Argument = int.Parse(missionTokens[1]) }); 252 } 253 else 254 { 255 errors.Add(string.Format("Team '{0}' has wrong number of tokens for mission index {1} (expecting 2)", Key, i)); 256 } 257 } 258 259 if (tokens.Count > 0) 260 { 261 teamType.IsReinforcable = int.Parse(tokens[0]) != 0; tokens.RemoveAt(0); 262 } 263 264 if (tokens.Count > 0) 265 { 266 teamType.IsPrebuilt = int.Parse(tokens[0]) != 0; tokens.RemoveAt(0); 267 } 268 269 Map.TeamTypes.Add(teamType); 270 } 271 catch (ArgumentOutOfRangeException) { } 272 } 273 } 274 275 var triggersSection = ini.Sections.Extract("Triggers"); 276 if (triggersSection != null) 277 { 278 foreach (var (Key, Value) in triggersSection) 279 { 280 var tokens = Value.Split(','); 281 if (tokens.Length >= 5) 282 { 283 var trigger = new Trigger { Name = Key }; 284 285 trigger.Event1.EventType = tokens[0]; 286 trigger.Event1.Data = long.Parse(tokens[2]); 287 trigger.Action1.ActionType = tokens[1]; 288 trigger.House = Map.HouseTypes.Where(t => t.Equals(tokens[3])).FirstOrDefault()?.Name ?? "None"; 289 trigger.Action1.Team = tokens[4]; 290 trigger.PersistantType = TriggerPersistantType.Volatile; 291 292 if (tokens.Length >= 6) 293 { 294 trigger.PersistantType = (TriggerPersistantType)int.Parse(tokens[5]); 295 } 296 297 Map.Triggers.Add(trigger); 298 } 299 else 300 { 301 errors.Add(string.Format("Trigger '{0}' has too few tokens (expecting at least 5)", Key)); 302 } 303 } 304 } 305 306 var terrainSection = ini.Sections.Extract("Terrain"); 307 if (terrainSection != null) 308 { 309 foreach (var (Key, Value) in terrainSection) 310 { 311 var cell = int.Parse(Key); 312 var tokens = Value.Split(','); 313 if (tokens.Length == 2) 314 { 315 var terrainType = Map.TerrainTypes.Where(t => t.Equals(tokens[0])).FirstOrDefault(); 316 if (terrainType != null) 317 { 318 if (!Map.Technos.Add(cell, new Terrain 319 { 320 Type = terrainType, 321 Icon = terrainType.IsTransformable ? 22 : 0, 322 Trigger = tokens[1] 323 })) 324 { 325 var techno = Map.Technos[cell]; 326 if (techno is Building building) 327 { 328 errors.Add(string.Format("Terrain '{0}' overlaps structure '{1}' in cell {2}, skipping", tokens[0], building.Type.Name, cell)); 329 } 330 else if (techno is Overlay overlay) 331 { 332 errors.Add(string.Format("Terrain '{0}' overlaps overlay '{1}' in cell {2}, skipping", tokens[0], overlay.Type.Name, cell)); 333 } 334 else if (techno is Terrain terrain) 335 { 336 errors.Add(string.Format("Terrain '{0}' overlaps terrain '{1}' in cell {2}, skipping", tokens[0], terrain.Type.Name, cell)); 337 } 338 else if (techno is InfantryGroup infantry) 339 { 340 errors.Add(string.Format("Terrain '{0}' overlaps infantry in cell {1}, skipping", tokens[0], cell)); 341 } 342 else if (techno is Unit unit) 343 { 344 errors.Add(string.Format("Terrain '{0}' overlaps unit '{1}' in cell {2}, skipping", tokens[0], unit.Type.Name, cell)); 345 } 346 else 347 { 348 errors.Add(string.Format("Terrain '{0}' overlaps unknown techno in cell {1}, skipping", tokens[0], cell)); 349 } 350 } 351 } 352 else 353 { 354 errors.Add(string.Format("Terrain '{0}' references unknown terrain", tokens[0])); 355 } 356 } 357 else 358 { 359 errors.Add(string.Format("Terrain '{0}' has wrong number of tokens (expecting 2)", Key)); 360 } 361 } 362 } 363 364 var overlaySection = ini.Sections.Extract("Overlay"); 365 if (overlaySection != null) 366 { 367 foreach (var (Key, Value) in overlaySection) 368 { 369 var cell = int.Parse(Key); 370 var overlayType = Map.OverlayTypes.Where(t => t.Equals(Value)).FirstOrDefault(); 371 if (overlayType != null) 372 { 373 Map.Overlay[cell] = new Overlay { Type = overlayType, Icon = 0 }; 374 } 375 else 376 { 377 errors.Add(string.Format("Overlay '{0}' references unknown overlay", Value)); 378 } 379 } 380 } 381 382 var smudgeSection = ini.Sections.Extract("Smudge"); 383 if (smudgeSection != null) 384 { 385 foreach (var (Key, Value) in smudgeSection) 386 { 387 var cell = int.Parse(Key); 388 var tokens = Value.Split(','); 389 if (tokens.Length == 3) 390 { 391 var smudgeType = Map.SmudgeTypes.Where(t => t.Equals(tokens[0])).FirstOrDefault(); 392 if (smudgeType != null) 393 { 394 if (((smudgeType.Flag & SmudgeTypeFlag.Bib) == SmudgeTypeFlag.None)) 395 { 396 Map.Smudge[cell] = new Smudge 397 { 398 Type = smudgeType, 399 Icon = 0, 400 Data = int.Parse(tokens[2]) 401 }; 402 } 403 else 404 { 405 errors.Add(string.Format("Smudge '{0}' is a bib, skipped", tokens[0])); 406 } 407 } 408 else 409 { 410 errors.Add(string.Format("Smudge '{0}' references unknown smudge", tokens[0])); 411 } 412 } 413 } 414 } 415 416 var infantrySections = ini.Sections.Extract("Infantry"); 417 if (infantrySections != null) 418 { 419 foreach (var (_, Value) in infantrySections) 420 { 421 var tokens = Value.Split(','); 422 if (tokens.Length == 8) 423 { 424 var infantryType = Map.InfantryTypes.Where(t => t.Equals(tokens[1])).FirstOrDefault(); 425 if (infantryType != null) 426 { 427 var cell = int.Parse(tokens[3]); 428 var infantryGroup = Map.Technos[cell] as InfantryGroup; 429 if ((infantryGroup == null) && (Map.Technos[cell] == null)) 430 { 431 infantryGroup = new InfantryGroup(); 432 Map.Technos.Add(cell, infantryGroup); 433 } 434 435 if (infantryGroup != null) 436 { 437 var stoppingPos = int.Parse(tokens[4]); 438 if (stoppingPos < Globals.NumInfantryStops) 439 { 440 var direction = (byte)((int.Parse(tokens[6]) + 0x08) & ~0x0F); 441 442 if (infantryGroup.Infantry[stoppingPos] == null) 443 { 444 infantryGroup.Infantry[stoppingPos] = new Infantry(infantryGroup) 445 { 446 Type = infantryType, 447 House = Map.HouseTypes.Where(t => t.Equals(tokens[0])).FirstOrDefault(), 448 Strength = int.Parse(tokens[2]), 449 Direction = Map.DirectionTypes.Where(d => d.Equals(direction)).FirstOrDefault(), 450 Mission = Map.MissionTypes.Where(t => t.Equals(tokens[5])).FirstOrDefault(), 451 Trigger = tokens[7] 452 }; 453 } 454 else 455 { 456 errors.Add(string.Format("Infantry '{0}' overlaps another infantry at position {1} in cell {2}, skipping", tokens[1], stoppingPos, cell)); 457 } 458 } 459 else 460 { 461 errors.Add(string.Format("Infantry '{0}' has invalid position {1} in cell {2}, skipping", tokens[1], stoppingPos, cell)); 462 } 463 } 464 else 465 { 466 var techno = Map.Technos[cell]; 467 if (techno is Building building) 468 { 469 errors.Add(string.Format("Infantry '{0}' overlaps structure '{1}' in cell {2}, skipping", tokens[1], building.Type.Name, cell)); 470 } 471 else if (techno is Overlay overlay) 472 { 473 errors.Add(string.Format("Infantry '{0}' overlaps overlay '{1}' in cell {2}, skipping", tokens[1], overlay.Type.Name, cell)); 474 } 475 else if (techno is Terrain terrain) 476 { 477 errors.Add(string.Format("Infantry '{0}' overlaps terrain '{1}' in cell {2}, skipping", tokens[1], terrain.Type.Name, cell)); 478 } 479 else if (techno is Unit unit) 480 { 481 errors.Add(string.Format("Infantry '{0}' overlaps unit '{1}' in cell {2}, skipping", tokens[1], unit.Type.Name, cell)); 482 } 483 else 484 { 485 errors.Add(string.Format("Infantry '{0}' overlaps unknown techno in cell {1}, skipping", tokens[1], cell)); 486 } 487 } 488 } 489 else 490 { 491 errors.Add(string.Format("Infantry '{0}' references unknown infantry", tokens[1])); 492 } 493 } 494 else 495 { 496 errors.Add(string.Format("Infantry '{0}' has wrong number of tokens (expecting 8)", tokens[1])); 497 } 498 } 499 } 500 501 var unitsSections = ini.Sections.Extract("Units"); 502 if (unitsSections != null) 503 { 504 foreach (var (_, Value) in unitsSections) 505 { 506 var tokens = Value.Split(','); 507 if (tokens.Length == 7) 508 { 509 var unitType = Map.UnitTypes.Where(t => t.IsUnit && t.Equals(tokens[1])).FirstOrDefault(); 510 if (unitType != null) 511 { 512 var direction = (byte)((int.Parse(tokens[4]) + 0x08) & ~0x0F); 513 514 var cell = int.Parse(tokens[3]); 515 if (!Map.Technos.Add(cell, new Unit() 516 { 517 Type = unitType, 518 House = Map.HouseTypes.Where(t => t.Equals(tokens[0])).FirstOrDefault(), 519 Strength = int.Parse(tokens[2]), 520 Direction = Map.DirectionTypes.Where(d => d.Equals(direction)).FirstOrDefault(), 521 Mission = Map.MissionTypes.Where(t => t.Equals(tokens[5])).FirstOrDefault(), 522 Trigger = tokens[6] 523 })) 524 { 525 var techno = Map.Technos[cell]; 526 if (techno is Building building) 527 { 528 errors.Add(string.Format("Unit '{0}' overlaps structure '{1}' in cell {2}, skipping", tokens[1], building.Type.Name, cell)); 529 } 530 else if (techno is Overlay overlay) 531 { 532 errors.Add(string.Format("Unit '{0}' overlaps overlay '{1}' in cell {2}, skipping", tokens[1], overlay.Type.Name, cell)); 533 } 534 else if (techno is Terrain terrain) 535 { 536 errors.Add(string.Format("Unit '{0}' overlaps terrain '{1}' in cell {2}, skipping", tokens[1], terrain.Type.Name, cell)); 537 } 538 else if (techno is InfantryGroup infantry) 539 { 540 errors.Add(string.Format("Unit '{0}' overlaps infantry in cell {1}, skipping", tokens[1], cell)); 541 } 542 else if (techno is Unit unit) 543 { 544 errors.Add(string.Format("Unit '{0}' overlaps unit '{1}' in cell {2}, skipping", tokens[1], unit.Type.Name, cell)); 545 } 546 else 547 { 548 errors.Add(string.Format("Unit '{0}' overlaps unknown techno in cell {1}, skipping", tokens[1], cell)); 549 } 550 } 551 } 552 else 553 { 554 errors.Add(string.Format("Unit '{0}' references unknown unit", tokens[1])); 555 } 556 } 557 else 558 { 559 errors.Add(string.Format("Unit '{0}' has wrong number of tokens (expecting 7)", tokens[1])); 560 } 561 } 562 } 563 564 var aircraftSections = ini.Sections.Extract("Aircraft"); 565 if (aircraftSections != null) 566 { 567 foreach (var (_, Value) in aircraftSections) 568 { 569 var tokens = Value.Split(','); 570 if (tokens.Length == 6) 571 { 572 var aircraftType = Map.UnitTypes.Where(t => t.IsAircraft && t.Equals(tokens[1])).FirstOrDefault(); 573 if (aircraftType != null) 574 { 575 var direction = (byte)((int.Parse(tokens[4]) + 0x08) & ~0x0F); 576 577 var cell = int.Parse(tokens[3]); 578 if (!Map.Technos.Add(cell, new Unit() 579 { 580 Type = aircraftType, 581 House = Map.HouseTypes.Where(t => t.Equals(tokens[0])).FirstOrDefault(), 582 Strength = int.Parse(tokens[2]), 583 Direction = Map.DirectionTypes.Where(d => d.Equals(direction)).FirstOrDefault(), 584 Mission = Map.MissionTypes.Where(t => t.Equals(tokens[5])).FirstOrDefault() 585 })) 586 { 587 var techno = Map.Technos[cell]; 588 if (techno is Building building) 589 { 590 errors.Add(string.Format("Aircraft '{0}' overlaps structure '{1}' in cell {2}, skipping", tokens[1], building.Type.Name, cell)); 591 } 592 else if (techno is Overlay overlay) 593 { 594 errors.Add(string.Format("Aircraft '{0}' overlaps overlay '{1}' in cell {2}, skipping", tokens[1], overlay.Type.Name, cell)); 595 } 596 else if (techno is Terrain terrain) 597 { 598 errors.Add(string.Format("Aircraft '{0}' overlaps terrain '{1}' in cell {2}, skipping", tokens[1], terrain.Type.Name, cell)); 599 } 600 else if (techno is InfantryGroup infantry) 601 { 602 errors.Add(string.Format("Aircraft '{0}' overlaps infantry in cell {1}, skipping", tokens[1], cell)); 603 } 604 else if (techno is Unit unit) 605 { 606 errors.Add(string.Format("Aircraft '{0}' overlaps unit '{1}' in cell {2}, skipping", tokens[1], unit.Type.Name, cell)); 607 } 608 else 609 { 610 errors.Add(string.Format("Aircraft '{0}' overlaps unknown techno in cell {1}, skipping", tokens[1], cell)); 611 } 612 } 613 } 614 else 615 { 616 errors.Add(string.Format("Aircraft '{0}' references unknown aircraft", tokens[1])); 617 } 618 } 619 else 620 { 621 errors.Add(string.Format("Aircraft '{0}' has wrong number of tokens (expecting 6)", tokens[1])); 622 } 623 } 624 } 625 626 var structuresSection = ini.Sections.Extract("Structures"); 627 if (structuresSection != null) 628 { 629 foreach (var (_, Value) in structuresSection) 630 { 631 var tokens = Value.Split(','); 632 if (tokens.Length == 6) 633 { 634 var buildingType = Map.BuildingTypes.Where(t => t.Equals(tokens[1])).FirstOrDefault(); 635 if (buildingType != null) 636 { 637 var direction = (byte)((int.Parse(tokens[4]) + 0x08) & ~0x0F); 638 639 var cell = int.Parse(tokens[3]); 640 if (!Map.Buildings.Add(cell, new Building() 641 { 642 Type = buildingType, 643 House = Map.HouseTypes.Where(t => t.Equals(tokens[0])).FirstOrDefault(), 644 Strength = int.Parse(tokens[2]), 645 Direction = Map.DirectionTypes.Where(d => d.Equals(direction)).FirstOrDefault(), 646 Trigger = tokens[5] 647 })) 648 { 649 var techno = Map.Technos[cell]; 650 if (techno is Building building) 651 { 652 errors.Add(string.Format("Structure '{0}' overlaps structure '{1}' in cell {2}, skipping", tokens[1], building.Type.Name, cell)); 653 } 654 else if (techno is Overlay overlay) 655 { 656 errors.Add(string.Format("Structure '{0}' overlaps overlay '{1}' in cell {2}, skipping", tokens[1], overlay.Type.Name, cell)); 657 } 658 else if (techno is Terrain terrain) 659 { 660 errors.Add(string.Format("Structure '{0}' overlaps terrain '{1}' in cell {2}, skipping", tokens[1], terrain.Type.Name, cell)); 661 } 662 else if (techno is InfantryGroup infantry) 663 { 664 errors.Add(string.Format("Structure '{0}' overlaps infantry in cell {1}, skipping", tokens[1], cell)); 665 } 666 else if (techno is Unit unit) 667 { 668 errors.Add(string.Format("Structure '{0}' overlaps unit '{1}' in cell {2}, skipping", tokens[1], unit.Type.Name, cell)); 669 } 670 else 671 { 672 errors.Add(string.Format("Structure '{0}' overlaps unknown techno in cell {1}, skipping", tokens[1], cell)); 673 } 674 } 675 } 676 else 677 { 678 errors.Add(string.Format("Structure '{0}' references unknown structure", tokens[1])); 679 } 680 } 681 else 682 { 683 errors.Add(string.Format("Structure '{0}' has wrong number of tokens (expecting 6)", tokens[1])); 684 } 685 } 686 } 687 688 var baseSection = ini.Sections.Extract("Base"); 689 if (baseSection != null) 690 { 691 foreach (var (Key, Value) in baseSection) 692 { 693 if (int.TryParse(Key, out int priority)) 694 { 695 var tokens = Value.Split(','); 696 if (tokens.Length == 2) 697 { 698 var buildingType = Map.BuildingTypes.Where(t => t.Equals(tokens[0])).FirstOrDefault(); 699 if (buildingType != null) 700 { 701 var coord = int.Parse(tokens[1]); 702 var location = new Point((coord >> 8) & 0x3F, (coord >> 24) & 0x3F); 703 if (Map.Buildings.OfType<Building>().Where(x => x.Location == location).FirstOrDefault().Occupier is Building building) 704 { 705 building.BasePriority = priority; 706 } 707 else 708 { 709 Map.Buildings.Add(location, new Building() 710 { 711 Type = buildingType, 712 Strength = 256, 713 Direction = DirectionTypes.North, 714 BasePriority = priority, 715 IsPrebuilt = false 716 }); 717 } 718 } 719 else 720 { 721 errors.Add(string.Format("Base priority {0} references unknown structure '{1}'", priority, tokens[0])); 722 } 723 } 724 else 725 { 726 errors.Add(string.Format("Base priority {0} has wrong number of tokens (expecting 2)", priority)); 727 } 728 } 729 else if (!Key.Equals("Count", StringComparison.CurrentCultureIgnoreCase)) 730 { 731 errors.Add(string.Format("Invalid base priority '{0}' (expecting integer)", Key)); 732 } 733 } 734 } 735 736 var waypointsSection = ini.Sections.Extract("Waypoints"); 737 if (waypointsSection != null) 738 { 739 foreach (var (Key, Value) in waypointsSection) 740 { 741 if (int.TryParse(Key, out int waypoint)) 742 { 743 if (int.TryParse(Value, out int cell)) 744 { 745 if ((waypoint >= 0) && (waypoint < Map.Waypoints.Length)) 746 { 747 if (Map.Metrics.Contains(cell)) 748 { 749 Map.Waypoints[waypoint].Cell = cell; 750 } 751 else 752 { 753 Map.Waypoints[waypoint].Cell = null; 754 if (cell != -1) 755 { 756 errors.Add(string.Format("Waypoint {0} cell value {1} out of range (expecting between {2} and {3})", waypoint, cell, 0, Map.Metrics.Length - 1)); 757 } 758 } 759 } 760 else if (cell != -1) 761 { 762 errors.Add(string.Format("Waypoint {0} out of range (expecting between {1} and {2})", waypoint, 0, Map.Waypoints.Length - 1)); 763 } 764 } 765 else 766 { 767 errors.Add(string.Format("Waypoint {0} has invalid cell '{1}' (expecting integer)", waypoint, Value)); 768 } 769 } 770 else 771 { 772 errors.Add(string.Format("Invalid waypoint '{0}' (expecting integer)", Key)); 773 } 774 } 775 } 776 777 var cellTriggersSection = ini.Sections.Extract("CellTriggers"); 778 if (cellTriggersSection != null) 779 { 780 foreach (var (Key, Value) in cellTriggersSection) 781 { 782 if (int.TryParse(Key, out int cell)) 783 { 784 if (Map.Metrics.Contains(cell)) 785 { 786 Map.CellTriggers[cell] = new CellTrigger 787 { 788 Trigger = Value 789 }; 790 } 791 else 792 { 793 errors.Add(string.Format("Cell trigger {0} outside map bounds", cell)); 794 } 795 } 796 else 797 { 798 errors.Add(string.Format("Invalid cell trigger '{0}' (expecting integer)", Key)); 799 } 800 } 801 } 802 803 foreach (var house in Map.Houses) 804 { 805 if (house.Type.ID < 0) 806 { 807 continue; 808 } 809 810 var houseSection = ini.Sections.Extract(house.Type.Name); 811 if (houseSection != null) 812 { 813 INI.ParseSection(new MapContext(Map, false), houseSection, house); 814 house.Enabled = true; 815 } 816 else 817 { 818 house.Enabled = false; 819 } 820 } 821 822 UpdateBasePlayerHouse(); 823 824 extraSections = ini.Sections; 825 826 Map.EndUpdate(); 827 828 return errors; 829 } 830 831 private void LoadBinary(BinaryReader reader) 832 { 833 Map.Templates.Clear(); 834 835 for (var y = 0; y < Map.Metrics.Height; ++y) 836 { 837 for (var x = 0; x < Map.Metrics.Width; ++x) 838 { 839 var typeValue = reader.ReadByte(); 840 var iconValue = reader.ReadByte(); 841 var templateType = Map.TemplateTypes.Where(t => t.Equals(typeValue)).FirstOrDefault(); 842 if ((templateType != null) && !templateType.Theaters.Contains(Map.Theater)) 843 { 844 templateType = null; 845 } 846 if ((templateType ?? TemplateTypes.Clear) != TemplateTypes.Clear) 847 { 848 if (iconValue >= templateType.NumIcons) 849 { 850 templateType = null; 851 } 852 } 853 Map.Templates[x, y] = (templateType != null) ? new Template { Type = templateType, Icon = iconValue } : null; 854 } 855 } 856 } 857 858 public bool Save(string path, FileType fileType) 859 { 860 if (!Validate()) 861 { 862 return false; 863 } 864 865 switch (fileType) 866 { 867 case FileType.INI: 868 case FileType.BIN: 869 { 870 var iniPath = Path.ChangeExtension(path, ".ini"); 871 var binPath = Path.ChangeExtension(path, ".bin"); 872 var tgaPath = Path.ChangeExtension(path, ".tga"); 873 var jsonPath = Path.ChangeExtension(path, ".json"); 874 875 var ini = new INI(); 876 using (var iniWriter = new StreamWriter(iniPath)) 877 using (var binStream = new FileStream(binPath, FileMode.Create)) 878 using (var binWriter = new BinaryWriter(binStream)) 879 using (var tgaStream = new FileStream(tgaPath, FileMode.Create)) 880 using (var jsonStream = new FileStream(jsonPath, FileMode.Create)) 881 using (var jsonWriter = new JsonTextWriter(new StreamWriter(jsonStream))) 882 { 883 SaveINI(ini, fileType); 884 iniWriter.Write(ini.ToString()); 885 SaveBinary(binWriter); 886 SaveMapPreview(tgaStream); 887 SaveJSON(jsonWriter); 888 } 889 } 890 break; 891 case FileType.MEG: 892 case FileType.PGM: 893 { 894 using (var iniStream = new MemoryStream()) 895 using (var binStream = new MemoryStream()) 896 using (var tgaStream = new MemoryStream()) 897 using (var jsonStream = new MemoryStream()) 898 using (var binWriter = new BinaryWriter(binStream)) 899 using (var jsonWriter = new JsonTextWriter(new StreamWriter(jsonStream))) 900 using (var megafileBuilder = new MegafileBuilder(@"", path)) 901 { 902 var ini = new INI(); 903 SaveINI(ini, fileType); 904 var iniWriter = new StreamWriter(iniStream); 905 iniWriter.Write(ini.ToString()); 906 iniWriter.Flush(); 907 iniStream.Position = 0; 908 909 SaveBinary(binWriter); 910 binWriter.Flush(); 911 binStream.Position = 0; 912 913 SaveMapPreview(tgaStream); 914 tgaStream.Position = 0; 915 916 SaveJSON(jsonWriter); 917 jsonWriter.Flush(); 918 jsonStream.Position = 0; 919 920 var iniFile = Path.ChangeExtension(Path.GetFileName(path), ".ini").ToUpper(); 921 var binFile = Path.ChangeExtension(Path.GetFileName(path), ".bin").ToUpper(); 922 var tgaFile = Path.ChangeExtension(Path.GetFileName(path), ".tga").ToUpper(); 923 var jsonFile = Path.ChangeExtension(Path.GetFileName(path), ".json").ToUpper(); 924 925 megafileBuilder.AddFile(iniFile, iniStream); 926 megafileBuilder.AddFile(binFile, binStream); 927 megafileBuilder.AddFile(tgaFile, tgaStream); 928 megafileBuilder.AddFile(jsonFile, jsonStream); 929 megafileBuilder.Write(); 930 } 931 } 932 break; 933 default: 934 throw new NotSupportedException(); 935 } 936 937 return true; 938 } 939 940 private void SaveINI(INI ini, FileType fileType) 941 { 942 if (extraSections != null) 943 { 944 ini.Sections.AddRange(extraSections); 945 } 946 947 INI.WriteSection(new MapContext(Map, false), ini.Sections.Add("Basic"), Map.BasicSection); 948 INI.WriteSection(new MapContext(Map, false), ini.Sections.Add("Map"), Map.MapSection); 949 950 if (fileType != FileType.PGM) 951 { 952 INI.WriteSection(new MapContext(Map, false), ini.Sections.Add("Steam"), Map.SteamSection); 953 } 954 955 ini.Sections.Remove("Briefing"); 956 if (!string.IsNullOrEmpty(Map.BriefingSection.Briefing)) 957 { 958 var briefingSection = ini.Sections.Add("Briefing"); 959 briefingSection["Text"] = Map.BriefingSection.Briefing.Replace(Environment.NewLine, "@"); 960 } 961 962 var cellTriggersSection = ini.Sections.Add("CellTriggers"); 963 foreach (var (cell, cellTrigger) in Map.CellTriggers) 964 { 965 cellTriggersSection[cell.ToString()] = cellTrigger.Trigger; 966 } 967 968 var teamTypesSection = ini.Sections.Add("TeamTypes"); 969 foreach(var teamType in Map.TeamTypes) 970 { 971 var classes = teamType.Classes 972 .Select(c => string.Format("{0}:{1}", c.Type.Name.ToLower(), c.Count)) 973 .ToArray(); 974 var missions = teamType.Missions 975 .Select(m => string.Format("{0}:{1}", m.Mission, m.Argument)) 976 .ToArray(); 977 978 var tokens = new List<string> 979 { 980 teamType.House.Name, 981 teamType.IsRoundAbout ? "1" : "0", 982 teamType.IsLearning ? "1" : "0", 983 teamType.IsSuicide ? "1" : "0", 984 teamType.IsAutocreate ? "1" : "0", 985 teamType.IsMercenary ? "1" : "0", 986 teamType.RecruitPriority.ToString(), 987 teamType.MaxAllowed.ToString(), 988 teamType.InitNum.ToString(), 989 teamType.Fear.ToString(), 990 classes.Length.ToString(), 991 string.Join(",", classes), 992 missions.Length.ToString(), 993 string.Join(",", missions), 994 teamType.IsReinforcable ? "1" : "0", 995 teamType.IsPrebuilt ? "1" : "0" 996 }; 997 998 teamTypesSection[teamType.Name] = string.Join(",", tokens.Where(t => !string.IsNullOrEmpty(t))); 999 } 1000 1001 var triggersSection = ini.Sections.Add("Triggers"); 1002 foreach (var trigger in Map.Triggers) 1003 { 1004 if (string.IsNullOrEmpty(trigger.Name)) 1005 { 1006 continue; 1007 } 1008 1009 var tokens = new List<string> 1010 { 1011 trigger.Event1.EventType, 1012 trigger.Action1.ActionType, 1013 trigger.Event1.Data.ToString(), 1014 trigger.House, 1015 trigger.Action1.Team, 1016 ((int)trigger.PersistantType).ToString() 1017 }; 1018 1019 triggersSection[trigger.Name] = string.Join(",", tokens); 1020 } 1021 1022 var waypointsSection = ini.Sections.Add("Waypoints"); 1023 for (var i = 0; i < Map.Waypoints.Length; ++i) 1024 { 1025 var waypoint = Map.Waypoints[i]; 1026 if (waypoint.Cell.HasValue) 1027 { 1028 waypointsSection[i.ToString()] = waypoint.Cell.Value.ToString(); 1029 } 1030 } 1031 1032 var baseSection = ini.Sections.Add("Base"); 1033 var baseBuildings = Map.Buildings.OfType<Building>().Where(x => x.Occupier.BasePriority >= 0).OrderByDescending(x => x.Occupier.BasePriority).ToArray(); 1034 var baseIndex = baseBuildings.Length - 1; 1035 foreach (var (location, building) in baseBuildings) 1036 { 1037 var key = baseIndex.ToString("D3"); 1038 baseIndex--; 1039 1040 baseSection[key] = string.Format("{0},{1}", 1041 building.Type.Name.ToUpper(), 1042 ((location.Y & 0x3F) << 24) | ((location.X & 0x3F) << 8) 1043 ); 1044 } 1045 baseSection["Count"] = baseBuildings.Length.ToString(); 1046 1047 var infantrySection = ini.Sections.Add("Infantry"); 1048 var infantryIndex = 0; 1049 foreach (var (location, infantryGroup) in Map.Technos.OfType<InfantryGroup>()) 1050 { 1051 for (var i = 0; i < infantryGroup.Infantry.Length; ++i) 1052 { 1053 var infantry = infantryGroup.Infantry[i]; 1054 if (infantry == null) 1055 { 1056 continue; 1057 } 1058 1059 var key = infantryIndex.ToString("D3"); 1060 infantryIndex++; 1061 1062 Map.Metrics.GetCell(location, out int cell); 1063 infantrySection[key] = string.Format("{0},{1},{2},{3},{4},{5},{6},{7}", 1064 infantry.House.Name, 1065 infantry.Type.Name, 1066 infantry.Strength, 1067 cell, 1068 i, 1069 infantry.Mission, 1070 infantry.Direction.ID, 1071 infantry.Trigger 1072 ); 1073 } 1074 } 1075 1076 var structuresSection = ini.Sections.Add("Structures"); 1077 var structureIndex = 0; 1078 foreach (var (location, building) in Map.Buildings.OfType<Building>().Where(x => x.Occupier.IsPrebuilt)) 1079 { 1080 var key = structureIndex.ToString("D3"); 1081 structureIndex++; 1082 1083 Map.Metrics.GetCell(location, out int cell); 1084 structuresSection[key] = string.Format("{0},{1},{2},{3},{4},{5}", 1085 building.House.Name, 1086 building.Type.Name, 1087 building.Strength, 1088 cell, 1089 building.Direction.ID, 1090 building.Trigger 1091 ); 1092 } 1093 1094 var unitsSection = ini.Sections.Add("Units"); 1095 var unitIndex = 0; 1096 foreach (var (location, unit) in Map.Technos.OfType<Unit>().Where(u => u.Occupier.Type.IsUnit)) 1097 { 1098 var key = unitIndex.ToString("D3"); 1099 unitIndex++; 1100 1101 Map.Metrics.GetCell(location, out int cell); 1102 unitsSection[key] = string.Format("{0},{1},{2},{3},{4},{5},{6}", 1103 unit.House.Name, 1104 unit.Type.Name, 1105 unit.Strength, 1106 cell, 1107 unit.Direction.ID, 1108 unit.Mission, 1109 unit.Trigger 1110 ); 1111 } 1112 1113 var aircraftSection = ini.Sections.Add("Aircraft"); 1114 var aircraftIndex = 0; 1115 foreach (var (location, aircraft) in Map.Technos.OfType<Unit>().Where(u => u.Occupier.Type.IsAircraft)) 1116 { 1117 var key = aircraftIndex.ToString("D3"); 1118 aircraftIndex++; 1119 1120 Map.Metrics.GetCell(location, out int cell); 1121 aircraftSection[key] = string.Format("{0},{1},{2},{3},{4},{5}", 1122 aircraft.House.Name, 1123 aircraft.Type.Name, 1124 aircraft.Strength, 1125 cell, 1126 aircraft.Direction.ID, 1127 aircraft.Mission 1128 ); 1129 } 1130 1131 foreach (var house in Map.Houses) 1132 { 1133 if ((house.Type.ID < 0) || !house.Enabled) 1134 { 1135 continue; 1136 } 1137 1138 INI.WriteSection(new MapContext(Map, false), ini.Sections.Add(house.Type.Name), house); 1139 } 1140 1141 var overlaySection = ini.Sections.Add("Overlay"); 1142 foreach (var (cell, overlay) in Map.Overlay) 1143 { 1144 overlaySection[cell.ToString()] = overlay.Type.Name; 1145 } 1146 1147 var smudgeSection = ini.Sections.Add("Smudge"); 1148 foreach (var (cell, smudge) in Map.Smudge.Where(item => (item.Value.Type.Flag & SmudgeTypeFlag.Bib) == SmudgeTypeFlag.None)) 1149 { 1150 smudgeSection[cell.ToString()] = string.Format("{0},{1},{2}", smudge.Type.Name, cell, smudge.Data); 1151 } 1152 1153 var terrainSection = ini.Sections.Add("Terrain"); 1154 foreach (var (location, terrain) in Map.Technos.OfType<Terrain>()) 1155 { 1156 Map.Metrics.GetCell(location, out int cell); 1157 terrainSection[cell.ToString()] = string.Format("{0},None", terrain.Type.Name); 1158 } 1159 } 1160 1161 private void SaveBinary(BinaryWriter writer) 1162 { 1163 for (var y = 0; y < Map.Metrics.Height; ++y) 1164 { 1165 for (var x = 0; x < Map.Metrics.Width; ++x) 1166 { 1167 var template = Map.Templates[x, y]; 1168 if (template != null) 1169 { 1170 writer.Write((byte)template.Type.ID); 1171 writer.Write((byte)template.Icon); 1172 } 1173 else 1174 { 1175 writer.Write(byte.MaxValue); 1176 writer.Write(byte.MaxValue); 1177 } 1178 } 1179 } 1180 } 1181 1182 private void SaveMapPreview(Stream stream) 1183 { 1184 Map.GenerateMapPreview().Save(stream); 1185 } 1186 1187 private void SaveJSON(JsonTextWriter writer) 1188 { 1189 writer.WriteStartObject(); 1190 writer.WritePropertyName("MapTileX"); 1191 writer.WriteValue(Map.MapSection.X); 1192 writer.WritePropertyName("MapTileY"); 1193 writer.WriteValue(Map.MapSection.Y); 1194 writer.WritePropertyName("MapTileWidth"); 1195 writer.WriteValue(Map.MapSection.Width); 1196 writer.WritePropertyName("MapTileHeight"); 1197 writer.WriteValue(Map.MapSection.Height); 1198 writer.WritePropertyName("Theater"); 1199 writer.WriteValue(Map.MapSection.Theater.Name.ToUpper()); 1200 writer.WritePropertyName("Waypoints"); 1201 writer.WriteStartArray(); 1202 foreach (var waypoint in Map.Waypoints.Where(w => (w.Flag == WaypointFlag.PlayerStart) && w.Cell.HasValue)) 1203 { 1204 writer.WriteValue(waypoint.Cell.Value); 1205 } 1206 writer.WriteEndArray(); 1207 writer.WriteEndObject(); 1208 } 1209 1210 private bool Validate() 1211 { 1212 StringBuilder sb = new StringBuilder("Error(s) during map validation:"); 1213 1214 bool ok = true; 1215 int numAircraft = Map.Technos.OfType<Unit>().Where(u => u.Occupier.Type.IsAircraft).Count(); 1216 int numBuildings = Map.Buildings.OfType<Building>().Where(x => x.Occupier.IsPrebuilt).Count(); 1217 int numInfantry = Map.Technos.OfType<InfantryGroup>().Sum(item => item.Occupier.Infantry.Count(i => i != null)); 1218 int numTerrain = Map.Technos.OfType<Terrain>().Count(); 1219 int numUnits = Map.Technos.OfType<Unit>().Where(u => u.Occupier.Type.IsUnit).Count(); 1220 int numWaypoints = Map.Waypoints.Count(w => w.Cell.HasValue); 1221 1222 if (numAircraft > Constants.MaxAircraft) 1223 { 1224 sb.Append(Environment.NewLine + string.Format("Maximum number of aircraft exceeded ({0} > {1})", numAircraft, Constants.MaxAircraft)); 1225 ok = false; 1226 } 1227 1228 if (numBuildings > Constants.MaxBuildings) 1229 { 1230 sb.Append(Environment.NewLine + string.Format("Maximum number of structures exceeded ({0} > {1})", numBuildings, Constants.MaxBuildings)); 1231 ok = false; 1232 } 1233 1234 if (numInfantry > Constants.MaxInfantry) 1235 { 1236 sb.Append(Environment.NewLine + string.Format("Maximum number of infantry exceeded ({0} > {1})", numInfantry, Constants.MaxInfantry)); 1237 ok = false; 1238 } 1239 1240 if (numTerrain > Constants.MaxTerrain) 1241 { 1242 sb.Append(Environment.NewLine + string.Format("Maximum number of terrain objects exceeded ({0} > {1})", numTerrain, Constants.MaxTerrain)); 1243 ok = false; 1244 } 1245 1246 if (numUnits > Constants.MaxUnits) 1247 { 1248 sb.Append(Environment.NewLine + string.Format("Maximum number of units exceeded ({0} > {1})", numUnits, Constants.MaxUnits)); 1249 ok = false; 1250 } 1251 1252 if (Map.TeamTypes.Count > Constants.MaxTeams) 1253 { 1254 sb.Append(Environment.NewLine + string.Format("Maximum number of team types exceeded ({0} > {1})", Map.TeamTypes.Count, Constants.MaxTeams)); 1255 ok = false; 1256 } 1257 1258 if (Map.Triggers.Count > Constants.MaxTriggers) 1259 { 1260 sb.Append(Environment.NewLine + string.Format("Maximum number of triggers exceeded ({0} > {1})", Map.Triggers.Count, Constants.MaxTriggers)); 1261 ok = false; 1262 } 1263 1264 if (!Map.BasicSection.SoloMission && (numWaypoints < 2)) 1265 { 1266 sb.Append(Environment.NewLine + "Skirmish/Multiplayer maps need at least 2 waypoints for player starting locations."); 1267 ok = false; 1268 } 1269 1270 var homeWaypoint = Map.Waypoints.Where(w => w.Equals("Home")).FirstOrDefault(); 1271 if (Map.BasicSection.SoloMission && !homeWaypoint.Cell.HasValue) 1272 { 1273 sb.Append(Environment.NewLine + string.Format("Single-player maps need the Home waypoint to be placed.", Map.Triggers.Count, Constants.MaxTriggers)); 1274 ok = false; 1275 } 1276 1277 if (!ok) 1278 { 1279 MessageBox.Show(sb.ToString(), "Validation Error", MessageBoxButtons.OK, MessageBoxIcon.Error); 1280 } 1281 1282 return ok; 1283 } 1284 1285 private void BasicSection_PropertyChanged(object sender, PropertyChangedEventArgs e) 1286 { 1287 switch (e.PropertyName) 1288 { 1289 case "Player": 1290 { 1291 UpdateBasePlayerHouse(); 1292 } 1293 break; 1294 } 1295 } 1296 1297 private void MapSection_PropertyChanged(object sender, PropertyChangedEventArgs e) 1298 { 1299 switch (e.PropertyName) 1300 { 1301 case "Theater": 1302 { 1303 Map.InitTheater(GameType); 1304 } 1305 break; 1306 } 1307 } 1308 1309 private void UpdateBasePlayerHouse() 1310 { 1311 Map.BasicSection.BasePlayer = HouseTypes.GetBasePlayer(Map.BasicSection.Player); 1312 1313 var basePlayer = Map.HouseTypes.Where(h => h.Equals(Map.BasicSection.BasePlayer)).FirstOrDefault() ?? Map.HouseTypes.First(); 1314 foreach (var (_, building) in Map.Buildings.OfType<Building>()) 1315 { 1316 if (!building.IsPrebuilt) 1317 { 1318 building.House = basePlayer; 1319 } 1320 } 1321 } 1322 1323 #region IDisposable Support 1324 private bool disposedValue = false; 1325 1326 protected virtual void Dispose(bool disposing) 1327 { 1328 if (!disposedValue) 1329 { 1330 if (disposing) 1331 { 1332 MapImage?.Dispose(); 1333 } 1334 disposedValue = true; 1335 } 1336 } 1337 1338 public void Dispose() 1339 { 1340 Dispose(true); 1341 } 1342 #endregion 1343 } 1344 }