CnC_Remastered_Collection

Command and Conquer: Red Alert
Log | Files | Refs | README | LICENSE

Map.cs (23856B)


      1 //
      2 // Copyright 2020 Electronic Arts Inc.
      3 //
      4 // The Command & Conquer Map Editor and corresponding source code is free 
      5 // software: you can redistribute it and/or modify it under the terms of 
      6 // the GNU General Public License as published by the Free Software Foundation, 
      7 // either version 3 of the License, or (at your option) any later version.
      8 
      9 // The Command & Conquer Map Editor and corresponding source code is distributed 
     10 // in the hope that it will be useful, but with permitted additional restrictions 
     11 // under Section 7 of the GPL. See the GNU General Public License in LICENSE.TXT 
     12 // distributed with this program. You should have received a copy of the 
     13 // GNU General Public License along with permitted additional restrictions 
     14 // with this program. If not, see https://github.com/electronicarts/CnC_Remastered_Collection
     15 using MobiusEditor.Interface;
     16 using MobiusEditor.Render;
     17 using MobiusEditor.Utility;
     18 using System;
     19 using System.Collections.Generic;
     20 using System.Collections.ObjectModel;
     21 using System.ComponentModel;
     22 using System.Drawing;
     23 using System.Drawing.Drawing2D;
     24 using System.Linq;
     25 using TGASharpLib;
     26 
     27 namespace MobiusEditor.Model
     28 {
     29     [Flags]
     30     public enum MapLayerFlag
     31     {
     32         None            = 0,
     33         Basic           = 1 << 0,
     34         Map             = 1 << 1,
     35         Template        = 1 << 2,
     36         Terrain         = 1 << 3,
     37         Resources       = 1 << 4,
     38         Walls           = 1 << 5,
     39         Overlay         = 1 << 6,
     40         Smudge          = 1 << 7,
     41         Waypoints       = 1 << 8,
     42         CellTriggers    = 1 << 9,
     43         Houses          = 1 << 10,
     44         Infantry        = 1 << 11,
     45         Units           = 1 << 12,
     46         Buildings       = 1 << 13,
     47         Boundaries      = 1 << 14,
     48         TechnoTriggers  = 1 << 15,
     49 
     50         OverlayAll = Resources | Walls | Overlay,
     51         Technos = Terrain | Walls | Infantry | Units | Buildings,
     52 
     53         All = int.MaxValue
     54     }
     55 
     56     public class MapContext : ITypeDescriptorContext
     57     {
     58         public IContainer Container { get; private set; }
     59 
     60         public object Instance { get; private set; }
     61 
     62         public PropertyDescriptor PropertyDescriptor { get; private set; }
     63 
     64         public Map Map => Instance as Map;
     65 
     66         public readonly bool FractionalPercentages;
     67 
     68         public MapContext(Map map, bool fractionalPercentages)
     69         {
     70             Instance = map;
     71             FractionalPercentages = fractionalPercentages;
     72         }
     73 
     74         public object GetService(Type serviceType) => null;
     75 
     76         public void OnComponentChanged() { }
     77 
     78         public bool OnComponentChanging() => true;
     79     }
     80 
     81     public class Map : ICloneable
     82     {
     83         private int updateCount = 0;
     84         private bool updating = false;
     85         private IDictionary<MapLayerFlag, ISet<Point>> invalidateLayers = new Dictionary<MapLayerFlag, ISet<Point>>();
     86         private bool invalidateOverlappers;
     87 
     88         public readonly BasicSection BasicSection;
     89 
     90         public readonly MapSection MapSection = new MapSection();
     91 
     92         public readonly BriefingSection BriefingSection = new BriefingSection();
     93 
     94         public readonly SteamSection SteamSection = new SteamSection();
     95 
     96         public TheaterType Theater { get => MapSection.Theater; set => MapSection.Theater = value; }
     97 
     98         public Point TopLeft
     99         {
    100             get => new Point(MapSection.X, MapSection.Y);
    101             set { MapSection.X = value.X; MapSection.Y = value.Y; }
    102         }
    103 
    104         public Size Size
    105         {
    106             get => new Size(MapSection.Width, MapSection.Height);
    107             set { MapSection.Width = value.Width; MapSection.Height = value.Height; }
    108         }
    109 
    110         public Rectangle Bounds
    111         {
    112             get => new Rectangle(TopLeft, Size);
    113             set { MapSection.X = value.Left; MapSection.Y = value.Top; MapSection.Width = value.Width; MapSection.Height = value.Height; }
    114         }
    115 
    116         public readonly Type HouseType;
    117 
    118         public readonly HouseType[] HouseTypes;
    119 
    120         public readonly List<TheaterType> TheaterTypes;
    121 
    122         public readonly List<TemplateType> TemplateTypes;
    123 
    124         public readonly List<TerrainType> TerrainTypes;
    125 
    126         public readonly List<OverlayType> OverlayTypes;
    127 
    128         public readonly List<SmudgeType> SmudgeTypes;
    129 
    130         public readonly string[] EventTypes;
    131 
    132         public readonly string[] ActionTypes;
    133 
    134         public readonly string[] MissionTypes;
    135 
    136         public readonly List<DirectionType> DirectionTypes;
    137 
    138         public readonly List<InfantryType> InfantryTypes;
    139 
    140         public readonly List<UnitType> UnitTypes;
    141 
    142         public readonly List<BuildingType> BuildingTypes;
    143 
    144         public readonly string[] TeamMissionTypes;
    145 
    146         public readonly CellMetrics Metrics;
    147 
    148         public readonly CellGrid<Template> Templates;
    149 
    150         public readonly CellGrid<Overlay> Overlay;
    151 
    152         public readonly CellGrid<Smudge> Smudge;
    153 
    154         public readonly OccupierSet<ICellOccupier> Technos;
    155 
    156         public readonly OccupierSet<ICellOccupier> Buildings;
    157 
    158         public readonly OverlapperSet<ICellOverlapper> Overlappers;
    159 
    160         public readonly Waypoint[] Waypoints;
    161 
    162         public readonly CellGrid<CellTrigger> CellTriggers;
    163 
    164         public readonly ObservableCollection<Trigger> Triggers;
    165 
    166         public readonly List<TeamType> TeamTypes;
    167 
    168         public House[] Houses;
    169 
    170         public readonly List<string> MovieTypes;
    171 
    172         public int TiberiumOrGoldValue { get; set; }
    173 
    174         public int GemValue { get; set; }
    175 
    176         public int TotalResources
    177         {
    178             get
    179             {
    180                 int totalResources = 0;
    181                 foreach (var (cell, value) in Overlay)
    182                 {
    183                     if (value.Type.IsResource)
    184                     {
    185                         totalResources += (value.Icon + 1) * (value.Type.IsGem ? GemValue : TiberiumOrGoldValue);
    186                     }
    187                 }
    188                 return totalResources;
    189             }
    190         }
    191 
    192         public Map(BasicSection basicSection, TheaterType theater, Size cellSize, Type houseType,
    193             IEnumerable<HouseType> houseTypes, IEnumerable<TheaterType> theaterTypes, IEnumerable<TemplateType> templateTypes,
    194             IEnumerable<TerrainType> terrainTypes, IEnumerable<OverlayType> overlayTypes, IEnumerable<SmudgeType> smudgeTypes,
    195             IEnumerable<string> eventTypes, IEnumerable<string> actionTypes, IEnumerable<string> missionTypes,
    196             IEnumerable<DirectionType> directionTypes, IEnumerable<InfantryType> infantryTypes, IEnumerable<UnitType> unitTypes,
    197             IEnumerable<BuildingType> buildingTypes, IEnumerable<string> teamMissionTypes, IEnumerable<Waypoint> waypoints,
    198             IEnumerable<string> movieTypes)
    199         {
    200             BasicSection = basicSection;
    201 
    202             HouseType = houseType;
    203             HouseTypes = houseTypes.ToArray();
    204             TheaterTypes = new List<TheaterType>(theaterTypes);
    205             TemplateTypes = new List<TemplateType>(templateTypes);
    206             TerrainTypes = new List<TerrainType>(terrainTypes);
    207             OverlayTypes = new List<OverlayType>(overlayTypes);
    208             SmudgeTypes = new List<SmudgeType>(smudgeTypes);
    209             EventTypes = eventTypes.ToArray();
    210             ActionTypes = actionTypes.ToArray();
    211             MissionTypes = missionTypes.ToArray();
    212             DirectionTypes = new List<DirectionType>(directionTypes);
    213             InfantryTypes = new List<InfantryType>(infantryTypes);
    214             UnitTypes = new List<UnitType>(unitTypes);
    215             BuildingTypes = new List<BuildingType>(buildingTypes);
    216             TeamMissionTypes = teamMissionTypes.ToArray();
    217             MovieTypes = new List<string>(movieTypes);
    218 
    219             Metrics = new CellMetrics(cellSize);
    220             Templates = new CellGrid<Template>(Metrics);
    221             Overlay = new CellGrid<Overlay>(Metrics);
    222             Smudge = new CellGrid<Smudge>(Metrics);
    223             Technos = new OccupierSet<ICellOccupier>(Metrics);
    224             Buildings = new OccupierSet<ICellOccupier>(Metrics);
    225             Overlappers = new OverlapperSet<ICellOverlapper>(Metrics);
    226             Triggers = new ObservableCollection<Trigger>();
    227             TeamTypes = new List<TeamType>();
    228             Houses = HouseTypes.Select(t => { var h = (House)Activator.CreateInstance(HouseType, t); h.SetDefault(); return h; }).ToArray();
    229             Waypoints = waypoints.ToArray();
    230             CellTriggers = new CellGrid<CellTrigger>(Metrics);
    231 
    232             MapSection.SetDefault();
    233             BriefingSection.SetDefault();
    234             SteamSection.SetDefault();
    235             Templates.Clear();
    236             Overlay.Clear();
    237             Smudge.Clear();
    238             Technos.Clear();
    239             Overlappers.Clear();
    240             CellTriggers.Clear();
    241 
    242             TopLeft = new Point(1, 1);
    243             Size = Metrics.Size - new Size(2, 2);
    244             Theater = theater;
    245 
    246             Overlay.CellChanged += Overlay_CellChanged;
    247             Technos.OccupierAdded += Technos_OccupierAdded;
    248             Technos.OccupierRemoved += Technos_OccupierRemoved;
    249             Buildings.OccupierAdded += Buildings_OccupierAdded;
    250             Buildings.OccupierRemoved += Buildings_OccupierRemoved;
    251             Triggers.CollectionChanged += Triggers_CollectionChanged;
    252         }
    253 
    254         public void BeginUpdate()
    255         {
    256             updateCount++;
    257         }
    258 
    259         public void EndUpdate()
    260         {
    261             if (--updateCount == 0)
    262             {
    263                 Update();
    264             }
    265         }
    266 
    267         public void InitTheater(GameType gameType)
    268         {
    269             foreach (var templateType in TemplateTypes)
    270             {
    271                 templateType.Init(Theater);
    272             }
    273 
    274             foreach (var smudgeType in SmudgeTypes)
    275             {
    276                 smudgeType.Init(Theater);
    277             }
    278 
    279             foreach (var overlayType in OverlayTypes)
    280             {
    281                 overlayType.Init(Theater);
    282             }
    283 
    284             foreach (var terrainType in TerrainTypes)
    285             {
    286                 terrainType.Init(Theater);
    287             }
    288 
    289             foreach (var infantryType in InfantryTypes)
    290             {
    291                 infantryType.Init(gameType, Theater, HouseTypes.Where(h => h.Equals(infantryType.OwnerHouse)).FirstOrDefault(), DirectionTypes.Where(d => d.Facing == FacingType.South).First());
    292             }
    293 
    294             foreach (var unitType in UnitTypes)
    295             {
    296                 unitType.Init(gameType, Theater, HouseTypes.Where(h => h.Equals(unitType.OwnerHouse)).FirstOrDefault(), DirectionTypes.Where(d => d.Facing == FacingType.North).First());
    297             }
    298 
    299             foreach (var buildingType in BuildingTypes)
    300             {
    301                 buildingType.Init(gameType, Theater, HouseTypes.Where(h => h.Equals(buildingType.OwnerHouse)).FirstOrDefault(), DirectionTypes.Where(d => d.Facing == FacingType.North).First());
    302             }
    303         }
    304 
    305         private void Update()
    306         {
    307             updating = true;
    308 
    309             if (invalidateLayers.TryGetValue(MapLayerFlag.Resources, out ISet<Point> locations))
    310             {
    311                 UpdateResourceOverlays(locations);
    312             }
    313 
    314             if (invalidateLayers.TryGetValue(MapLayerFlag.Walls, out locations))
    315             {
    316                 UpdateWallOverlays(locations);
    317             }
    318 
    319             if (invalidateOverlappers)
    320             {
    321                 Overlappers.Clear();
    322                 foreach (var (location, techno) in Technos)
    323                 {
    324                     if (techno is ICellOverlapper)
    325                     {
    326                         Overlappers.Add(location, techno as ICellOverlapper);
    327                     }
    328                 }
    329             }
    330 
    331             invalidateLayers.Clear();
    332             invalidateOverlappers = false;
    333             updating = false;
    334         }
    335 
    336         private void UpdateResourceOverlays(ISet<Point> locations)
    337         {
    338             var tiberiumCounts = new int[] { 0, 1, 3, 4, 6, 7, 8, 10, 11 };
    339             var gemCounts = new int[] { 0, 0, 0, 1, 1, 1, 2, 2, 2 };
    340 
    341             foreach (var (cell, overlay) in Overlay.IntersectsWith(locations).Where(o => o.Value.Type.IsResource))
    342             {
    343                 int count = 0;
    344                 foreach (var facing in CellMetrics.AdjacentFacings)
    345                 {
    346                     var adjacentTiberium = Overlay.Adjacent(cell, facing);
    347                     if (adjacentTiberium?.Type.IsResource ?? false)
    348                     {
    349                         count++;
    350                     }
    351                 }
    352 
    353                 overlay.Icon = overlay.Type.IsGem ? gemCounts[count] : tiberiumCounts[count];
    354             }
    355         }
    356 
    357         private void UpdateWallOverlays(ISet<Point> locations)
    358         {
    359             foreach (var (cell, overlay) in Overlay.IntersectsWith(locations).Where(o => o.Value.Type.IsWall))
    360             {
    361                 var northWall = Overlay.Adjacent(cell, FacingType.North);
    362                 var eastWall = Overlay.Adjacent(cell, FacingType.East);
    363                 var southWall = Overlay.Adjacent(cell, FacingType.South);
    364                 var westWall = Overlay.Adjacent(cell, FacingType.West);
    365 
    366                 int icon = 0;
    367                 if (northWall?.Type == overlay.Type)
    368                 {
    369                     icon |= 1;
    370                 }
    371                 if (eastWall?.Type == overlay.Type)
    372                 {
    373                     icon |= 2;
    374                 }
    375                 if (southWall?.Type == overlay.Type)
    376                 {
    377                     icon |= 4;
    378                 }
    379                 if (westWall?.Type == overlay.Type)
    380                 {
    381                     icon |= 8;
    382                 }
    383 
    384                 overlay.Icon = icon;
    385             }
    386         }
    387 
    388         private void RemoveBibs(Building building)
    389         {
    390             var bibCells = Smudge.IntersectsWith(building.BibCells).Where(x => (x.Value.Type.Flag & SmudgeTypeFlag.Bib) != SmudgeTypeFlag.None).Select(x => x.Cell).ToArray();
    391             foreach (var cell in bibCells)
    392             {
    393                 Smudge[cell] = null;
    394             }
    395             building.BibCells.Clear();
    396         }
    397 
    398         private void AddBibs(Point location, Building building)
    399         {
    400             if (!building.Type.HasBib)
    401             {
    402                 return;
    403             }
    404 
    405             var bib1Type = SmudgeTypes.Where(t => t.Flag == SmudgeTypeFlag.Bib1).FirstOrDefault();
    406             var bib2Type = SmudgeTypes.Where(t => t.Flag == SmudgeTypeFlag.Bib2).FirstOrDefault();
    407             var bib3Type = SmudgeTypes.Where(t => t.Flag == SmudgeTypeFlag.Bib3).FirstOrDefault();
    408 
    409             SmudgeType bibType = null;
    410             switch (building.Type.Size.Width)
    411             {
    412                 case 2:
    413                     bibType = bib3Type;
    414                     break;
    415                 case 3:
    416                     bibType = bib2Type;
    417                     break;
    418                 case 4:
    419                     bibType = bib1Type;
    420                     break;
    421             }
    422             if (bibType != null)
    423             {
    424                 int icon = 0;
    425                 for (var y = 0; y < bibType.Size.Height; ++y)
    426                 {
    427                     for (var x = 0; x < bibType.Size.Width; ++x, ++icon)
    428                     {
    429                         if (Metrics.GetCell(new Point(location.X + x, location.Y + building.Type.Size.Height + y - 1), out int subCell))
    430                         {
    431                             Smudge[subCell] = new Smudge
    432                             {
    433                                 Type = bibType,
    434                                 Icon = icon,
    435                                 Data = 0,
    436                                 Tint = building.Tint
    437                             };
    438                             building.BibCells.Add(subCell);
    439                         }
    440                     }
    441                 }
    442             }
    443         }
    444 
    445         public Map Clone()
    446         {
    447             var map = new Map(BasicSection, Theater, Metrics.Size, HouseType,
    448                 HouseTypes, TheaterTypes, TemplateTypes, TerrainTypes, OverlayTypes, SmudgeTypes,
    449                 EventTypes, ActionTypes, MissionTypes, DirectionTypes, InfantryTypes, UnitTypes,
    450                 BuildingTypes, TeamMissionTypes, Waypoints, MovieTypes)
    451             {
    452                 TopLeft = TopLeft,
    453                 Size = Size
    454             };
    455 
    456             map.BeginUpdate();
    457 
    458             MapSection.CopyTo(map.MapSection);
    459             BriefingSection.CopyTo(map.BriefingSection);
    460             SteamSection.CopyTo(map.SteamSection);
    461             Templates.CopyTo(map.Templates);
    462             Overlay.CopyTo(map.Overlay);
    463             Smudge.CopyTo(map.Smudge);
    464             CellTriggers.CopyTo(map.CellTriggers);
    465             Array.Copy(Houses, map.Houses, map.Houses.Length);
    466 
    467             foreach (var trigger in Triggers)
    468             {
    469                 map.Triggers.Add(trigger);
    470             }
    471 
    472             foreach (var (location, occupier) in Technos)
    473             {
    474                 if (occupier is InfantryGroup infantryGroup)
    475                 {
    476                     var newInfantryGroup = new InfantryGroup();
    477                     Array.Copy(infantryGroup.Infantry, newInfantryGroup.Infantry, newInfantryGroup.Infantry.Length);
    478                     map.Technos.Add(location, newInfantryGroup);
    479                 }
    480                 else if (!(occupier is Building))
    481                 {
    482                     map.Technos.Add(location, occupier);
    483                 }
    484             }
    485 
    486             foreach (var (location, building) in Buildings)
    487             {
    488                 map.Buildings.Add(location, building);
    489             }
    490 
    491             map.TeamTypes.AddRange(TeamTypes);
    492 
    493             map.EndUpdate();
    494 
    495             return map;
    496         }
    497 
    498         public TGA GeneratePreview(Size previewSize, bool sharpen)
    499         {
    500             var mapBounds = new Rectangle(
    501                 Bounds.Left * Globals.OriginalTileWidth,
    502                 Bounds.Top * Globals.OriginalTileHeight,
    503                 Bounds.Width * Globals.OriginalTileWidth,
    504                 Bounds.Height * Globals.OriginalTileHeight
    505             );
    506             var previewScale = Math.Min(previewSize.Width / (float)mapBounds.Width, previewSize.Height / (float)mapBounds.Height);
    507             var scaledSize = new Size((int)(previewSize.Width / previewScale), (int)(previewSize.Height / previewScale));
    508 
    509             using (var fullBitmap = new Bitmap(Metrics.Width * Globals.OriginalTileWidth, Metrics.Height * Globals.OriginalTileHeight))
    510             using (var croppedBitmap = new Bitmap(previewSize.Width, previewSize.Height))
    511             {
    512                 var locations = Bounds.Points().ToHashSet();
    513                 using (var g = Graphics.FromImage(fullBitmap))
    514                 {
    515                     MapRenderer.Render(GameType.None, this, g, locations, MapLayerFlag.Template | MapLayerFlag.Resources, 1);
    516                 }
    517 
    518                 using (var g = Graphics.FromImage(croppedBitmap))
    519                 {
    520                     Matrix transform = new Matrix();
    521                     transform.Scale(previewScale, previewScale);
    522                     transform.Translate((scaledSize.Width - mapBounds.Width) / 2, (scaledSize.Height - mapBounds.Height) / 2);
    523 
    524                     g.Transform = transform;
    525                     g.Clear(Color.Black);
    526                     g.DrawImage(fullBitmap, new Rectangle(0, 0, mapBounds.Width, mapBounds.Height), mapBounds, GraphicsUnit.Pixel);
    527                 }
    528 
    529                 fullBitmap.Dispose();
    530 
    531                 if (sharpen)
    532                 {
    533                     using (var sharpenedImage = croppedBitmap.Sharpen(1.0f))
    534                     {
    535                         croppedBitmap.Dispose();
    536                         return TGA.FromBitmap(sharpenedImage);
    537                     }
    538                 }
    539                 else
    540                 {
    541                     return TGA.FromBitmap(croppedBitmap);
    542                 }
    543             }
    544         }
    545 
    546         public TGA GenerateMapPreview()
    547         {
    548             return GeneratePreview(Globals.MapPreviewSize, false);
    549         }
    550 
    551         public TGA GenerateWorkshopPreview()
    552         {
    553             return GeneratePreview(Globals.WorkshopPreviewSize, true);
    554         }
    555 
    556         object ICloneable.Clone()
    557         {
    558             return Clone();
    559         }
    560 
    561         private void Overlay_CellChanged(object sender, CellChangedEventArgs<Overlay> e)
    562         {
    563             if (e.OldValue?.Type.IsWall ?? false)
    564             {
    565                 Buildings.Remove(e.OldValue);
    566             }
    567 
    568             if (e.Value?.Type.IsWall ?? false)
    569             {
    570                 Buildings.Add(e.Location, e.Value);
    571             }
    572 
    573             if (updating)
    574             {
    575                 return;
    576             }
    577 
    578             foreach (var overlay in new Overlay[] { e.OldValue, e.Value })
    579             {
    580                 if (overlay == null)
    581                 {
    582                     continue;
    583                 }
    584 
    585                 MapLayerFlag layer = MapLayerFlag.None;
    586                 if (overlay.Type.IsResource)
    587                 {
    588                     layer = MapLayerFlag.Resources;
    589                 }
    590                 else if (overlay.Type.IsWall)
    591                 {
    592                     layer = MapLayerFlag.Walls;
    593                 }
    594                 else
    595                 {
    596                     continue;
    597                 }
    598 
    599                 if (!invalidateLayers.TryGetValue(layer, out ISet<Point> locations))
    600                 {
    601                     locations = new HashSet<Point>();
    602                     invalidateLayers[layer] = locations;
    603                 }
    604 
    605                 locations.UnionWith(Rectangle.Inflate(new Rectangle(e.Location, new Size(1, 1)), 1, 1).Points());
    606             }
    607 
    608             if (updateCount == 0)
    609             {
    610                 Update();
    611             }
    612         }
    613 
    614         private void Technos_OccupierAdded(object sender, OccupierAddedEventArgs<ICellOccupier> e)
    615         {
    616             if (e.Occupier is ICellOverlapper overlapper)
    617             {
    618                 if (updateCount == 0)
    619                 {
    620                     Overlappers.Add(e.Location, overlapper);
    621                 }
    622                 else
    623                 {
    624                     invalidateOverlappers = true;
    625                 }
    626             }
    627         }
    628 
    629         private void Technos_OccupierRemoved(object sender, OccupierRemovedEventArgs<ICellOccupier> e)
    630         {
    631             if (e.Occupier is ICellOverlapper overlapper)
    632             {
    633                 if (updateCount == 0)
    634                 {
    635                     Overlappers.Remove(overlapper);
    636                 }
    637                 else
    638                 {
    639                     invalidateOverlappers = true;
    640                 }
    641             }
    642         }
    643 
    644         private void Buildings_OccupierAdded(object sender, OccupierAddedEventArgs<ICellOccupier> e)
    645         {
    646             if (e.Occupier is Building building)
    647             {
    648                 Technos.Add(e.Location, e.Occupier, building.Type.BaseOccupyMask);
    649                 AddBibs(e.Location, building);
    650             }
    651             else
    652             {
    653                 Technos.Add(e.Location, e.Occupier);
    654             }
    655         }
    656 
    657         private void Buildings_OccupierRemoved(object sender, OccupierRemovedEventArgs<ICellOccupier> e)
    658         {
    659             if (e.Occupier is Building building)
    660             {
    661                 RemoveBibs(building);
    662             }
    663 
    664             Technos.Remove(e.Occupier);
    665         }
    666 
    667         private void Triggers_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    668         {
    669             foreach (var (_, building) in Buildings.OfType<Building>())
    670             {
    671                 if (!string.IsNullOrEmpty(building.Trigger))
    672                 {
    673                     if (Triggers.Where(t => building.Trigger.Equals(t.Name)).FirstOrDefault() == null)
    674                     {
    675                         building.Trigger = Trigger.None;
    676                     }
    677                 }
    678             }
    679         }
    680     }
    681 }