Module: Lich::Common::MapBase::InstanceMethods

Defined in:
documented/common/map/map_base.rb

Overview

Instance methods for Room/Map objects

Instance Method Summary collapse

Instance Method Details

#descObject

Deprecated methods for backward compatibility



947
948
949
# File 'documented/common/map/map_base.rb', line 947

def desc
  @description
end

#dijkstra(destination = nil) ⇒ Array<Hash>? Also known as: dijkstra_hashes

Run Dijkstra's algorithm from this room.

Returns Hashes keyed by room id. These were Arrays indexed by room id until 5.20.0: the conversion allocated two Arrays sized by the highest room id reached, which dominated the cost on a sparse map and gained nothing, since every caller only ever indexes by room id. Access is unchanged - previous and distances read the same either way, and an unreached room still yields nil. #size and #length now count the rooms actually reached rather than the highest id plus one, and iteration yields pairs rather than slots.

Parameters:

  • destination (Integer, Array, nil) (defaults to: nil)

    Target room(s) or nil for full graph

Returns:

  • (Array<Hash>, nil)

    [previous, distances] keyed by room id, or nil when the search failed



808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
# File 'documented/common/map/map_base.rb', line 808

def dijkstra(destination = nil)
  self.class.load unless self.class.loaded?
  source = @id
  visited = {}
  shortest_distances_hash = {}
  previous_hash = {}

  pq = MinHeap.new
  pq.push(0, source)
  shortest_distances_hash[source] = 0

  check_destination = proc do |v, dist|
    case destination
    when Integer
      v == destination
    when Array
      destination.include?(v) && dist < 20
    else
      false
    end
  end

  until pq.empty?
    current_dist, v = pq.pop

    next if visited[v]
    break if check_destination.call(v, current_dist)

    visited[v] = true

    # A wayto edge can name a room that is gone. Under the NilClass
    # patch the enumeration was simply skipped; without it the raise
    # would be rescued below and lose the whole search.
    room = self.class.list[v]
    next if room.nil?

    room.wayto.keys.each do |adj_room|
      adj_room_i = adj_room.to_i
      next if visited[adj_room_i]

      edge_weight = if room.timeto[adj_room].is_a?(StringProc)
                      room.timeto[adj_room].call
                    else
                      room.timeto[adj_room]
                    end

      next unless edge_weight

      new_distance = current_dist + edge_weight

      if !shortest_distances_hash[adj_room_i] || shortest_distances_hash[adj_room_i] > new_distance
        shortest_distances_hash[adj_room_i] = new_distance
        previous_hash[adj_room_i] = v
        pq.push(new_distance, adj_room_i)
      end
    end
  end

  [previous_hash, shortest_distances_hash]
rescue StandardError => e
  echo "Map.dijkstra: error: #{e}"
  respond e.backtrace
  nil
end

#find_all_nearest_by_tag(tag_name) ⇒ Array<Integer>

Find all rooms with a specific tag, sorted by distance

Parameters:

  • tag_name (String)

    Tag to search for

Returns:

  • (Array<Integer>)

    Room IDs sorted by distance



921
922
923
924
925
926
927
928
# File 'documented/common/map/map_base.rb', line 921

def find_all_nearest_by_tag(tag_name)
  target_list = self.class.rooms_by_tag(tag_name)
  _, shortest_distances = dijkstra
  return [] if shortest_distances.nil?

  target_list.delete_if { |room_num| shortest_distances[room_num].nil? }
  target_list.sort { |a, b| shortest_distances[a] <=> shortest_distances[b] }
end

#find_nearest(target_list) ⇒ Integer?

Find nearest room from a list

Parameters:

  • target_list (Array<Integer>)

    List of room IDs to search

Returns:

  • (Integer, nil)

    Nearest room ID



933
934
935
936
937
938
939
940
941
942
943
944
# File 'documented/common/map/map_base.rb', line 933

def find_nearest(target_list)
  target_list = target_list.collect(&:to_i)
  if target_list.include?(@id)
    @id
  else
    _, shortest_distances = dijkstra(target_list)
    return nil if shortest_distances.nil?

    valid_rooms = target_list.select { |room_num| shortest_distances[room_num].is_a?(Numeric) }
    valid_rooms.min_by { |room_num| shortest_distances[room_num] }
  end
end

#find_nearest_by_tag(tag_name) ⇒ Integer?

Find nearest room with a specific tag

Parameters:

  • tag_name (String)

    Tag to search for

Returns:

  • (Integer, nil)

    Room ID of nearest tagged room



907
908
909
910
911
912
913
914
915
916
# File 'documented/common/map/map_base.rb', line 907

def find_nearest_by_tag(tag_name)
  target_list = self.class.rooms_by_tag(tag_name)
  return @id if target_list.include?(@id)

  _, shortest_distances = dijkstra(target_list)
  return nil if shortest_distances.nil?

  target_list.delete_if { |room_num| shortest_distances[room_num].nil? }
  target_list.sort { |a, b| shortest_distances[a] <=> shortest_distances[b] }.first
end

#geonil

Placeholder for game-specific geographic metadata.

Returns:

  • (nil)

    always nil in the base implementation



998
999
1000
# File 'documented/common/map/map_base.rb', line 998

def geo
  nil
end

#inside?Boolean

