CoralMUD-0.15/
CoralMUD-0.15/core/
CoralMUD-0.15/data/
CoralMUD-0.15/data/areas/
CoralMUD-0.15/data/help/
CoralMUD-0.15/data/players/
CoralMUD-0.15/lib/automap/
CoralMUD-0.15/lib/items/

class Integer
  def gri
    Vnum.get_room_index self
  end
end

# Example:  sect_city or sect_forest
class Sector 
  attr_accessor :name, :symbol, :symbolw, :path_options, :wall_options
  @@sector_hash = {} # for fast lookups using symbols.  Was considerably more efficient.
  @@map_tiles = {} # for fast lookup of map tile. 
  def initialize n, sym, wall_op, path_op
    @name = n
    @symbol = sym
    @symbolw = (sym.to_s + "w").to_sym
    @wall_options = wall_op
    @path_options = path_op
    @@sector_hash[sym] = self # add this to the list.
  end

  def to_s
    "#{@name} (#{@wall_options} #{@path_options})"
  end

  # lookup a sector given a name or a symbol
  def self.lookup name
    if name.is_a? Symbol
      #log :debug, "Sector.sector_lookup(#{name}) returns #{@@sector_hash[name].to_str}"
      found = @@sector_hash[name]

      if found == nil
        temp = @@sector_hash.values
        temp = temp.select {|v| v.symbolw == name}
        found = temp[0]
      end
      return found
    end
    name.downcase!
    # select from list
    temp = @@sector_hash.values # array of values
    temp = temp.select {|v| v.name.start_with?(name) }
    
    return temp[0]
  end
end

# Room sector definitions.
#            NAME          SYMBOL       [[Color], north/south, east/west],   
# Sector.new("name",      :sect_symbol, [[], wall tiles],           [[], path tiles.
begin
  log :info, "Loading sector definitions."
  Sector.new("forest",    :sect_forest, [['g', 'G'], ['|'], '-'],     [['w'], ['.']])
  Sector.new("fancy",     :sect_fancy,  [['P'], ['##'], '##'],        [['C'], ['.']])
  Sector.new("city",      :sect_city,   [['W'], ['##'], '##'],        [['C'], ['.']])
  Sector.new("inside",    :sect_inside, [['n'], ['##'], '##'],        [['D'], ['.']])
  Sector.new("fountain",  :sect_fount,  [['W'], ['##']],               [['B'], ['0']])
  Sector.new("alley",     :sect_alley,  [['y', 'Y'], ['|'], '-'],     [['w'], ['.']])
  Sector.new("water",     :sect_water,  [['b','B'], ['##'], '##'],    [['b', 'B'], ['~', '-']])
  Sector.new("beach",     :sect_beach,  [['Y'], ['##'], '##'],        [['y', 'Y'], ['.']]) 
  Sector.new("hills",     :sect_hills,  [['g', 'G'], ['~'], '~'],     [['y'], ['.']])
  Sector.new("dock",      :sect_dock,   [['y'], ['=']],               [['D'], ['.']])
  Sector.new("void",      :sect_void,   [['n'], [' '], ' '],          [['x'], [' ']])
rescue Exception=>e
  log :error, "Sector table failed to load properly."
  log_exception e
end

# just used for rooms because of the nature of needing a way to look them up very quickly.
# It's also legacy.  
class Vnum
  @count = 1
  @rooms = {}

  # find a free vnum.  
  # Static variable is used since we really don't care what the number is
  # This way multiple searches won't require rehashing ground.
  def self.gen_vnum
    # then we don't care where.
    while self.get_room_index(@count)
      @count += 1
    end
    return @count
  end

  def self.get_room_index xnum
    @rooms[xnum]
  end


  # Adds a room to this area. The room has to be within the vnum range.
  def self.inject_room r 
	  @rooms[r.vnum] = r
  end
  def self.rooms
    @rooms
  end
end

