Class: Demiurge::TmxLocation

Inherits:
Location show all
Defined in:
lib/demiurge/tmx.rb

Overview

A TmxLocation is a special location that is attached to a tile layout, a TMX file. This affects the size and shape of the room, and how agents may travel through it. TmxLocations have X and Y coordinates (grid coordinates) for their positions.

Constant Summary

Constants inherited from ActionItem

ActionItem::ACTION_LEGAL_KEYS

Instance Attribute Summary

Attributes inherited from StateItem

#engine, #name

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Location

#add_exit, #exits, #finished_init, #initialize, #location, #location_name, #zone, #zone_name

Methods inherited from Container

#contents_names, #ensure_contains, #ensure_does_not_contain, #finished_init, #initialize, #intentions_for_next_step, #item_change_location, #move_item_inside, #receive_offer

Methods inherited from ActionItem

#__state_internal, #finished_init, #get_action, #get_actions_with_tags, #initialize, #intentions_for_next_step, #location, #location_name, #position, #register_actions, #run_action, #zone, #zone_name

Methods inherited from StateItem

#agent?, from_name_type, #get_structure, #initialize, #intentions_for_next_step, #state, #state_type, #zone?

Constructor Details

This class inherits a constructor from Demiurge::Location

Class Method Details

.position_to_coords(pos) ⇒ Object

Parse a tiled position string and return the X and Y tile coordinates



140
141
142
143
# File 'lib/demiurge/tmx.rb', line 140

def self.position_to_coords(pos)
  loc, x, y = position_to_loc_coords(pos)
  return x, y
end

.position_to_loc_coords(pos) ⇒ Object

Parse a tiled position string and return the location name and the X and Y tile coordinates



146
147
148
149
150
151
152
153
154
# File 'lib/demiurge/tmx.rb', line 146

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

.tile_cache_entry(state_manasource_tile_layout, state_tile_layout) ⇒ Object

Technically a StateItem of any kind has to be okay with its state changing totally out from under it at any time. One way around that for TMX is a tile cache to parse new entries and re-return old ones. This means if a file is changed at runtime, its cache entry won't be reloaded.



297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# File 'lib/demiurge/tmx.rb', line 297

def self.tile_cache_entry(state_manasource_tile_layout, state_tile_layout)
  smtl = state_manasource_tile_layout
  stl = state_tile_layout
  @tile_cache ||= {
    "manasource_tile_layout" => {},
    "tile_layout" => {},
  }
  if smtl
    @tile_cache["manasource_tile_layout"][smtl] ||= Demiurge.sprites_from_manasource_tmx(smtl)
  elsif stl
    @tile_cache["tile_layout"][stl] ||= Demiurge.sprites_from_tmx(stl)
  else
    raise "A TMX location must have some kind of tile layout!"
  end
end

Instance Method Details

For a TmxLocation's legal position, find somewhere not covered as a collision on the collision map.



241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'lib/demiurge/tmx.rb', line 241

def any_legal_position
  loc_tiles = self.tiles
  if tiles[:collision]
    # We have a collision layer? Fabulous. Scan upper-left to lower-right until we get something non-collidable.
    (0...tiles[:spritestack][:width]).each do |x|
      (0...tiles[:spritestack][:height]).each do |y|
        if tiles[:collision][y][x] == 0
          # We found a walkable spot.
          return "#{@name}##{x},#{y}"
        end
      end
    end
  else
    # Is there a start location? If so, return it. Guaranteed good, right?
    start_loc = tiles[:objects].detect { |obj| obj[:name] == "start location" }
    if start_loc
      x = start_loc[:x] / tiles[:spritestack][:tilewidth]
      y = start_loc[:y] / tiles[:spritestack][:tileheight]
      return "#{@name}##{x},#{y}"
    end
    # If no start location and no collision data, is there a first location with coordinates?
    if tiles[:objects].first[:x]
      obj = tiles[:objects].first
      return "#{@name}##{x},#{y}"
    end
    # Screw it, just return the upper left corner.
    return "#{@name}#0,0"
  end
end

#can_accomodate_agent?(agent, position) ⇒ Boolean

Determine whether this position can accomodate the given agent's shape and size.

Returns:

  • (Boolean)


195
196
197
198
199
200
# File 'lib/demiurge/tmx.rb', line 195

def can_accomodate_agent?(agent, position)
  loc, x, y = TmxLocation.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

#can_accomodate_dimensions?(left_x, upper_y, width, height) ⇒ Boolean

Determine whether this coordinate location can accomodate a rectangular item of the given coordinate dimensions.

Returns:

  • (Boolean)


204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/demiurge/tmx.rb', line 204

def can_accomodate_dimensions?(left_x, upper_y, width, height)
  return false if left_x < 0 || upper_y < 0
  right_x = left_x + width - 1
  lower_y = upper_y + height - 1
  return false if right_x >= tiles[:spritestack][:width] || lower_y >= tiles[:spritestack][:height]
  return true unless tiles[:collision]
  (left_x..right_x).each do |x|
    (upper_y..lower_y).each do |y|
      return false if tiles[:collision][y][x] != 0
    end
  end
  return true
end

#can_accomodate_shape?(left_x, upper_y, shape) ⇒ Boolean

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.

TODO: figure out some configurable way to specify what tile value means invalid for TMX maps with more complex collision logic.

Returns:

  • (Boolean)


228
229
230
231
232
233
234
235
236
237
# File 'lib/demiurge/tmx.rb', line 228

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

#item_change_position(item, old_pos, new_pos) ⇒ Object

When an item changes position in a TmxLocation, check if the new position leads out an exit. If so, send them where the exit leads instead.



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/demiurge/tmx.rb', line 159

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

#tilesObject

Return the tile object for this location



272
273
274
275
# File 'lib/demiurge/tmx.rb', line 272

def tiles
  raise("A TMX location (name: #{@name.inspect}) must have a tile layout!") unless state["tile_layout"] || state["manasource_tile_layout"]
  TmxLocation.tile_cache_entry(state["manasource_tile_layout"], state["tile_layout"])
end

#tmx_object_by_name(name) ⇒ Object

Return a TMX object's structure, for an object of the given name, or nil.



278
279
280
# File 'lib/demiurge/tmx.rb', line 278

def tmx_object_by_name(name)
  tiles[:objects].detect { |o| o[:name] == name }
end

#tmx_object_coords_by_name(name) ⇒ Object

Return the tile coordinates of the TMX object with the given name, or nil.



283
284
285
286
287
288
289
290
# File 'lib/demiurge/tmx.rb', line 283

def tmx_object_coords_by_name(name)
  obj = tiles[:objects].detect { |o| o[:name] == name }
  if obj
    [ obj[:x] / tiles[:spritesheet][:tilewidth], obj[:y] / tiles[:spritesheet][:tileheight] ]
  else
    nil
  end
end

#valid_coordinate?(x, y) ⇒ Boolean

This checks the coordinate's validity, but not relative to any specific person/item/whatever that could occupy the space.

Returns:

  • (Boolean)


187
188
189
190
191
192
# File 'lib/demiurge/tmx.rb', line 187

def valid_coordinate?(x, y)
  return false if x < 0 || y < 0
  return false if x >= tiles[:spritestack][:width] || y >= tiles[:spritestack][:height]
  return true unless tiles[:collision]
  return tiles[:collision][y][x] == 0
end

#valid_position?(pos) ⇒ Boolean

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.

Returns:

  • (Boolean)


178
179
180
181
182
183
# File 'lib/demiurge/tmx.rb', line 178

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