123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681 |
- //
- // Copyright 2020 Electronic Arts Inc.
- //
- // The Command & Conquer Map Editor and corresponding source code is free
- // software: you can redistribute it and/or modify it under the terms of
- // the GNU General Public License as published by the Free Software Foundation,
- // either version 3 of the License, or (at your option) any later version.
- // The Command & Conquer Map Editor and corresponding source code is distributed
- // in the hope that it will be useful, but with permitted additional restrictions
- // under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT
- // distributed with this program. You should have received a copy of the
- // GNU General Public License along with permitted additional restrictions
- // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
- using MobiusEditor.Interface;
- using MobiusEditor.Render;
- using MobiusEditor.Utility;
- using System;
- using System.Collections.Generic;
- using System.Collections.ObjectModel;
- using System.ComponentModel;
- using System.Drawing;
- using System.Drawing.Drawing2D;
- using System.Linq;
- using TGASharpLib;
- namespace MobiusEditor.Model
- {
- [Flags]
- public enum MapLayerFlag
- {
- None = 0,
- Basic = 1 << 0,
- Map = 1 << 1,
- Template = 1 << 2,
- Terrain = 1 << 3,
- Resources = 1 << 4,
- Walls = 1 << 5,
- Overlay = 1 << 6,
- Smudge = 1 << 7,
- Waypoints = 1 << 8,
- CellTriggers = 1 << 9,
- Houses = 1 << 10,
- Infantry = 1 << 11,
- Units = 1 << 12,
- Buildings = 1 << 13,
- Boundaries = 1 << 14,
- TechnoTriggers = 1 << 15,
- OverlayAll = Resources | Walls | Overlay,
- Technos = Terrain | Walls | Infantry | Units | Buildings,
- All = int.MaxValue
- }
- 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 => Instance as Map;
- public readonly bool FractionalPercentages;
- public MapContext(Map map, bool fractionalPercentages)
- {
- Instance = map;
- FractionalPercentages = fractionalPercentages;
- }
- public object GetService(Type serviceType) => null;
- public void OnComponentChanged() { }
- public bool OnComponentChanging() => true;
- }
- public class Map : ICloneable
- {
- private int updateCount = 0;
- private bool updating = false;
- private IDictionary<MapLayerFlag, ISet<Point>> invalidateLayers = new Dictionary<MapLayerFlag, ISet<Point>>();
- private bool invalidateOverlappers;
- public readonly BasicSection BasicSection;
- public readonly MapSection MapSection = new MapSection();
- public readonly BriefingSection BriefingSection = new BriefingSection();
- public readonly SteamSection SteamSection = new SteamSection();
- public TheaterType Theater { get => MapSection.Theater; set => MapSection.Theater = value; }
- public Point TopLeft
- {
- get => new Point(MapSection.X, MapSection.Y);
- set { MapSection.X = value.X; MapSection.Y = value.Y; }
- }
- public Size Size
- {
- get => new Size(MapSection.Width, MapSection.Height);
- set { MapSection.Width = value.Width; 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; }
- }
- public readonly Type HouseType;
- public readonly HouseType[] HouseTypes;
- 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 string[] ActionTypes;
- public readonly string[] MissionTypes;
- public readonly List<DirectionType> DirectionTypes;
- public readonly List<InfantryType> InfantryTypes;
- public readonly List<UnitType> UnitTypes;
- public readonly List<BuildingType> BuildingTypes;
- public readonly string[] 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 readonly CellGrid<CellTrigger> CellTriggers;
- public readonly ObservableCollection<Trigger> Triggers;
- public readonly List<TeamType> TeamTypes;
- public House[] Houses;
- public readonly List<string> MovieTypes;
- public int TiberiumOrGoldValue { get; set; }
- public int GemValue { get; set; }
- public int TotalResources
- {
- get
- {
- int totalResources = 0;
- foreach (var (cell, value) in Overlay)
- {
- if (value.Type.IsResource)
- {
- totalResources += (value.Icon + 1) * (value.Type.IsGem ? GemValue : TiberiumOrGoldValue);
- }
- }
- return totalResources;
- }
- }
- public Map(BasicSection basicSection, TheaterType theater, Size cellSize, Type houseType,
- IEnumerable<HouseType> houseTypes, IEnumerable<TheaterType> theaterTypes, IEnumerable<TemplateType> templateTypes,
- IEnumerable<TerrainType> terrainTypes, IEnumerable<OverlayType> overlayTypes, IEnumerable<SmudgeType> smudgeTypes,
- IEnumerable<string> eventTypes, IEnumerable<string> actionTypes, IEnumerable<string> missionTypes,
- IEnumerable<DirectionType> directionTypes, IEnumerable<InfantryType> infantryTypes, IEnumerable<UnitType> unitTypes,
- IEnumerable<BuildingType> buildingTypes, IEnumerable<string> teamMissionTypes, IEnumerable<Waypoint> waypoints,
- IEnumerable<string> movieTypes)
- {
- BasicSection = basicSection;
- HouseType = houseType;
- HouseTypes = houseTypes.ToArray();
- TheaterTypes = new List<TheaterType>(theaterTypes);
- TemplateTypes = new List<TemplateType>(templateTypes);
- TerrainTypes = new List<TerrainType>(terrainTypes);
- OverlayTypes = new List<OverlayType>(overlayTypes);
- SmudgeTypes = new List<SmudgeType>(smudgeTypes);
- EventTypes = eventTypes.ToArray();
- ActionTypes = actionTypes.ToArray();
- MissionTypes = missionTypes.ToArray();
- DirectionTypes = new List<DirectionType>(directionTypes);
- InfantryTypes = new List<InfantryType>(infantryTypes);
- UnitTypes = new List<UnitType>(unitTypes);
- BuildingTypes = new List<BuildingType>(buildingTypes);
- TeamMissionTypes = teamMissionTypes.ToArray();
- MovieTypes = new List<string>(movieTypes);
- Metrics = new CellMetrics(cellSize);
- Templates = new CellGrid<Template>(Metrics);
- Overlay = new CellGrid<Overlay>(Metrics);
- Smudge = new CellGrid<Smudge>(Metrics);
- Technos = new OccupierSet<ICellOccupier>(Metrics);
- Buildings = new OccupierSet<ICellOccupier>(Metrics);
- Overlappers = new OverlapperSet<ICellOverlapper>(Metrics);
- Triggers = new ObservableCollection<Trigger>();
- TeamTypes = new List<TeamType>();
- Houses = HouseTypes.Select(t => { var h = (House)Activator.CreateInstance(HouseType, t); h.SetDefault(); return h; }).ToArray();
- Waypoints = waypoints.ToArray();
- CellTriggers = new CellGrid<CellTrigger>(Metrics);
- MapSection.SetDefault();
- BriefingSection.SetDefault();
- SteamSection.SetDefault();
- Templates.Clear();
- Overlay.Clear();
- Smudge.Clear();
- Technos.Clear();
- Overlappers.Clear();
- CellTriggers.Clear();
- TopLeft = new Point(1, 1);
- Size = Metrics.Size - new Size(2, 2);
- Theater = theater;
- Overlay.CellChanged += Overlay_CellChanged;
- Technos.OccupierAdded += Technos_OccupierAdded;
- Technos.OccupierRemoved += Technos_OccupierRemoved;
- Buildings.OccupierAdded += Buildings_OccupierAdded;
- Buildings.OccupierRemoved += Buildings_OccupierRemoved;
- Triggers.CollectionChanged += Triggers_CollectionChanged;
- }
- public void BeginUpdate()
- {
- updateCount++;
- }
- public void EndUpdate()
- {
- if (--updateCount == 0)
- {
- Update();
- }
- }
- public void InitTheater(GameType gameType)
- {
- foreach (var templateType in TemplateTypes)
- {
- templateType.Init(Theater);
- }
- foreach (var smudgeType in SmudgeTypes)
- {
- smudgeType.Init(Theater);
- }
- foreach (var overlayType in OverlayTypes)
- {
- overlayType.Init(Theater);
- }
- foreach (var terrainType in TerrainTypes)
- {
- terrainType.Init(Theater);
- }
- foreach (var infantryType in InfantryTypes)
- {
- infantryType.Init(gameType, Theater, HouseTypes.Where(h => h.Equals(infantryType.OwnerHouse)).FirstOrDefault(), DirectionTypes.Where(d => d.Facing == FacingType.South).First());
- }
- foreach (var unitType in UnitTypes)
- {
- unitType.Init(gameType, Theater, HouseTypes.Where(h => h.Equals(unitType.OwnerHouse)).FirstOrDefault(), DirectionTypes.Where(d => d.Facing == FacingType.North).First());
- }
- foreach (var buildingType in BuildingTypes)
- {
- buildingType.Init(gameType, Theater, HouseTypes.Where(h => h.Equals(buildingType.OwnerHouse)).FirstOrDefault(), DirectionTypes.Where(d => d.Facing == FacingType.North).First());
- }
- }
- private void Update()
- {
- updating = true;
- if (invalidateLayers.TryGetValue(MapLayerFlag.Resources, out ISet<Point> locations))
- {
- UpdateResourceOverlays(locations);
- }
- if (invalidateLayers.TryGetValue(MapLayerFlag.Walls, out locations))
- {
- UpdateWallOverlays(locations);
- }
- if (invalidateOverlappers)
- {
- Overlappers.Clear();
- foreach (var (location, techno) in Technos)
- {
- if (techno is ICellOverlapper)
- {
- Overlappers.Add(location, techno as ICellOverlapper);
- }
- }
- }
- invalidateLayers.Clear();
- invalidateOverlappers = false;
- updating = false;
- }
- private void UpdateResourceOverlays(ISet<Point> locations)
- {
- var tiberiumCounts = new int[] { 0, 1, 3, 4, 6, 7, 8, 10, 11 };
- var gemCounts = new int[] { 0, 0, 0, 1, 1, 1, 2, 2, 2 };
- foreach (var (cell, overlay) in Overlay.IntersectsWith(locations).Where(o => o.Value.Type.IsResource))
- {
- int count = 0;
- foreach (var facing in CellMetrics.AdjacentFacings)
- {
- var adjacentTiberium = Overlay.Adjacent(cell, facing);
- if (adjacentTiberium?.Type.IsResource ?? false)
- {
- count++;
- }
- }
- overlay.Icon = overlay.Type.IsGem ? gemCounts[count] : tiberiumCounts[count];
- }
- }
- private void UpdateWallOverlays(ISet<Point> locations)
- {
- foreach (var (cell, overlay) in Overlay.IntersectsWith(locations).Where(o => o.Value.Type.IsWall))
- {
- var northWall = Overlay.Adjacent(cell, FacingType.North);
- var eastWall = Overlay.Adjacent(cell, FacingType.East);
- var southWall = Overlay.Adjacent(cell, FacingType.South);
- var westWall = Overlay.Adjacent(cell, 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 RemoveBibs(Building building)
- {
- var bibCells = Smudge.IntersectsWith(building.BibCells).Where(x => (x.Value.Type.Flag & SmudgeTypeFlag.Bib) != SmudgeTypeFlag.None).Select(x => x.Cell).ToArray();
- foreach (var cell in bibCells)
- {
- Smudge[cell] = null;
- }
- building.BibCells.Clear();
- }
- private void AddBibs(Point location, Building building)
- {
- if (!building.Type.HasBib)
- {
- return;
- }
- var bib1Type = SmudgeTypes.Where(t => t.Flag == SmudgeTypeFlag.Bib1).FirstOrDefault();
- var bib2Type = SmudgeTypes.Where(t => t.Flag == SmudgeTypeFlag.Bib2).FirstOrDefault();
- var bib3Type = SmudgeTypes.Where(t => t.Flag == SmudgeTypeFlag.Bib3).FirstOrDefault();
- SmudgeType bibType = null;
- switch (building.Type.Size.Width)
- {
- case 2:
- bibType = bib3Type;
- break;
- case 3:
- bibType = bib2Type;
- break;
- case 4:
- bibType = bib1Type;
- break;
- }
- if (bibType != null)
- {
- int icon = 0;
- for (var y = 0; y < bibType.Size.Height; ++y)
- {
- for (var x = 0; x < bibType.Size.Width; ++x, ++icon)
- {
- if (Metrics.GetCell(new Point(location.X + x, location.Y + building.Type.Size.Height + y - 1), out int subCell))
- {
- Smudge[subCell] = new Smudge
- {
- Type = bibType,
- Icon = icon,
- Data = 0,
- Tint = building.Tint
- };
- building.BibCells.Add(subCell);
- }
- }
- }
- }
- }
- public Map Clone()
- {
- var map = new Map(BasicSection, Theater, Metrics.Size, HouseType,
- HouseTypes, TheaterTypes, TemplateTypes, TerrainTypes, OverlayTypes, SmudgeTypes,
- EventTypes, ActionTypes, MissionTypes, DirectionTypes, InfantryTypes, UnitTypes,
- BuildingTypes, TeamMissionTypes, Waypoints, MovieTypes)
- {
- TopLeft = TopLeft,
- Size = Size
- };
- map.BeginUpdate();
- MapSection.CopyTo(map.MapSection);
- BriefingSection.CopyTo(map.BriefingSection);
- SteamSection.CopyTo(map.SteamSection);
- Templates.CopyTo(map.Templates);
- Overlay.CopyTo(map.Overlay);
- Smudge.CopyTo(map.Smudge);
- CellTriggers.CopyTo(map.CellTriggers);
- Array.Copy(Houses, map.Houses, map.Houses.Length);
- foreach (var trigger in Triggers)
- {
- map.Triggers.Add(trigger);
- }
- foreach (var (location, occupier) in Technos)
- {
- if (occupier is InfantryGroup infantryGroup)
- {
- var 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 (var (location, building) in Buildings)
- {
- map.Buildings.Add(location, building);
- }
- map.TeamTypes.AddRange(TeamTypes);
- map.EndUpdate();
- return map;
- }
- public TGA GeneratePreview(Size previewSize, bool sharpen)
- {
- var mapBounds = new Rectangle(
- Bounds.Left * Globals.OriginalTileWidth,
- Bounds.Top * Globals.OriginalTileHeight,
- Bounds.Width * Globals.OriginalTileWidth,
- Bounds.Height * Globals.OriginalTileHeight
- );
- var previewScale = Math.Min(previewSize.Width / (float)mapBounds.Width, previewSize.Height / (float)mapBounds.Height);
- var scaledSize = new Size((int)(previewSize.Width / previewScale), (int)(previewSize.Height / previewScale));
- using (var fullBitmap = new Bitmap(Metrics.Width * Globals.OriginalTileWidth, Metrics.Height * Globals.OriginalTileHeight))
- using (var croppedBitmap = new Bitmap(previewSize.Width, previewSize.Height))
- {
- var locations = Bounds.Points().ToHashSet();
- using (var g = Graphics.FromImage(fullBitmap))
- {
- MapRenderer.Render(GameType.None, this, g, locations, MapLayerFlag.Template | MapLayerFlag.Resources, 1);
- }
- using (var g = Graphics.FromImage(croppedBitmap))
- {
- 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);
- }
- fullBitmap.Dispose();
- if (sharpen)
- {
- using (var sharpenedImage = croppedBitmap.Sharpen(1.0f))
- {
- croppedBitmap.Dispose();
- return TGA.FromBitmap(sharpenedImage);
- }
- }
- else
- {
- return TGA.FromBitmap(croppedBitmap);
- }
- }
- }
- public TGA GenerateMapPreview()
- {
- return GeneratePreview(Globals.MapPreviewSize, false);
- }
- public TGA GenerateWorkshopPreview()
- {
- return GeneratePreview(Globals.WorkshopPreviewSize, true);
- }
- object ICloneable.Clone()
- {
- return Clone();
- }
- private void Overlay_CellChanged(object sender, CellChangedEventArgs<Overlay> e)
- {
- if (e.OldValue?.Type.IsWall ?? false)
- {
- Buildings.Remove(e.OldValue);
- }
- if (e.Value?.Type.IsWall ?? false)
- {
- Buildings.Add(e.Location, e.Value);
- }
- if (updating)
- {
- return;
- }
- foreach (var overlay in new Overlay[] { e.OldValue, e.Value })
- {
- if (overlay == null)
- {
- continue;
- }
- MapLayerFlag layer = MapLayerFlag.None;
- if (overlay.Type.IsResource)
- {
- layer = MapLayerFlag.Resources;
- }
- else if (overlay.Type.IsWall)
- {
- layer = MapLayerFlag.Walls;
- }
- else
- {
- continue;
- }
- if (!invalidateLayers.TryGetValue(layer, out ISet<Point> locations))
- {
- locations = new HashSet<Point>();
- invalidateLayers[layer] = locations;
- }
- locations.UnionWith(Rectangle.Inflate(new Rectangle(e.Location, new Size(1, 1)), 1, 1).Points());
- }
- if (updateCount == 0)
- {
- Update();
- }
- }
- private void Technos_OccupierAdded(object sender, OccupierAddedEventArgs<ICellOccupier> e)
- {
- if (e.Occupier is ICellOverlapper overlapper)
- {
- if (updateCount == 0)
- {
- Overlappers.Add(e.Location, overlapper);
- }
- else
- {
- invalidateOverlappers = true;
- }
- }
- }
- private void Technos_OccupierRemoved(object sender, OccupierRemovedEventArgs<ICellOccupier> e)
- {
- if (e.Occupier is ICellOverlapper overlapper)
- {
- if (updateCount == 0)
- {
- Overlappers.Remove(overlapper);
- }
- else
- {
- invalidateOverlappers = true;
- }
- }
- }
- private void Buildings_OccupierAdded(object sender, OccupierAddedEventArgs<ICellOccupier> e)
- {
- if (e.Occupier is Building building)
- {
- Technos.Add(e.Location, e.Occupier, building.Type.BaseOccupyMask);
- AddBibs(e.Location, building);
- }
- else
- {
- Technos.Add(e.Location, e.Occupier);
- }
- }
- private void Buildings_OccupierRemoved(object sender, OccupierRemovedEventArgs<ICellOccupier> e)
- {
- if (e.Occupier is Building building)
- {
- RemoveBibs(building);
- }
- Technos.Remove(e.Occupier);
- }
- private void Triggers_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
- {
- foreach (var (_, building) in Buildings.OfType<Building>())
- {
- if (!string.IsNullOrEmpty(building.Trigger))
- {
- if (Triggers.Where(t => building.Trigger.Equals(t.Name)).FirstOrDefault() == null)
- {
- building.Trigger = Trigger.None;
- }
- }
- }
- }
- }
- }
|