lib/demiurge/location.rb in demiurge-0.2.0 vs lib/demiurge/location.rb in demiurge-0.4.0
- old
+ new
@@ -79,7 +79,163 @@
# subclass of Location you're dealing with.
def exits
@state["exits"]
end
+ # Returns an array of position strings for positions adjacent to
+ # the one given. In some areas this won't be meaningful. But for
+ # most "plain" areas, this gives possibilities of where is
+ # moveable for simple AIs.
+ #
+ # @return [Array<String>] Array of position strings
+ # @since 0.0.1
+ def adjacent_positions(pos, options = {})
+ @state["exits"].map { |e| e["to"] }
+ end
+ end
+
+ # A TiledLocation is a location that uses #x,y format for positions
+ # for a 2D grid in the location. Something like a TMX file defines a
+ # TiledLocation, but so does an infinitely generated tiled space.
+ #
+ # @since 0.3.0
+ class TiledLocation < Location
+ # Parse a tiled position string and return the X and Y tile coordinates
+ #
+ # @param pos [String] The position string to parse
+ # @return [Array<Integer,Integer>] The x, y coordinates
+ # @since 0.2.0
+ def self.position_to_coords(pos)
+ loc, x, y = position_to_loc_coords(pos)
+ return x, y
+ end
+
+ # Parse a tiled position string and return the location name and the X and Y tile coordinates
+ #
+ # @param pos [String] The position string to parse
+ # @return [Array<String,Integer,Integer>] The location name and x, y coordinates
+ # @since 0.2.0
+ def self.position_to_loc_coords(pos)
+ loc, coords = pos.split("#",2)
+ if coords
+ x, y = coords.split(",")
+ return loc, x.to_i, y.to_i
+ else
+ return loc, nil, nil
+ end
+ end
+
+ # When an item changes position in a TiledLocation, check if the
+ # new position leads out an exit. If so, send them where the exit
+ # leads instead.
+ #
+ # @param item [String] The item changing position
+ # @param old_pos [String] The position string the item is moving *from*
+ # @param new_pos [String] The position string the item is moving *to*
+ # @return [void]
+ # @since 0.2.0
+ def item_change_position(item, old_pos, new_pos)
+ exit = @state["exits"].detect { |e| e["from"] == new_pos }
+ return super unless exit # No exit? Do what you were going to.
+
+ # Going to hit an exit? Cancel this motion and enqueue an
+ # intention to do so? Or just send them through? If the former,
+ # it's very hard to unblockably pass through an exit, even if
+ # that's what's wanted. If the latter, it's very hard to make
+ # going through an exit blockable.
+
+ # Eh, just send them through for now. We'll figure out how to
+ # make detecting and blocking exit intentions easy later.
+
+ item_change_location(item, old_pos, exit["to"])
+ end
+
+ # This just determines if the position is valid at all. It does
+ # *not* check walkable/swimmable or even if it's big enough for a
+ # humanoid to stand in.
+ #
+ # @param pos [String] The position being checked
+ # @return [Boolean] Whether the position is valid
+ # @since 0.2.0
+ def valid_position?(pos)
+ return false unless pos[0...@name.size] == @name
+ return false unless pos[@name.size] == "#"
+ x, y = pos[(@name.size + 1)..-1].split(",", 2).map(&:to_i)
+ valid_coordinate?(x, y)
+ end
+
+ # Determine whether this position can accomodate the given agent's shape and size.
+ #
+ # @param agent [Demiurge::Agent] The agent being checked
+ # @param position [String] The position being checked
+ # @return [Boolean] Whether the position can accomodate the agent
+ # @since 0.2.0
+ def can_accomodate_agent?(agent, position)
+ loc, x, y = TiledLocation.position_to_loc_coords(position)
+ raise "Location #{@name.inspect} asked about different location #{loc.inspect} in can_accomodate_agent!" if loc != @name
+ shape = agent.state["shape"] || "humanoid"
+ can_accomodate_shape?(x, y, shape)
+ end
+
+ # Whether the coordinate is valid
+ #
+ # @param x [Integer] The X coordinate
+ # @param y [Integer] The Y coordinate
+ # @return [Boolean] Whether the coordinate is valid
+ # @since 0.3.0
+ def valid_coordinate?(x,y)
+ true
+ end
+
+ # Whether the location can accomodate an object of this size.
+ #
+ # @param left_x [Integer] The lower (leftmost) X coordinate
+ # @param upper_y [Integer] The higher (upper) Y coordinate
+ # @param width [Integer] How wide the checked object is
+ # @param height [Integer] How high the checked object is
+ # @return [Boolean] Whether the object can be accomodated
+ # @since 0.3.0
+ def can_accomodate_dimensions?(left_x, upper_y, width, height)
+ true
+ end
+
+ # Determine whether this coordinate location can accomodate an
+ # item of the given shape.
+ #
+ # For now, don't distinguish between walkable/swimmable or
+ # whatever, just say a collision value of 0 means valid,
+ # everything else is invalid.
+ #
+ # @param left_x [Integer] The lower (leftmost) X coordinate
+ # @param upper_y [Integer] The higher (upper) Y coordinate
+ # @return [Boolean] Whether the object can be accomodated
+ # @since 0.3.0
+ def can_accomodate_shape?(left_x, upper_y, shape)
+ case shape
+ when "humanoid"
+ return can_accomodate_dimensions?(left_x, upper_y, 2, 1)
+ when "tiny"
+ return can_accomodate_dimensions?(left_x, upper_y, 1, 1)
+ else
+ raise "Unknown shape #{shape.inspect} passed to can_accomodate_shape!"
+ end
+ end
+
+ # Return a legal position in this location
+ #
+ # @return [String] A legal position string
+ # @since 0.3.0
+ def any_legal_position
+ "#{@name}#0,0"
+ end
+
+ # Return the list of valid adjacent positions from this one
+ def adjacent_positions(pos, options = {})
+ location, pos_spec = pos.split("#", 2)
+ loc = @engine.item_by_name(location)
+ x, y = pos_spec.split(",").map(&:to_i)
+
+ shape = options[:shape] || "humanoid"
+ [[x - 1, y], [x + 1, y], [x, y - 1], [x, y + 1]].select { |xp, yp| loc.can_accomodate_shape?(xp, yp, shape) }
+ end
end
end