Check if room is indoors

Returns:

  • (Boolean)

    true if room is indoors



754
755
756
# File 'documented/common/map/map_base.rb', line 754

def inside?
  !outside?
end

#inspectObject

Inspect room details



759
760
761
762
763
# File 'documented/common/map/map_base.rb', line 759

def inspect
  instance_variables.collect do |var|
    "#{var}=#{instance_variable_get(var).inspect}"
  end.join("\n")
end

#json_extra_fieldsObject

Override in subclasses to add game-specific fields to JSON output. Must return a Hash. Nil/empty values are filtered automatically.



767
768
769
# File 'documented/common/map/map_base.rb', line 767

def json_extra_fields
  {}
end

#map_nameString?

Returns the map image filename for this room.

Provided for backward compatibility. Equivalent to the @image attribute.

Returns:

  • (String, nil)

    the image filename, or nil if not set



956
957
958
# File 'documented/common/map/map_base.rb', line 956

def map_name
  @image
end

#map_roomsizeInteger?

Returns the width of this room's bounding box on the map image.

Provided for backward compatibility. Computes image_coords - image_coords.

Returns:

  • (Integer, nil)

    the width in pixels, or nil if image_coords is not set



989
990
991
992
993
# File 'documented/common/map/map_base.rb', line 989

def map_roomsize
  return nil if @image_coords.nil?

  image_coords[2] - image_coords[0]
end

#map_xInteger?

Returns the center X coordinate on the map image for this room.

Computes the midpoint of the image_coords bounding box. Provided for backward compatibility.

Returns:

  • (Integer, nil)

    the X coordinate, or nil if image_coords is not set



966
967
968
969
970
# File 'documented/common/map/map_base.rb', line 966

def map_x
  return nil if @image_coords.nil?

  ((image_coords[0] + image_coords[2]) / 2.0).round
end

#map_yInteger?

Returns the center Y coordinate on the map image for this room.

Computes the midpoint of the image_coords bounding box. Provided for backward compatibility.

Returns:

  • (Integer, nil)

    the Y coordinate, or nil if image_coords is not set



978
979
980
981
982
# File 'documented/common/map/map_base.rb', line 978

def map_y
  return nil if @image_coords.nil?

  ((image_coords[1] + image_coords[3]) / 2.0).round
end

#outside?Boolean

Check if room is outdoors Works for both GemStone and DragonRealms:

  • "Obvious paths:" indicates outdoor
  • "Obvious exits:" indicates indoor

Returns:

  • (Boolean)

    true if room is outdoors



746
747
748
749
750
# File 'documented/common/map/map_base.rb', line 746

def outside?
  return false if @paths.nil? || @paths.empty?

  @paths.last =~ /^Obvious paths:/ ? true : false
end

#path_to(destination) ⇒ Array<Integer>?

Find path from this room to destination

Parameters:

  • destination (Integer)

    Target room ID

Returns:

  • (Array<Integer>, nil)

    Array of room IDs representing rooms to traverse (excluding source, including destination)



879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
# File 'documented/common/map/map_base.rb', line 879

def path_to(destination)
  self.class.load unless self.class.loaded?
  destination = destination.to_i
  previous, = dijkstra(destination)
  # dijkstra returns nil when the search itself failed.
  return nil if previous.nil?
  return nil unless previous[destination]

  path = [destination]
  seen = { destination => true }
  until previous[path[-1]] == @id
    step = previous[path[-1]]
    # A chain that never reaches this room is not a usable path. The
    # hash yields nil for a missing predecessor, and a chain that
    # revisits a room is a cycle; either would loop forever. Dijkstra
    # should not produce either, but path_to is a public entry point and
    # dijkstra is overridable.
    return nil if step.nil? || seen[step]

    seen[step] = true
    path.push(step)
  end
  path.reverse
end

#tags=(value) ⇒ nil

Replace this room's tags and drop the tag index

Parameters:

  • value (Array, nil)

    new tag names

Returns:

  • (nil)

    Ruby ignores a writer's return value, so room.tags = x evaluates to x regardless of what this returns



731
732
733
734
# File 'documented/common/map/map_base.rb', line 731

def tags=(value)
  @tags = TagList.new(value, self.class)
  self.class.reset_tag_index
end

#to_iObject

Convert room to integer (room ID)



737
738
739
# File 'documented/common/map/map_base.rb', line 737

def to_i
  @id
end

#to_json(*_args) ⇒ Object

Convert room to JSON



772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
# File 'documented/common/map/map_base.rb', line 772

def to_json(*_args)
  mapjson = {
    id: @id,
    title: @title,
    description: @description,
    paths: @paths,
    location: @location,
    climate: @climate,
    terrain: @terrain,
    wayto: @wayto&.sort_by { |k, _v| k.to_i }&.to_h,
    timeto: @timeto&.sort_by { |k, _v| k.to_i }&.to_h,
    image: @image,
    image_coords: @image_coords,
    tags: @tags&.sort_by { |tag| [tag.downcase, tag] },
    check_location: @check_location,
    unique_loot: @unique_loot,
    uid: @uid
  }
  mapjson.merge!(json_extra_fields)
  mapjson.delete_if { |_a, b| b.nil? || (b.is_a?(Array) && b.empty?) }
  JSON.pretty_generate(mapjson)
end