Files
infinitefusion-e18/Data/Scripts/004_Game classes/004_Game_Map.rb

566 lines
19 KiB
Ruby

#===============================================================================
# ** Game_Map
#------------------------------------------------------------------------------
# This class handles the map. It includes scrolling and passable determining
# functions. Refer to "$game_map" for the instance of this class.
#===============================================================================
class Game_Map
attr_accessor :map_id
attr_accessor :tileset_name # tileset file name
attr_accessor :autotile_names # autotile file name
attr_reader :passages # passage table
attr_reader :priorities # priority table
attr_reader :terrain_tags # terrain tag table
attr_reader :events # events
attr_accessor :panorama_name # panorama file name
attr_accessor :panorama_hue # panorama hue
attr_accessor :fog_name # fog file name
attr_accessor :fog_hue # fog hue
attr_accessor :fog_opacity # fog opacity level
attr_accessor :fog_blend_type # fog blending method
attr_accessor :fog_zoom # fog zoom rate
attr_accessor :fog_sx # fog sx
attr_accessor :fog_sy # fog sy
attr_reader :fog_ox # fog x-coordinate starting point
attr_reader :fog_oy # fog y-coordinate starting point
attr_reader :fog_tone # fog color tone
attr_accessor :battleback_name # battleback file name
attr_reader :display_x # display x-coordinate * 128
attr_reader :display_y # display y-coordinate * 128
attr_accessor :need_refresh # refresh request flag
TILE_WIDTH = 32
TILE_HEIGHT = 32
X_SUBPIXELS = 4
Y_SUBPIXELS = 4
REAL_RES_X = TILE_WIDTH * X_SUBPIXELS
REAL_RES_Y = TILE_HEIGHT * Y_SUBPIXELS
def initialize
@map_id = 0
@display_x = 0
@display_y = 0
end
def setup(map_id)
@map_id = map_id
@map = load_data(sprintf("Data/Map%03d.rxdata", map_id))
tileset = $data_tilesets[@map.tileset_id]
updateTileset
@fog_ox = 0
@fog_oy = 0
@fog_tone = Tone.new(0, 0, 0, 0)
@fog_tone_target = Tone.new(0, 0, 0, 0)
@fog_tone_duration = 0
@fog_tone_timer_start = nil
@fog_opacity_duration = 0
@fog_opacity_target = 0
@fog_opacity_timer_start = nil
self.display_x = 0
self.display_y = 0
@need_refresh = false
EventHandlers.trigger(:on_game_map_setup, map_id, @map, tileset)
@events = {}
@map.events.each_key do |i|
@events[i] = Game_Event.new(@map_id, @map.events[i], self)
end
@common_events = {}
(1...$data_common_events.size).each do |i|
@common_events[i] = Game_CommonEvent.new(i)
end
@scroll_distance_x = 0
@scroll_distance_y = 0
@scroll_speed = 4
end
def updateTileset
tileset = $data_tilesets[@map.tileset_id]
@tileset_name = tileset.tileset_name
@autotile_names = tileset.autotile_names
@panorama_name = tileset.panorama_name
@panorama_hue = tileset.panorama_hue
@fog_name = tileset.fog_name
@fog_hue = tileset.fog_hue
@fog_opacity = tileset.fog_opacity
@fog_blend_type = tileset.fog_blend_type
@fog_zoom = tileset.fog_zoom
@fog_sx = tileset.fog_sx
@fog_sy = tileset.fog_sy
@battleback_name = tileset.battleback_name
@passages = tileset.passages
@priorities = tileset.priorities
@terrain_tags = tileset.terrain_tags
end
def width; return @map.width; end
def height; return @map.height; end
def encounter_list; return @map.encounter_list; end
def encounter_step; return @map.encounter_step; end
def data; return @map.data; end
def tileset_id; return @map.tileset_id; end
def bgm; return @map.bgm; end
def name
return pbGetMapNameFromId(@map_id)
end
def metadata
return GameData::MapMetadata.try_get(@map_id)
end
# Returns the name of this map's BGM. If it's night time, returns the night
# version of the BGM (if it exists).
def bgm_name
if PBDayNight.isNight? && FileTest.audio_exist?("Audio/BGM/" + @map.bgm.name + "_n")
return @map.bgm.name + "_n"
end
return @map.bgm.name
end
# Autoplays background music
# Plays music called "[normal BGM]_n" if it's night time and it exists
def autoplayAsCue
pbCueBGM(bgm_name, 1.0, @map.bgm.volume, @map.bgm.pitch) if @map.autoplay_bgm
pbBGSPlay(@map.bgs) if @map.autoplay_bgs
end
# Plays background music
# Plays music called "[normal BGM]_n" if it's night time and it exists
def autoplay
pbBGMPlay(bgm_name, @map.bgm.volume, @map.bgm.pitch) if @map.autoplay_bgm
pbBGSPlay(@map.bgs) if @map.autoplay_bgs
end
def valid?(x, y)
return x >= 0 && x < width && y >= 0 && y < height
end
def validLax?(x, y)
return x >= -10 && x <= width + 10 && y >= -10 && y <= height + 10
end
def passable?(x, y, dir, self_event = nil)
return false if !valid?(x, y)
bit = (1 << ((dir / 2) - 1)) & 0x0f
events.each_value do |event|
next if event.tile_id <= 0
next if event == self_event
next if !event.at_coordinate?(x, y)
next if event.through
next if GameData::TerrainTag.try_get(@terrain_tags[event.tile_id]).ignore_passability
passage = @passages[event.tile_id]
return false if passage & bit != 0
return false if passage & 0x0f == 0x0f
return true if @priorities[event.tile_id] == 0
end
return playerPassable?(x, y, dir, self_event) if self_event == $game_player
# All other events
newx = x
newy = y
case dir
when 1
newx -= 1
newy += 1
when 2
newy += 1
when 3
newx += 1
newy += 1
when 4
newx -= 1
when 6
newx += 1
when 7
newx -= 1
newy -= 1
when 8
newy -= 1
when 9
newx += 1
newy -= 1
end
return false if !valid?(newx, newy)
[2, 1, 0].each do |i|
tile_id = data[x, y, i]
terrain = GameData::TerrainTag.try_get(@terrain_tags[tile_id])
# If already on water, only allow movement to another water tile
if self_event && terrain.can_surf_freely
[2, 1, 0].each do |j|
facing_tile_id = data[newx, newy, j]
next if facing_tile_id == 0
return false if facing_tile_id.nil?
facing_terrain = GameData::TerrainTag.try_get(@terrain_tags[facing_tile_id])
if facing_terrain.id != :None && !facing_terrain.ignore_passability
return facing_terrain.can_surf_freely
end
end
return false
# Can't walk onto ice
elsif terrain.ice
return false
elsif self_event && self_event.x == x && self_event.y == y
# Can't walk onto ledges
[2, 1, 0].each do |j|
facing_tile_id = data[newx, newy, j]
next if facing_tile_id == 0
return false if facing_tile_id.nil?
facing_terrain = GameData::TerrainTag.try_get(@terrain_tags[facing_tile_id])
return false if facing_terrain.ledge
break if facing_terrain.id != :None && !facing_terrain.ignore_passability
end
end
next if terrain&.ignore_passability
next if tile_id == 0
# Regular passability checks
passage = @passages[tile_id]
return false if passage & bit != 0 || passage & 0x0f == 0x0f
return true if @priorities[tile_id] == 0
end
return true
end
def playerPassable?(x, y, dir, self_event = nil)
bit = (1 << ((dir / 2) - 1)) & 0x0f
[2, 1, 0].each do |i|
tile_id = data[x, y, i]
next if tile_id == 0
terrain = GameData::TerrainTag.try_get(@terrain_tags[tile_id])
passage = @passages[tile_id]
if terrain
# Ignore bridge tiles if not on a bridge
next if terrain.bridge && $PokemonGlobal.bridge == 0
# Make water tiles passable if player is surfing
return true if $PokemonGlobal.surfing && terrain.can_surf && !terrain.waterfall
# Prevent cycling in really tall grass/on ice
return false if $PokemonGlobal.bicycle && (terrain.must_walk || terrain.must_walk_or_run)
# Depend on passability of bridge tile if on bridge
if terrain.bridge && $PokemonGlobal.bridge > 0
return (passage & bit == 0 && passage & 0x0f != 0x0f)
end
end
next if terrain&.ignore_passability
# Regular passability checks
return false if passage & bit != 0 || passage & 0x0f == 0x0f
return true if @priorities[tile_id] == 0
end
return true
end
# Returns whether the position x,y is fully passable (there is no blocking
# event there, and the tile is fully passable in all directions).
def passableStrict?(x, y, d, self_event = nil)
return false if !valid?(x, y)
events.each_value do |event|
next if event == self_event || event.tile_id < 0 || event.through
next if !event.at_coordinate?(x, y)
next if GameData::TerrainTag.try_get(@terrain_tags[event.tile_id]).ignore_passability
return false if @passages[event.tile_id] & 0x0f != 0
return true if @priorities[event.tile_id] == 0
end
[2, 1, 0].each do |i|
tile_id = data[x, y, i]
next if tile_id == 0
next if GameData::TerrainTag.try_get(@terrain_tags[tile_id]).ignore_passability
return false if @passages[tile_id] & 0x0f != 0
return true if @priorities[tile_id] == 0
end
return true
end
def bush?(x, y)
[2, 1, 0].each do |i|
tile_id = data[x, y, i]
next if tile_id == 0
return false if GameData::TerrainTag.try_get(@terrain_tags[tile_id]).bridge &&
$PokemonGlobal.bridge > 0
return true if @passages[tile_id] & 0x40 == 0x40
end
return false
end
def deepBush?(x, y)
[2, 1, 0].each do |i|
tile_id = data[x, y, i]
next if tile_id == 0
terrain = GameData::TerrainTag.try_get(@terrain_tags[tile_id])
return false if terrain.bridge && $PokemonGlobal.bridge > 0
return true if terrain.deep_bush && @passages[tile_id] & 0x40 == 0x40
end
return false
end
def counter?(x, y)
[2, 1, 0].each do |i|
tile_id = data[x, y, i]
next if tile_id == 0
passage = @passages[tile_id]
return true if passage & 0x80 == 0x80
end
return false
end
def terrain_tag(x, y, countBridge = false)
if valid?(x, y)
[2, 1, 0].each do |i|
tile_id = data[x, y, i]
next if tile_id == 0
terrain = GameData::TerrainTag.try_get(@terrain_tags[tile_id])
next if terrain.id == :None || terrain.ignore_passability
next if !countBridge && terrain.bridge && $PokemonGlobal.bridge == 0
return terrain
end
end
return GameData::TerrainTag.get(:None)
end
# Unused.
def check_event(x, y)
self.events.each_value do |event|
return event.id if event.at_coordinate?(x, y)
end
end
def display_x=(value)
return if @display_x == value
@display_x = value
if metadata&.snap_edges
max_x = (self.width - (Graphics.width.to_f / TILE_WIDTH)) * REAL_RES_X
@display_x = [0, [@display_x, max_x].min].max
end
$map_factory&.setMapsInRange
end
def display_y=(value)
return if @display_y == value
@display_y = value
if metadata&.snap_edges
max_y = (self.height - (Graphics.height.to_f / TILE_HEIGHT)) * REAL_RES_Y
@display_y = [0, [@display_y, max_y].min].max
end
$map_factory&.setMapsInRange
end
def scroll_up(distance)
self.display_y -= distance
end
def scroll_down(distance)
self.display_y += distance
end
def scroll_left(distance)
self.display_x -= distance
end
def scroll_right(distance)
self.display_x += distance
end
# speed is:
# 1: moves 1 tile in 1.6 seconds
# 2: moves 1 tile in 0.8 seconds
# 3: moves 1 tile in 0.4 seconds
# 4: moves 1 tile in 0.2 seconds
# 5: moves 1 tile in 0.1 seconds
# 6: moves 1 tile in 0.05 seconds
def start_scroll(direction, distance, speed = 4)
return if direction <= 0 || direction == 5 || direction >= 10
if [1, 3, 4, 6, 7, 9].include?(direction) # horizontal
@scroll_distance_x = distance
@scroll_distance_x *= -1 if [1, 4, 7].include?(direction)
end
if [1, 2, 3, 7, 8, 9].include?(direction) # vertical
@scroll_distance_y = distance
@scroll_distance_y *= -1 if [7, 8, 9].include?(direction)
end
@scroll_speed = speed
@scroll_start_x = display_x
@scroll_start_y = display_y
@scroll_timer_start = System.uptime
end
# The two distances can be positive or negative.
def start_scroll_custom(distance_x, distance_y, speed = 4)
return if distance_x == 0 && distance_y == 0
@scroll_distance_x = distance_x
@scroll_distance_y = distance_y
@scroll_speed = speed
@scroll_start_x = display_x
@scroll_start_y = display_y
@scroll_timer_start = System.uptime
end
def scrolling?
return (@scroll_distance_x || 0) != 0 || (@scroll_distance_y || 0) != 0
end
# duration is time in 1/20ths of a second.
def start_fog_tone_change(tone, duration)
if duration == 0
@fog_tone = tone.clone
return
end
@fog_tone_initial = @fog_tone.clone
@fog_tone_target = tone.clone
@fog_tone_duration = duration / 20.0
@fog_tone_timer_start = $stats.play_time
end
# duration is time in 1/20ths of a second.
def start_fog_opacity_change(opacity, duration)
if duration == 0
@fog_opacity = opacity.to_f
return
end
@fog_opacity_initial = @fog_opacity
@fog_opacity_target = opacity.to_f
@fog_opacity_duration = duration / 20.0
@fog_opacity_timer_start = $stats.play_time
end
def set_tile(x, y, layer, id = 0)
self.data[x, y, layer] = id
end
def erase_tile(x, y, layer)
set_tile(x, y, layer, 0)
end
def refresh
@events.each_value do |event|
event.refresh
end
@common_events.each_value do |common_event|
common_event.refresh
end
@need_refresh = false
end
def update
uptime_now = System.uptime
play_now = $stats.play_time
# Refresh maps if necessary
if $map_factory
$map_factory.maps.each { |i| i.refresh if i.need_refresh }
$map_factory.setCurrentMap
end
# If scrolling
if (@scroll_distance_x || 0) != 0
duration = @scroll_distance_x.abs * TILE_WIDTH.to_f / (10 * (2**@scroll_speed))
scroll_offset = lerp(0, @scroll_distance_x, duration, @scroll_timer_start, uptime_now)
self.display_x = @scroll_start_x + (scroll_offset * REAL_RES_X)
@scroll_distance_x = 0 if scroll_offset == @scroll_distance_x
end
if (@scroll_distance_y || 0) != 0
duration = @scroll_distance_y.abs * TILE_HEIGHT.to_f / (10 * (2**@scroll_speed))
scroll_offset = lerp(0, @scroll_distance_y, duration, @scroll_timer_start, uptime_now)
self.display_y = @scroll_start_y + (scroll_offset * REAL_RES_Y)
@scroll_distance_y = 0 if scroll_offset == @scroll_distance_y
end
# Only update events that are on-screen
if !$game_temp.in_menu
@events.each_value { |event| event.update }
end
# Update common events
@common_events.each_value { |common_event| common_event.update }
# Update fog
@fog_scroll_last_update_timer = uptime_now if !@fog_scroll_last_update_timer
scroll_mult = (uptime_now - @fog_scroll_last_update_timer) * 5
@fog_ox -= @fog_sx * scroll_mult
@fog_oy -= @fog_sy * scroll_mult
@fog_scroll_last_update_timer = uptime_now
if @fog_tone_timer_start
@fog_tone.red = lerp(@fog_tone_initial.red, @fog_tone_target.red, @fog_tone_duration, @fog_tone_timer_start, play_now)
@fog_tone.green = lerp(@fog_tone_initial.green, @fog_tone_target.green, @fog_tone_duration, @fog_tone_timer_start, play_now)
@fog_tone.blue = lerp(@fog_tone_initial.blue, @fog_tone_target.blue, @fog_tone_duration, @fog_tone_timer_start, play_now)
@fog_tone.gray = lerp(@fog_tone_initial.gray, @fog_tone_target.gray, @fog_tone_duration, @fog_tone_timer_start, play_now)
if play_now - @fog_tone_timer_start >= @fog_tone_duration
@fog_tone_initial = nil
@fog_tone_timer_start = nil
end
end
if @fog_opacity_timer_start
@fog_opacity = lerp(@fog_opacity_initial, @fog_opacity_target, @fog_opacity_duration, @fog_opacity_timer_start, play_now)
if play_now - @fog_opacity_timer_start >= @fog_opacity_duration
@fog_opacity_initial = nil
@fog_opacity_timer_start = nil
end
end
end
end
#===============================================================================
#
#===============================================================================
# Scroll the map in the given direction by the given distance at the (optional)
# given speed.
def pbScrollMap(direction, distance, speed = 4)
if speed == 0
if [1, 2, 3].include?(direction)
$game_map.scroll_down(distance * Game_Map::REAL_RES_Y)
elsif [7, 8, 9].include?(direction)
$game_map.scroll_up(distance * Game_Map::REAL_RES_Y)
end
if [3, 6, 9].include?(direction)
$game_map.scroll_right(distance * Game_Map::REAL_RES_X)
elsif [1, 4, 7].include?(direction)
$game_map.scroll_left(distance * Game_Map::REAL_RES_X)
end
else
$game_map.start_scroll(direction, distance, speed)
loop do
Graphics.update
Input.update
pbUpdateSceneMap
break if !$game_map.scrolling?
end
end
end
# Scroll the map to center on the given coordinates at the (optional) given
# speed. The scroll can happen in up to two parts, depending on where the target
# is relative to the current location: an initial diagonal movement and a
# following cardinal (vertical/horizontal) movement.
def pbScrollMapTo(x, y, speed = 4)
if !$game_map.valid?(x, y)
print "pbScrollMapTo: given x,y is invalid"
return
elsif !(0..6).include?(speed)
print "pbScrollMapTo: invalid speed (0-6 only)"
return
end
# Get tile coordinates that the screen is currently scrolled to
screen_offset_x = (Graphics.width - Game_Map::TILE_WIDTH) * Game_Map::X_SUBPIXELS / 2
screen_offset_y = (Graphics.height - Game_Map::TILE_HEIGHT) * Game_Map::Y_SUBPIXELS / 2
current_tile_x = ($game_map.display_x + screen_offset_x) / Game_Map::REAL_RES_X
current_tile_y = ($game_map.display_y + screen_offset_y) / Game_Map::REAL_RES_Y
offset_x = x - current_tile_x
offset_y = y - current_tile_y
return if offset_x == 0 && offset_y == 0
if speed == 0
if offset_y > 0
$game_map.scroll_down(offset_y.abs * Game_Map::REAL_RES_Y)
elsif offset_y < 0
$game_map.scroll_up(offset_y.abs * Game_Map::REAL_RES_Y)
end
if offset_x > 0
$game_map.scroll_right(offset_x.abs * Game_Map::REAL_RES_X)
elsif offset_x < 0
$game_map.scroll_left(offset_x.abs * Game_Map::REAL_RES_X)
end
else
$game_map.start_scroll_custom(offset_x, offset_y, speed)
loop do
Graphics.update
Input.update
pbUpdateSceneMap
break if !$game_map.scrolling?
end
end
end
# Scroll the map to center on the player at the (optional) given speed.
def pbScrollMapToPlayer(speed = 4)
pbScrollMapTo($game_player.x, $game_player.y, speed)
end