class Exit
  attr_accessor :towards_room, :from_room, :direction
  attr_accessor :flags_state, :flags
  def initialize(xnum=nil,xnum2=nil,dir=nil)
    return if xnum == nil
    @direction = dir
    @towards_room = xnum.gri || xnum
    @from_room = xnum2.gri || xnum2

    @from_room.exit_list[dir] = self if @from_room.is_a?(Room)
  end

  def open
    @flags_state = {} if !@flags_state
    @flags_state.toggle(:closed)
    oe =  @towards_room.gri.exit_list[@direction.exit_code_rev] 
    if oe && oe.towards_room.gri == @from_room.gri
      oe.flags_state = {} if !oe.flags_state
      oe.flags_state.toggle(:closed)
    end
  end
  def close
    @flags_state = {} if !@flags_state
    @flags_state.toggle(:closed)
    oe =  @towards_room.gri.exit_list[@direction.exit_code_rev]
    if oe && oe.towards_room.gri == @from_room.gri
      @flags_state = {} if !oe.flags_state
      oe.flags_state.toggle(:closed)
    end
  end

  # remove the exit.
  def do_delete
    4.times do |x|
      ex = @from_room.exit_list[x]
      next if !ex
      if ex == self
        @from_room.exit_list[x] = nil
        return
      end
    end
  end

  def to_s
    "#{@towards_room} #{@from_room} #{@directon}"
  end

  def enter xplayer
    xplayer.from_room
    
    if @from_room.is_a?(Integer)
      @from_room = @from_room.gri
    end
    if @towards_room.is_a?(Integer)
      @towards_room = @towards_room.gri
    end

    @from_room.gri.text_to_room ("#{xplayer.short_desc.capitalize} leaves #{@direction.exit_code_to_s}." + ENDL)
    @towards_room.gri.text_to_room ("#{xplayer.short_desc.capitalize} has arrived." + ENDL)

    @towards_room.accept_player(xplayer)
    xplayer.execute_command("look");
  end  
end


module RoomDSL
  include ScriptEnvironment


end

