context: #region License
  // ====================================================
  // Project Porcupine Copyright(C) 2016 Team Porcupine
  // This program comes with ABSOLUTELY NO WARRANTY; This is free software, 
  // and you are welcome to redistribute it under certain conditions; See 
  // file LICENSE, which is part of this source code package, for details.
  // ====================================================
  #endregion
  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Xml;
  using System.Xml.Schema;
  using System.Xml.Serialization;
  using MoonSharp.Interpreter;
  using UnityEngine;
  
  [MoonSharpUserData]
  public class Room : IXmlSerializable
  {
      // Dictionary with the amount of gas in room stored in preasure(in atm) multiplyed by number of tiles.
      private Dictionary<string, float> atmosphericGasses; 
      private Dictionary<string, string> deltaGas;
  
      private List<Tile> tiles;
  
      public Room()
      {
          tiles = new List<Tile>();
          atmosphericGasses = new Dictionary<string, float>();
          deltaGas = new Dictionary<string, string>();
      }
  
      public int ID
      {
          get
          {
              return World.Current.GetRoomID(this);
          }
      }
  
      public static void EqualiseGasByTile(Tile tile, float leakFactor)
      {
          List<Room> roomsDone = new List<Room>();
          foreach (Tile t in tile.GetNeighbours())
          {
              // Skip tiles with a null room (i.e. outside).
              // TODO: Verify that gas still leaks to the outside
              // somehow.
              if (t.Room == null)
              {
                  continue;
              }
  
              if (roomsDone.Contains(t.Room) == false)
              {
                  foreach (Room r in roomsDone)
                  {
                      t.Room.EqualiseGas(r, leakFactor);
                  }
  
                  roomsDone.Add(t.Room);
              }
          }
      }
  
      public static void DoRoomFloodFill(Tile sourceTile, bool onlyIfOutside = false)
      {
          // SourceFurniture is the piece of furniture that may be
          // splitting two existing rooms, or may be the final 
          // enclosing piece to form a new room.
          // Check the NESW neighbours of the furniture's tile
          // and do flood fill from them.
          World world = World.Current;
  
          Room oldRoom = sourceTile.Room;
  
          if (oldRoom != null)
          {
              // Save the size of old room before we start removing tiles.
              // Needed for gas calculations.
              int sizeOfOldRoom = oldRoom.GetSize();
  
              // The source tile had a room, so this must be a new piece of furniture
              // that is potentially dividing this old room into as many as four new rooms.
  
              // Try building new rooms for each of our NESW directions.
              foreach (Tile t in sourceTile.GetNeighbours())
              {
                  if (t.Room != null && (onlyIfOutside == false || t.Room.IsOutsideRoom()))
                  {
                      ActualFloodFill(t, oldRoom, sizeOfOldRoom);
                  }
              }
  
              sourceTile.Room = null;
  
              oldRoom.tiles.Remove(sourceTile);
  
              // If this piece of furniture was added to an existing room
              // (which should always be true assuming with consider "outside" to be a big room)
              // delete that room and assign all tiles within to be "outside" for now.
              if (oldRoom.IsOutsideRoom() == false)
              {
                  // At this point, oldRoom shouldn't have any more tiles left in it,
                  // so in practice this "DeleteRoom" should mostly only need
                  // to remove the room from the world's list.
                  if (oldRoom.tiles.Count > 0)
                  {
                      Debug.ULogErrorChannel("Room", "'oldRoom' still has tiles assigned to it. This is clearly wrong.");
                  }
  
                  world.DeleteRoom(oldRoom);
              }
          }
          else
          {
              // oldRoom is null, which means the source tile was probably a wall,
              // though this MAY not be the case any longer (i.e. the wall was 
              // probably deconstructed. So the only thing we have to try is
              // to spawn ONE new room starting from the tile in question.
  
              // You need to delete the surrounding rooms so a new room can be created
              // This doesn't work for the gas calculations and needs to be fixed.
              foreach (Tile t in sourceTile.GetNeighbours())
              {
                  if (t.Room != null && (onlyIfOutside == false || t.Room.IsOutsideRoom()))
                  {
                      world.DeleteRoom(t.Room);
                  }
              }
  
              ActualFloodFill(sourceTile, null, 0);
          }
      }
  
      public void AssignTile(Tile t)
      {
          if (tiles.Contains(t))
          {
              // This tile already in this room.
              return;
          }
  
          if (t.Room != null)
          {
              // Belongs to some other room.
              t.Room.tiles.Remove(t);
          }
  
          t.Room = this;
          tiles.Add(t);
      }
  
      public void ReturnTilesToOutsideRoom()
      {
          for (int i = 0; i < tiles.Count; i++)
          {
              // Assign to outside.
              tiles[i].Room = World.Current.GetOutsideRoom();
          }
  
          tiles = new List<Tile>();
      }
  
      public bool IsOutsideRoom()
      {
          return this == World.Current.GetOutsideRoom();
      }
  
      public int GetSize()
      {
          return tiles.Count();
      }
  
      public Tile[] FindExits()
      {
          List<Tile> exits = new List<Tile>();
          foreach (Tile tile in tiles)
          {
              Tile[] neighbours = tile.GetNeighbours();
              foreach (Tile tile2 in neighbours)
              {
                  if (tile2 != null && tile2.Furniture != null)
                  {
                      if (tile2.Furniture.IsExit())
                      {
                          // We have found an exit
                          exits.Add(tile2);
                      }
                  }      
              }
          }
  
          return exits.ToArray();
      }
          
      public Dictionary<Tile, Room> GetNeighbours()
      {
          Dictionary<Tile, Room> neighboursRooms = new Dictionary<Tile, Room>();
  
          Tile[] exits = this.FindExits();
  
          foreach (Tile t in exits)
          {
              // Loop over the exits to find a different room
              Tile[] neighbours = t.GetNeighbours();
              foreach (Tile tile2 in neighbours)
              {
                  if (tile2 == null || tile2.Room == null)
                  {
                      continue;
                  }
  
                  // We have found a room
                  if (tile2.Room != this)
                  {
                      neighboursRooms[tile2] = tile2.Room;
                  }
              }
          }
  
          return neighboursRooms;
      }
  
      // Changes gas by an amount in preasure(in atm) multiplyed by number of tiles
      public void ChangeGas(string name, float amount)
      {
          if (IsOutsideRoom())
          {
              return;
          }
  
          if (atmosphericGasses.ContainsKey(name))
          {
              atmosphericGasses[name] += amount;
              if (Mathf.Sign(amount) == 1)
              {
                  deltaGas[name] = "+";
              }
              else
              {
                  deltaGas[name] = "-";
              }
          }
          else
          {
              atmosphericGasses[name] = amount;
              deltaGas[name] = "=";
          }
  
          if (atmosphericGasses[name] < 0)
          {
              atmosphericGasses[name] = 0;
          }
      }
  
      public string ChangedGases(string name)
      {
          if (deltaGas.ContainsKey(name))
          {
              return deltaGas[name];
          }
  
          return "=";
      }
  
      public void EqualiseGas(Room otherRoom, float leakFactor)
      {
          if (otherRoom == null)
          {
              return;
          }
  
          List<string> gasses = this.GetGasNames().ToList();
          gasses = gasses.Union(otherRoom.GetGasNames().ToList()).ToList();
          foreach (string gas in gasses)
          {
              float pressureDifference = this.GetGasPressure(gas) - otherRoom.GetGasPressure(gas);
              this.ChangeGas(gas, (-1) * pressureDifference * leakFactor);
              otherRoom.ChangeGas(gas, pressureDifference * leakFactor);
          }
      }
  
      // Gets absolute gas amount in preasure(in atm) multiplyed by number of tiles.
      public float GetGasAmount(string name)
      {
          if (atmosphericGasses.ContainsKey(name))
          {
              return atmosphericGasses[name];
          }
  
          return 0;
      }
  
      // Gets gas amount in preasure(in atm).
      public float GetGasPressure(string name)
      {
          if (atmosphericGasses.ContainsKey(name))
          {
              return atmosphericGasses[name] / GetSize();
          }
  
          return 0;
      }
  
      public float GetGasFraction(string name)
      {
          if (atmosphericGasses.ContainsKey(name) == false)
          {
              return 0;
          }
  
          float t = 0;
  
          foreach (string n in atmosphericGasses.Keys)
          {
              t += atmosphericGasses[n];
          }
  
          return atmosphericGasses[name] / t;
      }
  
      public float GetTotalGasPressure()
      {
          float t = 0;
  
          foreach (string n in atmosphericGasses.Keys)
          {
              t += atmosphericGasses[n];
          }
  
          return t;
      }
  
      public string[] GetGasNames()
      {
          return atmosphericGasses.Keys.ToArray();
      }
  
      public XmlSchema GetSchema()
      {
          return null;
      }
  
      public void WriteXml(XmlWriter writer)
      {
          // Write out gas info.
          foreach (string k in atmosphericGasses.Keys)
          {
              writer.WriteStartElement("Param");
              writer.WriteAttributeString("name", k);
              writer.WriteAttributeString("value", atmosphericGasses[k].ToString());
              writer.WriteEndElement();
          }
      }
  
      public void ReadXml(XmlReader reader)
      {
          // Read gas info.
          if (reader.ReadToDescendant("Param"))
          {
              do
              {
                  string k = reader.GetAttribute("name");
                  float v = float.Parse(reader.GetAttribute("value"));
                  atmosphericGasses[k] = v;
              }
              while (reader.ReadToNextSibling("Param"));
          }
      }
  
      protected static void ActualFloodFill(Tile tile, Room oldRoom, int sizeOfOldRoom)
      {
          if (tile == null)
          {
              // We are trying to flood fill off the map, so just return
              // without doing anything.
              return;
          }
  
          if (tile.Room != oldRoom)
          {
              // This tile was already assigned to another "new" room, which means
              // that the direction picked isn't isolated. So we can just return
              // without creating a new room.
              return;
          }
  
  
gt:         if (tile.Furniture != null && tile.Furniture.RoomEnclosure)