diff --git a/CnCTDRAMapEditor/Controls/ObjectProperties.cs b/CnCTDRAMapEditor/Controls/ObjectProperties.cs index aff0532..a1ef614 100644 --- a/CnCTDRAMapEditor/Controls/ObjectProperties.cs +++ b/CnCTDRAMapEditor/Controls/ObjectProperties.cs @@ -138,8 +138,8 @@ namespace MobiusEditor.Controls case Building building: isOnMap = building.IsPrebuilt; items = Plugin.Map.FilterStructureTriggers().Select(t => t.Name).Distinct().ToArray(); - filteredEvents = Plugin.Map.EventTypes.Where(ac => Plugin.Map.StructureEventTypes.Contains(ac)).Distinct().ToArray(); - filteredActions = Plugin.Map.ActionTypes.Where(ac => Plugin.Map.StructureActionTypes.Contains(ac)).Distinct().ToArray(); + filteredEvents = Plugin.Map.EventTypes.Where(ac => Plugin.Map.BuildingEventTypes.Contains(ac)).Distinct().ToArray(); + filteredActions = Plugin.Map.ActionTypes.Where(ac => Plugin.Map.BuildingActionTypes.Contains(ac)).Distinct().ToArray(); break; default: items = Plugin.Map.Triggers.Select(t => t.Name).Distinct().ToArray(); diff --git a/CnCTDRAMapEditor/Dialogs/MapSettingsDialog.cs b/CnCTDRAMapEditor/Dialogs/MapSettingsDialog.cs index 67cb018..b011a62 100644 --- a/CnCTDRAMapEditor/Dialogs/MapSettingsDialog.cs +++ b/CnCTDRAMapEditor/Dialogs/MapSettingsDialog.cs @@ -254,7 +254,8 @@ namespace MobiusEditor.Dialogs briefingSettingsTracker.TryGetMember("Briefing", out object brf); if (brf is String brief) { - if (!plugin.EvaluateBriefing(brief, out String message) && !String.IsNullOrEmpty(message)) + string message = plugin.EvaluateBriefing(brief); + if (message != null) { message += "\n\nPress Cancel to go back and edit the briefing, or OK to ignore the issue and continue."; DialogResult dres = MessageBox.Show(message, "Warning", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button1); diff --git a/CnCTDRAMapEditor/Interface/ICellOccupier.cs b/CnCTDRAMapEditor/Interface/ICellOccupier.cs index 80c11ac..cfeb494 100644 --- a/CnCTDRAMapEditor/Interface/ICellOccupier.cs +++ b/CnCTDRAMapEditor/Interface/ICellOccupier.cs @@ -16,6 +16,7 @@ namespace MobiusEditor.Interface { public interface ICellOccupier { + /// Footprint of this object, determining where no other objects can be placed. bool[,] OccupyMask { get; } } } diff --git a/CnCTDRAMapEditor/Interface/ICellOverlapper.cs b/CnCTDRAMapEditor/Interface/ICellOverlapper.cs index 7a69f3b..4656591 100644 --- a/CnCTDRAMapEditor/Interface/ICellOverlapper.cs +++ b/CnCTDRAMapEditor/Interface/ICellOverlapper.cs @@ -18,7 +18,9 @@ namespace MobiusEditor.Interface { public interface ICellOverlapper { + /// Rectangular bounds of this overlapper. Rectangle OverlapBounds { get; } + /// Determines for each cell whether other graphics drawn under this one are considered to be mostly visible. bool[,] OpaqueMask { get; } } } diff --git a/CnCTDRAMapEditor/Interface/IGamePlugin.cs b/CnCTDRAMapEditor/Interface/IGamePlugin.cs index 0c593ee..764ecad 100644 --- a/CnCTDRAMapEditor/Interface/IGamePlugin.cs +++ b/CnCTDRAMapEditor/Interface/IGamePlugin.cs @@ -38,42 +38,113 @@ namespace MobiusEditor.Interface public interface IGamePlugin : IDisposable { + /// Name of the game. string Name { get; } + /// The game type as enum. GameType GameType { get; } + /// True if the plugin is initialised to handle a megamap. bool IsMegaMap { get; } + /// The map. Map Map { get; } + /// The map image to show. Image MapImage { get; } + /// Feedback handler that can be attached to the plugin. IFeedBackHandler FeedBackHandler { get; set; } + /// True if the currently loaded map was modified. bool Dirty { get; set; } + /// Extra ini text that can be freely edited by the user. string ExtraIniText { get; set; } + /// + /// Create a new map in the chosen theater. + /// + /// The name of the theater to use. void New(string theater); + /// + /// Load a map. + /// + /// Path of the map to load. + /// File type of the actual file in the path, so accompanying files can be loaded correctly. + /// Any issues encountered when loading the map. IEnumerable Load(string path, FileType fileType); + /// + /// Save the current map to the given path, with the given file type. + /// + /// Path of the map to save. + /// File type of the actual file in the path, so accompanying files can be saved correctly. + /// true if the saving succeeded. bool Save(string path, FileType fileType); + /// + /// Save the current map to the given path, with the given file type. + /// + /// Path of the map to save. + /// File type of the actual file in the path, so accompanying files can be saved correctly. + /// Custom preview given to the map. + /// True to not resave the preview on disc when doing the save operation. + /// true if the saving succeeded. bool Save(string path, FileType fileType, Bitmap customPreview, bool dontResavePreview); + /// + /// Validate the map to see if there are any blocking errors preventing it from saving. + /// + /// true if the validation succeeded. string Validate(); + /// + /// Generates an overview of how many items are on the map and how many are allowed, and does a trigger analysis. + /// + /// The generated map items overview. IEnumerable AssessMapItems(); + /// + /// Retrieves a hash set of all houses for which production is started by triggers. + /// + /// A hash set of all houses for which production is started by triggers. HashSet GetHousesWithProduction(); + /// + /// Returns an array containing the reveal radius for each waypoint on the map. + /// + /// The map to gheck the waypoints on. + /// False for small flare reveal, true for large area reveal. + /// int[] GetRevealRadiusForWaypoints(Map map, bool forLargeReveal); + /// + /// Check whether there are any errors in the currect scripting. + /// + /// List of triggers to check. + /// True to fetch extra data from the map, such as map objects that triggers can be linked to. + /// True to prefix the trigger name before every line. If false, each trigger's analysis will be preceded with a header line containing the trigger name. + /// True to report fatal issues only. + /// Returns true if fatal issues were encountered. + /// True to fix issues that are encountered whenever possible. + /// Returns true if issues were fixed. + /// A summation of the encountered issues. IEnumerable CheckTriggers(IEnumerable triggers, bool includeExternalData, bool prefixNames, bool fatalOnly, out bool fatal, bool fix, out bool wasFixed); + /// + /// Checks whether the name has a default map name that is considered empty by this game plugin. + /// + /// Map name to check. + /// True if the given name is considered empty by this game plugin. bool MapNameIsEmpty(string name); - Boolean EvaluateBriefing(string briefing, out string message); + /// + /// Checks whether the briefing has any kind of issues concerning length or supported characters. + /// + /// The briefing to check + /// Null if everything is okay, otherwise any issues to show on the user interface. + string EvaluateBriefing(string briefing); } } diff --git a/CnCTDRAMapEditor/Interface/ITeamColor.cs b/CnCTDRAMapEditor/Interface/ITeamColor.cs index 7d39c18..42442bb 100644 --- a/CnCTDRAMapEditor/Interface/ITeamColor.cs +++ b/CnCTDRAMapEditor/Interface/ITeamColor.cs @@ -12,10 +12,22 @@ namespace MobiusEditor.Interface { public interface ITeamColor { + /// Name of the color, used as identifier. string Name { get; } + /// A general color representing this team color. Color BaseColor { get; } + + /// + /// Apply this color to a given image. + /// + /// The image to process. void ApplyToImage(Bitmap image); + + /// + /// Apply this color to a given image, and output the actually-used bounds of the graphics. + /// + /// The image to process. + /// The actually-used nontransparent area of the graphics that need to be painted. void ApplyToImage(Bitmap image, out Rectangle opaqueBounds); - void ApplyToImage(byte[] bytes, int width, int height, int bytesPerPixel, int stride, Rectangle? opaqueBounds); } } \ No newline at end of file diff --git a/CnCTDRAMapEditor/Interface/ITechnoType.cs b/CnCTDRAMapEditor/Interface/ITechnoType.cs index 950fe21..41fb40d 100644 --- a/CnCTDRAMapEditor/Interface/ITechnoType.cs +++ b/CnCTDRAMapEditor/Interface/ITechnoType.cs @@ -16,11 +16,17 @@ namespace MobiusEditor.Interface { public interface ITechnoType: IBrowsableType { + /// Object ID sbyte ID { get; } + /// Object ini name string Name { get; } + /// True if this object has a weapon. This affects the default orders for placing it on the map. bool IsArmed { get; } + /// True if this object is an aircraft, and is normally not placeable on the map. bool IsAircraft { get; } + /// True if this object is a fixed-wing aircraft. This treats it as 16-frame rotation, and affects the default orders for placing it on the map. bool IsFixedWing { get; } + /// True if this object can harvest resources. This affects the default orders for placing it on the map. bool IsHarvester { get; } } } diff --git a/CnCTDRAMapEditor/Model/BuildingType.cs b/CnCTDRAMapEditor/Model/BuildingType.cs index 40fecb0..5796b2f 100644 --- a/CnCTDRAMapEditor/Model/BuildingType.cs +++ b/CnCTDRAMapEditor/Model/BuildingType.cs @@ -24,14 +24,23 @@ namespace MobiusEditor.Model [Flags] public enum BuildingTypeFlag { + /// No flags set. None = 0, + /// Produces structures. Factory = (1 << 0), + /// Has a bib attached. Bib = (1 << 1), + /// Is a fake building. Fake = (1 << 2), + /// Has a rotating turret, and accepts a Facing value in the ini file. Turret = (1 << 3), + /// Only has a single frame of graphics. SingleFrame = (1 << 4), + /// Does not adjust to house colors. NoRemap = (1 << 5), + /// Is flat on the ground; anything around will overlap this building's graphics. Flat = (1 << 6), + /// Can show a gap area-of-effect radius indicator. IsGapGenerator = (1 << 7), } @@ -70,7 +79,8 @@ namespace MobiusEditor.Model get { return (Flag & BuildingTypeFlag.Bib) == BuildingTypeFlag.Bib; } set { - if (value) + // Bibs are only supported for widths 2 to 4 + if (value && Size.Width >= 2 && Size.Width <= 4) { Flag |= BuildingTypeFlag.Bib; } @@ -118,7 +128,8 @@ namespace MobiusEditor.Model this.OwnerHouse = ownerHouse; this.Theaters = theaters; this.FactoryOverlay = factoryOverlay; - this.RecalculateBibs(); + // Check on width and disable if needed. This also calls RecalculateBibs. + this.HasBib = this.HasBib; } public BuildingType(sbyte id, string name, string textId, int powerProd, int powerUse, int width, int height, string occupyMask, string ownerHouse, TheaterType[] theaters, String graphicsSource, BuildingTypeFlag flag) diff --git a/CnCTDRAMapEditor/Model/Map.cs b/CnCTDRAMapEditor/Model/Map.cs index e621ae6..c2104be 100644 --- a/CnCTDRAMapEditor/Model/Map.cs +++ b/CnCTDRAMapEditor/Model/Map.cs @@ -79,14 +79,14 @@ namespace MobiusEditor.Model public PropertyDescriptor PropertyDescriptor { get; private set; } - public Map Map => Instance as Map; + public Map Map => this.Instance as Map; public readonly bool FractionalPercentages; public MapContext(Map map, bool fractionalPercentages) { - Instance = map; - FractionalPercentages = fractionalPercentages; + this.Instance = map; + this.FractionalPercentages = fractionalPercentages; } public object GetService(Type serviceType) => null; @@ -168,24 +168,24 @@ namespace MobiusEditor.Model public readonly SteamSection SteamSection = new SteamSection(); - public TheaterType Theater { get => MapSection.Theater; set => MapSection.Theater = value; } + public TheaterType Theater { get => this.MapSection.Theater; set => this.MapSection.Theater = value; } public Point TopLeft { - get => new Point(MapSection.X, MapSection.Y); - set { MapSection.X = value.X; MapSection.Y = value.Y; } + 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(MapSection.Width, MapSection.Height); - set { MapSection.Width = value.Width; MapSection.Height = value.Height; } + 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(TopLeft, Size); - set { MapSection.X = value.Left; MapSection.Y = value.Top; MapSection.Width = value.Width; MapSection.Height = value.Height; } + 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; @@ -209,13 +209,13 @@ namespace MobiusEditor.Model public readonly string[] EventTypes; public readonly HashSet CellEventTypes; public readonly HashSet UnitEventTypes; - public readonly HashSet StructureEventTypes; + public readonly HashSet BuildingEventTypes; public readonly HashSet TerrainEventTypes; public readonly string[] ActionTypes; public readonly HashSet CellActionTypes; public readonly HashSet UnitActionTypes; - public readonly HashSet StructureActionTypes; + public readonly HashSet BuildingActionTypes; public readonly HashSet TerrainActionTypes; public readonly string[] MissionTypes; @@ -240,11 +240,11 @@ namespace MobiusEditor.Model { get { - if (BasicSection == null || !BasicSection.ExpansionEnabled) + if (this.BasicSection == null || !this.BasicSection.ExpansionEnabled) { - return AllInfantryTypes.Where(inf => !inf.IsExpansionUnit).ToList(); + return this.AllInfantryTypes.Where(inf => !inf.IsExpansionUnit).ToList(); } - return AllInfantryTypes.ToList(); + return this.AllInfantryTypes.ToList(); } } @@ -253,11 +253,11 @@ namespace MobiusEditor.Model { get { - if (BasicSection == null || !BasicSection.ExpansionEnabled) + if (this.BasicSection == null || !this.BasicSection.ExpansionEnabled) { - return AllUnitTypes.Where(un => !un.IsExpansionUnit).ToList(); + return this.AllUnitTypes.Where(un => !un.IsExpansionUnit).ToList(); } - return AllUnitTypes.ToList(); + return this.AllUnitTypes.ToList(); } } @@ -268,11 +268,11 @@ namespace MobiusEditor.Model { get { - if (BasicSection == null || !BasicSection.ExpansionEnabled) + if (this.BasicSection == null || !this.BasicSection.ExpansionEnabled) { - return AllTeamTechnoTypes.Where(tc => (tc is UnitType ut && !ut.IsExpansionUnit) || (tc is InfantryType it && !it.IsExpansionUnit)).ToList(); + return this.AllTeamTechnoTypes.Where(tc => (tc is UnitType ut && !ut.IsExpansionUnit) || (tc is InfantryType it && !it.IsExpansionUnit)).ToList(); } - return AllTeamTechnoTypes.ToList(); + return this.AllTeamTechnoTypes.ToList(); } } @@ -321,14 +321,14 @@ namespace MobiusEditor.Model private List triggers; public List Triggers { - get { return triggers; } + get { return this.triggers; } set { - triggers = value; + 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(); - NotifyTriggersUpdate(); + this.NotifyTriggersUpdate(); } } @@ -343,23 +343,248 @@ namespace MobiusEditor.Model public readonly string ThemeEmpty; public readonly List ThemeTypes; + ///The value for the basic resource on the map. public int TiberiumOrGoldValue { get; set; } + /// The value for the high-value resource on the map. public int GemValue { get; set; } - public int TotalResources + /// Gets the total amount of resources on the map inside the map border. + public int ResourcesOnMap { get { - return GetTotalResources(false); + return this.GetTotalResources(true); } } - public int ResourcesInBounds + /// + /// Initialises a new Map object. + /// + /// ini [Basic] section in the specific object type for this game. + /// Theater of the map. + /// Size of the map, in cells. + /// Type to use for creating House objects from houseTypes. + /// The list of house types. + /// The list of team colors to use for multiplayer start location indicators. + /// The list of all theaters supported by this game. + /// The list of all template types supported by this game. + /// The list of all terrain types supported by this game. + /// The list of all overlay types supported by this game. + /// The list of all smudge types supported by this game. + /// The list of all event types supported by this game. + /// The list of all event types applicable to cells in this game. + /// The list of all event types applicable to units in this game. + /// The list of all event types applicable to buildings in this game. + /// The list of all event types applicable to terrain in this game. + /// The list of all action types supported by this game. + /// The list of all action types applicable to cells in this game. + /// The list of all action types applicable to units in this game. + /// The list of all action types applicable to buildings in this game. + /// The list of all action types applicable to terrain in this game. + /// The list of all mission types (orders) supported by this game. + /// The default mission for armed units. + /// The default mission for unarmed units. + /// The default mission for harvesting units. + /// The default mission for air units. + /// The list of all direction types applicable to units. + /// The list of all direction types applicable to structures. + /// The list of all infantry types. + /// The list of all unit types. + /// The list of all building types. + /// The list of all mission types (orders) usable by teams. + /// The list of all techno types usable in teams. + /// The list of waypoints. + /// The radius that is revealed around a dropzone waypoint. + /// The radius that is affected by a gap generator. + /// The radius that is affected by a radar jammer. + /// The list of all movies usable by this game. + /// The name to use for detecting and saving an empty movie entry. + /// The list of all music usable by this game. + /// The name to use for detecting and saving an empty music entry. + /// The value for the basic resource on the map. + /// The value for the high-value resource on the map. + public Map(BasicSection basicSection, TheaterType theater, Size cellSize, Type houseType, IEnumerable houseTypes, + IEnumerable flagColors, IEnumerable theaterTypes, IEnumerable templateTypes, + IEnumerable terrainTypes, IEnumerable overlayTypes, IEnumerable smudgeTypes, + IEnumerable eventTypes, IEnumerable cellEventTypes, IEnumerable unitEventTypes, IEnumerable buildingEventTypes, IEnumerable terrainEventTypes, + IEnumerable actionTypes, IEnumerable cellActionTypes, IEnumerable unitActionTypes, IEnumerable buildingActionTypes, IEnumerable terrainActionTypes, + IEnumerable missionTypes, string armedMission, string unarmedMission, string harvestMission, string aircraftMission, + IEnumerable unitDirectionTypes, IEnumerable buildingDirectionTypes, IEnumerable infantryTypes, + IEnumerable unitTypes, IEnumerable buildingTypes, IEnumerable teamMissionTypes, IEnumerable teamTechnoTypes, + IEnumerable waypoints, int dropZoneRadius, int gapRadius, int jamRadius, IEnumerable movieTypes, string emptyMovie, IEnumerable themeTypes, string emptyTheme, + int tiberiumOrGoldValue, int gemValue) { - get + 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(theaterTypes); + this.TemplateTypes = new List(templateTypes); + this.TerrainTypes = new List(terrainTypes); + this.OverlayTypes = new List(overlayTypes); + this.SmudgeTypes = new List(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(unitDirectionTypes); + this.BuildingDirectionTypes = new List(buildingDirectionTypes); + this.AllInfantryTypes = new List(infantryTypes); + this.AllUnitTypes = new List(unitTypes); + this.BuildingTypes = new List(buildingTypes); + this.TeamMissionTypes = teamMissionTypes.ToArray(); + this.AllTeamTechnoTypes = new List(teamTechnoTypes); + this.MovieEmpty = emptyMovie; + this.MovieTypes = new List(movieTypes); + this.ThemeEmpty = emptyTheme; + this.ThemeTypes = new List(themeTypes); + this.TiberiumOrGoldValue = tiberiumOrGoldValue; + this.GemValue = gemValue; + this.Metrics = new CellMetrics(cellSize); + this.Templates = new CellGrid