2023-06-09 11:44:39 +02:00

2036 lines
90 KiB
C#

//
// 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.Event;
using MobiusEditor.Interface;
using MobiusEditor.Render;
using MobiusEditor.Utility;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using TGASharpLib;
namespace MobiusEditor.Model
{
// Keep this list synchronised with Map.MapLayerNames
[Flags]
public enum MapLayerFlag: int
{
None = 0,
Template = 1 << 00,
Terrain = 1 << 01,
Infantry = 1 << 02,
Units = 1 << 03,
Buildings = 1 << 04,
Overlay = 1 << 05,
Walls = 1 << 06,
Resources = 1 << 07,
Smudge = 1 << 08,
Waypoints = 1 << 09,
Boundaries = 1 << 10,
MapSymmetry = 1 << 11,
MapGrid = 1 << 12,
WaypointsIndic = 1 << 13,
FootballArea = 1 << 14,
CellTriggers = 1 << 15,
TechnoTriggers = 1 << 16,
BuildingRebuild = 1 << 17,
BuildingFakes = 1 << 18,
EffectRadius = 1 << 19,
WaypointRadius = 1 << 20,
CrateOutlines = 1 << 21,
OverlayAll = Resources | Walls | Overlay,
Technos = Terrain | Infantry | Units | Buildings,
MapLayers = Terrain | Resources | Walls | Overlay | Smudge | Infantry | Units | Buildings | Waypoints,
/// <summary>Listing of layers that don't need a full map repaint.</summary>
Indicators = Boundaries | MapSymmetry | MapGrid | WaypointsIndic | FootballArea | CellTriggers
| TechnoTriggers | BuildingRebuild | BuildingFakes | EffectRadius | WaypointRadius | CrateOutlines,
All = Int32.MaxValue
}
public class TriggersChangedEventArgs : EventArgs
{
}
public class MapContext : ITypeDescriptorContext
{
public IContainer Container { get; private set; }
public object Instance { get; private set; }
public PropertyDescriptor PropertyDescriptor { get; private set; }
public Map Map => this.Instance as Map;
public readonly bool FractionalPercentages;
public MapContext(Map map, bool fractionalPercentages)
{
this.Instance = map;
this.FractionalPercentages = fractionalPercentages;
}
public object GetService(Type serviceType) => null;
public void OnComponentChanged() { }
public bool OnComponentChanging() => true;
}
public class Map : ICloneable
{
private static readonly int randomSeed;
static Map()
{
randomSeed = Guid.NewGuid().GetHashCode();
}
// Keep this list synchronised with the MapLayerFlag enum
public static String[] MapLayerNames = {
// Map layers
"Template",
"Terrain",
"Infantry",
"Units",
"Buildings",
"Overlay",
"Walls",
"Resources",
"Smudge",
"Waypoints",
// Indicators
"Map boundaries",
"Map symmetry",
"Map grid",
"Waypoint labels",
"Football goal areas",
"Cell triggers",
"Object triggers",
"Building rebuild priorities",
"Building 'fake' labels",
"Jam / gap radiuses",
"Waypoint reveal radiuses",
"Crate outlines"
};
private static int[] tiberiumStages = new int[] { 0, 1, 3, 4, 6, 7, 8, 10, 11 };
private static int[] gemStages = new int[] { 0, 0, 0, 1, 1, 1, 2, 2, 2 };
private static Regex tileInfoSplitRegex = new Regex("^([^:]+):(\\d+)$", RegexOptions.Compiled);
private int updateCount = 0;
private bool updating = false;
private IDictionary<MapLayerFlag, ISet<Point>> invalidateLayers = new Dictionary<MapLayerFlag, ISet<Point>>();
private bool invalidateOverlappers;
public event EventHandler<MapRefreshEventArgs> RulesChanged;
public void NotifyRulesChanges(ISet<Point> refreshPoints)
{
if (RulesChanged != null)
{
this.RulesChanged(this, new MapRefreshEventArgs(refreshPoints));
}
}
public event EventHandler<MapRefreshEventArgs> MapContentsChanged;
public void NotifyMapContentsChanged(ISet<Point> refreshPoints)
{
if (MapContentsChanged != null)
{
this.MapContentsChanged(this, new MapRefreshEventArgs(refreshPoints));
}
}
public readonly BasicSection BasicSection;
public readonly MapSection MapSection;
public readonly BriefingSection BriefingSection = new BriefingSection();
public readonly SteamSection SteamSection = new SteamSection();
public TheaterType Theater { get => this.MapSection.Theater; set => this.MapSection.Theater = value; }
public Point TopLeft
{
get => new Point(this.MapSection.X, this.MapSection.Y);
set { this.MapSection.X = value.X; this.MapSection.Y = value.Y; }
}
public Size Size
{
get => new Size(this.MapSection.Width, this.MapSection.Height);
set { this.MapSection.Width = value.Width; this.MapSection.Height = value.Height; }
}
public Rectangle Bounds
{
get => new Rectangle(this.TopLeft, this.Size);
set { this.MapSection.X = value.Left; this.MapSection.Y = value.Top; this.MapSection.Width = value.Width; this.MapSection.Height = value.Height; }
}
public readonly Type HouseType;
public readonly HouseType[] HouseTypes;
public readonly HouseType[] HouseTypesIncludingNone;
public readonly IEnumerable<ITeamColor> FlagColors;
public readonly List<TheaterType> TheaterTypes;
public readonly List<TemplateType> TemplateTypes;
public readonly List<TerrainType> TerrainTypes;
public readonly List<OverlayType> OverlayTypes;
public readonly List<SmudgeType> SmudgeTypes;
public readonly string[] EventTypes;
public readonly HashSet<string> CellEventTypes;
public readonly HashSet<string> UnitEventTypes;
public readonly HashSet<string> BuildingEventTypes;
public readonly HashSet<string> TerrainEventTypes;
public readonly string[] ActionTypes;
public readonly HashSet<string> CellActionTypes;
public readonly HashSet<string> UnitActionTypes;
public readonly HashSet<string> BuildingActionTypes;
public readonly HashSet<string> TerrainActionTypes;
public readonly string[] MissionTypes;
private const string defaultMission = "Guard";
private readonly string inputMissionArmed;
private readonly string inputMissionUnarmed;
private readonly string inputMissionAircraft;
private readonly string inputMissionHarvest;
public readonly string DefaultMissionArmed;
public readonly string DefaultMissionUnarmed;
public readonly string DefaultMissionAircraft;
public readonly string DefaultMissionHarvest;
public readonly List<DirectionType> BuildingDirectionTypes;
public readonly List<DirectionType> UnitDirectionTypes;
public readonly List<InfantryType> AllInfantryTypes;
public List<InfantryType> InfantryTypes
{
get
{
if (this.BasicSection == null || !this.BasicSection.ExpansionEnabled)
{
return this.AllInfantryTypes.Where(inf => !inf.IsExpansionUnit).ToList();
}
return this.AllInfantryTypes.ToList();
}
}
public readonly List<UnitType> AllUnitTypes;
public List<UnitType> UnitTypes
{
get
{
if (this.BasicSection == null || !this.BasicSection.ExpansionEnabled)
{
return this.AllUnitTypes.Where(un => !un.IsExpansionUnit).ToList();
}
return this.AllUnitTypes.ToList();
}
}
public readonly List<BuildingType> BuildingTypes;
public readonly List<ITechnoType> AllTeamTechnoTypes;
public List<ITechnoType> TeamTechnoTypes
{
get
{
if (this.BasicSection == null || !this.BasicSection.ExpansionEnabled)
{
return this.AllTeamTechnoTypes.Where(tc => (tc is UnitType ut && !ut.IsExpansionUnit) || (tc is InfantryType it && !it.IsExpansionUnit)).ToList();
}
return this.AllTeamTechnoTypes.ToList();
}
}
public readonly TeamMission[] TeamMissionTypes;
public readonly CellMetrics Metrics;
public readonly CellGrid<Template> Templates;
public readonly CellGrid<Overlay> Overlay;
public readonly CellGrid<Smudge> Smudge;
public readonly OccupierSet<ICellOccupier> Technos;
public readonly OccupierSet<ICellOccupier> Buildings;
public readonly OverlapperSet<ICellOverlapper> Overlappers;
public readonly Waypoint[] Waypoints;
public event EventHandler<EventArgs> WaypointsUpdated;
public void NotifyWaypointsUpdate()
{
if (WaypointsUpdated != null)
WaypointsUpdated(this, new EventArgs());
}
public int DropZoneRadius { get; set; }
public int GapRadius { get; set; }
public int RadarJamRadius { get; set; }
public readonly CellGrid<CellTrigger> CellTriggers;
public event EventHandler<EventArgs> TriggersUpdated;
public void NotifyTriggersUpdate()
{
if (TriggersUpdated != null)
TriggersUpdated(this, new EventArgs());
}
private List<Trigger> triggers;
public List<Trigger> Triggers
{
get { return this.triggers; }
set
{
this.triggers = value;
// Only an actual replacing of the list will call these, but they can be called manually after an update.
// A bit more manual than the whole ObservableCollection system, but a lot less cumbersome.
//CleanUpTriggers();
this.NotifyTriggersUpdate();
}
}
public readonly List<TeamType> TeamTypes;
public House[] Houses;
public House[] HousesIncludingNone;
public string MovieEmpty;
public readonly List<string> MovieTypes;
public readonly string ThemeEmpty;
public readonly List<string> ThemeTypes;
///<summary>The value for the basic resource on the map.</summary>
public int TiberiumOrGoldValue { get; set; }
/// <summary>The value for the high-value resource on the map.</summary>
public int GemValue { get; set; }
/// <summary>Gets the total amount of resources on the map inside the map border.</summary>
public int ResourcesOnMap
{
get
{
return this.GetTotalResources(true);
}
}
/// <summary>
/// Initialises a new Map object.
/// </summary>
/// <param name="basicSection">ini [Basic] section in the specific object type for this game.</param>
/// <param name="theater">Theater of the map.</param>
/// <param name="cellSize">Size of the map, in cells.</param>
/// <param name="houseType">Type to use for creating House objects from houseTypes.</param>
/// <param name="houseTypes">The list of house types.</param>
/// <param name="flagColors">The list of team colors to use for multiplayer start location indicators.</param>
/// <param name="theaterTypes">The list of all theaters supported by this game.</param>
/// <param name="templateTypes">The list of all template types supported by this game.</param>
/// <param name="terrainTypes">The list of all terrain types supported by this game.</param>
/// <param name="overlayTypes">The list of all overlay types supported by this game.</param>
/// <param name="smudgeTypes">The list of all smudge types supported by this game.</param>
/// <param name="eventTypes">The list of all event types supported by this game.</param>
/// <param name="cellEventTypes">The list of all event types applicable to cells in this game.</param>
/// <param name="unitEventTypes">The list of all event types applicable to units in this game.</param>
/// <param name="buildingEventTypes">The list of all event types applicable to buildings in this game.</param>
/// <param name="terrainEventTypes">The list of all event types applicable to terrain in this game.</param>
/// <param name="actionTypes">The list of all action types supported by this game.</param>
/// <param name="cellActionTypes">The list of all action types applicable to cells in this game.</param>
/// <param name="unitActionTypes">The list of all action types applicable to units in this game.</param>
/// <param name="buildingActionTypes">The list of all action types applicable to buildings in this game.</param>
/// <param name="terrainActionTypes">The list of all action types applicable to terrain in this game.</param>
/// <param name="missionTypes">The list of all mission types (orders) supported by this game.</param>
/// <param name="armedMission">The default mission for armed units.</param>
/// <param name="unarmedMission">The default mission for unarmed units.</param>
/// <param name="harvestMission">The default mission for harvesting units.</param>
/// <param name="aircraftMission">The default mission for air units.</param>
/// <param name="unitDirectionTypes">The list of all direction types applicable to units.</param>
/// <param name="buildingDirectionTypes">The list of all direction types applicable to structures.</param>
/// <param name="infantryTypes">The list of all infantry types.</param>
/// <param name="unitTypes">The list of all unit types.</param>
/// <param name="buildingTypes">The list of all building types.</param>
/// <param name="teamMissionTypes">The list of all mission types (orders) usable by teams.</param>
/// <param name="teamTechnoTypes">The list of all techno types usable in teams.</param>
/// <param name="waypoints">The list of waypoints.</param>
/// <param name="dropZoneRadius">The radius that is revealed around a dropzone waypoint.</param>
/// <param name="gapRadius">The radius that is affected by a gap generator.</param>
/// <param name="jamRadius">The radius that is affected by a radar jammer.</param>
/// <param name="movieTypes">The list of all movies usable by this game.</param>
/// <param name="emptyMovie">The name to use for detecting and saving an empty movie entry.</param>
/// <param name="themeTypes">The list of all music usable by this game.</param>
/// <param name="emptyTheme">The name to use for detecting and saving an empty music entry.</param>
/// <param name="tiberiumOrGoldValue">The value for the basic resource on the map.</param>
/// <param name="gemValue">The value for the high-value resource on the map.</param>
public Map(BasicSection basicSection, TheaterType theater, Size cellSize, Type houseType, IEnumerable<HouseType> houseTypes,
IEnumerable<ITeamColor> flagColors, IEnumerable<TheaterType> theaterTypes, IEnumerable<TemplateType> templateTypes,
IEnumerable<TerrainType> terrainTypes, IEnumerable<OverlayType> overlayTypes, IEnumerable<SmudgeType> smudgeTypes,
IEnumerable<string> eventTypes, IEnumerable<string> cellEventTypes, IEnumerable<string> unitEventTypes, IEnumerable<string> buildingEventTypes, IEnumerable<string> terrainEventTypes,
IEnumerable<string> actionTypes, IEnumerable<string> cellActionTypes, IEnumerable<string> unitActionTypes, IEnumerable<string> buildingActionTypes, IEnumerable<string> terrainActionTypes,
IEnumerable<string> missionTypes, string armedMission, string unarmedMission, string harvestMission, string aircraftMission,
IEnumerable<DirectionType> unitDirectionTypes, IEnumerable<DirectionType> buildingDirectionTypes, IEnumerable<InfantryType> infantryTypes,
IEnumerable<UnitType> unitTypes, IEnumerable<BuildingType> buildingTypes, IEnumerable<TeamMission> teamMissionTypes, IEnumerable<ITechnoType> teamTechnoTypes,
IEnumerable<Waypoint> waypoints, int dropZoneRadius, int gapRadius, int jamRadius, IEnumerable<string> movieTypes, string emptyMovie, IEnumerable<string> themeTypes, string emptyTheme,
int tiberiumOrGoldValue, int gemValue)
{
this.MapSection = new MapSection(cellSize);
this.BasicSection = basicSection;
this.HouseType = houseType;
this.HouseTypesIncludingNone = houseTypes.ToArray();
this.HouseTypes = this.HouseTypesIncludingNone.Where(h => h.ID >= 0).ToArray();
this.FlagColors = flagColors.ToArray();
this.TheaterTypes = new List<TheaterType>(theaterTypes);
this.TemplateTypes = new List<TemplateType>(templateTypes);
this.TerrainTypes = new List<TerrainType>(terrainTypes);
this.OverlayTypes = new List<OverlayType>(overlayTypes);
this.SmudgeTypes = new List<SmudgeType>(smudgeTypes);
this.EventTypes = eventTypes.ToArray();
this.CellEventTypes = cellEventTypes.ToHashSet(StringComparer.OrdinalIgnoreCase);
this.UnitEventTypes = unitEventTypes.ToHashSet(StringComparer.OrdinalIgnoreCase);
this.BuildingEventTypes = buildingEventTypes.ToHashSet(StringComparer.OrdinalIgnoreCase);
this.TerrainEventTypes = terrainEventTypes.ToHashSet(StringComparer.OrdinalIgnoreCase);
this.CellActionTypes = cellActionTypes.ToHashSet(StringComparer.OrdinalIgnoreCase);
this.UnitActionTypes = unitActionTypes.ToHashSet(StringComparer.OrdinalIgnoreCase);
this.BuildingActionTypes = buildingActionTypes.ToHashSet(StringComparer.OrdinalIgnoreCase);
this.TerrainActionTypes = terrainActionTypes.ToHashSet(StringComparer.OrdinalIgnoreCase);
this.ActionTypes = actionTypes.ToArray();
this.MissionTypes = missionTypes.ToArray();
string defMission = this.MissionTypes.Where(m => m.Equals(defaultMission, StringComparison.OrdinalIgnoreCase)).FirstOrDefault() ?? this.MissionTypes.First();
// Unfiltered originals, to ensure this remains correct when cloning.
this.inputMissionArmed = armedMission;
this.inputMissionUnarmed = unarmedMission;
this.inputMissionAircraft = harvestMission;
this.inputMissionHarvest = aircraftMission;
this.DefaultMissionArmed = this.MissionTypes.Where(m => m.Equals(armedMission, StringComparison.OrdinalIgnoreCase)).FirstOrDefault() ?? defMission;
this.DefaultMissionUnarmed = this.MissionTypes.Where(m => m.Equals(unarmedMission, StringComparison.OrdinalIgnoreCase)).FirstOrDefault() ?? defMission;
// Reverts to "Stop" if there are no resources (RA indoor)
this.DefaultMissionHarvest = this.OverlayTypes.Any(ov => ov.IsResource) ? this.MissionTypes.Where(m => m.Equals(harvestMission, StringComparison.OrdinalIgnoreCase)).FirstOrDefault() ?? this.DefaultMissionUnarmed : this.DefaultMissionUnarmed;
// Only "Unload" will make them stay on the spot as expected.
this.DefaultMissionAircraft = this.MissionTypes.Where(m => m.Equals(aircraftMission, StringComparison.OrdinalIgnoreCase)).FirstOrDefault() ?? defMission;
this.UnitDirectionTypes = new List<DirectionType>(unitDirectionTypes);
this.BuildingDirectionTypes = new List<DirectionType>(buildingDirectionTypes);
this.AllInfantryTypes = new List<InfantryType>(infantryTypes);
this.AllUnitTypes = new List<UnitType>(unitTypes);
this.BuildingTypes = new List<BuildingType>(buildingTypes);
this.TeamMissionTypes = teamMissionTypes.ToArray();
this.AllTeamTechnoTypes = new List<ITechnoType>(teamTechnoTypes);
this.MovieEmpty = emptyMovie;
this.MovieTypes = new List<string>(movieTypes);
this.ThemeEmpty = emptyTheme;
this.ThemeTypes = new List<string>(themeTypes);
this.TiberiumOrGoldValue = tiberiumOrGoldValue;
this.GemValue = gemValue;
this.Metrics = new CellMetrics(cellSize);
this.Templates = new CellGrid<Template>(this.Metrics);
this.Overlay = new CellGrid<Overlay>(this.Metrics);
this.Smudge = new CellGrid<Smudge>(this.Metrics);
this.Technos = new OccupierSet<ICellOccupier>(this.Metrics);
this.Buildings = new OccupierSet<ICellOccupier>(this.Metrics);
this.Overlappers = new OverlapperSet<ICellOverlapper>(this.Metrics);
this.triggers = new List<Trigger>();
this.TeamTypes = new List<TeamType>();
this.HousesIncludingNone = this.HouseTypesIncludingNone.Select(t => { House h = (House)Activator.CreateInstance(this.HouseType, t); h.SetDefault(); return h; }).ToArray();
this.Houses = this.HousesIncludingNone.Where(h => h.Type.ID >= 0).ToArray();
this.Waypoints = waypoints.ToArray();
for (int i = 0; i < this.Waypoints.Length; ++i)
{
// Deep clone, with current metric to allow showing waypoints as cell coordinates.
this.Waypoints[i] = new Waypoint(this.Waypoints[i].Name, this.Waypoints[i].Flag, this.Metrics, this.Waypoints[i].Cell);
}
this.DropZoneRadius = dropZoneRadius;
this.GapRadius = gapRadius;
this.RadarJamRadius = jamRadius;
this.CellTriggers = new CellGrid<CellTrigger>(this.Metrics);
this.MapSection.SetDefault();
this.BriefingSection.SetDefault();
this.SteamSection.SetDefault();
this.Templates.Clear();
this.Overlay.Clear();
this.Smudge.Clear();
this.Technos.Clear();
this.Overlappers.Clear();
this.CellTriggers.Clear();
this.TopLeft = new Point(1, 1);
this.Size = this.Metrics.Size - new Size(2, 2);
this.Theater = theater;
this.Overlay.CellChanged += this.Overlay_CellChanged;
this.Technos.OccupierAdded += this.Technos_OccupierAdded;
this.Technos.OccupierRemoved += this.Technos_OccupierRemoved;
this.Buildings.OccupierAdded += this.Buildings_OccupierAdded;
this.Buildings.OccupierRemoved += this.Buildings_OccupierRemoved;
}
public void BeginUpdate()
{
this.updateCount++;
}
public void EndUpdate()
{
if (--this.updateCount == 0)
{
this.Update();
}
}
public void InitTheater(GameType gameType)
{
foreach (TemplateType templateType in this.TemplateTypes.Where(itm => itm.Theaters == null || itm.Theaters.Contains(this.Theater)))
{
templateType.Init(this.Theater);
}
foreach (SmudgeType smudgeType in this.SmudgeTypes.Where(itm => !Globals.FilterTheaterObjects || itm.Theaters == null || itm.Theaters.Contains(this.Theater)))
{
smudgeType.Init(this.Theater);
}
foreach (OverlayType overlayType in this.OverlayTypes.Where(itm => !Globals.FilterTheaterObjects || itm.Theaters == null || itm.Theaters.Contains(this.Theater)))
{
overlayType.Init(gameType, this.Theater);
}
foreach (TerrainType terrainType in this.TerrainTypes.Where(itm => !Globals.FilterTheaterObjects || itm.Theaters == null || itm.Theaters.Contains(this.Theater)))
{
terrainType.Init(this.Theater);
}
// Ignore expansion status for these; they can still be enabled later.
DirectionType infDir = this.UnitDirectionTypes.Where(d => d.Facing == FacingType.South).First();
foreach (InfantryType infantryType in this.AllInfantryTypes)
{
infantryType.Init(gameType, this.Theater, this.HouseTypesIncludingNone.Where(h => h.Equals(infantryType.OwnerHouse)).FirstOrDefault(), infDir);
}
DirectionType unitDir = this.UnitDirectionTypes.Where(d => d.Facing == FacingType.SouthWest).First();
foreach (UnitType unitType in this.AllUnitTypes)
{
unitType.Init(gameType, this.Theater, this.HouseTypesIncludingNone.Where(h => h.Equals(unitType.OwnerHouse)).FirstOrDefault(), unitDir);
}
DirectionType bldDir = this.UnitDirectionTypes.Where(d => d.Facing == FacingType.North).First();
foreach (BuildingType buildingType in this.BuildingTypes.Where(itm => !Globals.FilterTheaterObjects || itm.Theaters == null || itm.Theaters.Contains(this.Theater)))
{
buildingType.Init(gameType, this.Theater, this.HouseTypesIncludingNone.Where(h => h.Equals(buildingType.OwnerHouse)).FirstOrDefault(), bldDir);
}
}
private void Update()
{
this.updating = true;
if (this.invalidateLayers.TryGetValue(MapLayerFlag.Resources, out ISet<Point> locations))
{
this.UpdateResourceOverlays(locations, true);
}
if (this.invalidateLayers.TryGetValue(MapLayerFlag.Walls, out locations))
{
this.UpdateWallOverlays(locations);
}
if (this.invalidateLayers.TryGetValue(MapLayerFlag.Overlay, out locations))
{
this.UpdateConcreteOverlays(locations);
//UpdateConcreteOverlays_ORIG(locations);
}
if (this.invalidateOverlappers)
{
this.Overlappers.Clear();
foreach ((Point location, ICellOccupier techno) in this.Technos)
{
if (techno is ICellOverlapper)
{
this.Overlappers.Add(location, techno as ICellOverlapper);
}
}
}
this.invalidateLayers.Clear();
this.invalidateOverlappers = false;
this.updating = false;
}
/// <summary>
/// Returns the combined value of all resources placed on the map. Can be specified to only calculate inside the set map bounds.
/// This method does not rely on the set icon, but recalculates the density of all resource cells.
/// </summary>
/// <param name="inBounds">Specifically calculate only resources inside the set map bounds.</param>
/// <returns>The combined value of all resources on the map.</returns>
private int GetTotalResources(bool inBounds)
{
int totalResources = 0;
foreach ((Int32 cell, Overlay value) in this.Overlay)
{
Point point;
if (!value.Type.IsResource || !this.Metrics.GetLocation(cell, out point))
{
continue;
}
if (inBounds && !this.Bounds.Contains(point))
{
continue;
}
int adj = 0;
foreach (FacingType facing in CellMetrics.AdjacentFacings)
{
Overlay ovl;
if (this.Metrics.Adjacent(point, facing, out Point adjPoint)
&& (ovl = this.Overlay[adjPoint]) != null && ovl.Type.IsResource)
{
if (inBounds && !this.Bounds.Contains(adjPoint))
{
continue;
}
adj++;
}
}
int thickness = value.Type.IsGem ? gemStages[adj] : tiberiumStages[adj];
// Harvesting has a bug where the final stage returns a value of 0 since it uses the 0-based icon index.
// Harvesting one gem stage fills one bail, plus 3 extra bails. Last stage is 0 (due to that bug), but still gets the extra bails.
if (Globals.ApplyHarvestBug)
{
totalResources += value.Type.IsGem ? thickness * this.GemValue + this.GemValue * 3 : thickness * this.TiberiumOrGoldValue;
}
else
{
// Fixed logic, in case it is repaired in the code.
totalResources += (thickness + 1) * (value.Type.IsGem ? this.GemValue * 4 : this.TiberiumOrGoldValue);
}
}
return totalResources;
}
/// <summary>
/// Update resource overlay to the desired density and randomised type.
/// </summary>
/// <param name="locations">Set of Locations on which changes occurred.</param>
/// <param name="reduceOutOfBounds">True if resources out of bounds are reduced to minimum size and marked to be tinted red.</param>
/// <remarks> This function is separate from GetTotalResources because it only updates the specified areas.</remarks>
public void UpdateResourceOverlays(ISet<Point> locations, bool reduceOutOfBounds)
{
Rectangle checkBounds = reduceOutOfBounds ? this.Bounds : this.Metrics.Bounds;
OverlayType[] tiberiumOrGoldTypes = this.OverlayTypes.Where(t => t.IsTiberiumOrGold).ToArray();
if (tiberiumOrGoldTypes.Length == 0) tiberiumOrGoldTypes = null;
OverlayType[] gemTypes = this.OverlayTypes.Where(t => t.IsGem).ToArray();
if (gemTypes.Length == 0) gemTypes = null;
foreach ((Point location, Overlay overlay) in this.Overlay.IntersectsWithPoints(locations).Where(o => o.Value.Type.IsResource))
{
int count = 0;
bool inBounds = checkBounds.Contains(location);
if (inBounds)
{
foreach (FacingType facing in CellMetrics.AdjacentFacings)
{
if (this.Metrics.Adjacent(location, facing, out Point adjacent) && checkBounds.Contains(adjacent))
{
Overlay adjacentOverlay = this.Overlay[adjacent];
if (adjacentOverlay?.Type.IsResource ?? false)
{
count++;
}
}
}
}
OverlayType ovType = overlay.Type;
if (ovType.IsGem && gemTypes != null)
{
overlay.Type = gemTypes[new Random(randomSeed ^ location.GetHashCode()).Next(gemTypes.Length)];
}
else if (ovType.IsTiberiumOrGold && tiberiumOrGoldTypes != null)
{
overlay.Type = tiberiumOrGoldTypes[new Random(randomSeed ^ location.GetHashCode()).Next(tiberiumOrGoldTypes.Length)];
}
overlay.Tint = inBounds ? Color.White : Color.FromArgb(0x80, 0xFF, 0x80, 0x80);
overlay.Icon = overlay.Type.IsGem ? gemStages[count] : tiberiumStages[count];
}
}
private void UpdateWallOverlays(ISet<Point> locations)
{
foreach ((Point location, Overlay overlay) in this.Overlay.IntersectsWithPoints(locations).Where(o => o.Value.Type.IsWall))
{
Overlay northWall = this.Overlay.Adjacent(location, FacingType.North);
Overlay eastWall = this.Overlay.Adjacent(location, FacingType.East);
Overlay southWall = this.Overlay.Adjacent(location, FacingType.South);
Overlay westWall = this.Overlay.Adjacent(location, FacingType.West);
int icon = 0;
if (northWall?.Type == overlay.Type)
{
icon |= 1;
}
if (eastWall?.Type == overlay.Type)
{
icon |= 2;
}
if (southWall?.Type == overlay.Type)
{
icon |= 4;
}
if (westWall?.Type == overlay.Type)
{
icon |= 8;
}
overlay.Icon = icon;
}
}
private void UpdateConcreteOverlays(ISet<Point> locations)
{
foreach ((Int32 cell, Overlay overlay) in this.Overlay.IntersectsWithCells(locations).Where(o => o.Value.Type.IsConcrete))
{
// Cells to check around the current cell. In order: top, topnext, next, bottomnext, bottom
FacingType[] even = { FacingType.North, FacingType.NorthWest, FacingType.West, FacingType.SouthWest, FacingType.South };
FacingType[] odd = { FacingType.North, FacingType.NorthEast, FacingType.East, FacingType.SouthEast, FacingType.South };
int isodd = cell & 1;
FacingType[] adjCells = isodd != 0 ? odd : even;
Boolean[] conc = new bool[adjCells.Length];
for (int i = 0; i < adjCells.Length; i++)
{
Overlay neighbor = this.Overlay.Adjacent(cell, adjCells[i]);
if (neighbor?.Type == overlay.Type)
{
int ic = overlay.Icon;
if (ic < 4 || (ic > 7 && ic < 12))
{
conc[i] = true;
}
}
}
// Unified logic so the operation becomes identical for the even and odd cells.
// This still isn't a 100% match with the game, but that's because the version in-game is a buggy mess.
bool top = conc[0];
bool topnext = conc[1];
bool next = conc[2];
bool bottomnext = conc[3];
bool bottom = conc[4];
int state = 0;
if (top && next || topnext && next)
{
state = bottom ? 1 : 5;
}
else if (bottom && next || bottom && bottomnext)
{
state = topnext ? 1 : 4;
}
else if (top && topnext)
{
state = 5;
}
// For some reason the odd and even icons for state 0 are swapped compared to all the others, so
// an extra check has to be added for that. Otherwise just "(icon * 2) + 1 - isodd" would suffice.
overlay.Icon = state == 0 ? isodd : (state * 2) + 1 - isodd;
// Possibly todo: add concrete to fill up corners and actually complete the logic as intended?
// This would require that all logic in the editor treats these corner states (2 and 3) as 'expendable',
// automatically overwriting them with other overlay, walls or tiberium, and not saving them in the ini.
}
}
private enum ConcreteEnum
{
C_NONE = -1,
C_LEFT = 0,
C_RIGHT = 1,
C_RIGHT_UPDOWN = 2,
C_LEFT_UPDOWN = 3,
C_UP_RIGHT = 4,
C_UP_LEFT = 5,
C_DOWN_RIGHT = 6,
C_DOWN_LEFT = 7,
C_RIGHT_DOWN = 8,
C_LEFT_DOWN = 9,
C_RIGHT_UP = 10,
C_LEFT_UP = 11,
C_UPDOWN_RIGHT = 12,
C_UPDOWN_LEFT = 13
}
private void UpdateConcreteOverlays_ORIG(ISet<Point> locations)
{
foreach ((Int32 cell, Overlay overlay) in this.Overlay.IntersectsWithCells(locations).Where(o => o.Value.Type.IsConcrete))
{
// Original logic as it is in the game code. Still doesn't match reality, probably due to bugs in the logic to add side cells.
FacingType[] odd = { FacingType.North, FacingType.NorthEast, FacingType.East, FacingType.SouthEast, FacingType.South };
FacingType[] even = { FacingType.North, FacingType.South, FacingType.SouthWest, FacingType.West, FacingType.NorthWest };
int isodd = cell & 1;
FacingType[] cells = isodd != 0 ? odd : even;
int index = 0;
for (int i = 0; i < cells.Length; i++)
{
Overlay neighbor = this.Overlay.Adjacent(cell, cells[i]);
if (neighbor != null && neighbor.Type == overlay.Type)
{
int ic = overlay.Icon;
if (ic < 4 || (ic > 7 && ic < 12))
{
index |= (1 << i);
}
}
}
const int OF_N = 0x01;
const int OF_NE = 0x02;
const int OF_E = 0x04;
const int OF_SE = 0x08;
const int OF_S = 0x10;
const int EF_N = 0x01;
const int EF_NW = 0x10;
const int EF_W = 0x08;
const int EF_SW = 0x04;
const int EF_S = 0x02;
ConcreteEnum icon = 0;
if (isodd != 0)
{
switch (index)
{
case OF_NE:
case OF_N | OF_NE:
case OF_E | OF_N:
case OF_E | OF_NE:
case OF_N | OF_NE | OF_E:
case OF_S | OF_N | OF_NE:
icon = ConcreteEnum.C_RIGHT_UP; // right - up
break;
case OF_SE:
case OF_E | OF_SE:
case OF_S | OF_SE:
case OF_S | OF_E:
case OF_S | OF_SE | OF_E:
case OF_S | OF_SE | OF_N:
icon = ConcreteEnum.C_RIGHT_DOWN; // right - down
break;
case OF_SE | OF_NE:
case OF_SE | OF_NE | OF_N:
case OF_SE | OF_NE | OF_S:
case OF_SE | OF_NE | OF_S | OF_N:
case OF_SE | OF_E | OF_N:
case OF_SE | OF_E | OF_NE | OF_N:
case OF_S | OF_E | OF_N:
case OF_S | OF_E | OF_NE:
case OF_S | OF_E | OF_NE | OF_N:
case OF_S | OF_SE | OF_E | OF_N:
case OF_S | OF_SE | OF_E | OF_NE | OF_N:
case OF_S | OF_SE | OF_E | OF_NE:
icon = ConcreteEnum.C_RIGHT_UPDOWN; // right - up - down
break;
default:
icon = ConcreteEnum.C_RIGHT; // right
break;
}
}
else
{
switch (index)
{
case EF_NW:
case EF_NW | EF_N:
case EF_W | EF_N:
case EF_NW | EF_W | EF_N:
case EF_NW | EF_W:
case EF_NW | EF_S | EF_N:
icon = ConcreteEnum.C_LEFT_UP; // left - up
break;
case EF_SW:
case EF_SW | EF_S:
case EF_W | EF_S:
case EF_W | EF_SW | EF_S:
case EF_W | EF_SW:
case EF_SW | EF_S | EF_N:
icon = ConcreteEnum.C_LEFT_DOWN; // left - down
break;
case EF_NW | EF_SW:
case EF_NW | EF_SW | EF_N:
case EF_NW | EF_SW | EF_S:
case EF_NW | EF_SW | EF_S | EF_N:
case EF_W | EF_S | EF_N:
case EF_W | EF_SW | EF_N:
case EF_W | EF_SW | EF_S | EF_N:
case EF_NW | EF_W | EF_S:
case EF_NW | EF_W | EF_S | EF_N:
case EF_NW | EF_W | EF_SW | EF_S | EF_N:
case EF_NW | EF_W | EF_SW | EF_N:
case EF_NW | EF_W | EF_SW | EF_S:
icon = ConcreteEnum.C_LEFT_UPDOWN; // left - up - down
break;
default:
icon = ConcreteEnum.C_LEFT; // left
break;
}
}
overlay.Icon = (int)icon;
}
}
public void SetMapTemplatesRaw(byte[] data, int width, int height, Dictionary<int,string> types, string fillType)
{
int maxY = Math.Min(this.Metrics.Height, height);
int maxX = Math.Min(this.Metrics.Width, width);
Dictionary<int, TemplateType> replaceTypes = new Dictionary<int, TemplateType>();
Dictionary<int, int> replaceIcons = new Dictionary<int, int>();
int fillIcon;
TemplateType fillTile;
this.SplitTileInfo(fillType, out fillTile, out fillIcon, "fillType", false);
if (fillTile != null)
{
Point? fillPoint = fillTile.GetIconPoint(fillIcon);
if (!fillPoint.HasValue || !fillTile.IsValidIcon(fillPoint.Value))
{
fillIcon = fillTile.GetIconIndex(fillTile.GetFirstValidIcon());
}
}
foreach (KeyValuePair<int, string> kvp in types)
{
string tileType = kvp.Value;
int tileIcon;
TemplateType tile;
this.SplitTileInfo(tileType, out tile, out tileIcon, "types", false);
replaceTypes[kvp.Key] = tile;
if (tile != null)
{
if ((tile.Flag & TemplateTypeFlag.Group) == TemplateTypeFlag.Group)
{
tile = tileIcon >= tile.GroupTiles.Length ?
null : this.TemplateTypes.Where(t => t.Name == tile.GroupTiles[tileIcon]).FirstOrDefault();
replaceTypes[kvp.Key] = tile;
tileIcon = 0;
}
else
{
Point? tilePoint = tile.GetIconPoint(tileIcon);
if (!tilePoint.HasValue || !tile.IsValidIcon(tilePoint.Value))
{
tileIcon = fillTile.GetIconIndex(fillTile.GetFirstValidIcon());
}
}
replaceIcons[kvp.Key] = tileIcon;
}
}
this.Templates.Clear();
int lineOffset = 0;
int stride = width * 4;
for (int y = 0; y < maxY; ++y)
{
int offset = lineOffset;
for (int x = 0; x < maxX; ++x)
{
// ARGB = [BB GG RR AA]
int col = 0xFF << 24 | data[offset + 2] << 16 | data[offset + 1] << 8 | data[offset];
TemplateType curr;
if (replaceTypes.TryGetValue(col, out curr))
{
// If clear terrain, don't bother doing anything; this started with a map clear.
if (curr != null)
{
int icon = replaceIcons.ContainsKey(col) ? replaceIcons[col] : 0;
this.Templates[y, x] = curr == null ? null : new Template { Type = curr, Icon = icon };
}
}
else if (fillTile != null)
{
this.Templates[y, x] = new Template { Type = fillTile, Icon = fillIcon };
}
offset+=4;
}
lineOffset += stride;
}
}
public void SplitTileInfo(string tileType, out TemplateType tile, out int tileIcon, string context, bool safe)
{
tileIcon = 0;
tile = null;
if (tileType == null)
{
return;
}
Match m = tileInfoSplitRegex.Match(tileType);
if (m.Success)
{
tileType = m.Groups[1].Value;
tileIcon = Int32.Parse(m.Groups[2].Value);
}
tile = this.TemplateTypes.Where(t => String.Equals(tileType, t.Name, StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
if (tile == null)
{
if (!safe)
{
throw new ArgumentException(String.Format("Cannot find tile type '{0}'!", tileType), context);
}
}
else if (!tile.Theaters.Contains(this.Theater))
{
if (!safe)
{
throw new ArgumentException(String.Format("Tile type '{0}' does not exist in theater {1}.", tileType, this.Theater.Name), context);
}
else
{
tile = null;
}
}
}
public string GetCellDescription(Point location, Point subPixel)
{
if (!this.Metrics.GetCell(location, out int cell))
{
return "No cell";
}
bool inBounds = this.Bounds.Contains(location);
StringBuilder sb = new StringBuilder();
sb.AppendFormat("X = {0}, Y = {1}, Cell = {2}", location.X, location.Y, cell);
Template template = this.Templates[cell];
TemplateType templateType = template?.Type;
if (templateType != null)
{
sb.AppendFormat(", Template = {0} ({1})", templateType.DisplayName, template.Icon);
}
Smudge smudge = this.Smudge[cell];
SmudgeType smudgeType = smudge?.Type;
if (smudgeType != null)
{
sb.AppendFormat(", Smudge = {0}{1}", smudgeType.DisplayName, smudgeType.IsAutoBib ? " (Attached)" : String.Empty);
}
Overlay overlay = this.Overlay[cell];
OverlayType overlayType = overlay?.Type;
if (overlayType != null)
{
if (overlayType.IsResource)
{
sb.AppendFormat(", Resource = {0} Stage {1}", overlayType.DisplayName, inBounds ? overlay.Icon.ToString() : "-");
}
else
{
sb.AppendFormat(", Overlay = {0}", overlayType.DisplayName);
}
}
Terrain terrain = this.Technos[location] as Terrain;
TerrainType terrainType = terrain?.Type;
if (terrainType != null)
{
sb.AppendFormat(", Terrain = {0}", terrainType.DisplayName);
}
if (this.Technos[location] is InfantryGroup infantryGroup)
{
InfantryStoppingType i = InfantryGroup.ClosestStoppingTypes(subPixel).First();
Infantry inf = infantryGroup.Infantry[(int)i];
if (inf != null)
{
sb.AppendFormat(", Infantry = {0} ({1})", inf.Type.DisplayName, InfantryGroup.GetStoppingTypeName(i));
}
}
Unit unit = this.Technos[location] as Unit;
UnitType unitType = unit?.Type;
if (unitType != null)
{
sb.AppendFormat(", Unit = {0}", unitType.DisplayName);
}
Building building = this.Buildings[location] as Building;
BuildingType buildingType = building?.Type;
if (buildingType != null)
{
sb.AppendFormat(", Building = {0}", buildingType.DisplayName);
}
return sb.ToString();
}
public HouseType GetBaseHouse(GameType gameType)
{
House noneHouse = this.HousesIncludingNone.Where(h => h.Type.ID < 0).FirstOrDefault();
if (noneHouse != null && gameType == GameType.TiberianDawn)
{
return noneHouse.Type;
}
else
{
return this.HouseTypes.Where(h => h.Equals(this.BasicSection.BasePlayer)).FirstOrDefault() ?? this.HouseTypes.First();
}
}
private void RemoveBibs(Building building)
{
Int32[] bibCells = this.Smudge.IntersectsWithCells(building.BibCells).Where(x => x.Value.Type.IsAutoBib).Select(x => x.Cell).ToArray();
foreach (Int32 cell in bibCells)
{
this.Smudge[cell] = null;
}
building.BibCells.Clear();
}
private void AddBibs(Point location, Building building)
{
Dictionary<Point, Smudge> bibPoints = building.GetBib(location, this.SmudgeTypes);
if (bibPoints == null)
{
return;
}
foreach(Point p in bibPoints.Keys)
{
if (this.Metrics.GetCell(p, out int subCell))
{
this.Smudge[subCell] = bibPoints[p];
building.BibCells.Add(subCell);
}
}
}
public Map Clone(bool forPreview)
{
Waypoint[] wpPreview = new Waypoint[this.Waypoints.Length + (forPreview ? 1 : 0)];
Array.Copy(this.Waypoints, wpPreview, this.Waypoints.Length);
if (forPreview)
{
wpPreview[this.Waypoints.Length] = new Waypoint("", null);
}
// This is a shallow clone; the map is new, but the placed contents all still reference the original objects.
// These shallow copies are used for map preview during editing, where dummy objects can be added without any issue.
Map map = new Map(this.BasicSection, this.Theater, this.Metrics.Size, this.HouseType, this.HouseTypesIncludingNone,
this.FlagColors, this.TheaterTypes, this.TemplateTypes, this.TerrainTypes, this.OverlayTypes, this.SmudgeTypes,
this.EventTypes, this.CellEventTypes, this.UnitEventTypes, this.BuildingEventTypes, this.TerrainEventTypes,
this.ActionTypes, this.CellActionTypes, this.UnitActionTypes, this.BuildingActionTypes, this.TerrainActionTypes,
this.MissionTypes, this.inputMissionArmed, this.inputMissionUnarmed, this.inputMissionHarvest, this.inputMissionAircraft,
this.UnitDirectionTypes, this.BuildingDirectionTypes, this.AllInfantryTypes, this.AllUnitTypes, this.BuildingTypes, this.TeamMissionTypes,
this.AllTeamTechnoTypes, wpPreview, this.DropZoneRadius, this.GapRadius, this.RadarJamRadius, this.MovieTypes, this.MovieEmpty, this.ThemeTypes, this.ThemeEmpty,
this.TiberiumOrGoldValue, this.GemValue)
{
TopLeft = TopLeft,
Size = Size
};
map.BeginUpdate();
this.MapSection.CopyTo(map.MapSection);
this.BriefingSection.CopyTo(map.BriefingSection);
this.SteamSection.CopyTo(map.SteamSection);
Array.Copy(this.Houses, map.Houses, map.Houses.Length);
map.Triggers.AddRange(this.Triggers);
this.Templates.CopyTo(map.Templates);
this.Overlay.CopyTo(map.Overlay);
this.Smudge.CopyTo(map.Smudge);
this.CellTriggers.CopyTo(map.CellTriggers);
foreach ((Point location, ICellOccupier occupier) in this.Technos)
{
if (occupier is InfantryGroup infantryGroup)
{
// This creates an InfantryGroup not linked to its infantry, but it is necessary
// to ensure that the real InfantryGroups are not polluted with dummy objects.
InfantryGroup newInfantryGroup = new InfantryGroup();
Array.Copy(infantryGroup.Infantry, newInfantryGroup.Infantry, newInfantryGroup.Infantry.Length);
map.Technos.Add(location, newInfantryGroup);
}
else if (!(occupier is Building))
{
map.Technos.Add(location, occupier);
}
}
foreach ((Point location, ICellOccupier building) in this.Buildings)
{
// Silly side effect: this fixes any building bibs.
map.Buildings.Add(location, building);
}
map.TeamTypes.AddRange(this.TeamTypes);
map.EndUpdate();
return map;
}
public IEnumerable<Trigger> FilterCellTriggers()
{
return this.FilterCellTriggers(this.Triggers);
}
public IEnumerable<Trigger> FilterCellTriggers(IEnumerable<Trigger> triggers)
{
foreach (Trigger trigger in FilterTriggersByEvent(this.CellEventTypes, triggers).Concat(this.FilterTriggersByAction(this.CellActionTypes, triggers).Distinct()))
{
yield return trigger;
}
}
public IEnumerable<Trigger> FilterUnitTriggers()
{
return this.FilterUnitTriggers(this.Triggers);
}
public IEnumerable<Trigger> FilterUnitTriggers(IEnumerable<Trigger> triggers)
{
foreach (Trigger trigger in FilterTriggersByEvent(this.UnitEventTypes, triggers).Concat(this.FilterTriggersByAction(this.UnitActionTypes, triggers).Distinct()))
{
yield return trigger;
}
}
public IEnumerable<Trigger> FilterStructureTriggers()
{
return this.FilterStructureTriggers(this.Triggers);
}
public IEnumerable<Trigger> FilterStructureTriggers(IEnumerable<Trigger> triggers)
{
foreach (Trigger trigger in FilterTriggersByEvent(this.BuildingEventTypes, triggers).Concat(this.FilterTriggersByAction(this.BuildingActionTypes, triggers).Distinct()))
{
yield return trigger;
}
}
public IEnumerable<Trigger> FilterTerrainTriggers()
{
return this.FilterTerrainTriggers(this.Triggers);
}
public IEnumerable<Trigger> FilterTerrainTriggers(IEnumerable<Trigger> triggers)
{
foreach (Trigger trigger in FilterTriggersByEvent(this.TerrainEventTypes, triggers).Concat(this.FilterTriggersByAction(this.TerrainActionTypes, triggers).Distinct()))
{
yield return trigger;
}
}
public static IEnumerable<Trigger> FilterTriggersByEvent(HashSet<String> allowedEventTypes, IEnumerable<Trigger> triggers)
{
foreach (Trigger trig in triggers)
{
if (trig.Event1 != null && allowedEventTypes.Contains(trig.Event1.EventType)
|| ((trig.EventControl == TriggerMultiStyleType.Or || trig.EventControl == TriggerMultiStyleType.And)
&& trig.Event2 != null && allowedEventTypes.Contains(trig.Event2.EventType)))
{
yield return trig;
}
}
}
public static String MakeAllowedTriggersToolTip(string[] filteredEvents, string[] filteredActions)
{
StringBuilder tooltip = new StringBuilder();
bool hasEvents = filteredEvents != null && filteredEvents.Length > 0;
bool hasActions = filteredActions != null && filteredActions.Length > 0;
if (hasEvents)
{
tooltip.Append("Allowed trigger events:\n\u2022 ")
.Append(String.Join("\n\u2022 ", filteredEvents));
if (hasActions)
tooltip.Append('\n');
}
if (hasActions)
{
tooltip.Append("Allowed trigger actions:\n\u2022 ")
.Append(String.Join("\n\u2022 ", filteredActions));
}
return hasEvents || hasActions ? tooltip.ToString() : null;
}
public IEnumerable<Trigger> FilterTriggersByAction(HashSet<String> allowedActionTypes, IEnumerable<Trigger> triggers)
{
foreach (Trigger trig in triggers)
{
if (trig.Action1 != null && allowedActionTypes.Contains(trig.Action1.ActionType)
|| (trig.Action2 != null && allowedActionTypes.Contains(trig.Action2.ActionType)))
{
yield return trig;
}
}
}
public IEnumerable<ITechno> GetAllTechnos()
{
foreach ((Point location, ICellOccupier occupier) in this.Technos)
{
if (occupier is InfantryGroup infantryGroup)
{
foreach (Infantry inf in infantryGroup.Infantry)
{
if (inf != null)
{
yield return inf;
}
}
}
else if (occupier is ITechno techno)
{
yield return techno;
}
}
}
public string GetDefaultMission(ITechnoType techno)
{
return this.GetDefaultMission(techno, this.DefaultMissionArmed);
}
public string GetDefaultMission(ITechnoType techno, String currentMission)
{
if (techno.IsHarvester)
{
return this.DefaultMissionHarvest;
}
if (techno.IsAircraft && !techno.IsFixedWing)
{
// Ground-landable aircraft. Default order should be 'Unload' to make it land on the spot it spawned on.
return this.DefaultMissionAircraft;
}
if (!techno.IsArmed)
{
return this.DefaultMissionUnarmed;
}
// Automatically switch from other default missions to the general 'Guard' one, but don't change custom-picked mission like 'Hunt4.
if (currentMission == this.DefaultMissionHarvest || currentMission == this.DefaultMissionAircraft || currentMission == this.DefaultMissionUnarmed)
{
return this.DefaultMissionArmed;
}
return currentMission;
}
public ICellOccupier FindBlockingObject(int cell, ICellOccupier obj, out int blockingCell)
{
blockingCell = -1;
if (this.Metrics.GetLocation(cell, out Point p))
{
bool[,] mask;
int ylen;
int xlen;
// First check bounds without bib.
if (obj is BuildingType bld)
{
mask = bld.BaseOccupyMask;
ylen = mask.GetLength(0);
xlen = mask.GetLength(1);
for (Int32 y = 0; y < ylen; ++y)
{
for (Int32 x = 0; x < xlen; ++x)
{
if (mask[y, x])
{
if (!this.Metrics.GetCell(new Point(p.X + x, p.Y + y), out int targetCell))
{
blockingCell = -1;
return null;
}
ICellOccupier techno = this.Technos[targetCell];
ICellOccupier b = this.Buildings[targetCell];
if (techno != null || b != null)
{
blockingCell = targetCell;
return techno ?? b;
}
}
}
}
}
mask = obj.OccupyMask;
ylen = mask.GetLength(0);
xlen = mask.GetLength(1);
for (Int32 y = 0; y < ylen; ++y)
{
for (Int32 x = 0; x < xlen; ++x)
{
if (mask[y, x])
{
if (!this.Metrics.GetCell(new Point(p.X + x, p.Y + y), out int targetCell))
{
blockingCell = -1;
return null;
}
ICellOccupier techno = this.Technos[targetCell];
ICellOccupier b = this.Buildings[targetCell];
if (techno != null || b != null)
{
blockingCell = targetCell;
return techno ?? b;
}
}
}
}
}
return null;
}
public TGA GeneratePreview(Size previewSize, GameType gameType, bool renderAll, bool sharpen)
{
MapLayerFlag toRender = MapLayerFlag.Template | (renderAll ? MapLayerFlag.OverlayAll | MapLayerFlag.Smudge | MapLayerFlag.Technos : MapLayerFlag.Resources);
return this.GeneratePreview(previewSize, gameType, toRender, true, true, sharpen);
}
public TGA GeneratePreview(Size previewSize, GameType gameType, MapLayerFlag toRender, bool smooth, bool crop, bool sharpen)
{
Rectangle mapBounds;
HashSet<Point> locations = this.Metrics.Bounds.Points().ToHashSet();;
if (crop)
{
mapBounds = new Rectangle(this.Bounds.Left * Globals.OriginalTileWidth, this.Bounds.Top * Globals.OriginalTileHeight,
this.Bounds.Width * Globals.OriginalTileWidth, this.Bounds.Height * Globals.OriginalTileHeight);
//locations = Bounds.Points().ToHashSet();
}
else
{
mapBounds = new Rectangle(0, 0, this.Metrics.Width * Globals.OriginalTileWidth, this.Metrics.Height * Globals.OriginalTileHeight);
//locations
}
Single previewScale = Math.Min(previewSize.Width / (float)mapBounds.Width, previewSize.Height / (float)mapBounds.Height);
Size scaledSize = new Size((int)(previewSize.Width / previewScale), (int)(previewSize.Height / previewScale));
using (Bitmap fullBitmap = new Bitmap(this.Metrics.Width * Globals.OriginalTileWidth, this.Metrics.Height * Globals.OriginalTileHeight))
using (Bitmap croppedBitmap = new Bitmap(previewSize.Width, previewSize.Height))
{
using (Graphics g = Graphics.FromImage(fullBitmap))
{
MapRenderer.SetRenderSettings(g, smooth);
MapRenderer.Render(gameType, this, g, locations, toRender, 1);
}
using (Graphics g = Graphics.FromImage(croppedBitmap))
{
MapRenderer.SetRenderSettings(g, true);
Matrix transform = new Matrix();
transform.Scale(previewScale, previewScale);
transform.Translate((scaledSize.Width - mapBounds.Width) / 2, (scaledSize.Height - mapBounds.Height) / 2);
g.Transform = transform;
g.Clear(Color.Black);
g.DrawImage(fullBitmap, new Rectangle(0, 0, mapBounds.Width, mapBounds.Height), mapBounds, GraphicsUnit.Pixel);
}
if (sharpen)
{
using (Bitmap sharpenedImage = croppedBitmap.Sharpen(1.0f))
{
return TGA.FromBitmap(sharpenedImage);
}
}
else
{
return TGA.FromBitmap(croppedBitmap);
}
}
}
public TGA GenerateMapPreview(GameType gameType, bool renderAll)
{
return this.GeneratePreview(Globals.MapPreviewSize, gameType, renderAll, false);
}
public TGA GenerateWorkshopPreview(GameType gameType, bool renderAll)
{
return this.GeneratePreview(Globals.WorkshopPreviewSize, gameType, renderAll, true);
}
object ICloneable.Clone()
{
return this.Clone(false);
}
private void Overlay_CellChanged(object sender, CellChangedEventArgs<Overlay> e)
{
if (e.OldValue?.Type.IsWall ?? false)
{
this.Buildings.Remove(e.OldValue);
}
if (e.Value?.Type.IsWall ?? false)
{
this.Buildings.Add(e.Location, e.Value);
}
if (this.updating)
{
return;
}
foreach (Overlay overlay in new Overlay[] { e.OldValue, e.Value })
{
if (overlay == null)
{
continue;
}
MapLayerFlag layer;
if (overlay.Type.IsResource)
{
layer = MapLayerFlag.Resources;
}
else if (overlay.Type.IsWall)
{
layer = MapLayerFlag.Walls;
}
else if (overlay.Type.IsConcrete)
{
layer = MapLayerFlag.Overlay;
}
else
{
continue;
}
if (!this.invalidateLayers.TryGetValue(layer, out ISet<Point> locations))
{
locations = new HashSet<Point>();
this.invalidateLayers[layer] = locations;
}
locations.UnionWith(Rectangle.Inflate(new Rectangle(e.Location, new Size(1, 1)), 1, 1).Points());
}
if (this.updateCount == 0)
{
this.Update();
}
}
private void Technos_OccupierAdded(object sender, OccupierAddedEventArgs<ICellOccupier> e)
{
if (e.Occupier is ICellOverlapper overlapper)
{
if (this.updateCount == 0)
{
this.Overlappers.Add(e.Location, overlapper);
}
else
{
this.invalidateOverlappers = true;
}
}
}
private void Technos_OccupierRemoved(object sender, OccupierRemovedEventArgs<ICellOccupier> e)
{
if (e.Occupier is ICellOverlapper overlapper)
{
if (this.updateCount == 0)
{
this.Overlappers.Remove(overlapper);
}
else
{
this.invalidateOverlappers = true;
}
}
}
private void Buildings_OccupierAdded(object sender, OccupierAddedEventArgs<ICellOccupier> e)
{
if (e.Occupier is Building building)
{
this.Technos.Add(e.Location, e.Occupier, building.Type.BaseOccupyMask);
this.AddBibs(e.Location, building);
}
else
{
this.Technos.Add(e.Location, e.Occupier);
}
}
private void Buildings_OccupierRemoved(object sender, OccupierRemovedEventArgs<ICellOccupier> e)
{
if (e.Occupier is Building building)
{
this.RemoveBibs(building);
}
this.Technos.Remove(e.Occupier);
}
public bool RemoveExpansionUnits()
{
HashSet<Point> refreshPoints = new HashSet<Point>();
bool changed = false;
if (this.BasicSection.ExpansionEnabled)
{
// Expansion is enabled. Nothing to do.
return false;
}
// Technos on map
List<(Point, ICellOccupier)> toDelete = new List<(Point, ICellOccupier)>();
foreach ((Point p, ICellOccupier occup) in this.Technos)
{
if (occup is Unit un)
{
if (un.Type.IsExpansionUnit)
{
toDelete.Add((p, occup));
}
}
else if (occup is InfantryGroup ifg)
{
if (ifg.Infantry.Any(inf => inf != null && inf.Type.IsExpansionUnit))
{
toDelete.Add((p, occup));
}
}
}
foreach ((Point point, ICellOccupier occup) in toDelete)
{
if (occup is Unit un)
{
Rectangle? refreshArea = this.Overlappers[un];
if (refreshArea.HasValue)
{
refreshPoints.UnionWith(refreshArea.Value.Points());
}
//mapPanel.Invalidate(map, un);
this.Technos.Remove(occup);
changed = true;
}
else if (occup is InfantryGroup infantryGroup)
{
Infantry[] inf = infantryGroup.Infantry;
for (int i = 0; i < inf.Length; ++i)
{
if (inf[i] != null && (inf[i].Type.Flag & UnitTypeFlag.IsExpansionUnit) == UnitTypeFlag.IsExpansionUnit)
{
inf[i] = null;
changed = true;
}
}
bool delGroup = inf.All(i => i == null);
Rectangle? refreshArea = this.Overlappers[infantryGroup];
if (refreshArea.HasValue)
{
refreshPoints.UnionWith(refreshArea.Value.Points());
}
//mapPanel.Invalidate(map, infantryGroup);
if (delGroup)
{
this.Technos.Remove(infantryGroup);
}
}
}
// Teamtypes
foreach (TeamType teamtype in this.TeamTypes)
{
List<TeamTypeClass> toRemove = new List<TeamTypeClass>();
foreach (TeamTypeClass ttclass in teamtype.Classes)
{
if ((ttclass.Type is UnitType ut && ut.IsExpansionUnit) || (ttclass.Type is InfantryType it && it.IsExpansionUnit))
{
toRemove.Add(ttclass);
}
}
foreach (TeamTypeClass ttclass in toRemove)
{
teamtype.Classes.Remove(ttclass);
changed = true;
}
}
this.NotifyMapContentsChanged(refreshPoints);
return changed;
}
/// <summary>
/// Applies trigger renames, and does the checks to see whether all attached objects are still valid for the trigger type they are connected to.
/// The undo and redo lists can be used to track these actions, with the celtrigger location keeping track of cell triggers that might get removed by this.
/// </summary>
/// <param name="renameActions">All rename actions</param>
/// <param name="undoList">Undo list, linking objects to their original trigger value</param>
/// <param name="redoList">Redo list, linking objects to their final trigger value</param>
/// <param name="cellTriggerLocations">Locations for all modified cell triggers</param>
/// <param name="newTriggers">Triggers list to use to check for trigger links to objects.</param>
public void ApplyTriggerChanges(List<(String Name1, String Name2)> renameActions, out Dictionary<object, string> undoList, out Dictionary<object, string> redoList, out Dictionary<CellTrigger, int> cellTriggerLocations, List<Trigger> newTriggers)
{
undoList = new Dictionary<object, string>();
redoList = new Dictionary<object, string>();
cellTriggerLocations = new Dictionary<CellTrigger, int>();
foreach ((String name1, String name2) in renameActions)
{
if (Trigger.IsEmpty(name1))
{
continue;
}
foreach ((Point location, Building building) in this.Buildings.OfType<Building>().Where(x => x.Occupier.IsPrebuilt))
{
if (String.Equals(building.Trigger, name1, StringComparison.OrdinalIgnoreCase))
{
if (!undoList.ContainsKey(building))
undoList[building] = building.Trigger;
redoList[building] = name2;
building.Trigger = name2;
}
}
foreach (ITechno techno in this.GetAllTechnos())
{
if (String.Equals(techno.Trigger, name1, StringComparison.OrdinalIgnoreCase))
{
if (!undoList.ContainsKey(techno))
{
undoList[techno] = techno.Trigger;
}
redoList[techno] = name2;
techno.Trigger = name2;
}
}
foreach (TeamType team in this.TeamTypes)
{
if (String.Equals(team.Trigger, name1, StringComparison.OrdinalIgnoreCase))
{
if (!undoList.ContainsKey(team))
{
undoList[team] = team.Trigger;
}
redoList[team] = name2;
team.Trigger = name2;
}
}
foreach ((int cell, CellTrigger value) in this.CellTriggers)
{
if (String.Equals(value.Trigger, name1, StringComparison.OrdinalIgnoreCase))
{
if (!undoList.ContainsKey(value))
{
undoList[value] = value.Trigger;
}
redoList[value] = name2;
// if name2 is "None", the post-trigger-assignment sweep will clean this up.
value.Trigger = name2;
if (!cellTriggerLocations.ContainsKey(value))
{
cellTriggerLocations[value] = cell;
}
}
}
}
this.CleanUpTriggers(newTriggers, undoList, redoList, cellTriggerLocations);
}
private void CleanUpTriggers(List<Trigger> triggers, Dictionary<object, string> undoList, Dictionary<object, string> redoList, Dictionary<CellTrigger, int> cellTriggerLocations)
{
// Clean techno types
HashSet<string> availableTriggers = triggers.Select(t => t.Name).ToHashSet(StringComparer.OrdinalIgnoreCase);
HashSet<string> availableUnitTriggers = this.FilterUnitTriggers(triggers).Select(t => t.Name).ToHashSet(StringComparer.OrdinalIgnoreCase);
HashSet<string> availableBuildingTriggers = this.FilterStructureTriggers(triggers).Select(t => t.Name).ToHashSet(StringComparer.OrdinalIgnoreCase);
HashSet<string> availableTerrainTriggers = this.FilterTerrainTriggers(triggers).Select(t => t.Name).ToHashSet(StringComparer.OrdinalIgnoreCase);
foreach (ITechno techno in this.GetAllTechnos())
{
if (techno is Infantry infantry)
{
this.CheckTechnoTrigger(infantry, availableUnitTriggers, undoList, redoList);
}
else if (techno is Unit unit)
{
this.CheckTechnoTrigger(unit, availableUnitTriggers, undoList, redoList);
}
else if (techno is Building building)
{
this.CheckTechnoTrigger(building, availableBuildingTriggers, undoList, redoList);
}
else if (techno is Terrain terrain)
{
this.CheckTechnoTrigger(terrain, availableTerrainTriggers, undoList, redoList);
}
}
// Clean teamtypes
foreach (TeamType team in this.TeamTypes)
{
String trig = team.Trigger;
if (!Trigger.IsEmpty(trig) && !availableUnitTriggers.Contains(trig))
{
if (undoList != null && !undoList.ContainsKey(team))
{
undoList.Add(team, trig);
}
if (redoList != null)
{
redoList[team] = Trigger.None;
}
team.Trigger = Trigger.None;
}
}
// Clean triggers. Not covered in undo/redo actions since it is applied on the new list directly.
foreach (Trigger trig in triggers)
{
if (trig == null)
{
continue;
}
if (trig.Action1.Trigger != Trigger.None && !availableTriggers.Contains(trig.Action1.Trigger))
{
trig.Action1.Trigger = Trigger.None;
}
if (trig.Action2.Trigger != Trigger.None && !availableTriggers.Contains(trig.Action2.Trigger))
{
trig.Action2.Trigger = Trigger.None;
}
}
this.CleanUpCellTriggers(triggers, undoList, redoList, cellTriggerLocations);
}
private void CheckTechnoTrigger(ITechno techno, HashSet<String> availableTriggers, Dictionary<object, string> undoList, Dictionary<object, string> redoList)
{
String trig = techno.Trigger;
if (!Trigger.IsEmpty(trig) && !availableTriggers.Contains(trig))
{
if (undoList != null && !undoList.ContainsKey(techno))
{
undoList.Add(techno, trig);
}
if (redoList != null)
{
redoList[techno] = Trigger.None;
}
techno.Trigger = Trigger.None;
}
}
private void CleanUpCellTriggers(List<Trigger> triggers, Dictionary<object, string> undoList, Dictionary<object, string> redoList, Dictionary<CellTrigger, int> cellTriggerLocations)
{
HashSet<string> placeableTrigs = this.FilterCellTriggers(triggers).Select(t => t.Name).ToHashSet(StringComparer.OrdinalIgnoreCase);
List<int> cellsToClear = new List<int>();
foreach ((int cell, CellTrigger value) in this.CellTriggers)
{
if (Trigger.IsEmpty(value.Trigger) || !placeableTrigs.Contains(value.Trigger))
{
if (undoList != null && !undoList.ContainsKey(value))
{
undoList.Add(value, value.Trigger);
}
if (redoList != null)
{
redoList[value] = Trigger.None;
}
cellTriggerLocations[value] = cell;
cellsToClear.Add(cell);
}
}
for (int i = 0; i < cellsToClear.Count; ++i)
{
this.CellTriggers[cellsToClear[i]] = null;
}
}
public void ApplyTeamTypeRenames(List<(String Name1, String Name2)> renameActions)
{
foreach ((String name1, String name2) in renameActions)
{
if (TeamType.IsEmpty(name1))
{
continue;
}
foreach (Trigger trigger in this.triggers)
{
if (String.Equals(trigger.Event1.Team, name1, StringComparison.OrdinalIgnoreCase))
{
trigger.Event1.Team = name2;
}
if (String.Equals(trigger.Event2.Team, name1, StringComparison.OrdinalIgnoreCase))
{
trigger.Event2.Team = name2;
}
if (String.Equals(trigger.Action1.Team, name1, StringComparison.OrdinalIgnoreCase))
{
trigger.Action1.Team = name2;
}
if (String.Equals(trigger.Action2.Team, name1, StringComparison.OrdinalIgnoreCase))
{
trigger.Action2.Team = name2;
}
}
}
}
public IEnumerable<string> AssessPower(HashSet<string> housesWithProd)
{
Dictionary<String, int[]> powerWithUnbuilt = new Dictionary<string, int[]>(StringComparer.CurrentCultureIgnoreCase);
Dictionary<String, int[]> powerWithoutUnbuilt = new Dictionary<string, int[]>(StringComparer.CurrentCultureIgnoreCase);
foreach (HouseType house in this.HouseTypes)
{
if (housesWithProd.Contains(house.Name))
{
powerWithUnbuilt[house.Name] = new int[2];
}
powerWithoutUnbuilt[house.Name] = new int[2];
}
foreach ((_, Building bld) in this.Buildings.OfType<Building>())
{
int bldUsage = bld.Type.PowerUsage;
int bldProd = bld.Type.PowerProduction;
int[] housePwr;
// These should all belong to the "rebuild house" due to internal property change listeners; no need to explicitly check.
if (!bld.IsPrebuilt)
{
foreach (string house in housesWithProd)
{
if (powerWithUnbuilt.TryGetValue(house, out housePwr))
{
housePwr[0] += bldUsage;
housePwr[1] += bldProd;
}
}
}
else if (powerWithUnbuilt.TryGetValue(bld.House.Name, out housePwr))
{
housePwr[0] += bldUsage;
housePwr[1] += bldProd;
}
if (bld.IsPrebuilt && powerWithoutUnbuilt.TryGetValue(bld.House.Name, out housePwr))
{
housePwr[0] += bldUsage;
housePwr[1] += bldProd;
}
}
List<string> info = new List<string>();
List<string> prodHouses = new List<string>();
foreach (HouseType house in this.HouseTypes)
{
if (housesWithProd.Contains(house.Name))
{
prodHouses.Add(house.Name);
}
}
info.Add("Production-capable Houses: " + (prodHouses.Count == 0 ? "None" : String.Join(", ", prodHouses.ToArray())));
foreach (HouseType house in this.HouseTypes)
{
int[] housePwrAll;
int[] housePwrBuilt;
if (powerWithUnbuilt.TryGetValue(house.Name, out housePwrAll))
{
powerWithoutUnbuilt.TryGetValue(house.Name, out housePwrBuilt);
int houseUsage = housePwrAll[0]; // PowerUsage;
int houseProd = housePwrAll[1]; // PowerProduction;
int houseUsageBuilt = housePwrBuilt[0]; // PowerUsage;
int houseProdBuilt = housePwrBuilt[1]; // PowerProduction;
String houseInfo = String.Format("{0}: {1} - Produces {2}, uses {3}. (Without unbuilt: {4} - Produces {5}, uses {6}.)",
house.Name, houseProd < houseUsage ? "!!" : "OK", houseProd, houseUsage,
houseProdBuilt < houseUsageBuilt ? "!!" : "OK", houseProdBuilt, houseUsageBuilt);
info.Add(houseInfo);
}
else if (powerWithoutUnbuilt.TryGetValue(house.Name, out housePwrBuilt))
{
int houseUsageBuilt = housePwrBuilt[0]; // PowerUsage;
int houseProdBuilt = housePwrBuilt[1]; // PowerProduction;
String houseInfo = String.Format("{0}: {1} - Produces {2}, uses {3}.",
house.Name, houseProdBuilt < houseUsageBuilt ? "!!" : "OK", houseProdBuilt, houseUsageBuilt);
info.Add(houseInfo);
}
}
return info;
}
public IEnumerable<string> AssessStorage(HashSet<string> housesWithProd)
{
Dictionary<String, int> storageWithUnbuilt = new Dictionary<string, int>(StringComparer.CurrentCultureIgnoreCase);
Dictionary<String, int> storageWithoutUnbuilt = new Dictionary<string, int>(StringComparer.CurrentCultureIgnoreCase);
foreach (HouseType house in this.HouseTypes)
{
if (housesWithProd.Contains(house.Name))
{
storageWithUnbuilt[house.Name] = 0;
}
storageWithoutUnbuilt[house.Name] = 0;
}
foreach ((_, Building bld) in this.Buildings.OfType<Building>())
{
int bldStorage = bld.Type.Storage;
if (!bld.IsPrebuilt)
{
foreach (string house in housesWithProd)
{
if (storageWithUnbuilt.ContainsKey(house))
{
storageWithUnbuilt[house] += bldStorage;
}
}
}
else if (storageWithUnbuilt.ContainsKey(bld.House.Name))
{
storageWithUnbuilt[bld.House.Name] += bldStorage;
}
if (bld.IsPrebuilt && storageWithoutUnbuilt.ContainsKey(bld.House.Name))
{
storageWithoutUnbuilt[bld.House.Name] += bldStorage;
}
}
List<string> info = new List<string>();
List<string> prodHouses = new List<string>();
foreach (HouseType house in this.HouseTypes)
{
if (housesWithProd.Contains(house.Name))
{
prodHouses.Add(house.Name);
}
}
info.Add("Production-capable Houses: " + (prodHouses.Count == 0 ? "None" : String.Join(", ", prodHouses.ToArray())));
foreach (HouseType house in this.HouseTypes)
{
int houseStorageBuilt;
if (storageWithUnbuilt.TryGetValue(house.Name, out int houseStorageAll))
{
storageWithoutUnbuilt.TryGetValue(house.Name, out houseStorageBuilt);
String houseInfo = String.Format("{0}: Storage capacity: {1}. (Without unbuilt: {2})",
house.Name, houseStorageAll, houseStorageBuilt);
info.Add(houseInfo);
}
else if (storageWithoutUnbuilt.TryGetValue(house.Name, out houseStorageBuilt))
{
String houseInfo = String.Format("{0}: Storage capacity: {1}.",
house.Name, houseStorageBuilt);
info.Add(houseInfo);
}
}
return info;
}
public void ResetCachedGraphics()
{
// Dispose of cached images. This is non-destructive; the type objects themselves
// don't actually get disposed. Their thumbnail simply gets disposed and cleared.
foreach (ITechnoType technoType in this.AllTeamTechnoTypes)
{
// units, boats, aircraft, infantry
technoType.Reset();
}
// probably not needed since it's in the team techno types.
foreach (UnitType unitType in this.AllUnitTypes)
{
unitType.Reset();
}
// probably not needed since it's in the team techno types.
foreach (InfantryType infantryType in this.AllInfantryTypes)
{
infantryType.Reset();
}
foreach (BuildingType buildingType in this.BuildingTypes)
{
buildingType.Reset();
}
foreach (TemplateType template in this.TemplateTypes)
{
template.Reset();
}
foreach (TerrainType terrainType in this.TerrainTypes)
{
terrainType.Reset();
}
foreach (OverlayType overlayType in this.OverlayTypes)
{
overlayType.Reset();
}
foreach (SmudgeType smudgeType in this.SmudgeTypes)
{
smudgeType.Reset();
}
}
}
}