# a single room.
class Room 
  attr_accessor :vnum, :exit_list, :people, :name, :sector, :desc

  def to_configure_properties
    ['@name', '@vtag', '@vnum', '@sector', '@exit_list']
  end

  include CoralMUD::FileIO # standard saving mechanisms.
  include CoralMUD::VirtualTags # vtags and indexing
  include CoralMUD::HasStuff # can contain things.
  include RoomDSL

  # called by the create command if this function exists.
  def self.create ch
    r = self.new(Vnum.gen_vnum) 
    r.namespace = ch.in_room.namespace  # have to set it so gen_generic_tag will work correctly.
    r.assign_tag Tag.gen_generic_tag(r), ch.in_room.namespace
    return r
  end

  def flags
    (@flags ||= {})
  end

  def initialize xnum=nil
    # Instance variables  
    @name = DEFAULT_STRING
    @sector = :sect_city
    @desc = "There is little unique to be seen here."
    @people = nil
    @vnum = xnum
    @exit_list = [nil, nil, nil, nil, nil, nil]
    $room_list << self

    associate_with_area if @vnum != nil
  end



  # unlink the room completely.  
  def do_delete
    moved = []
    # evacuate the room.
    if @people
      moved = @people.dup
      moved.each do |person|
        # send them to a safe vnum...we're going to use vnum 1
        person.go_anywhere
      end
    end

    4.times do |i|
      ex = self.exit_list[i]
      next if ex == nil

      log :debug, "ex #{ex.direction} deleted"

      if ex.towards_room.gri.exit_list[ex.direction.exit_code_rev]      
        if ex.towards_room.gri.exit_list[ex.direction.exit_code_rev].towards_room.gri == self # if it's the same room as being deleted we delete 
          ex.towards_room.gri.exit_list[ex.direction.exit_code_rev].do_delete
        end
      end
      ex.do_delete
    end

    moved.each do |p|
      p.execute_command("look")
    end
    Tag.clear_tag(self)
    @vtag = nil
    a = @vnum / 1000
    Vnum.rooms[@vnum % 1000] = nil # unlinked from main list.
  end

  def associate_with_area
    Vnum.inject_room self
  end

  def to_s
    mxptag("send 'edit #{Tag.full_tag(self)}'")+ "[Room #{@vtag}]" + mxptag("/send")
  end

  def self.load_rooms
    log :debug, "Load_rooms: Loading all rooms."

    rfiles = File.join("data/rooms", "*.yml")

    Dir.glob(rfiles).each do |a_file|
      room = Room.new()
      room.load_from_file(a_file) # loads each room file.
      room.associate_with_area # Must be done after we have the vnum
      
    end
  end

  def data_transform_on_load version, map
    # transform :exit_list from an array of  
    ar = map["@exit_list"]
    exits = [nil, nil, nil, nil, nil, nil] # all nil
    while !ar.empty? 
      each_arr = ar.shift
      dir, toward_room, flags = *each_arr

      exits[dir] = Exit.new()
      exits[dir].towards_room = toward_room
      exits[dir].from_room = self  
      exits[dir].direction = dir
      if flags && !flags.empty?
        exits[dir].flags = flags
        exits[dir].flags_state = flags.dup # set the flags state
      end
    end
    map["@exit_list"] = exits

    # transform the tag using the namespace
    vtag = map['@vtag']
    if vtag
      assign_tag vtag, map['@namespace']
      vtag = @vtag
      map['@vtag'] = vtag
    end
    return map
  end

  def data_transform_on_save map
    ex = map["@exit_list"] # transform the exit_list into something we can save more reliably.
    arr = []
    ex.each do |e|
      next if e == nil

      arr << [e.direction, e.towards_room.gri.vnum, e.flags]
    end
    map["@exit_list"] = arr

    vtag = map['@vtag'] 
    if vtag
      map['@vtag'] = vtag.to_s
    end 

    return map
  end

  # returns self
  def gri
    self
  end

  # does a circular exit check.
  def circular_check a
    return false if (self.exit_list[a[0]] == nil || self.exit_list[a[1]] == nil)
    return false if (self.exit_list[a[0]].towards_room.gri.exit_list[a[1]] == nil || self.exit_list[a[1]].towards_room.gri.exit_list[a[0]] == nil)
    return false if ((r = self.exit_list[a[0]].towards_room.gri.exit_list[a[1]].towards_room.gri) != self.exit_list[a[1]].towards_room.gri.exit_list[a[0]].towards_room.gri)
    return r
  end

  # for each direction in a room that exists.
  def each_dir
    self.exit_list.each do |e|
      yield e if e
    end
  end
  # do a dfs search with a code block
  def each_bfs (option_hash={:full_traverse=>true})
    def adjust_context context, dir
      con = context.dup
      case dir
      when 0 then con[:y] += 1
      when 1 then con[:x] += 1
      when 2 then con[:y] -= 1
      when 3 then con[:x] -= 1
      end
      return con
    end
    color_list, white_list = [], []
    full_traverse = option_hash[:full_traverse] 
    yield_x_range, yield_y_range = option_hash[:yield_range]

    # white list contains data to yield and process each pass.
    white_list << [self, {:x=>0, :y=>0}]
    color_list << self

    # while we still have something to act upon process it.
    # And yield the result to our block of code.
    while !white_list.empty?
      r, context = white_list.shift()

      yield(r, context) if yield_x_range === context[:x] && yield_y_range === context[:y]

      r.each_dir do |e|                 
        next if full_traverse == false && (e.flags_state.is_set(:soft_door) || e.flags_state.is_set(:closed))
        if e.towards_room.gri == nil
          e.do_delete
          next
        end
        if !color_list.include?(e.towards_room.gri)
          white_list << [e.towards_room.gri, adjust_context(context, e.direction)]
          color_list << e.towards_room.gri 
        end
      end
    end
    return nil
  end

  # do a dfs search with a code block
  def each_dfs
    color_list, white_list = [], []

    white_list << self
    color_list << self

    while !white_list.empty?
      r = white_list.pop()

      # do some code for each room found in the BFS.
      yield r

      r.each_dir do |e|
        if !color_list.include?(e.towards_room.gri)
          white_list << r.exit_list[i].towards_room.gri
          color_list << r.exit_list[i].towards_room.gri
        end
      end
    end
    return nil
  end


  # Create new rooms. Connect them. Nil is valid 
  # By default direction is unset. If no direction both must exist.
  # Also, input should havbeen checked by now.
  def Room.dig_rooms(rvnum1, rvnum2, direction)
    if direction >= 6 || direction < 0
      return false #failed
    end

    # lookup 2 rooms.
    r1, r2 = rvnum1.gri, rvnum2.gri

    # if either is nil now set it up.
    r1 = Room.new(rvnum1) if !r1
    r2 = Room.new(rvnum2) if !r2

    # creates exits with hook back
    Exit.new(rvnum2, rvnum1, direction)
    Exit.new(rvnum1, rvnum2, direction.exit_code_rev)

    return r2
  end

  def accept_player(player)
    @people = [] if !@people 
    @people << player
    player.in_room = self
  end

  def remove_player(player)
    @people.delete player if @people
    @people = nil if @people && @people.empty?
    player.in_room = nil
  end

  # display a type of message to the room.
  def display type, actor, blacklist, template, *arg
    type = [type].flatten
    template = ERB.new(template, 3)
    @people.each do |other| 
      all_true = true
      type.each do |condition|
        break if not all_true

        all_true = case condition
          when String then eval(condition)
          when Symbol
          case condition
            when :visual then !other.is_blind?
            when :sound then !other.is_deaf?
            when :physical then !other.is_dumb?
            else false
          end
          else false
        end 
      end

      if all_true and !blacklist.include?(other)
        other.view(template.result(binding) + ENDL) 
      end
    end
  end
  #method to send to every character in the room.
  def text_to_room txt
    if @people
      @people.each do |p| p.text_to_player(txt) end
    end
  end
end

def save_all_rooms
  $room_list.each do |r|
    r.save_room
  end  
end

def area_lookup num
  t = num / 1000
  return $area_list[t]
end