diff --git a/.rubocop.yml b/.rubocop.yml index 7e36688f0..53df72f85 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -154,7 +154,7 @@ Style/SymbolArray: Style/WordArray: EnforcedStyle: brackets -# Patentheses around the condition in a ternary operator helps to differentiate +# Parentheses around the condition in a ternary operator helps to differentiate # it from the true/false results. Style/TernaryParentheses: EnforcedStyle: require_parentheses diff --git a/Data/Scripts/001_Technical/002_RubyUtilities.rb b/Data/Scripts/001_Technical/002_RubyUtilities.rb index 6fc2184fa..3965cfcd3 100644 --- a/Data/Scripts/001_Technical/002_RubyUtilities.rb +++ b/Data/Scripts/001_Technical/002_RubyUtilities.rb @@ -359,7 +359,7 @@ class CallbackWrapper def execute(given_block = nil, *args) execute_block = given_block || @code_block @params.each do |key, value| - args.instance_variable_set("@#{key.to_s}", value) + args.instance_variable_set("@#{key}", value) end args.instance_eval(&execute_block) end diff --git a/Data/Scripts/001_Technical/005_PluginManager.rb b/Data/Scripts/001_Technical/005_PluginManager.rb index 42a1f3ee8..549ed7375 100644 --- a/Data/Scripts/001_Technical/005_PluginManager.rb +++ b/Data/Scripts/001_Technical/005_PluginManager.rb @@ -627,7 +627,11 @@ module PluginManager # get the order of plugins to interpret order, plugins = self.getPluginOrder # compile if necessary - self.compilePlugins(order, plugins) if self.needCompiling?(order, plugins) + if self.needCompiling?(order, plugins) + self.compilePlugins(order, plugins) + else + Console.echoln_li "Plugins were not compiled." + end # load plugins scripts = load_data("Data/PluginScripts.rxdata") echoed_plugins = [] diff --git a/Data/Scripts/002_Save data/001_SaveData.rb b/Data/Scripts/002_Save data/001_SaveData.rb index 6a7b76628..1afb656b5 100644 --- a/Data/Scripts/002_Save data/001_SaveData.rb +++ b/Data/Scripts/002_Save data/001_SaveData.rb @@ -79,19 +79,4 @@ module SaveData end return hash end - - # Moves a save file from the old Saved Games folder to the new - # location specified by {FILE_PATH}. Does nothing if a save file - # already exists in {FILE_PATH}. - def self.move_old_windows_save - return if File.file?(FILE_PATH) - game_title = System.game_title.gsub(/[^\w ]/, "_") - home = ENV["HOME"] || ENV["HOMEPATH"] - return if home.nil? - old_location = File.join(home, "Saved Games", game_title) - return unless File.directory?(old_location) - old_file = File.join(old_location, "Game.rxdata") - return unless File.file?(old_file) - File.move(old_file, FILE_PATH) - end end diff --git a/Data/Scripts/002_Save data/002_SaveData_Value.rb b/Data/Scripts/002_Save data/002_SaveData_Value.rb index e20c35426..8871a878a 100644 --- a/Data/Scripts/002_Save data/002_SaveData_Value.rb +++ b/Data/Scripts/002_Save data/002_SaveData_Value.rb @@ -202,7 +202,7 @@ module SaveData validate id => Symbol @values.delete_if { |value| value.id == id } end - + # @param save_data [Hash] save data to validate # @return [Boolean] whether the given save data is valid def self.valid?(save_data) diff --git a/Data/Scripts/003_Game processing/001_StartGame.rb b/Data/Scripts/003_Game processing/001_StartGame.rb index b803a2e94..9143bc376 100644 --- a/Data/Scripts/003_Game processing/001_StartGame.rb +++ b/Data/Scripts/003_Game processing/001_StartGame.rb @@ -19,7 +19,6 @@ module Game # Loads bootup data from save file (if it exists) or creates bootup data (if # it doesn't). def self.set_up_system - SaveData.move_old_windows_save if System.platform[/Windows/] save_data = (SaveData.exists?) ? SaveData.read_from_file(SaveData::FILE_PATH) : {} if save_data.empty? SaveData.initialize_bootup_values diff --git a/Data/Scripts/004_Game classes/009_Game_Player.rb b/Data/Scripts/004_Game classes/009_Game_Player.rb index 8bb2be923..862416ea7 100644 --- a/Data/Scripts/004_Game classes/009_Game_Player.rb +++ b/Data/Scripts/004_Game classes/009_Game_Player.rb @@ -474,7 +474,7 @@ class Game_Player < Game_Character if !@moved_last_frame || @stopped_last_frame # Started a new step if pbTerrainTag.ice set_movement_type(:ice_sliding) - else#if !@move_route_forcing + else faster = can_run? if $PokemonGlobal&.diving set_movement_type((faster) ? :diving_fast : :diving) diff --git a/Data/Scripts/004_Game classes/012_Game_FollowerFactory.rb b/Data/Scripts/004_Game classes/012_Game_FollowerFactory.rb index 6c5bea3df..1619f3db5 100644 --- a/Data/Scripts/004_Game classes/012_Game_FollowerFactory.rb +++ b/Data/Scripts/004_Game classes/012_Game_FollowerFactory.rb @@ -278,14 +278,8 @@ class Game_FollowerFactory facing_tile = $map_factory.getFacingTile # Assumes player is 1x1 tile in size each_follower do |event, follower| - if event.at_coordinate?($game_player.x, $game_player.y) # Underneath player - next if !event.over_trigger? - elsif facing_tile && event.map.map_id == facing_tile[0] && - event.at_coordinate?(facing_tile[1], facing_tile[2]) # On facing tile - next if event.over_trigger? - else # Somewhere else - next - end + next if !facing_tile || event.map.map_id != facing_tile[0] || + !event.at_coordinate?(facing_tile[1], facing_tile[2]) # Not on facing tile next if event.jumping? follower.interact(event) end diff --git a/Data/Scripts/006_Map renderer/004_TileDrawingHelper.rb b/Data/Scripts/006_Map renderer/004_TileDrawingHelper.rb index 95680e76f..2e60ab43e 100644 --- a/Data/Scripts/006_Map renderer/004_TileDrawingHelper.rb +++ b/Data/Scripts/006_Map renderer/004_TileDrawingHelper.rb @@ -37,23 +37,38 @@ class TileDrawingHelper 36, 24, 36, 24, 21, 6, 21, 4, 36, 24, 36, 24, 20, 2, 20, 0 ] - def self.tableNeighbors(data, x, y) + def self.tableNeighbors(data, x, y, layer = nil) return 0 if x < 0 || x >= data.xsize return 0 if y < 0 || y >= data.ysize - t = data[x, y] + if layer.nil? + t = data[x, y] + else + t = data[x, y, layer] + end xp1 = [x + 1, data.xsize - 1].min yp1 = [y + 1, data.ysize - 1].min xm1 = [x - 1, 0].max ym1 = [y - 1, 0].max i = 0 - i |= 0x01 if data[x, ym1] == t # N - i |= 0x02 if data[xp1, ym1] == t # NE - i |= 0x04 if data[xp1, y] == t # E - i |= 0x08 if data[xp1, yp1] == t # SE - i |= 0x10 if data[x, yp1] == t # S - i |= 0x20 if data[xm1, yp1] == t # SW - i |= 0x40 if data[xm1, y] == t # W - i |= 0x80 if data[xm1, ym1] == t # NW + if layer.nil? + i |= 0x01 if data[ x, ym1] == t # N + i |= 0x02 if data[xp1, ym1] == t # NE + i |= 0x04 if data[xp1, y] == t # E + i |= 0x08 if data[xp1, yp1] == t # SE + i |= 0x10 if data[ x, yp1] == t # S + i |= 0x20 if data[xm1, yp1] == t # SW + i |= 0x40 if data[xm1, y] == t # W + i |= 0x80 if data[xm1, ym1] == t # NW + else + i |= 0x01 if data[ x, ym1, layer] == t # N + i |= 0x02 if data[xp1, ym1, layer] == t # NE + i |= 0x04 if data[xp1, y, layer] == t # E + i |= 0x08 if data[xp1, yp1, layer] == t # SE + i |= 0x10 if data[ x, yp1, layer] == t # S + i |= 0x20 if data[xm1, yp1, layer] == t # SW + i |= 0x40 if data[xm1, y, layer] == t # W + i |= 0x80 if data[xm1, ym1, layer] == t # NW + end return i end diff --git a/Data/Scripts/007_Objects and windows/010_DrawText.rb b/Data/Scripts/007_Objects and windows/010_DrawText.rb index 9917a92ef..2a306b881 100644 --- a/Data/Scripts/007_Objects and windows/010_DrawText.rb +++ b/Data/Scripts/007_Objects and windows/010_DrawText.rb @@ -935,58 +935,67 @@ def drawBitmapBuffer(chars) end def drawSingleFormattedChar(bitmap, ch) - if ch[5] # If a graphic + if ch[5] # If a graphic graphic = Bitmap.new(ch[0]) graphicRect = ch[15] bitmap.blt(ch[1], ch[2], graphic, graphicRect, ch[8].alpha) graphic.dispose + return + end + bitmap.font.bold = ch[6] if bitmap.font.bold != ch[6] + bitmap.font.italic = ch[7] if bitmap.font.italic != ch[7] + bitmap.font.name = ch[12] if bitmap.font.name != ch[12] + bitmap.font.size = ch[13] if bitmap.font.size != ch[13] + if ch[9] # shadow + if ch[10] # underline + bitmap.fill_rect(ch[1], ch[2] + ch[4] - [(ch[4] - bitmap.font.size) / 2, 0].max - 2, + ch[3], 4, ch[9]) + end + if ch[11] # strikeout + bitmap.fill_rect(ch[1], ch[2] + 2 + (ch[4] / 2), ch[3], 4, ch[9]) + end + end + if ch[0] == "\n" || ch[0] == "\r" || ch[0] == " " || isWaitChar(ch[0]) + bitmap.font.color = ch[8] if bitmap.font.color != ch[8] else - bitmap.font.size = ch[13] if bitmap.font.size != ch[13] - if ch[0] != "\n" && ch[0] != "\r" && ch[0] != " " && !isWaitChar(ch[0]) - bitmap.font.bold = ch[6] if bitmap.font.bold != ch[6] - bitmap.font.italic = ch[7] if bitmap.font.italic != ch[7] - bitmap.font.name = ch[12] if bitmap.font.name != ch[12] - offset = 0 - if ch[9] # shadow - bitmap.font.color = ch[9] - if (ch[16] & 1) != 0 # outline - offset = 1 - bitmap.draw_text(ch[1], ch[2], ch[3] + 2, ch[4], ch[0]) - bitmap.draw_text(ch[1], ch[2] + 1, ch[3] + 2, ch[4], ch[0]) - bitmap.draw_text(ch[1], ch[2] + 2, ch[3] + 2, ch[4], ch[0]) - bitmap.draw_text(ch[1] + 1, ch[2], ch[3] + 2, ch[4], ch[0]) - bitmap.draw_text(ch[1] + 1, ch[2] + 2, ch[3] + 2, ch[4], ch[0]) - bitmap.draw_text(ch[1] + 2, ch[2], ch[3] + 2, ch[4], ch[0]) - bitmap.draw_text(ch[1] + 2, ch[2] + 1, ch[3] + 2, ch[4], ch[0]) - bitmap.draw_text(ch[1] + 2, ch[2] + 2, ch[3] + 2, ch[4], ch[0]) - elsif (ch[16] & 2) != 0 # outline 2 - offset = 2 - bitmap.draw_text(ch[1], ch[2], ch[3] + 4, ch[4], ch[0]) - bitmap.draw_text(ch[1], ch[2] + 2, ch[3] + 4, ch[4], ch[0]) - bitmap.draw_text(ch[1], ch[2] + 4, ch[3] + 4, ch[4], ch[0]) - bitmap.draw_text(ch[1] + 2, ch[2], ch[3] + 4, ch[4], ch[0]) - bitmap.draw_text(ch[1] + 2, ch[2] + 4, ch[3] + 4, ch[4], ch[0]) - bitmap.draw_text(ch[1] + 4, ch[2], ch[3] + 4, ch[4], ch[0]) - bitmap.draw_text(ch[1] + 4, ch[2] + 2, ch[3] + 4, ch[4], ch[0]) - bitmap.draw_text(ch[1] + 4, ch[2] + 4, ch[3] + 4, ch[4], ch[0]) - else - bitmap.draw_text(ch[1] + 2, ch[2], ch[3] + 2, ch[4], ch[0]) - bitmap.draw_text(ch[1], ch[2] + 2, ch[3] + 2, ch[4], ch[0]) - bitmap.draw_text(ch[1] + 2, ch[2] + 2, ch[3] + 2, ch[4], ch[0]) - end + offset = 0 + if ch[9] # shadow + bitmap.font.color = ch[9] + if (ch[16] & 1) != 0 # outline + offset = 1 + bitmap.draw_text(ch[1], ch[2], ch[3] + 2, ch[4], ch[0]) + bitmap.draw_text(ch[1], ch[2] + 1, ch[3] + 2, ch[4], ch[0]) + bitmap.draw_text(ch[1], ch[2] + 2, ch[3] + 2, ch[4], ch[0]) + bitmap.draw_text(ch[1] + 1, ch[2], ch[3] + 2, ch[4], ch[0]) + bitmap.draw_text(ch[1] + 1, ch[2] + 2, ch[3] + 2, ch[4], ch[0]) + bitmap.draw_text(ch[1] + 2, ch[2], ch[3] + 2, ch[4], ch[0]) + bitmap.draw_text(ch[1] + 2, ch[2] + 1, ch[3] + 2, ch[4], ch[0]) + bitmap.draw_text(ch[1] + 2, ch[2] + 2, ch[3] + 2, ch[4], ch[0]) + elsif (ch[16] & 2) != 0 # outline 2 + offset = 2 + bitmap.draw_text(ch[1], ch[2], ch[3] + 4, ch[4], ch[0]) + bitmap.draw_text(ch[1], ch[2] + 2, ch[3] + 4, ch[4], ch[0]) + bitmap.draw_text(ch[1], ch[2] + 4, ch[3] + 4, ch[4], ch[0]) + bitmap.draw_text(ch[1] + 2, ch[2], ch[3] + 4, ch[4], ch[0]) + bitmap.draw_text(ch[1] + 2, ch[2] + 4, ch[3] + 4, ch[4], ch[0]) + bitmap.draw_text(ch[1] + 4, ch[2], ch[3] + 4, ch[4], ch[0]) + bitmap.draw_text(ch[1] + 4, ch[2] + 2, ch[3] + 4, ch[4], ch[0]) + bitmap.draw_text(ch[1] + 4, ch[2] + 4, ch[3] + 4, ch[4], ch[0]) + else + bitmap.draw_text(ch[1] + 2, ch[2], ch[3] + 2, ch[4], ch[0]) + bitmap.draw_text(ch[1], ch[2] + 2, ch[3] + 2, ch[4], ch[0]) + bitmap.draw_text(ch[1] + 2, ch[2] + 2, ch[3] + 2, ch[4], ch[0]) end - bitmap.font.color = ch[8] if bitmap.font.color != ch[8] - bitmap.draw_text(ch[1] + offset, ch[2] + offset, ch[3], ch[4], ch[0]) - elsif bitmap.font.color != ch[8] - bitmap.font.color = ch[8] - end - if ch[10] # underline - bitmap.fill_rect(ch[1], ch[2] + ch[4] - 4 - [(ch[4] - bitmap.font.size) / 2, 0].max - 2, - ch[3] - 2, 2, ch[8]) - end - if ch[11] # strikeout - bitmap.fill_rect(ch[1], ch[2] + (ch[4] / 2) - 4, ch[3] - 2, 2, ch[8]) end + bitmap.font.color = ch[8] if bitmap.font.color != ch[8] + bitmap.draw_text(ch[1] + offset, ch[2] + offset, ch[3], ch[4], ch[0]) + end + if ch[10] # underline + bitmap.fill_rect(ch[1], ch[2] + ch[4] - [(ch[4] - bitmap.font.size) / 2, 0].max - 2, + ch[3] - 2, 2, ch[8]) + end + if ch[11] # strikeout + bitmap.fill_rect(ch[1], ch[2] + 2 + (ch[4] / 2), ch[3] - 2, 2, ch[8]) end end diff --git a/Data/Scripts/007_Objects and windows/011_Messages.rb b/Data/Scripts/007_Objects and windows/011_Messages.rb index d098be41a..0f1153a43 100644 --- a/Data/Scripts/007_Objects and windows/011_Messages.rb +++ b/Data/Scripts/007_Objects and windows/011_Messages.rb @@ -819,7 +819,7 @@ def pbShowCommandsWithHelp(msgwindow, commands, help, cmdIfCancel = 0, defaultCm Input.update end msgwin.letterbyletter = oldlbl - msgwin.dispose if !msgwindow + pbDisposeMessageWindow(msgwin) if !msgwindow return ret end diff --git a/Data/Scripts/010_Data/001_GameData.rb b/Data/Scripts/010_Data/001_GameData.rb index 97daec1ab..74d55f981 100644 --- a/Data/Scripts/010_Data/001_GameData.rb +++ b/Data/Scripts/010_Data/001_GameData.rb @@ -240,5 +240,8 @@ module GameData Metadata.load PlayerMetadata.load MapMetadata.load + DungeonTileset.load + DungeonParameters.load + PhoneMessage.load end end diff --git a/Data/Scripts/010_Data/002_PBS data/001_MiscPBSData.rb b/Data/Scripts/010_Data/002_PBS data/001_MiscPBSData.rb index 3d37cfbe9..8b5da65b0 100644 --- a/Data/Scripts/010_Data/002_PBS data/001_MiscPBSData.rb +++ b/Data/Scripts/010_Data/002_PBS data/001_MiscPBSData.rb @@ -3,7 +3,6 @@ #=============================================================================== class Game_Temp attr_accessor :town_map_data - attr_accessor :phone_messages_data attr_accessor :regional_dexes_data attr_accessor :battle_animations_data attr_accessor :move_to_battle_animation_data @@ -13,7 +12,6 @@ end def pbClearData if $game_temp $game_temp.town_map_data = nil - $game_temp.phone_messages_data = nil $game_temp.regional_dexes_data = nil $game_temp.battle_animations_data = nil $game_temp.move_to_battle_animation_data = nil @@ -37,17 +35,6 @@ def pbLoadTownMapData return $game_temp.town_map_data end -#=============================================================================== -# Method to get phone call data. -#=============================================================================== -def pbLoadPhoneData - $game_temp = Game_Temp.new if !$game_temp - if !$game_temp.phone_messages_data && pbRgssExists?("Data/phone.dat") - $game_temp.phone_messages_data = load_data("Data/phone.dat") - end - return $game_temp.phone_messages_data -end - #=============================================================================== # Method to get Regional Dexes data. #=============================================================================== diff --git a/Data/Scripts/010_Data/002_PBS data/002_PhoneDatabase.rb b/Data/Scripts/010_Data/002_PBS data/002_PhoneDatabase.rb deleted file mode 100644 index ab8cd7912..000000000 --- a/Data/Scripts/010_Data/002_PBS data/002_PhoneDatabase.rb +++ /dev/null @@ -1,24 +0,0 @@ -#=============================================================================== -# Phone data -#=============================================================================== -class PhoneDatabase - attr_accessor :generics - attr_accessor :greetings - attr_accessor :greetingsMorning - attr_accessor :greetingsEvening - attr_accessor :bodies1 - attr_accessor :bodies2 - attr_accessor :battleRequests - attr_accessor :trainers - - def initialize - @generics = [] - @greetings = [] - @greetingsMorning = [] - @greetingsEvening = [] - @bodies1 = [] - @bodies2 = [] - @battleRequests = [] - @trainers = [] - end -end diff --git a/Data/Scripts/010_Data/002_PBS data/003_Type.rb b/Data/Scripts/010_Data/002_PBS data/002_Type.rb similarity index 100% rename from Data/Scripts/010_Data/002_PBS data/003_Type.rb rename to Data/Scripts/010_Data/002_PBS data/002_Type.rb diff --git a/Data/Scripts/010_Data/002_PBS data/004_Ability.rb b/Data/Scripts/010_Data/002_PBS data/003_Ability.rb similarity index 100% rename from Data/Scripts/010_Data/002_PBS data/004_Ability.rb rename to Data/Scripts/010_Data/002_PBS data/003_Ability.rb diff --git a/Data/Scripts/010_Data/002_PBS data/005_Move.rb b/Data/Scripts/010_Data/002_PBS data/004_Move.rb similarity index 100% rename from Data/Scripts/010_Data/002_PBS data/005_Move.rb rename to Data/Scripts/010_Data/002_PBS data/004_Move.rb diff --git a/Data/Scripts/010_Data/002_PBS data/006_Item.rb b/Data/Scripts/010_Data/002_PBS data/005_Item.rb similarity index 100% rename from Data/Scripts/010_Data/002_PBS data/006_Item.rb rename to Data/Scripts/010_Data/002_PBS data/005_Item.rb diff --git a/Data/Scripts/010_Data/002_PBS data/007_BerryPlant.rb b/Data/Scripts/010_Data/002_PBS data/006_BerryPlant.rb similarity index 100% rename from Data/Scripts/010_Data/002_PBS data/007_BerryPlant.rb rename to Data/Scripts/010_Data/002_PBS data/006_BerryPlant.rb diff --git a/Data/Scripts/010_Data/002_PBS data/008_Species.rb b/Data/Scripts/010_Data/002_PBS data/007_Species.rb similarity index 100% rename from Data/Scripts/010_Data/002_PBS data/008_Species.rb rename to Data/Scripts/010_Data/002_PBS data/007_Species.rb diff --git a/Data/Scripts/010_Data/002_PBS data/009_Species_Files.rb b/Data/Scripts/010_Data/002_PBS data/008_Species_Files.rb similarity index 97% rename from Data/Scripts/010_Data/002_PBS data/009_Species_Files.rb rename to Data/Scripts/010_Data/002_PBS data/008_Species_Files.rb index bb6409d38..2a2e2fb92 100644 --- a/Data/Scripts/010_Data/002_PBS data/009_Species_Files.rb +++ b/Data/Scripts/010_Data/002_PBS data/008_Species_Files.rb @@ -57,6 +57,11 @@ module GameData return (ret) ? ret : pbResolveBitmap("Graphics/Pokemon/Eggs/000") end + def self.egg_cracks_sprite_filename(species, form) + ret = self.check_egg_graphic_file("Graphics/Pokemon/Eggs/", species, form, "_cracks") + return (ret) ? ret : pbResolveBitmap("Graphics/Pokemon/Eggs/000_cracks") + end + def self.sprite_filename(species, form = 0, gender = 0, shiny = false, shadow = false, back = false, egg = false) return self.egg_sprite_filename(species, form) if egg return self.back_sprite_filename(species, form, gender, shiny, shadow) if back diff --git a/Data/Scripts/010_Data/002_PBS data/010_SpeciesMetrics.rb b/Data/Scripts/010_Data/002_PBS data/009_SpeciesMetrics.rb similarity index 100% rename from Data/Scripts/010_Data/002_PBS data/010_SpeciesMetrics.rb rename to Data/Scripts/010_Data/002_PBS data/009_SpeciesMetrics.rb diff --git a/Data/Scripts/010_Data/002_PBS data/011_ShadowPokemon.rb b/Data/Scripts/010_Data/002_PBS data/010_ShadowPokemon.rb similarity index 100% rename from Data/Scripts/010_Data/002_PBS data/011_ShadowPokemon.rb rename to Data/Scripts/010_Data/002_PBS data/010_ShadowPokemon.rb diff --git a/Data/Scripts/010_Data/002_PBS data/012_Ribbon.rb b/Data/Scripts/010_Data/002_PBS data/011_Ribbon.rb similarity index 100% rename from Data/Scripts/010_Data/002_PBS data/012_Ribbon.rb rename to Data/Scripts/010_Data/002_PBS data/011_Ribbon.rb diff --git a/Data/Scripts/010_Data/002_PBS data/013_Encounter.rb b/Data/Scripts/010_Data/002_PBS data/012_Encounter.rb similarity index 100% rename from Data/Scripts/010_Data/002_PBS data/013_Encounter.rb rename to Data/Scripts/010_Data/002_PBS data/012_Encounter.rb diff --git a/Data/Scripts/010_Data/002_PBS data/014_TrainerType.rb b/Data/Scripts/010_Data/002_PBS data/013_TrainerType.rb similarity index 100% rename from Data/Scripts/010_Data/002_PBS data/014_TrainerType.rb rename to Data/Scripts/010_Data/002_PBS data/013_TrainerType.rb diff --git a/Data/Scripts/010_Data/002_PBS data/015_Trainer.rb b/Data/Scripts/010_Data/002_PBS data/014_Trainer.rb similarity index 100% rename from Data/Scripts/010_Data/002_PBS data/015_Trainer.rb rename to Data/Scripts/010_Data/002_PBS data/014_Trainer.rb diff --git a/Data/Scripts/010_Data/002_PBS data/016_Metadata.rb b/Data/Scripts/010_Data/002_PBS data/015_Metadata.rb similarity index 100% rename from Data/Scripts/010_Data/002_PBS data/016_Metadata.rb rename to Data/Scripts/010_Data/002_PBS data/015_Metadata.rb diff --git a/Data/Scripts/010_Data/002_PBS data/017_PlayerMetadata.rb b/Data/Scripts/010_Data/002_PBS data/016_PlayerMetadata.rb similarity index 100% rename from Data/Scripts/010_Data/002_PBS data/017_PlayerMetadata.rb rename to Data/Scripts/010_Data/002_PBS data/016_PlayerMetadata.rb diff --git a/Data/Scripts/010_Data/002_PBS data/018_MapMetadata.rb b/Data/Scripts/010_Data/002_PBS data/017_MapMetadata.rb similarity index 100% rename from Data/Scripts/010_Data/002_PBS data/018_MapMetadata.rb rename to Data/Scripts/010_Data/002_PBS data/017_MapMetadata.rb diff --git a/Data/Scripts/010_Data/002_PBS data/018_DungeonTileset.rb b/Data/Scripts/010_Data/002_PBS data/018_DungeonTileset.rb new file mode 100644 index 000000000..d9b10f8fd --- /dev/null +++ b/Data/Scripts/010_Data/002_PBS data/018_DungeonTileset.rb @@ -0,0 +1,209 @@ +module GameData + class DungeonTileset + attr_reader :id + attr_reader :tile_type_ids + attr_reader :snap_to_large_grid # "large" means 2x2 tiles + attr_reader :large_void_tiles # "large" means 2x2 tiles + attr_reader :large_wall_tiles # "large" means 1x2 or 2x1 tiles depending on side + attr_reader :large_floor_tiles # "large" means 2x2 tiles + attr_reader :double_walls + attr_reader :floor_patch_under_walls + attr_reader :thin_north_wall_offset + attr_reader :flags + + DATA = {} + DATA_FILENAME = "dungeon_tilesets.dat" + + SCHEMA = { + "Autotile" => [:autotile, "us"], + "Tile" => [:tile, "us"], + "SnapToLargeGrid" => [:snap_to_large_grid, "b"], + "LargeVoidTiles" => [:large_void_tiles, "b"], + "LargeWallTiles" => [:large_wall_tiles, "b"], + "LargeFloorTiles" => [:large_floor_tiles, "b"], + "DoubleWalls" => [:double_walls, "b"], + "FloorPatchUnderWalls" => [:floor_patch_under_walls, "b"], + "ThinNorthWallOffset" => [:thin_north_wall_offset, "i"], + "Flags" => [:flags, "*s"] + } + + extend ClassMethodsIDNumbers + include InstanceMethods + + # @param other [self, Integer] + # @return [self] + def self.try_get(other) + validate other => [Integer, self] + return other if other.is_a?(self) + return (self::DATA.has_key?(other)) ? self::DATA[other] : self.get(self::DATA.keys.first) + end + + def initialize(hash) + @id = hash[:id] + @snap_to_large_grid = hash[:snap_to_large_grid] || false + @large_void_tiles = hash[:large_void_tiles] || false + @large_wall_tiles = hash[:large_wall_tiles] || false + @large_floor_tiles = hash[:large_floor_tiles] || false + @double_walls = hash[:double_walls] || false + @floor_patch_under_walls = hash[:floor_patch_under_walls] || false + @thin_north_wall_offset = hash[:thin_north_wall_offset] || 0 + @flags = hash[:flags] || [] + @tile_type_ids = {} + set_tile_type_ids(hash) + end + + def set_tile_type_ids(hash) + [hash[:autotile], hash[:tile]].each_with_index do |array, i| + array.each do |tile_info| + next if !tile_info + tile_type = tile_info[1].downcase.to_sym + if tile_type == :walls + if @double_walls + if @large_wall_tiles + push_tile(:wall_1, 384 + tile_info[0] + 33) + push_tile(:wall_2, 384 + tile_info[0] + 34) + push_tile(:wall_3, 384 + tile_info[0] + 36) + push_tile(:wall_4, 384 + tile_info[0] + 17) + push_tile(:wall_6, 384 + tile_info[0] + 20) + push_tile(:wall_7, 384 + tile_info[0] + 9) + push_tile(:wall_8, 384 + tile_info[0] + 10) + push_tile(:wall_9, 384 + tile_info[0] + 12) + push_tile(:wall_in_1, 384 + tile_info[0] + 23) + push_tile(:wall_in_3, 384 + tile_info[0] + 22) + push_tile(:wall_in_7, 384 + tile_info[0] + 31) + push_tile(:wall_in_9, 384 + tile_info[0] + 30) + push_tile(:upper_wall_1, 384 + tile_info[0] + 40) + push_tile(:upper_wall_2, 384 + tile_info[0] + 42) + push_tile(:upper_wall_3, 384 + tile_info[0] + 45) + push_tile(:upper_wall_4, 384 + tile_info[0] + 16) + push_tile(:upper_wall_6, 384 + tile_info[0] + 21) + push_tile(:upper_wall_7, 384 + tile_info[0] + 0) + push_tile(:upper_wall_8, 384 + tile_info[0] + 2) + push_tile(:upper_wall_9, 384 + tile_info[0] + 5) + push_tile(:upper_wall_in_1, 384 + tile_info[0] + 7) + push_tile(:upper_wall_in_3, 384 + tile_info[0] + 6) + push_tile(:upper_wall_in_7, 384 + tile_info[0] + 15) + push_tile(:upper_wall_in_9, 384 + tile_info[0] + 14) + else + push_tile(:wall_1, 384 + tile_info[0] + 25) + push_tile(:wall_2, 384 + tile_info[0] + 26) + push_tile(:wall_3, 384 + tile_info[0] + 27) + push_tile(:wall_4, 384 + tile_info[0] + 17) + push_tile(:wall_6, 384 + tile_info[0] + 19) + push_tile(:wall_7, 384 + tile_info[0] + 9) + push_tile(:wall_8, 384 + tile_info[0] + 10) + push_tile(:wall_9, 384 + tile_info[0] + 11) + push_tile(:wall_in_1, 384 + tile_info[0] + 22) + push_tile(:wall_in_3, 384 + tile_info[0] + 21) + push_tile(:wall_in_7, 384 + tile_info[0] + 30) + push_tile(:wall_in_9, 384 + tile_info[0] + 29) + push_tile(:upper_wall_1, 384 + tile_info[0] + 32) + push_tile(:upper_wall_2, 384 + tile_info[0] + 34) + push_tile(:upper_wall_3, 384 + tile_info[0] + 36) + push_tile(:upper_wall_4, 384 + tile_info[0] + 16) + push_tile(:upper_wall_6, 384 + tile_info[0] + 20) + push_tile(:upper_wall_7, 384 + tile_info[0] + 0) + push_tile(:upper_wall_8, 384 + tile_info[0] + 2) + push_tile(:upper_wall_9, 384 + tile_info[0] + 4) + push_tile(:upper_wall_in_1, 384 + tile_info[0] + 6) + push_tile(:upper_wall_in_3, 384 + tile_info[0] + 5) + push_tile(:upper_wall_in_7, 384 + tile_info[0] + 14) + push_tile(:upper_wall_in_9, 384 + tile_info[0] + 13) + end + elsif @large_wall_tiles + push_tile(:wall_1, 384 + tile_info[0] + 24) + push_tile(:wall_2, 384 + tile_info[0] + 25) + push_tile(:wall_3, 384 + tile_info[0] + 27) + push_tile(:wall_4, 384 + tile_info[0] + 8) + push_tile(:wall_6, 384 + tile_info[0] + 11) + push_tile(:wall_7, 384 + tile_info[0] + 0) + push_tile(:wall_8, 384 + tile_info[0] + 1) + push_tile(:wall_9, 384 + tile_info[0] + 3) + push_tile(:wall_in_1, 384 + tile_info[0] + 5) + push_tile(:wall_in_3, 384 + tile_info[0] + 4) + push_tile(:wall_in_7, 384 + tile_info[0] + 13) + push_tile(:wall_in_9, 384 + tile_info[0] + 12) + else + push_tile(:wall_1, 384 + tile_info[0] + 16) + push_tile(:wall_2, 384 + tile_info[0] + 17) + push_tile(:wall_3, 384 + tile_info[0] + 18) + push_tile(:wall_4, 384 + tile_info[0] + 8) + push_tile(:wall_6, 384 + tile_info[0] + 10) + push_tile(:wall_7, 384 + tile_info[0] + 0) + push_tile(:wall_8, 384 + tile_info[0] + 1) + push_tile(:wall_9, 384 + tile_info[0] + 2) + push_tile(:wall_in_1, 384 + tile_info[0] + 4) + push_tile(:wall_in_3, 384 + tile_info[0] + 3) + push_tile(:wall_in_7, 384 + tile_info[0] + 12) + push_tile(:wall_in_9, 384 + tile_info[0] + 11) + end + end + id = (i == 0) ? tile_info[0] * 48 : 384 + tile_info[0] + push_tile(tile_type, id, false) + end + end + end + + def push_tile(tile_type, id, auto = true) + @tile_type_ids[tile_type] ||= [] + @tile_type_ids[tile_type].push([id, auto]) + end + + def has_flag?(flag) + return @flags.any? { |f| f.downcase == flag.downcase } + end + + def has_decoration?(deco) + return @tile_type_ids.include?(deco) && @tile_type_ids[deco].length > 0 + end + + def get_random_tile_of_type(tile_type, dungeon, x, y, layer) + tiles = @tile_type_ids[tile_type] + return 0 if !tiles || tiles.empty? + ret = tiles.sample[0] + if ret < 384 # Autotile + nb = TileDrawingHelper.tableNeighbors(dungeon, x, y, layer) + variant = TileDrawingHelper::NEIGHBORS_TO_AUTOTILE_INDEX[nb] + ret += variant + else + case tile_type + when :void + if @large_void_tiles + ret += 1 if x.odd? + ret += 8 if y.odd? + end + when :floor + if large_floor_tiles + ret += 1 if x.odd? + ret += 8 if y.odd? + end + when :wall_2, :wall_8, :wall_top + ret += 1 if @large_wall_tiles && x.odd? + when :wall_4, :wall_6 + ret += 8 if @large_wall_tiles && y.odd? + end + # Different wall tiles for northern walls if there's another wall directly + # north of them (i.e. tree tiles that shouldn't have shaded grass because + # there isn't a tree-enclosed area there) + if @thin_north_wall_offset != 0 && [:wall_7, :wall_8, :wall_9].include?(tile_type) + ret += @thin_north_wall_offset if dungeon.tile_is_wall?(dungeon[x, y - 1, 1]) + end + end + return ret + end + + def property_from_string(str) + case str + when "SnapToLargeGrid" then return @snap_to_large_grid + when "LargeVoidTiles" then return @large_void_tiles + when "LargeWallTiles" then return @large_wall_tiles + when "LargeFloorTiles" then return @large_floor_tiles + when "DoubleWalls" then return @double_walls + when "FloorPatchUnderWalls" then return @floor_patch_under_walls + when "ThinNorthWallOffset" then return @thin_north_wall_offset + when "Flags" then return @flags + end + return nil + end + end +end diff --git a/Data/Scripts/010_Data/002_PBS data/020_DungeonParameters.rb b/Data/Scripts/010_Data/002_PBS data/020_DungeonParameters.rb new file mode 100644 index 000000000..9c0b0281f --- /dev/null +++ b/Data/Scripts/010_Data/002_PBS data/020_DungeonParameters.rb @@ -0,0 +1,138 @@ +# TODO: Add tileset number in here? +module GameData + class DungeonParameters + attr_reader :id, :area, :version + attr_reader :cell_count_x, :cell_count_y + attr_reader :cell_width, :cell_height + attr_reader :room_min_width, :room_min_height + attr_reader :room_max_width, :room_max_height + attr_reader :corridor_width, :random_corridor_shift + # Layouts: + # :full - every node in the map + # :no_corners - every node except for one in each corner + # :ring - every node around the edge of the map + # :antiring - every node except one that touches an edge of the map + # :plus - every node in a plus (+) shape + # :diagonal_up - every node in a line from bottom left to top right (/) + # :diagonal_down - every node in a line from top left to bottom right (\) + # :cross - every node in a cross (x) shape + # :quadrants - every node except the middles of each edge (i.e. each corner bulges out) + attr_reader :node_layout, :room_layout + attr_reader :room_chance # Percentage of active roomable nodes that will become rooms + attr_reader :extra_connections_count + attr_reader :floor_patch_radius, :floor_patch_chance, :floor_patch_smooth_rate + attr_reader :floor_decoration_density, :floor_decoration_large_density + attr_reader :void_decoration_density, :void_decoration_large_density + attr_reader :rng_seed + attr_reader :flags + + DATA = {} + DATA_FILENAME = "dungeon_parameters.dat" + + SCHEMA = { + "DungeonSize" => [:dungeon_size, "vv"], + "CellSize" => [:cell_size, "vv"], + "MinRoomSize" => [:min_room_size, "vv"], + "MaxRoomSize" => [:max_room_size, "vv"], + "CorridorWidth" => [:corridor_width, "v"], + "ShiftCorridors" => [:shift_corridors, "b"], + "NodeLayout" => [:node_layout, "s"], + "RoomLayout" => [:room_layout, "s"], + "RoomChance" => [:room_chance, "v"], + "ExtraConnections" => [:extra_connections_count, "u"], + "FloorPatches" => [:floor_patches, "vvu"], + "FloorDecorations" => [:floor_decorations, "uu"], + "VoidDecorations" => [:void_decorations, "uu"], + "RNGSeed" => [:rng_seed, "u"], + "Flags" => [:flags, "*s"] + } + + extend ClassMethodsSymbols + include InstanceMethods + + # @param other [Symbol, String, self] + # @param version [Integer] + # @return [self] + def self.try_get(area, version = 0) + validate area => [Symbol, self, String] + validate version => Integer + area = area.id if area.is_a?(self) + area = area.to_sym if area.is_a?(String) + trial = sprintf("%s_%d", area, version).to_sym + area_version = (DATA[trial].nil?) ? area : trial + return (DATA.has_key?(area_version)) ? DATA[area_version] : self.new({}) + end + + def initialize(hash) + @id = hash[:id] + @area = hash[:area] + @version = hash[:version] || 0 + @cell_count_x = (hash[:dungeon_size]) ? hash[:dungeon_size][0] : 5 + @cell_count_y = (hash[:dungeon_size]) ? hash[:dungeon_size][1] : 5 + @cell_width = (hash[:cell_size]) ? hash[:cell_size][0] : 10 + @cell_height = (hash[:cell_size]) ? hash[:cell_size][1] : 10 + @room_min_width = (hash[:min_room_size]) ? hash[:min_room_size][0] : 5 + @room_min_height = (hash[:min_room_size]) ? hash[:min_room_size][1] : 5 + @room_max_width = (hash[:max_room_size]) ? hash[:max_room_size][0] : @cell_width - 1 + @room_max_height = (hash[:max_room_size]) ? hash[:max_room_size][1] : @cell_height - 1 + @corridor_width = hash[:corridor_width] || 2 + @random_corridor_shift = hash[:shift_corridors] + @node_layout = hash[:node_layout]&.downcase&.to_sym || :full + @room_layout = hash[:room_layout]&.downcase&.to_sym || :full + @room_chance = hash[:room_chance] || 70 + @extra_connections_count = hash[:extra_connections_count] || 2 + @floor_patch_radius = (hash[:floor_patches]) ? hash[:floor_patches][0] : 3 + @floor_patch_chance = (hash[:floor_patches]) ? hash[:floor_patches][1] : 75 + @floor_patch_smooth_rate = (hash[:floor_patches]) ? hash[:floor_patches][2] : 25 + @floor_decoration_density = (hash[:floor_decorations]) ? hash[:floor_decorations][0] : 50 + @floor_decoration_large_density = (hash[:floor_decorations]) ? hash[:floor_decorations][1] : 200 + @void_decoration_density = (hash[:void_decorations]) ? hash[:void_decorations][0] : 50 + @void_decoration_large_density = (hash[:void_decorations]) ? hash[:void_decorations][1] : 200 + @rng_seed = hash[:rng_seed] + @flags = hash[:flags] || [] + end + + def has_flag?(flag) + return @flags.any? { |f| f.downcase == flag.downcase } + end + + def rand_cell_center + x = (@cell_width / 2) + rand(-2..2) + y = (@cell_height / 2) + rand(-2..2) + return x, y + end + + def rand_room_size + width = @room_min_width + if @room_max_width > @room_min_width + width = rand(@room_min_width..@room_max_width) + end + height = @room_min_height + if @room_max_height > @room_min_height + height = rand(@room_min_height..@room_max_height) + end + return width, height + end + + def property_from_string(str) + case str + when "DungeonSize" then return [@cell_count_x, @cell_count_y] + when "CellSize" then return [@cell_width, @cell_height] + when "MinRoomSize" then return [@room_min_width, @room_min_height] + when "MaxRoomSize" then return [@room_max_width, @room_max_height] + when "CorridorWidth" then return @corridor_width + when "ShiftCorridors" then return @random_corridor_shift + when "NodeLayout" then return @node_layout + when "RoomLayout" then return @room_layout + when "RoomChance" then return @room_chance + when "ExtraConnections" then return @extra_connections_count + when "FloorPatches" then return [@floor_patch_radius, @floor_patch_chance, @floor_patch_smooth_rate] + when "FloorDecorations" then return [@floor_decoration_density, @floor_decoration_large_density] + when "VoidDecorations" then return [@void_decoration_density, @void_decoration_large_density] + when "RNGSeed" then return @rng_seed + when "Flags" then return @flags + end + return nil + end + end +end diff --git a/Data/Scripts/010_Data/002_PBS data/020_PhoneMessage.rb b/Data/Scripts/010_Data/002_PBS data/020_PhoneMessage.rb new file mode 100644 index 000000000..9f90c3223 --- /dev/null +++ b/Data/Scripts/010_Data/002_PBS data/020_PhoneMessage.rb @@ -0,0 +1,96 @@ +module GameData + class PhoneMessage + attr_reader :id + attr_reader :trainer_type, :real_name, :version + attr_reader :intro, :intro_morning, :intro_afternoon, :intro_evening + attr_reader :body, :body1, :body2 + attr_reader :battle_request, :battle_remind + attr_reader :end + + DATA = {} + DATA_FILENAME = "phone.dat" + + SCHEMA = { + "Intro" => [:intro, "q"], + "IntroMorning" => [:intro_morning, "q"], + "IntroAfternoon" => [:intro_afternoon, "q"], + "IntroEvening" => [:intro_evening, "q"], + "Body" => [:body, "q"], + "Body1" => [:body1, "q"], + "Body2" => [:body2, "q"], + "BattleRequest" => [:battle_request, "q"], + "BattleRemind" => [:battle_remind, "q"], + "End" => [:end, "q"] + } + + extend ClassMethodsSymbols + include InstanceMethods + + # @param tr_type [Symbol, String] + # @param tr_name [String] + # @param tr_version [Integer, nil] + # @return [Boolean] whether the given other is defined as a self + def self.exists?(tr_type, tr_name, tr_version = 0) + validate tr_type => [Symbol, String] + validate tr_name => [String] + key = [tr_type.to_sym, tr_name, tr_version] + return !self::DATA[key].nil? + end + + # @param tr_type [Symbol, String] + # @param tr_name [String] + # @param tr_version [Integer, nil] + # @return [self] + def self.get(tr_type, tr_name, tr_version = 0) + validate tr_type => [Symbol, String] + validate tr_name => [String] + key = [tr_type.to_sym, tr_name, tr_version] + raise "Phone messages not found for #{tr_type} #{tr_name} #{tr_version}." unless self::DATA.has_key?(key) + return self::DATA[key] + end + + # @param tr_type [Symbol, String] + # @param tr_name [String] + # @param tr_version [Integer, nil] + # @return [self, nil] + def self.try_get(tr_type, tr_name, tr_version = 0) + validate tr_type => [Symbol, String] + validate tr_name => [String] + key = [tr_type.to_sym, tr_name, tr_version] + return (self::DATA.has_key?(key)) ? self::DATA[key] : nil + end + + def initialize(hash) + @id = hash[:id] + @trainer_type = hash[:trainer_type] + @real_name = hash[:name] + @version = hash[:version] || 0 + @intro = hash[:intro] + @intro_morning = hash[:intro_morning] + @intro_afternoon = hash[:intro_afternoon] + @intro_evening = hash[:intro_evening] + @body = hash[:body] + @body1 = hash[:body1] + @body2 = hash[:body2] + @battle_request = hash[:battle_request] + @battle_remind = hash[:battle_remind] + @end = hash[:end] + end + + def property_from_string(str) + case str + when "Intro" then return @intro + when "IntroMorning" then return @intro_morning + when "IntroAfternoon" then return @intro_afternoon + when "IntroEvening" then return @intro_evening + when "Body" then return @body + when "Body1" then return @body1 + when "Body2" then return @body2 + when "BattleRequest" then return @battle_request + when "BattleRemind" then return @battle_remind + when "End" then return @end + end + return nil + end + end +end diff --git a/Data/Scripts/011_Battle/001_Battle/007_Battle_ActionRunning.rb b/Data/Scripts/011_Battle/001_Battle/007_Battle_ActionRunning.rb index 61ad9863c..5e46c83a1 100644 --- a/Data/Scripts/011_Battle/001_Battle/007_Battle_ActionRunning.rb +++ b/Data/Scripts/011_Battle/001_Battle/007_Battle_ActionRunning.rb @@ -21,6 +21,36 @@ class Battle return true end + # Return values: + # -1: Chose not to end the battle via Debug means + # 0: Couldn't end the battle via Debug means; carry on trying to run + # 1: Ended the battle via Debug means + def pbDebugRun + return 0 if !$DEBUG || !Input.press?(Input::CTRL) + commands = [_INTL("Treat as a win"), _INTL("Treat as a loss"), + _INTL("Treat as a draw"), _INTL("Treat as running away/forfeit")] + commands.push(_INTL("Treat as a capture")) if wildBattle? + commands.push(_INTL("Cancel")) + case pbShowCommands(_INTL("Choose the outcome of this battle."), commands) + when 0 # Win + @decision = 1 + when 1 # Loss + @decision = 2 + when 2 # Draw + @decision = 5 + when 3 # Run away/forfeit + pbSEPlay("Battle flee") + pbDisplayPaused(_INTL("You got away safely!")) + @decision = 3 + when 4 # Capture + return -1 if trainerBattle? + @decision = 4 + else + return -1 + end + return 1 + end + # Return values: # -1: Failed fleeing # 0: Wasn't possible to attempt fleeing, continue choosing action for the round @@ -36,17 +66,12 @@ class Battle @choices[idxBattler][2] = nil return -1 end - # Fleeing from trainer battles + # Debug ending the battle + debug_ret = pbDebugRun + return debug_ret if debug_ret != 0 + # Running from trainer battles if trainerBattle? - if $DEBUG && Input.press?(Input::CTRL) - if pbDisplayConfirm(_INTL("Treat this battle as a win?")) - @decision = 1 - return 1 - elsif pbDisplayConfirm(_INTL("Treat this battle as a loss?")) - @decision = 2 - return 1 - end - elsif @internalBattle + if @internalBattle pbDisplayPaused(_INTL("No! There's no running from a Trainer battle!")) elsif pbDisplayConfirm(_INTL("Would you like to forfeit the match and quit now?")) pbSEPlay("Battle flee") @@ -56,13 +81,6 @@ class Battle end return 0 end - # Fleeing from wild battles - if $DEBUG && Input.press?(Input::CTRL) - pbSEPlay("Battle flee") - pbDisplayPaused(_INTL("You got away safely!")) - @decision = 3 - return 1 - end if !@canRun pbDisplayPaused(_INTL("You can't escape!")) return 0 diff --git a/Data/Scripts/011_Battle/001_Battle/008_Battle_ActionOther.rb b/Data/Scripts/011_Battle/001_Battle/008_Battle_ActionOther.rb index e1f87c861..f6fe0b961 100644 --- a/Data/Scripts/011_Battle/001_Battle/008_Battle_ActionOther.rb +++ b/Data/Scripts/011_Battle/001_Battle/008_Battle_ActionOther.rb @@ -36,6 +36,9 @@ class Battle end def pbCall(idxBattler) + # Debug ending the battle + return if pbDebugRun != 0 + # Call the battler battler = @battlers[idxBattler] trainerName = pbGetOwnerName(idxBattler) pbDisplay(_INTL("{1} called {2}!", trainerName, battler.pbThis(true))) diff --git a/Data/Scripts/011_Battle/002_Battler/004_Battler_Statuses.rb b/Data/Scripts/011_Battle/002_Battler/004_Battler_Statuses.rb index 27d35437a..1ebf1ccec 100644 --- a/Data/Scripts/011_Battle/002_Battler/004_Battler_Statuses.rb +++ b/Data/Scripts/011_Battle/002_Battler/004_Battler_Statuses.rb @@ -24,7 +24,7 @@ class Battle::Battler def pbCanInflictStatus?(newStatus, user, showMessages, move = nil, ignoreStatus = false) return false if fainted? - selfInflicted = (user && user.index == @index) + self_inflicted = (user && user.index == @index) # Rest and Flame Orb/Toxic Orb only # Already have that status problem if self.status == newStatus && !ignoreStatus if showMessages @@ -41,13 +41,13 @@ class Battle::Battler return false end # Trying to replace a status problem with another one - if self.status != :NONE && !ignoreStatus && !selfInflicted + if self.status != :NONE && !ignoreStatus && !(self_inflicted && move) # Rest can replace a status problem @battle.pbDisplay(_INTL("It doesn't affect {1}...", pbThis(true))) if showMessages return false end # Trying to inflict a status problem on a Pokémon behind a substitute if @effects[PBEffects::Substitute] > 0 && !(move && move.ignoresSubstitute?(user)) && - !selfInflicted + !self_inflicted @battle.pbDisplay(_INTL("It doesn't affect {1}...", pbThis(true))) if showMessages return false end @@ -105,7 +105,7 @@ class Battle::Battler immAlly = nil if Battle::AbilityEffects.triggerStatusImmunityNonIgnorable(self.ability, self, newStatus) immuneByAbility = true - elsif selfInflicted || !@battle.moldBreaker + elsif self_inflicted || !@battle.moldBreaker if abilityActive? && Battle::AbilityEffects.triggerStatusImmunity(self.ability, self, newStatus) immuneByAbility = true else @@ -163,7 +163,7 @@ class Battle::Battler return false end # Safeguard immunity - if pbOwnSide.effects[PBEffects::Safeguard] > 0 && !selfInflicted && move && + if pbOwnSide.effects[PBEffects::Safeguard] > 0 && !self_inflicted && move && !(user && user.hasActiveAbility?(:INFILTRATOR)) @battle.pbDisplay(_INTL("{1}'s team is protected by Safeguard!", pbThis)) if showMessages return false diff --git a/Data/Scripts/011_Battle/006_Other battle code/008_Battle_AbilityEffects.rb b/Data/Scripts/011_Battle/006_Other battle code/008_Battle_AbilityEffects.rb index b2b11bf71..36df96eda 100644 --- a/Data/Scripts/011_Battle/006_Other battle code/008_Battle_AbilityEffects.rb +++ b/Data/Scripts/011_Battle/006_Other battle code/008_Battle_AbilityEffects.rb @@ -496,6 +496,12 @@ Battle::AbilityEffects::StatusImmunityFromAlly.add(:FLOWERVEIL, } ) +Battle::AbilityEffects::StatusImmunityFromAlly.add(:PASTELVEIL, + proc { |ability, battler, status| + next true if status == :POISON + } +) + Battle::AbilityEffects::StatusImmunityFromAlly.add(:SWEETVEIL, proc { |ability, battler, status| next true if status == :SLEEP @@ -561,6 +567,8 @@ Battle::AbilityEffects::StatusCure.add(:IMMUNITY, } ) +Battle::AbilityEffects::StatusCure.copy(:IMMUNITY, :PASTELVEIL) + Battle::AbilityEffects::StatusCure.add(:INSOMNIA, proc { |ability, battler| next if battler.status != :SLEEP diff --git a/Data/Scripts/012_Overworld/002_Battle triggering/004_Overworld_EncounterModifiers.rb b/Data/Scripts/012_Overworld/002_Battle triggering/004_Overworld_EncounterModifiers.rb index b0be8f32c..a1f69f4d1 100644 --- a/Data/Scripts/012_Overworld/002_Battle triggering/004_Overworld_EncounterModifiers.rb +++ b/Data/Scripts/012_Overworld/002_Battle triggering/004_Overworld_EncounterModifiers.rb @@ -32,10 +32,10 @@ EventHandlers.add(:on_wild_pokemon_created, :level_depends_on_party, # Note that you can only modify a partner trainer's Pokémon, and not the trainer # themselves nor their items this way, as those are generated from scratch # before each battle. -#EventHandlers.add(:on_trainer_load, :put_a_name_here, -# proc { |trainer| -# if trainer # An NPCTrainer object containing party/items/lose text, etc. -# YOUR CODE HERE -# end -# } -#) +# EventHandlers.add(:on_trainer_load, :put_a_name_here, +# proc { |trainer| +# if trainer # An NPCTrainer object containing party/items/lose text, etc. +# YOUR CODE HERE +# end +# } +# ) diff --git a/Data/Scripts/012_Overworld/006_Overworld_BerryPlants.rb b/Data/Scripts/012_Overworld/006_Overworld_BerryPlants.rb index 80cd6ad22..1271ab429 100644 --- a/Data/Scripts/012_Overworld/006_Overworld_BerryPlants.rb +++ b/Data/Scripts/012_Overworld/006_Overworld_BerryPlants.rb @@ -259,7 +259,8 @@ class BerryPlantSprite when 2 then @event.turn_down # X sprouted when 3 then @event.turn_left # X taller when 4 then @event.turn_right # X flowering - when 5 then @event.turn_up # X berries + else + @event.turn_up if berry_plant.growth_stage >= 5 # X berries end else @event.character_name = "Object ball" diff --git a/Data/Scripts/012_Overworld/008_Overworld_RandomDungeons.rb b/Data/Scripts/012_Overworld/008_Overworld_RandomDungeons.rb index 5b559912c..4f3eccc67 100644 --- a/Data/Scripts/012_Overworld/008_Overworld_RandomDungeons.rb +++ b/Data/Scripts/012_Overworld/008_Overworld_RandomDungeons.rb @@ -1,190 +1,15 @@ #=============================================================================== # Code that generates a random dungeon layout, and implements it in a given map. #=============================================================================== -module RandomDungeonGenerator - #============================================================================= - # This class is designed to favor different values more than a uniform - # random generator does. - #============================================================================= - class AntiRandom - def initialize(size) - @old = [] - @new = Array.new(size) { |i| i } - end - - def get - if @new.length == 0 # No new values - @new = @old.clone - @old.clear - end - if @old.length > 0 && rand(7) == 0 # Get old value - return @old[rand(@old.length)] - end - if @new.length > 0 # Get new value - ret = @new.delete_at(rand(@new.length)) - @old.push(ret) - return ret - end - return @old[rand(@old.length)] # Get old value - end - end - - #============================================================================= - # Contains constants that define what types of tiles a random dungeon map can - # consist of, and helper methods that translate those tiles into data usable - # by a map/printable to the console (for debug purposes). - #============================================================================= - module DungeonTile - VOID = 0 - ROOM = 1 - WALL = 2 - CORRIDOR = 3 - # Which autotile each type of tile uses (1-7) - TILE_IDS = { - VOID => 1, - ROOM => 2, - WALL => 3, - CORRIDOR => 2 - } - # Used for debugging when printing out an ASCII image of the dungeon - TEXT_SYMBOLS = { - VOID => "#", - ROOM => " ", - WALL => "=", - CORRIDOR => "." - } - - module_function - - def to_tile_id(value) - return TILE_IDS[value] || TILE_IDS[VOID] - end - - def to_text(value) - return TEXT_SYMBOLS[value] || "\e[30m\e[41m?\e[0m" - end - end - - #============================================================================= - # Helper functions that set tiles in the map to a particular type. - #============================================================================= - module DungeonMaze - CELL_WIDTH = 13 # Should be at least 7 - CELL_HEIGHT = 13 # Should be at least 7 - ROOM_MIN_WIDTH = 5 - ROOM_MAX_WIDTH = CELL_WIDTH - 2 # Should be at most CELL_WIDTH - 2 - ROOM_MIN_HEIGHT = 4 - ROOM_MAX_HEIGHT = CELL_HEIGHT - 3 # Should be at most CELL_HEIGHT - 3 - CORRIDOR_WIDTH = 3 - TURN_NONE = 0 - TURN_LEFT = 1 - TURN_RIGHT = 2 - TURN_BACK = 3 - @@corridor_layouts = nil - - module_function - - # Generates sets of tiles depicting corridors coming out of a room, for all - # combinations of the sides that they can come out of. - def generate_corridor_patterns - if !@@corridor_layouts - tiles = [] - x_offset = (CELL_WIDTH - CORRIDOR_WIDTH) / 2 - y_offset = (CELL_HEIGHT - CORRIDOR_WIDTH) / 2 - 16.times do |combo| - tiles[combo] = [] - (CELL_WIDTH * CELL_HEIGHT).times do |i| - tiles[combo][i] = DungeonTile::VOID - end - if (combo & EdgeMasks::NORTH) == 0 - paint_corridor(tiles[combo], x_offset, 0, CORRIDOR_WIDTH, y_offset + CORRIDOR_WIDTH) - end - if (combo & EdgeMasks::SOUTH) == 0 - paint_corridor(tiles[combo], x_offset, y_offset, CORRIDOR_WIDTH, CELL_HEIGHT - y_offset) - end - if (combo & EdgeMasks::EAST) == 0 - paint_corridor(tiles[combo], x_offset, y_offset, CELL_WIDTH - x_offset, CORRIDOR_WIDTH) - end - if (combo & EdgeMasks::WEST) == 0 - paint_corridor(tiles[combo], 0, y_offset, x_offset + CORRIDOR_WIDTH, CORRIDOR_WIDTH) - end - end - @@corridor_layouts = tiles - end - return @@corridor_layouts - end - - # Makes all tiles in a particular area corridor tiles. - def paint_corridor(tile, x, y, width, height) - height.times do |j| - width.times do |i| - tile[((y + j) * CELL_WIDTH) + (x + i)] = DungeonTile::CORRIDOR - end - end - end - - # Used to draw tiles from the given tile_layout and rotation (for corridors). - def paint_tile_layout(dungeon, dstX, dstY, tile_layout, rotation) - case rotation - when TURN_NONE - CELL_HEIGHT.times do |y| - CELL_WIDTH.times do |x| - dungeon[x + dstX, y + dstY] = tile_layout[(y * CELL_WIDTH) + x] - end - end - when TURN_LEFT - CELL_HEIGHT.times do |y| - CELL_WIDTH.times do |x| - dungeon[y + dstX, CELL_WIDTH - 1 - x + dstY] = tile_layout[(y * CELL_WIDTH) + x] - end - end - when TURN_RIGHT - CELL_HEIGHT.times do |y| - CELL_WIDTH.times do |x| - dungeon[CELL_HEIGHT - 1 - y + dstX, x + dstY] = tile_layout[(y * CELL_WIDTH) + x] - end - end - when TURN_BACK - CELL_HEIGHT.times do |y| - CELL_WIDTH.times do |x| - dungeon[CELL_WIDTH - 1 - x + dstX, CELL_HEIGHT - 1 - y + dstY] = tile_layout[(y * CELL_WIDTH) + x] - end - end - end - end - - # Draws a cell's contents, which is an underlying pattern based on tile - # _layout and a rotation (the corridors), and possibly a room on top of that. - def paint_cell_contents(dungeon, xDst, yDst, tile_layout, rotation) - return false if !tile_layout - # Draw the corridors - paint_tile_layout(dungeon, xDst, yDst, tile_layout, rotation) - return false if rand(100) < 30 - # Generate a randomly placed room - width = rand(ROOM_MIN_WIDTH..ROOM_MAX_WIDTH) - height = rand(ROOM_MIN_HEIGHT..ROOM_MAX_HEIGHT) - return false if width <= 0 || height <= 0 - centerX = (CELL_WIDTH / 2) + rand(5) - 2 - centerY = (CELL_HEIGHT / 2) + rand(5) - 2 - x = centerX - (width / 2) - y = centerY - (height / 2) - rect = [x, y, width, height] - rect[0] = rect[0].clamp(1, CELL_WIDTH - 1 - width) - rect[1] = rect[1].clamp(2, CELL_HEIGHT - 1 - height) # 2 because walls are 2 tiles tall - dungeon.paint_room(rect, xDst, yDst) - return true - end - end - +module RandomDungeon #============================================================================= # Bitwise values used to keep track of the generation of node connections. #============================================================================= module EdgeMasks - NORTH = 1 - WEST = 2 - EAST = 4 - SOUTH = 8 - VISITED = 16 + NORTH = 1 + EAST = 2 + SOUTH = 4 + WEST = 8 end #============================================================================= @@ -193,476 +18,1073 @@ module RandomDungeonGenerator #============================================================================= class MazeNode def initialize - @edges = 0 + @visitable = false + @visited = false + @room = false + block_all_edges # A bit being 1 means its edge is NOT connected to the adjacent node end - def setEdge(e); @edges |= e; end - def clearEdge(e); @edges &= ~e; end - def clear; @edges = 0; end - def set; @edges = 15; end - def getEdge(e); return (@edges & e) != 0; end - def isBlocked?; return @edges != 0; end + def edge_pattern; return @edges; end + def block_edge(e); @edges |= e; end + def connect_edge(e); @edges &= ~e; end + def block_all_edges; @edges = 15; end + def connect_all_edges; @edges = 0; end + def edge_blocked?(e); return (@edges & e) != 0; end + def all_edges_blocked?; return @edges != 0; end + def visitable?; return @visitable; end + def set_visitable; @visitable = true; end + def visited?; return @visited; end + def set_visited; @visited = true; end + def room?; return @room; end + def set_room; @room = true; end end #============================================================================= - # Vector class representing the location of a node. - #============================================================================= - class NodeListElement - attr_accessor :x, :y - - def initialize(x, y) - @x = x - @y = y - end - end - - #============================================================================= - # Maze generator. Given the number of cells horizontally and vertically in a - # map, connects all the cells together. - # A node is the boundary between two adjacent cells, which may or may not be a - # connection. + # Maze generator. Given the number of nodes horizontally and vertically in a + # map, connects all the nodes together. #============================================================================= class Maze - attr_accessor :cellWidth, :cellHeight, :nodeWidth, :nodeHeight + attr_accessor :node_count_x, :node_count_y DIRECTIONS = [EdgeMasks::NORTH, EdgeMasks::SOUTH, EdgeMasks::EAST, EdgeMasks::WEST] - def initialize(cw, ch) + def initialize(cw, ch, parameters) raise ArgumentError.new if cw == 0 || ch == 0 - @cellWidth = cw - @cellHeight = ch - @nodeWidth = cw + 1 - @nodeHeight = ch + 1 - @cells = [] - clearAllCells - @nodes = Array.new(@nodeWidth * @nodeHeight) { MazeNode.new } + @node_count_x = cw + @node_count_y = ch + @parameters = parameters + @nodes = Array.new(@node_count_x * @node_count_y) { MazeNode.new } end - def randomDir - return DIRECTIONS[rand(4)] + def valid_node?(x, y) + return x >= 0 && x < @node_count_x && y >= 0 && y < @node_count_y end - def getVisited(x, y) - return false if x < 0 || y < 0 || x >= cellWidth || x >= cellHeight - return (@cells[(y * cellWidth) + x] & EdgeMasks::VISITED) != 0 + def get_node(x, y) + return @nodes[(y * @node_count_x) + x] if valid_node?(x, y) + return nil end - def setVisited(x, y) - return if x < 0 || y < 0 || x >= cellWidth || x >= cellHeight - @cells[(y * cellWidth) + x] |= EdgeMasks::VISITED + def node_visited?(x, y) + return true if !valid_node?(x, y) || !@nodes[(y * @node_count_x) + x].visitable? + return @nodes[(y * @node_count_x) + x].visited? end - def clearVisited(x, y) - return if x < 0 || y < 0 || x >= cellWidth || x >= cellHeight - @cells[(y * cellWidth) + x] &= ~EdgeMasks::VISITED + def set_node_visited(x, y) + @nodes[(y * @node_count_x) + x].set_visited if valid_node?(x, y) end - def clearAllCells - (cellWidth * cellHeight).times do |c| - @cells[c] = 0 - end + def node_edge_blocked?(x, y, edge) + return false if !valid_node?(x, y) + return @nodes[(y * @node_count_x) + x].edge_blocked?(edge) end - def getEdgeNode(x, y, edge) - return false if x < 0 || y < 0 || x >= nodeWidth || y >= nodeHeight - return @nodes[(y * nodeWidth) + x].getEdge(edge) + def connect_node_edges(x, y, edge) + return if !valid_node?(x, y) + @nodes[(y * @node_count_x) + x].connect_edge(edge) + new_x, new_y, new_edge = get_coords_in_direction(x, y, edge, true) + raise ArgumentError.new if new_edge == 0 + @nodes[(new_y * @node_count_x) + new_x].connect_edge(new_edge) if valid_node?(new_x, new_y) end - def setEdgeNode(x, y, edge) - return if x < 0 || x >= nodeWidth || y < 0 || y >= nodeHeight - @nodes[(y * nodeWidth) + x].setEdge(edge) - e = 0 - nx = x - ny = y - case edge + def room_count + ret = 0 + @nodes.each { |node| ret += 1 if node.room? } + return ret + end + + def get_coords_in_direction(x, y, dir, include_direction = false) + new_x = x + new_y = y + new_dir = 0 + case dir when EdgeMasks::NORTH - e = EdgeMasks::SOUTH - ny = y - 1 + new_dir = EdgeMasks::SOUTH + new_y -= 1 when EdgeMasks::SOUTH - e = EdgeMasks::NORTH - ny = y + 1 - when EdgeMasks::EAST - e = EdgeMasks::WEST - nx = x + 1 + new_dir = EdgeMasks::NORTH + new_y += 1 when EdgeMasks::WEST - e = EdgeMasks::EAST - nx = x - 1 - else - return - end - return if nx < 0 || ny < 0 || nx >= nodeWidth || ny >= nodeHeight - @nodes[(ny * nodeWidth) + nx].setEdge(e) - end - - def setAllEdges - (nodeWidth * nodeHeight).times do |c| - @nodes[c].set - end - end - - def clearEdgeNode(x, y, edge) - return if x < 0 || x >= nodeWidth || y < 0 || y >= nodeHeight - @nodes[(y * nodeWidth) + x].clearEdge(edge) - e = 0 - nx = x - ny = y - case edge - when EdgeMasks::NORTH - e = EdgeMasks::SOUTH - ny -= 1 - when EdgeMasks::SOUTH - e = EdgeMasks::NORTH - ny += 1 + new_dir = EdgeMasks::EAST + new_x -= 1 when EdgeMasks::EAST - e = EdgeMasks::WEST - nx += 1 - when EdgeMasks::WEST - e = EdgeMasks::EAST - nx -= 1 - else - raise ArgumentError.new + new_dir = EdgeMasks::WEST + new_x += 1 end - return if nx < 0 || ny < 0 || nx >= nodeWidth || ny >= nodeHeight - @nodes[(ny * nodeWidth) + nx].clearEdge(e) + return new_x, new_y, new_dir if include_direction + return new_x, new_y end - def clearAllEdges - (nodeWidth * nodeHeight).times do |c| - @nodes[c].clear + #=========================================================================== + + def generate_layout + # Set visitable nodes + visitable_nodes = set_visitable_nodes + # Generate connections between all nodes + generate_depth_first_maze(visitable_nodes) + add_more_connections + # Spawn rooms in some nodes + spawn_rooms(visitable_nodes) + end + + # Returns whether the node at (x, y) is active in the given layout. + def check_active_node(x, y, layout) + case layout + when :no_corners + return false if [0, @node_count_x - 1].include?(x) && [0, @node_count_y - 1].include?(y) + when :ring + return false if x > 0 && x < @node_count_x - 1 && y > 0 && y < @node_count_y - 1 + when :antiring + return false if x == 0 || x == @node_count_x - 1 || y == 0 || y == @node_count_y - 1 + when :plus + return false if x != @node_count_x / 2 && y != @node_count_y / 2 + when :diagonal_up + return false if (x + y - @node_count_y + 1).abs >= 2 + when :diagonal_down + return false if (x - y).abs >= 2 + when :cross + return false if (x - y).abs >= 2 && (x + y - @node_count_y + 1).abs >= 2 + when :quadrants + return false if (x == 0 || x == @node_count_x - 1) && y >= 2 && y < @node_count_y - 2 + return false if (y == 0 || y == @node_count_y - 1) && x >= 2 && x < @node_count_x - 2 end + return true end - def isBlockedNode?(x, y) - return false if x < 0 || y < 0 || x >= nodeWidth || y >= nodeHeight - return @nodes[(y * nodeWidth) + x].isBlocked? - end - - def getEdgePattern(x, y) - pattern = 0 - pattern |= EdgeMasks::NORTH if getEdgeNode(x, y, EdgeMasks::NORTH) - pattern |= EdgeMasks::SOUTH if getEdgeNode(x, y, EdgeMasks::SOUTH) - pattern |= EdgeMasks::EAST if getEdgeNode(x, y, EdgeMasks::EAST) - pattern |= EdgeMasks::WEST if getEdgeNode(x, y, EdgeMasks::WEST) - return pattern - end - - def buildMazeWall(x, y, dir, len) - return if isBlockedNode?(x, y) - wx = x - wy = y - len.times do - ox = wx - oy = wy - case dir - when EdgeMasks::NORTH - wy -= 1 - when EdgeMasks::WEST - wx -= 1 - when EdgeMasks::EAST - wx += 1 - when EdgeMasks::SOUTH - wy += 1 - end - if isBlockedNode?(wx, wy) - setEdgeNode(ox, oy, dir) - return - end - setEdgeNode(ox, oy, dir) - end - end - - def buildNodeList - list = [] - nodeWidth.times do |x| - nodeHeight.times do |y| - list.push(NodeListElement.new(x, y)) + def set_visitable_nodes + visitable_nodes = [] + @node_count_y.times do |y| + @node_count_x.times do |x| + next if !check_active_node(x, y, @parameters.node_layout) + @nodes[(y * @node_count_x) + x].set_visitable + visitable_nodes.push([x, y]) end end - list.shuffle! - return list + return visitable_nodes end - def generateWallGrowthMaze(minWall = 0, maxWall = nil) - maxWall = cellWidth if !maxWall - nlist = buildNodeList - return if nlist.length == 0 - nlist.length.times do |c| - d = randomDir - len = rand(maxWall + 1) - x = nlist[c].x - y = nlist[c].y - buildMazeWall(x, y, d, len) - end + def generate_depth_first_maze(visitable_nodes) + # Pick a cell to start in + start = visitable_nodes.sample + sx = start[0] + sy = start[1] + # Generate a maze + connect_nodes_and_recurse_depth_first(sx, sy, 0) end - def recurseDepthFirst(x, y, depth) - setVisited(x, y) + def connect_nodes_and_recurse_depth_first(x, y, depth) + set_node_visited(x, y) dirs = DIRECTIONS.shuffle 4.times do |c| - d = dirs[c] - cx = x - cy = y - case d - when EdgeMasks::NORTH - cy -= 1 - when EdgeMasks::SOUTH - cy += 1 - when EdgeMasks::EAST - cx += 1 - when EdgeMasks::WEST - cx -= 1 - end - if cx >= 0 && cy >= 0 && cx < cellWidth && cy < cellHeight && !getVisited(cx, cy) - clearEdgeNode(x, y, d) - recurseDepthFirst(cx, cy, depth + 1) - end + dir = dirs[c] + cx, cy = get_coords_in_direction(x, y, dir) + next if node_visited?(cx, cy) + connect_node_edges(x, y, dir) + connect_nodes_and_recurse_depth_first(cx, cy, depth + 1) end end - def generateDepthFirstMaze - # Pick a cell to start in - sx = rand(cellWidth) - sy = rand(cellHeight) - # Set up all nodes - setAllEdges - # Generate a maze - recurseDepthFirst(sx, sy, 0) + def add_more_connections + return if @parameters.extra_connections_count == 0 + possible_conns = [] + @node_count_x.times do |x| + @node_count_y.times do |y| + node = @nodes[(y * @node_count_x) + x] + next if !node.visitable? + DIRECTIONS.each do |dir| + next if !node.edge_blocked?(dir) + cx, cy, cdir = get_coords_in_direction(x, y, dir, true) + new_node = get_node(cx, cy) + next if !new_node || !new_node.visitable? || !new_node.edge_blocked?(cdir) + possible_conns.push([x, y, dir]) + end + end + end + possible_conns.sample(@parameters.extra_connections_count).each do |conn| + connect_node_edges(*conn) + end + end + + def spawn_rooms(visitable_nodes) + roomable_nodes = [] + visitable_nodes.each { |coord| roomable_nodes.push(coord) if check_active_node(*coord, @parameters.room_layout) } + room_count = [roomable_nodes.length * @parameters.room_chance / 100, 1].max + return if room_count == 0 + rooms = roomable_nodes.sample(room_count) + rooms.each { |coords| @nodes[(coords[1] * @node_count_x) + coords[0]].set_room } end end #============================================================================= - # Random dungeon generator class. Calls class Maze to generate the abstract - # layout of the dungeon, and turns that into usable map data. + # Arrays of tile types in the dungeon map. #============================================================================= - class Dungeon - class DungeonTable - def initialize(dungeon) - @dungeon = dungeon - end - - def xsize; @dungeon.width; end - def ysize; @dungeon.height; end - - # Returns which tile in the tileset corresponds to the type of tile is at - # the given coordinates - def [](x, y) - return DungeonTile.to_tile_id(@dungeon[x, y]) - end - end - + class DungeonLayout attr_accessor :width, :height + alias xsize width + alias ysize height - BUFFER_X = 8 - BUFFER_Y = 6 + # Used for debugging when printing out an ASCII image of the dungeon + TEXT_SYMBOLS = { + :void => "#", + :room => " ", + :corridor => " ", + :void_decoration => "#", + :void_decoration_large => "#", + :floor_decoration => " ", + :floor_decoration_large => " ", + :floor_patch => " ", + :wall_top => " ", + :wall_1 => Console.markup_style("=", bg: :brown), + :wall_2 => Console.markup_style("=", bg: :brown), + :wall_3 => Console.markup_style("=", bg: :brown), + :wall_4 => Console.markup_style("=", bg: :brown), + :wall_6 => Console.markup_style("=", bg: :brown), + :wall_7 => Console.markup_style("=", bg: :brown), + :wall_8 => Console.markup_style("=", bg: :brown), + :wall_9 => Console.markup_style("=", bg: :brown), + :wall_in_1 => Console.markup_style("=", bg: :brown), + :wall_in_3 => Console.markup_style("=", bg: :brown), + :wall_in_7 => Console.markup_style("=", bg: :brown), + :wall_in_9 => Console.markup_style("=", bg: :brown), + :upper_wall_1 => Console.markup_style("~", bg: :gray), + :upper_wall_2 => Console.markup_style("~", bg: :gray), + :upper_wall_3 => Console.markup_style("~", bg: :gray), + :upper_wall_4 => Console.markup_style("~", bg: :gray), + :upper_wall_6 => Console.markup_style("~", bg: :gray), + :upper_wall_7 => Console.markup_style("~", bg: :gray), + :upper_wall_8 => Console.markup_style("~", bg: :gray), + :upper_wall_9 => Console.markup_style("~", bg: :gray), + :upper_wall_in_1 => Console.markup_style("~", bg: :gray), + :upper_wall_in_3 => Console.markup_style("~", bg: :gray), + :upper_wall_in_7 => Console.markup_style("~", bg: :gray), + :upper_wall_in_9 => Console.markup_style("~", bg: :gray), + } def initialize(width, height) @width = width @height = height - @array = [] + @array = [[], [], []] + clear + end + + def [](x, y, layer) + return @array[layer][(y * @width) + x] + end + + def []=(x, y, layer, value) + @array[layer][(y * @width) + x] = value + end + + def value(x, y) + return :void if x < 0 || x >= @width || y < 0 || y >= @height + ret = :void + [2, 1, 0].each do |layer| + return @array[layer][(y * @width) + x] if @array[layer][(y * @width) + x] != :none + end + return ret end def clear - (width * height).times do |i| - @array[i] = DungeonTile::VOID + @array.each_with_index do |arr, layer| + (@width * @height).times { |i| arr[i] = (layer == 0) ? :void : :none } end end + def set_wall(x, y, value) + @array[0][(y * @width) + x] = :room + @array[1][(y * @width) + x] = value + end + + def set_ground(x, y, value) + @array[0][(y * @width) + x] = value + @array[1][(y * @width) + x] = :none + end + def write ret = "" - i = 0 @height.times do |y| @width.times do |x| - ret += DungeonTile.to_text(value(x, y)) - i += 1 + ret += TEXT_SYMBOLS[value(x, y)] || "\e[30m\e[41m?\e[0m" end ret += "\r\n" end return ret end + end - def [](x, y) - return @array[(y * @width) + x] + #============================================================================= + # The main dungeon generator class. + #============================================================================= + class Dungeon + attr_accessor :width, :height + alias xsize width + alias ysize height + attr_accessor :parameters, :rng_seed + attr_accessor :tileset + + # 0 is none (index 0 only) or corridor/floor + # -1 are tile combinations that need special attention + # Other numbers correspond to tile types (see def get_wall_tile_for_coord) + FLOOR_NEIGHBOURS_TO_WALL = [ + 0, 2, 1, 2, 4, 11, 4, 11, 7, 9, 4, 11, 4, 11, 4, 11, + 8, 0, 17, 0, 17, 0, 17, 0, 8, 0, 17, 0, 17, 0, 17, 0, + 9, 13, -1, 13, 17, 0, 17, 0, 8, 0, 17, 0, 17, 0, 17, 0, + 8, 0, 17, 0, 17, 0, 17, 0, 8, 0, 17, 0, 17, 0, 17, 0, + 6, 13, 13, 13, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, + 19, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, + 6, 13, 13, 13, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, + 19, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, + 3, 2, 2, 2, 11, 11, 11, 11, -1, 11, 11, 11, 11, 11, 11, 11, + 19, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, + 6, 13, 13, 13, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, + 19, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, + 6, 13, 13, 13, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, + 19, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, + 6, 13, 13, 13, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, + 19, 0, 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0 + ] + + def initialize(width, height, tileset, parameters = nil) + @tileset = tileset + @buffer_x = ((Graphics.width.to_f / Game_Map::TILE_WIDTH) / 2).ceil + @buffer_y = ((Graphics.height.to_f / Game_Map::TILE_HEIGHT) / 2).ceil + if @tileset.snap_to_large_grid + @buffer_x += 1 if @buffer_x.odd? + @buffer_y += 1 if @buffer_x.odd? + end + @parameters = parameters || GameData::DungeonParameters.new({}) + if @tileset.snap_to_large_grid + @parameters.cell_width -= 1 if @parameters.cell_width.odd? + @parameters.cell_height -= 1 if @parameters.cell_height.odd? + @parameters.corridor_width += (@parameters.corridor_width == 1) ? 1 : -1 if @parameters.corridor_width.odd? + end + if width >= 20 + @width = width + else + @width = (width * @parameters.cell_width) + (2 * @buffer_x) + @width += 1 if @tileset.snap_to_large_grid && @width.odd? + end + if height >= 20 + @height = height + else + @height = (height * @parameters.cell_height) + (2 * @buffer_y) + @height += 1 if @tileset.snap_to_large_grid && @height.odd? + end + @usable_width = @width + @usable_height = @height + if @tileset.snap_to_large_grid + @usable_width -= 1 if @usable_width.odd? + @usable_height -= 1 if @usable_height.odd? + end + @map_data = DungeonLayout.new(@width, @height) + @need_redraw = false end - def []=(x, y, value) - @array[(y * @width) + x] = value + def [](x, y, layer = nil) + return @map_data.value(x, y) if layer.nil? + return @map_data[x, y, layer] end - def value(x, y) - return DungeonTile::VOID if x < 0 || y < 0 || x >= @width || y >= @height - return @array[(y * @width) + x] + def []=(x, y, layer, value) + @map_data[x, y, layer] = value end - # Unused - def get(x, y) - return false if x < 0 || y < 0 || x >= @width || y >= @height - return @array[(y * @width) + x] != DungeonTile::VOID + def write + return @map_data.write end - # Unused - def intersects?(r1, r2) - return !(((r2[0] + r2[2] <= r1[0]) || - (r2[0] >= r1[0] + r1[2]) || - (r2[1] + r2[3] <= r1[1]) || - (r2[1] >= r1[1] + r1[3])) && - ((r1[0] <= r2[0] + r2[2]) || - (r1[0] >= r2[0] + r2[2]) || - (r1[1] + r1[3] <= r2[1]) || - (r1[1] >= r2[1] + r2[3])) - ) - end + #=========================================================================== - # Returns whether the given coordinates are a room floor that isn't too close - # to a corridor + # Returns whether the given coordinates are a room floor that isn't too + # close to a corridor. For positioning events/the player upon entering. def isRoom?(x, y) - if value(x, y) == DungeonTile::ROOM - return false if value(x - 1, y - 1) == DungeonTile::CORRIDOR - return false if value( x, y - 1) == DungeonTile::CORRIDOR - return false if value(x + 1, y - 1) == DungeonTile::CORRIDOR - return false if value(x - 1, y) == DungeonTile::CORRIDOR - return false if value(x + 1, y) == DungeonTile::CORRIDOR - return false if value(x - 1, y + 1) == DungeonTile::CORRIDOR - return false if value( x, y + 1) == DungeonTile::CORRIDOR - return false if value(x + 1, y + 1) == DungeonTile::CORRIDOR - return true # No surrounding tiles are corridor floor - end - return false - end - - def isWall?(x, y) - if value(x, y) == DungeonTile::VOID - v1 = value(x, y + 1) - return true if [DungeonTile::ROOM, DungeonTile::CORRIDOR].include?(v1) - if v1 == DungeonTile::VOID # The tile below is void - v1 = value(x, y + 2) - return true if [DungeonTile::ROOM, DungeonTile::CORRIDOR].include?(v1) + return false if @map_data.value(x, y) != :room + (-1..1).each do |i| + (-1..1).each do |j| + next if i == 0 && j == 0 + return false if @map_data.value(x + i, y + j) == :corridor end end - return false + return true # No surrounding tiles are corridor floor end - def paint_room(rect, offsetX, offsetY) - ((rect[1] + offsetY)...(rect[1] + offsetY + rect[3])).each do |y| - ((rect[0] + offsetX)...(rect[0] + offsetX + rect[2])).each do |x| - self[x, y] = DungeonTile::ROOM - end - end + def tile_is_ground?(value) + return [:room, :corridor].include?(value) end + # Lower wall tiles only. + def tile_is_wall?(value) + return [:wall_1, :wall_2, :wall_3, :wall_4, :wall_6, :wall_7, :wall_8, :wall_9, + :wall_in_1, :wall_in_3, :wall_in_7, :wall_in_9].include?(value) + end + + def coord_is_ground?(x, y) + return tile_is_ground?(@map_data[x, y, 0]) && !tile_is_wall?(@map_data[x, y, 1]) + end + + #=========================================================================== + def generate - self.clear - maxWidth = @width - (BUFFER_X * 2) - maxHeight = @height - (BUFFER_Y * 2) - cellWidth = DungeonMaze::CELL_WIDTH - cellHeight = DungeonMaze::CELL_HEIGHT + @rng_seed = @parameters.rng_seed || Random.new_seed + Random.srand(@rng_seed) + maxWidth = @usable_width - (@buffer_x * 2) + maxHeight = @usable_height - (@buffer_y * 2) return if maxWidth < 0 || maxHeight < 0 - if maxWidth < cellWidth || maxHeight < cellHeight # Map is too small - maxWidth.times do |x| - maxHeight.times do |y| - self[x + BUFFER_X, y + BUFFER_Y] = DungeonTile::ROOM - end - end + loop do + @need_redraw = false + @map_data.clear + # Generate the basic layout of the map + generate_layout(maxWidth, maxHeight) + next if @need_redraw + # Draw walls + generate_walls(maxWidth, maxHeight) + next if @need_redraw + # Draw decorations + paint_decorations(maxWidth, maxHeight) + # Draw wall top tiles + paint_wall_top_tiles(maxWidth, maxHeight) + break #if !@need_redraw + end + end + + def generate_layout(maxWidth, maxHeight) + cellWidth = @parameters.cell_width + cellHeight = @parameters.cell_height + # Map is too small, make the whole map a room + if maxWidth < cellWidth || maxHeight < cellHeight + paint_ground_rect(@buffer_x, @buffer_y, maxWidth, maxHeight, :room) return end # Generate connections between cells - maze = Maze.new(maxWidth / cellWidth, maxHeight / cellHeight) - maze.generateDepthFirstMaze + maze = Maze.new(maxWidth / cellWidth, maxHeight / cellHeight, @parameters) + maze.generate_layout + # If no rooms were generated, make the whole map a room + if maze.room_count == 0 + paint_ground_rect(@buffer_x, @buffer_y, maxWidth, maxHeight, :room) + return + end # Draw each cell's contents in turn (room and corridors) - corridor_patterns = DungeonMaze.generate_corridor_patterns - roomcount = 0 (maxHeight / cellHeight).times do |y| (maxWidth / cellWidth).times do |x| - pattern = maze.getEdgePattern(x, y) - next if !DungeonMaze.paint_cell_contents( - self, BUFFER_X + (x * cellWidth), BUFFER_Y + (y * cellHeight), - corridor_patterns[pattern], DungeonMaze::TURN_NONE - ) - roomcount += 1 + paint_node_contents(@buffer_x + (x * cellWidth), @buffer_y + (y * cellHeight), maze.get_node(x, y)) end end - # If no rooms were generated, make the whole map a room - if roomcount == 0 + check_for_isolated_rooms + end + + def generate_walls(maxWidth, maxHeight) + # Lower layer + errors = [] + maxHeight.times do |y| maxWidth.times do |x| - maxHeight.times do |y| - self[x + BUFFER_X, y + BUFFER_Y] = DungeonTile::ROOM + next if !coord_is_ground?(@buffer_x + x, @buffer_y + y) + paint_walls_around_ground(@buffer_x + x, @buffer_y + y, 0, errors) + end + end + # Check for error tiles + errors.each do |coord| + resolve_wall_error(coord[0], coord[1], 0) + break if @need_redraw + end + return if @need_redraw + return if !@tileset.double_walls + # Upper layer + errors = [] + (maxHeight + 2).times do |y| + (maxWidth + 2).times do |x| + next if !tile_is_wall?(@map_data[@buffer_x + x - 1, @buffer_y + y - 1, 1]) + paint_walls_around_ground(@buffer_x + x - 1, @buffer_y + y - 1, 1, errors) + end + end + # Check for error tiles + errors.each do |coord| + resolve_wall_error(coord[0], coord[1], 1) + break if @need_redraw + end + end + + #=========================================================================== + + # Determines whether all floor tiles are contiguous. Sets @need_redraw if + # there are 2+ floor regions that are isolated from each other. + def check_for_isolated_rooms + # Get a floor tile as a starting position + start = nil + maxWidth = @usable_width - (@buffer_x * 2) + maxHeight = @usable_height - (@buffer_y * 2) + for y in 0...maxHeight + for x in 0...maxWidth + next if !tile_is_ground?(@map_data[x + @buffer_x, y + @buffer_y, 0]) + start = [x, y] + break + end + break if start + end + if !start + @need_redraw = true + return + end + # Flood fill (https://en.wikipedia.org/wiki/Flood_fill#Span_Filling) + to_check = [ + [start[0], start[0], start[1], 1], + [start[0], start[0], start[1] - 1, -1] + ] + visited = [] + loop do + break if to_check.empty? + checking = to_check.shift + x1, x2, y, dy = checking + x = x1 + if !visited[y * maxWidth + x] && tile_is_ground?(@map_data[x + @buffer_x, y + @buffer_y, 0]) + loop do + break if visited[y * maxWidth + x - 1] || !tile_is_ground?(@map_data[x - 1 + @buffer_x, y + @buffer_y, 0]) + visited[y * maxWidth + x - 1] = true + x -= 1 + end + end + to_check.push([x, x1 - 1, y - dy, -dy]) if x < x1 + loop do + break if x1 > x2 + loop do + break if visited[y * maxWidth + x1] || !tile_is_ground?(@map_data[x1 + @buffer_x, y + @buffer_y, 0]) + visited[y * maxWidth + x1] = true + to_check.push([x, x1, y + dy, dy]) + to_check.push([x2 + 1, x1, y - dy, -dy]) if x1 > x2 + x1 += 1 + end + x1 += 1 + loop do + break if x1 >= x2 + break if !visited[y * maxWidth + x1] && tile_is_ground?(@map_data[x1 + @buffer_x, y + @buffer_y, 0]) + x1 += 1 + end + x = x1 + end + end + # Check for unflooded floor tiles + for y in 0...maxHeight + for x in 0...maxWidth + next if visited[y * maxWidth + x] || !tile_is_ground?(@map_data[x + @buffer_x, y + @buffer_y, 0]) + @need_redraw = true + break + end + break if @need_redraw + end + end + + # Fixes (most) situations where it isn't immediately obvious how to draw a + # wall around a floor area. + def resolve_wall_error(x, y, layer = 0) + if layer == 0 + is_neighbour = lambda { |til| return tile_is_ground?(til) } + else + is_neighbour = lambda { |til| return tile_is_wall?(til) } + end + tile = { + :wall_1 => (layer == 0) ? :wall_1 : :upper_wall_1, + :wall_2 => (layer == 0) ? :wall_2 : :upper_wall_2, + :wall_3 => (layer == 0) ? :wall_3 : :upper_wall_3, + :wall_4 => (layer == 0) ? :wall_4 : :upper_wall_4, + :wall_6 => (layer == 0) ? :wall_6 : :upper_wall_6, + :wall_7 => (layer == 0) ? :wall_7 : :upper_wall_7, + :wall_8 => (layer == 0) ? :wall_8 : :upper_wall_8, + :wall_9 => (layer == 0) ? :wall_9 : :upper_wall_9, + :wall_in_1 => (layer == 0) ? :wall_in_1 : :upper_wall_in_1, + :wall_in_3 => (layer == 0) ? :wall_in_3 : :upper_wall_in_3, + :wall_in_7 => (layer == 0) ? :wall_in_7 : :upper_wall_in_7, + :wall_in_9 => (layer == 0) ? :wall_in_9 : :upper_wall_in_9, + :corridor => (layer == 0) ? :corridor : :void + } + neighbours = 0 + neighbours |= 0x01 if is_neighbour.call(@map_data.value(x, y - 1)) # N + neighbours |= 0x02 if is_neighbour.call(@map_data.value(x + 1, y - 1)) # NE + neighbours |= 0x04 if is_neighbour.call(@map_data.value(x + 1, y)) # E + neighbours |= 0x08 if is_neighbour.call(@map_data.value(x + 1, y + 1)) # SE + neighbours |= 0x10 if is_neighbour.call(@map_data.value(x, y + 1)) # S + neighbours |= 0x20 if is_neighbour.call(@map_data.value(x - 1, y + 1)) # SW + neighbours |= 0x40 if is_neighbour.call(@map_data.value(x - 1, y)) # W + neighbours |= 0x80 if is_neighbour.call(@map_data.value(x - 1, y - 1)) # NW + case neighbours + when 34 + # --f floor tile (dashes are walls) + # -o- this tile + # f-- floor tile + if @map_data.value(x - 1, y - 1) == :void + @map_data[x, y, 1] = tile[:wall_in_3] + @map_data[x - 1, y, 1] = tile[:wall_in_7] + @map_data[x, y - 1, 1] = tile[:wall_in_7] + @map_data.set_wall(x - 1, y - 1, tile[:wall_7]) + elsif @map_data.value(x + 1, y + 1) == :void + @map_data[x, y, 1] = tile[:wall_in_7] + @map_data[x + 1, y, 1] = tile[:wall_in_3] + @map_data[x, y + 1, 1] = tile[:wall_in_3] + @map_data.set_wall(x + 1, y + 1, tile[:wall_3]) + elsif @map_data[x, y - 1, 1] == tile[:wall_4] && @map_data[x - 1, y, 1] == tile[:wall_in_9] + @map_data[x, y, 1] = tile[:wall_in_3] + @map_data[x, y - 1, 1] = tile[:wall_in_7] + @map_data.set_ground(x - 1, y, tile[:corridor]) + @map_data[x - 1, y - 1, 1] = (@map_data[x - 1, y - 1, 1] == tile[:wall_6]) ? tile[:wall_in_9] : tile[:wall_8] + elsif @map_data[x, y - 1, 1] == tile[:wall_in_1] && @map_data[x - 1, y, 1] == tile[:wall_8] + @map_data[x, y, 1] = tile[:wall_in_3] + @map_data.set_ground(x, y - 1, tile[:corridor]) + @map_data[x - 1, y, 1] = tile[:wall_in_7] + @map_data[x - 1, y - 1, 1] = (@map_data[x - 1, y - 1, 1] == tile[:wall_2]) ? tile[:wall_in_1] : tile[:wall_4] + elsif @map_data[x, y - 1, 1] == tile[:wall_in_1] && @map_data[x - 1, y, 1] == tile[:wall_in_9] + @map_data[x, y, 1] = tile[:wall_in_3] + @map_data.set_ground(x, y - 1, tile[:corridor]) + @map_data.set_ground(x - 1, y, tile[:corridor]) + if @map_data[x - 1, y - 1, 1] == :error + @map_data[x - 1, y - 1, 1] = tile[:wall_in_7] + else + @map_data.set_ground(x - 1, y - 1, tile[:corridor]) + end + elsif @map_data[x, y + 1, 1] == tile[:wall_6] && @map_data[x + 1, y, 1] == tile[:wall_in_1] + @map_data[x, y, 1] = tile[:wall_in_7] + @map_data[x, y + 1, 1] = tile[:wall_in_3] + @map_data.set_ground(x + 1, y, tile[:corridor]) + @map_data[x + 1, y + 1, 1] = (@map_data[x + 1, y + 1, 1] == tile[:wall_4]) ? tile[:wall_in_1] : tile[:wall_2] + elsif @map_data[x, y + 1, 1] == tile[:wall_in_9] && @map_data[x + 1, y, 1] == tile[:wall_2] + @map_data[x, y, 1] = tile[:wall_in_7] + @map_data.set_ground(x, y + 1, tile[:corridor]) + @map_data[x + 1, y, 1] = tile[:wall_in_3] + @map_data[x + 1, y + 1, 1] = (@map_data[x + 1, y + 1, 1] == tile[:wall_8]) ? tile[:wall_in_9] : tile[:wall_6] + elsif @map_data[x, y + 1, 1] == tile[:wall_in_9] && @map_data[x + 1, y, 1] == tile[:wall_in_1] + @map_data[x, y, 1] = tile[:wall_in_7] + @map_data.set_ground(x, y + 1, tile[:corridor]) + @map_data.set_ground(x + 1, y, tile[:corridor]) + if @map_data[x + 1, y + 1, 1] == :error + @map_data[x + 1, y + 1, 1] = tile[:wall_in_3] + else + @map_data.set_ground(x + 1, y + 1, tile[:corridor]) + end + else + # Tile error can't be resolved; will redraw map + @need_redraw = true + end + when 136 + # f-- floor tile (dashes are walls) + # -o- this tile + # --f floor tile + if @map_data.value(x - 1, y + 1) == :void + @map_data[x, y, 1] = tile[:wall_in_9] + @map_data[x - 1, y, 1] = tile[:wall_in_1] + @map_data[x, y + 1, 1] = tile[:wall_in_1] + @map_data.set_wall(x - 1, y + 1, tile[:wall_1]) + elsif @map_data.value(x + 1, y - 1) == :void + @map_data[x, y, 1] = tile[:wall_in_1] + @map_data[x + 1, y, 1] = tile[:wall_in_9] + @map_data[x, y - 1, 1] = tile[:wall_in_9] + @map_data.set_wall(x + 1, y - 1, tile[:wall_9]) + elsif @map_data[x, y - 1, 1] == tile[:wall_6] && @map_data[x + 1, y, 1] == tile[:wall_in_7] + @map_data[x, y, 1] = tile[:wall_in_1] + @map_data[x, y - 1, 1] = tile[:wall_in_9] + @map_data.set_ground(x + 1, y, tile[:corridor]) + @map_data[x + 1, y - 1, 1] = (@map_data[x + 1, y - 1, 1] == tile[:wall_4]) ? tile[:wall_in_7] : tile[:wall_8] + elsif @map_data[x, y - 1, 1] == tile[:wall_in_3] && @map_data[x + 1, y, 1] == tile[:wall_8] + @map_data[x, y, 1] = tile[:wall_in_1] + @map_data.set_ground(x, y - 1, tile[:corridor]) + @map_data[x + 1, y, 1] = tile[:wall_in_9] + @map_data[x + 1, y - 1, 1] = (@map_data[x + 1, y - 1, 1] == tile[:wall_2]) ? tile[:wall_in_3] : tile[:wall_6] + elsif @map_data[x, y - 1, 1] == tile[:wall_in_3] && @map_data[x + 1, y, 1] == tile[:wall_in_7] + @map_data[x, y, 1] = tile[:wall_in_1] + @map_data.set_ground(x, y - 1, tile[:corridor]) + @map_data.set_ground(x + 1, y, tile[:corridor]) + if @map_data[x + 1, y - 1, 1] == :error + @map_data[x + 1, y - 1, 1] = tile[:wall_in_9] + else + @map_data.set_ground(x + 1, y - 1, tile[:corridor]) + end + elsif @map_data[x, y + 1, 1] == tile[:wall_4] && @map_data[x - 1, y, 1] == tile[:wall_in_3] + @map_data[x, y, 1] = tile[:wall_in_9] + @map_data[x, y + 1, 1] = tile[:wall_in_1] + @map_data.set_ground(x - 1, y, tile[:corridor]) + @map_data[x - 1, y + 1, 1] = (@map_data[x - 1, y + 1, 1] == tile[:wall_6]) ? tile[:wall_in_3] : tile[:wall_2] + elsif @map_data[x, y + 1, 1] == tile[:wall_in_7] && @map_data[x - 1, y, 1] == tile[:wall_2] + @map_data[x, y, 1] = tile[:wall_in_9] + @map_data.set_ground(x, y + 1, tile[:corridor]) + @map_data[x - 1, y, 1] = tile[:wall_in_1] + @map_data[x - 1, y + 1, 1] = (@map_data[x - 1, y + 1, 1] == tile[:wall_8]) ? tile[:wall_in_7] : tile[:wall_4] + elsif @map_data[x, y + 1, 1] == tile[:wall_in_7] && @map_data[x - 1, y, 1] == tile[:wall_in_3] + @map_data[x, y, 1] = tile[:wall_in_9] + @map_data.set_ground(x, y + 1, tile[:corridor]) + @map_data.set_ground(x - 1, y, tile[:corridor]) + if @map_data[x - 1, y + 1, 1] == :error + @map_data[x - 1, y + 1, 1] = tile[:wall_in_1] + else + @map_data.set_ground(x - 1, y + 1, tile[:corridor]) + end + else + # Tile error can't be resolved; will redraw map + @need_redraw = true + end + else + @need_redraw = true + raise "can't resolve error" + end + end + + #=========================================================================== + + # Draws a cell's contents, which is an underlying pattern based on + # tile_layout (the corridors), and possibly a room on top of that. + def paint_node_contents(cell_x, cell_y, node) + # Draw corridors connecting this room + paint_connections(cell_x, cell_y, node.edge_pattern) + # Generate a randomly placed room + paint_room(cell_x, cell_y) if node.room? + end + + def paint_ground_rect(x, y, width, height, tile) + height.times do |j| + width.times do |i| + @map_data[x + i, y + j, 0] = tile + end + end + end + + # Draws corridors leading from the node at (cell_x, cell_y). + def paint_connections(cell_x, cell_y, pattern) + x_offset = (@parameters.cell_width - @parameters.corridor_width) / 2 + y_offset = (@parameters.cell_height - @parameters.corridor_width) / 2 + if @parameters.random_corridor_shift + variance = @parameters.corridor_width + variance /= 2 if @tileset.snap_to_large_grid + if variance > 1 + x_shift = rand(variance) - (variance / 2) + y_shift = rand(variance) - (variance / 2) + if @tileset.snap_to_large_grid + x_shift *= 2 + y_shift *= 2 + end + x_offset += x_shift + y_offset += y_shift + end + end + if @tileset.snap_to_large_grid + x_offset += 1 if x_offset.odd? + y_offset += 1 if y_offset.odd? + end + if (pattern & RandomDungeon::EdgeMasks::NORTH) == 0 + paint_ground_rect(cell_x + x_offset, cell_y, + @parameters.corridor_width, y_offset + @parameters.corridor_width, + :corridor) + end + if (pattern & RandomDungeon::EdgeMasks::SOUTH) == 0 + paint_ground_rect(cell_x + x_offset, cell_y + y_offset, + @parameters.corridor_width, @parameters.cell_height - y_offset, + :corridor) + end + if (pattern & RandomDungeon::EdgeMasks::EAST) == 0 + paint_ground_rect(cell_x + x_offset, cell_y + y_offset, + @parameters.cell_width - x_offset, @parameters.corridor_width, + :corridor) + end + if (pattern & RandomDungeon::EdgeMasks::WEST) == 0 + paint_ground_rect(cell_x, cell_y + y_offset, + x_offset + @parameters.corridor_width, @parameters.corridor_width, + :corridor) + end + end + + # Draws a room at (cell_x, cell_y). + def paint_room(cell_x, cell_y) + width, height = @parameters.rand_room_size + return if width <= 0 || height <= 0 + if @tileset.snap_to_large_grid + width += (width <= @parameters.cell_width / 2) ? 1 : -1 if width.odd? + height += (height <= @parameters.cell_height / 2) ? 1 : -1 if height.odd? + end + center_x, center_y = @parameters.rand_cell_center + x = cell_x + center_x - (width / 2) + y = cell_y + center_y - (height / 2) + if @tileset.snap_to_large_grid + x += 1 if x.odd? + y += 1 if y.odd? + end + x = x.clamp(@buffer_x, @usable_width - @buffer_x - width ) + y = y.clamp(@buffer_y, @usable_height - @buffer_y - height ) + paint_ground_rect(x, y, width, height, :room) + end + + def paint_walls_around_ground(x, y, layer, errors) + (-1..1).each do |j| + (-1..1).each do |i| + next if i == 0 && j == 0 + next if @map_data[x + i, y + j, 0] != :void + tile = get_wall_tile_for_coord(x + i, y + j, layer) + if [:void, :corridor].include?(tile) + @map_data[x + i, y + j, 0] = tile + else + @map_data.set_wall(x + i, y + j, tile) + end + errors.push([x + i, y + j]) if tile == :error + end + end + end + + def get_wall_tile_for_coord(x, y, layer = 0) + if layer == 0 + is_neighbour = lambda { |x, y| return tile_is_ground?(@map_data.value(x, y)) } + else + is_neighbour = lambda { |x, y| return tile_is_wall?(@map_data[x, y, 1]) } + end + neighbours = 0 + neighbours |= 0x01 if is_neighbour.call(x, y - 1) # N + neighbours |= 0x02 if is_neighbour.call(x + 1, y - 1) # NE + neighbours |= 0x04 if is_neighbour.call(x + 1, y) # E + neighbours |= 0x08 if is_neighbour.call(x + 1, y + 1) # SE + neighbours |= 0x10 if is_neighbour.call(x, y + 1) # S + neighbours |= 0x20 if is_neighbour.call(x - 1, y + 1) # SW + neighbours |= 0x40 if is_neighbour.call(x - 1, y) # W + neighbours |= 0x80 if is_neighbour.call(x - 1, y - 1) # NW + case FLOOR_NEIGHBOURS_TO_WALL[neighbours] + when -1 then return :error # Needs special attention + when 1 then return (layer == 0) ? :wall_1 : :upper_wall_1 + when 2 then return (layer == 0) ? :wall_2 : :upper_wall_2 + when 3 then return (layer == 0) ? :wall_3 : :upper_wall_3 + when 4 then return (layer == 0) ? :wall_4 : :upper_wall_4 + when 6 then return (layer == 0) ? :wall_6 : :upper_wall_6 + when 7 then return (layer == 0) ? :wall_7 : :upper_wall_7 + when 8 then return (layer == 0) ? :wall_8 : :upper_wall_8 + when 9 then return (layer == 0) ? :wall_9 : :upper_wall_9 + when 11 then return (layer == 0) ? :wall_in_1 : :upper_wall_in_1 + when 13 then return (layer == 0) ? :wall_in_3 : :upper_wall_in_3 + when 17 then return (layer == 0) ? :wall_in_7 : :upper_wall_in_7 + when 19 then return (layer == 0) ? :wall_in_9 : :upper_wall_in_9 + end + return :void if neighbours == 0 || layer == 1 + return :corridor + end + + def paint_decorations(maxWidth, maxHeight) + # Large patches (grass/sandy area) + if @tileset.has_decoration?(:floor_patch) + (maxHeight / @parameters.cell_height).times do |j| + (maxWidth / @parameters.cell_width).times do |i| + next if rand(100) >= @parameters.floor_patch_chance + # Random placing of floor patch tiles + mid_x = i * @parameters.cell_width + rand(@parameters.cell_width) + mid_y = j * @parameters.cell_height + rand(@parameters.cell_height) + ((mid_y - @parameters.floor_patch_radius)..(mid_y + @parameters.floor_patch_radius)).each do |y| + ((mid_x - @parameters.floor_patch_radius)..(mid_x + @parameters.floor_patch_radius)).each do |x| + if @tileset.floor_patch_under_walls + next if !tile_is_ground?(@map_data[x + @buffer_x, y + @buffer_y, 0]) + else + next if !tile_is_ground?(@map_data.value(x + @buffer_x, y + @buffer_y)) + end + if (((mid_x - 1)..(mid_x + 1)).include?(x) && ((mid_y - 1)..(mid_y + 1)).include?(y)) || + rand(100) < @parameters.floor_patch_chance + @map_data[x + @buffer_x, y + @buffer_y, 0] = :floor_patch + end + end + end + # Smoothing of placed floor patch tiles + ((mid_y - @parameters.floor_patch_radius)..(mid_y + @parameters.floor_patch_radius)).each do |y| + ((mid_x - @parameters.floor_patch_radius)..(mid_x + @parameters.floor_patch_radius)).each do |x| + if @map_data[x + @buffer_x, y + @buffer_y, 0] == :floor_patch + adj_count = 0 + adj_count += 1 if @map_data[x + @buffer_x - 1, y + @buffer_y, 0] == :floor_patch + adj_count += 1 if @map_data[x + @buffer_x, y + @buffer_y - 1, 0] == :floor_patch + adj_count += 1 if @map_data[x + @buffer_x + 1, y + @buffer_y, 0] == :floor_patch + adj_count += 1 if @map_data[x + @buffer_x, y + @buffer_y + 1, 0] == :floor_patch + if adj_count == 0 || (adj_count == 1 && rand(100) < @parameters.floor_patch_smooth_rate * 2) + @map_data[x + @buffer_x, y + @buffer_y, 0] = :corridor + end + else + if @tileset.floor_patch_under_walls + next if !tile_is_ground?(@map_data[x + @buffer_x, y + @buffer_y, 0]) + else + next if !tile_is_ground?(@map_data.value(x + @buffer_x, y + @buffer_y)) + end + adj_count = 0 + adj_count += 1 if @map_data[x + @buffer_x - 1, y + @buffer_y, 0] == :floor_patch + adj_count += 1 if @map_data[x + @buffer_x, y + @buffer_y - 1, 0] == :floor_patch + adj_count += 1 if @map_data[x + @buffer_x + 1, y + @buffer_y, 0] == :floor_patch + adj_count += 1 if @map_data[x + @buffer_x, y + @buffer_y + 1, 0] == :floor_patch + if adj_count >= 2 && rand(100) < adj_count * @parameters.floor_patch_smooth_rate + @map_data[x + @buffer_x, y + @buffer_y, 0] = :floor_patch + end + end + end + end end end end - # Generate walls - @height.times do |y| - @width.times do |x| - self[x, y] = DungeonTile::WALL if isWall?(x, y) # Make appropriate tiles wall tiles + # 2x2 floor decoration (crater) + if @tileset.has_decoration?(:floor_decoration_large) + ((maxWidth * maxHeight) / @parameters.floor_decoration_large_density).times do + x = rand(maxWidth) + y = rand(maxHeight) + next if @map_data.value(x + @buffer_x, y + @buffer_y) != :room || + @map_data.value(x + @buffer_x + 1, y + @buffer_y) != :room || + @map_data.value(x + @buffer_x, y + @buffer_y + 1) != :room || + @map_data.value(x + @buffer_x + 1, y + @buffer_y + 1) != :room + 4.times do |c| + cx = x + @buffer_x + (c % 2) + cy = y + @buffer_y + (c / 2) + @map_data[cx, cy, 0] = (c == 0) ? :floor_decoration_large : :ignore + end + end + end + # 1x1 floor decoration + if @tileset.has_decoration?(:floor_decoration) + ((@usable_width * @usable_height) / @parameters.floor_decoration_density).times do + x = rand(@usable_width) + y = rand(@usable_height) + next if !coord_is_ground?(@buffer_x + x, @buffer_y + y) + @map_data[x + @buffer_x, y + @buffer_y, 0] = :floor_decoration + end + end + # 2x2 void decoration (crevice) + if @tileset.has_decoration?(:void_decoration_large) + ((@width * @height) / @parameters.void_decoration_large_density).times do + x = rand(@width - 1) + y = rand(@height - 1) + next if @map_data.value(x, y) != :void || + @map_data.value(x + 1, y) != :void || + @map_data.value(x, y + 1) != :void || + @map_data.value(x + 1, y + 1) != :void + 4.times do |c| + cx = x + (c % 2) + cy = y + (c / 2) + @map_data[cx, cy, 0] = (c == 0) ? :void_decoration_large : :ignore + end + end + end + # 1x1 void decoration (rock) + if @tileset.has_decoration?(:void_decoration) + ((@width * @height) / @parameters.void_decoration_density).times do + x = rand(@width) + y = rand(@height) + next if @map_data.value(x, y) != :void + @map_data[x, y, 0] = :void_decoration end end end - # Convert dungeon layout into proper map tiles + def paint_wall_top_tiles(maxWidth, maxHeight) + return if !@tileset.has_decoration?(:wall_top) + maxWidth.times do |x| + maxHeight.times do |y| + next if ![:wall_2, :wall_in_1, :wall_in_3].include?(@map_data[x + @buffer_x, y + 1 + @buffer_y, 1]) + @map_data[x + @buffer_x, y + @buffer_y, 2] = :wall_top + end + end + end + + #=========================================================================== + + # Convert dungeon layout into proper map tiles from a tileset, and modifies + # the given map's data accordingly. def generateMapInPlace(map) - tbl = DungeonTable.new(self) map.width.times do |i| map.height.times do |j| - nb = TileDrawingHelper.tableNeighbors(tbl, i, j) - tile = TileDrawingHelper::NEIGHBORS_TO_AUTOTILE_INDEX[nb] - map.data[i, j, 0] = tile + (48 * (tbl[i, j])) - map.data[i, j, 1] = 0 - map.data[i, j, 2] = 0 + 3.times do |layer| + tile_type = @map_data[i, j, layer] + tile_type = :floor if [:room, :corridor].include?(tile_type) + case tile_type + when :ignore + when :none + map.data[i, j, layer] = 0 + when :void_decoration_large, :floor_decoration_large + 4.times do |c| + tile = @tileset.get_random_tile_of_type(tile_type, self, i, j, layer) + tile += (c % 2) + 8 * (c / 2) if tile >= 384 # Regular tile + map.data[i + (c % 2), j + (c / 2), layer] = tile + end + else + tile = @tileset.get_random_tile_of_type(tile_type, self, i, j, layer) + map.data[i, j, layer] = tile + end + end end end end - end - #============================================================================= - # - #============================================================================= - # Get a random room tile that isn't too close to a corridor (to avoid blocking - # a room's entrance). - def self.pbRandomRoomTile(dungeon, tiles) - ar1 = AntiRandom.new(dungeon.width) - ar2 = AntiRandom.new(dungeon.height) - ((tiles.length + 1) * 1000).times do - x = ar1.get - y = ar2.get - next if !dungeon.isRoom?(x, y) || - tiles.any? { |item| (item[0] - x).abs < 2 && (item[1] - y).abs < 2 } - ret = [x, y] - tiles.push(ret) - return ret + # Returns a random room tile in the dungeon that isn't too close to a + # corridor (to avoid blocking a room's entrance). + def get_random_room_tile(occupied_tiles) + ar1 = AntiRandom.new(@width) + ar2 = AntiRandom.new(@height) + ((occupied_tiles.length + 1) * 1000).times do + x = ar1.get + y = ar2.get + next if !isRoom?(x, y) + next if occupied_tiles.any? { |item| (item[0] - x).abs < 2 && (item[1] - y).abs < 2 } + ret = [x, y] + occupied_tiles.push(ret) + return ret + end + return nil end - return nil - end - - # Test method that generates a dungeon map and prints it to the console. - # @param x_cells [Integer] the number of cells wide the dungeon will be - # @param y_cells [Intenger] the number of cells tall the dungeon will be - def self.generate_test_dungeon(x_cells = 4, y_cells = 4) - dungeon = Dungeon.new((Dungeon::BUFFER_X * 2) + (DungeonMaze::CELL_WIDTH * x_cells), - (Dungeon::BUFFER_Y * 2) + (DungeonMaze::CELL_HEIGHT * y_cells)) - dungeon.generate - echoln dungeon.write end end +#=============================================================================== +# Variables that determine which dungeon parameters to use to generate a random +# dungeon. +#=============================================================================== +class PokemonGlobalMetadata + attr_writer :dungeon_area, :dungeon_version + + def dungeon_area + return @dungeon_area || :none + end + + def dungeon_version + return @dungeon_version || 0 + end +end + +#=============================================================================== +# Code that generates a random dungeon layout, and implements it in a given map. +#=============================================================================== EventHandlers.add(:on_game_map_setup, :random_dungeon, proc { |map_id, map, _tileset_data| next if !GameData::MapMetadata.try_get(map_id)&.random_dungeon - # this map is a randomly generated dungeon - dungeon = RandomDungeonGenerator::Dungeon.new(map.width, map.height) + # Generate a random dungeon + tileset_data = GameData::DungeonTileset.try_get(map.tileset_id) + params = GameData::DungeonParameters.try_get($PokemonGlobal.dungeon_area, + $PokemonGlobal.dungeon_version) + dungeon = RandomDungeon::Dungeon.new(params.cell_count_x, params.cell_count_y, + tileset_data, params) dungeon.generate + map.width = dungeon.width + map.height = dungeon.height + map.data.resize(map.width, map.height, 3) dungeon.generateMapInPlace(map) - roomtiles = [] + occupied_tiles = [] + # Reposition the player + tile = dungeon.get_random_room_tile(occupied_tiles) + if tile + $game_temp.player_new_x = tile[0] + $game_temp.player_new_y = tile[1] + end # Reposition events map.events.each_value do |event| - tile = RandomDungeonGenerator.pbRandomRoomTile(dungeon, roomtiles) + tile = dungeon.get_random_room_tile(occupied_tiles) if tile event.x = tile[0] event.y = tile[1] end end - # Override transfer X and Y - tile = RandomDungeonGenerator.pbRandomRoomTile(dungeon, roomtiles) - if tile - $game_temp.player_new_x = tile[0] - $game_temp.player_new_y = tile[1] - end } ) + +#=============================================================================== +# TODO: Temporary debug function for testing random dungeon generation. +#=============================================================================== +MenuHandlers.add(:debug_menu, :test_random_dungeon, { + "name" => _INTL("Test Random Dungeon Generation"), + "parent" => :other_menu, + "description" => _INTL("Generates a random dungeon and echoes it to the console."), + "effect" => proc { + tileset = :cave # :forest # :cave + tileset_data = GameData::DungeonTileset.try_get((tileset == :forest) ? 23 : 7) + params = GameData::DungeonParameters.try_get(tileset) + dungeon = RandomDungeon::Dungeon.new(params.cell_count_x, params.cell_count_y, tileset_data, params) + dungeon.generate + echoln dungeon.rng_seed + echoln dungeon.write + } +}) diff --git a/Data/Scripts/013_Items/004_Item_Phone.rb b/Data/Scripts/013_Items/004_Item_Phone.rb index 627bac373..306536de8 100644 --- a/Data/Scripts/013_Items/004_Item_Phone.rb +++ b/Data/Scripts/013_Items/004_Item_Phone.rb @@ -1,20 +1,25 @@ # TODO: Add an information window with details of the person in a phone call. # Make this work with common event calls (create and dispose the info # window in start_message and end_message). -# TODO: Rewrite the Phone UI. Have more than one method. Choosable icons/marks -# for each contact? Show an icon representing phone signal. +# TODO: Look at the "ready to rematch" timers to see if they can be improved? +# Should they be limited to one trainer becoming ready every ~5 minutes? +# Should a rematch-ready contact become unready again after some time if +# they haven't told the player they're ready? +# TODO: See if incoming phone calls can be made optional somehow. Maybe just +# interrupt as normal with the start of the call and ask if the player +# wants to answer? Wait for a couple of seconds before asking to make sure +# the player doesn't accidentally skip/answer a call they didn't want to. +# TODO: Add a Debug way of upgrading old phone script calls to new ones, or at +# least to find events using old phone scripts for the dev to update. +# TODO: More Debug control over contacts (changing their "time to rebattle", +# unhiding hidden contacts, etc.) and the phone (time until next call). -# TODO: Add a trainer comment for giving a trainer a common event ID. # TODO: Add calling a contact at a particular time forcing rematch readiness. # Add trainer comments for this. # TODO: Allow individual trainers to never arrange a rematch by themself, thus # requiring the player to call them at their particular time of day/week. # TODO: Be able to put the Phone on silent mode (prevent all phone calls from # trainers, but allow scripted calls as normal). - -# TODO: Better messages, more customisation of messages. -# TODO: Add a Debug way of upgrading old phone script calls to new ones, or at -# least to find events using old phone scripts for the dev to update. #=============================================================================== # #=============================================================================== @@ -70,8 +75,8 @@ class Phone return true end - # Event, trainer type, name, versions_count = 1, start_version = 0 - # Map ID, event ID, trainer type, name, versions_count = 1, start_version = 0 + # Event, trainer type, name, versions_count = 1, start_version = 0, common event ID = 0 + # Map ID, event ID, trainer type, name, versions_count = 1, start_version = 0, common event ID = 0 # Map ID, name, common event ID def add(*args) if args[0].is_a?(Game_Event) @@ -82,9 +87,11 @@ class Phone contact = get(true, trainer_type, name, args[3] || 0) if contact contact.visible = true + @contacts.delete(contact) + @contacts.push(contact) else contact = Contact.new(true, args[0].map_id, args[0].id, - trainer_type, name, args[3] || 1, args[4] || 0) + trainer_type, name, args[3], args[4], args[5]) contact.increment_version @contacts.push(contact) end @@ -96,9 +103,11 @@ class Phone contact = get(true, trainer_type, name, args[4] || 0) if contact contact.visible = true + @contacts.delete(contact) + @contacts.push(contact) else contact = Contact.new(true, args[0], args[1], - trainer_type, name, args[4] || 1, args[5] || 0) + trainer_type, name, args[4], args[5], args[6]) contact.increment_version @contacts.push(contact) end @@ -108,14 +117,30 @@ class Phone contact = get(false, name) if contact contact.visible = true + @contacts.delete(contact) + @contacts.push(contact) else contact = Contact.new(false, *args) @contacts.push(contact) end end + sort_contacts return true end + # Rearranges the list of phone contacts to put all visible contacts first, + # followed by all invisible contacts. + def sort_contacts + new_contacts = [] + 2.times do |i| + @contacts.each do |con| + next if (i == 0 && !con.visible?) || (i == 1 && con.visible?) + new_contacts.push(con) + end + end + @contacts = new_contacts + end + #============================================================================= # Checks once every second. @@ -233,7 +258,7 @@ class Phone attr_accessor :trainer_type, :start_version, :versions_count, :version attr_accessor :time_to_ready, :rematch_flag, :variant_beaten attr_accessor :common_event_id - attr_accessor :visible + attr_reader :visible # Map ID, event ID, trainer type, name, versions count = 1, start version = 0 # Map ID, name, common event ID @@ -251,7 +276,7 @@ class Phone @variant_beaten = 0 @time_to_ready = 0 @rematch_flag = 0 # 0=counting down, 1=ready for rematch, 2=ready and told player - @common_event_id = 0 + @common_event_id = args[6] || 0 else # Non-trainer @map_id = args[0] @@ -280,6 +305,10 @@ class Phone end end + def can_hide? + return trainer? + end + def common_event_call? return @common_event_id > 0 end @@ -373,7 +402,13 @@ class Phone def make_incoming return if !can_make? contact = get_random_trainer_for_incoming_call - if contact + return if !contact + if contact.common_event_call? + if !pbCommonEvent(contact.common_event_id) + pbMessage(_INTL("{1}'s messages not defined.\nCouldn't call common event {2}.", + contact.display_name, contact.common_event_id)) + end + else call = generate_trainer_dialogue(contact) play(call, contact) end @@ -403,7 +438,7 @@ class Phone end end - def start_message(contact) + def start_message(contact = nil) pbMessage(_INTL("......\\wt[5] ......\\1")) end @@ -434,7 +469,7 @@ class Phone end_message(contact) end - def end_message(contact) + def end_message(contact = nil) pbMessage(_INTL("Click!\\wt[10]\n......\\wt[5] ......\\1")) end @@ -442,36 +477,58 @@ class Phone def generate_trainer_dialogue(contact) validate contact => Phone::Contact + # Get the set of messages to be used by the contact + messages = GameData::PhoneMessage.try_get(contact.trainer_type, contact.name, contact.version) + messages = GameData::PhoneMessage.try_get(contact.trainer_type, contact.name, contact.start_version) if !messages + messages = GameData::PhoneMessage::DATA["default"] if !messages + # Create lambda for choosing a random message and translating it get_random_message = lambda do |messages| + return "" if !messages msg = messages.sample return "" if !msg return pbGetMessageFromHash(MessageTypes::PhoneMessages, msg) end - phone_data = pbLoadPhoneData # Choose random greeting depending on time of day - ret = get_random_message.call(phone_data.greetings) + ret = get_random_message.call(messages.intro) time = pbGetTimeNow if PBDayNight.isMorning?(time) - modcall = get_random_message.call(phone_data.greetingsMorning) - ret = modcall if !nil_or_empty?(modcall) + mod_call = get_random_message.call(messages.intro_morning) + ret = mod_call if !nil_or_empty?(mod_call) + elsif PBDayNight.isAfternoon?(time) + mod_call = get_random_message.call(messages.intro_afternoon) + ret = mod_call if !nil_or_empty?(mod_call) elsif PBDayNight.isEvening?(time) - modcall = get_random_message.call(phone_data.greetingsEvening) - ret = modcall if !nil_or_empty?(modcall) + mod_call = get_random_message.call(messages.intro_evening) + ret = mod_call if !nil_or_empty?(mod_call) end ret += "\\m" - if Phone.rematches_enabled && (contact.rematch_flag == 1 || - (contact.rematch_flag == 2 && rand(100) < 50)) - # If ready for rematch, tell the player (50% chance to remind the player) - ret += get_random_message.call(phone_data.battleRequests) - contact.rematch_flag = 2 # Ready for rematch and told player - elsif rand(100) < 75 - # Choose random body - ret += get_random_message.call(phone_data.bodies1) - ret += "\\m" - ret += get_random_message.call(phone_data.bodies2) + # Choose main message set + if Phone.rematches_enabled && contact.rematch_flag > 0 + # Trainer is ready for a rematch, so tell/remind the player + if contact.rematch_flag == 1 # Tell the player + ret += get_random_message.call(messages.battle_request) + contact.rematch_flag = 2 # Ready for rematch and told player + elsif contact.rematch_flag == 2 # Remind the player + if messages.battle_remind + ret += get_random_message.call(messages.battle_remind) + else + ret += get_random_message.call(messages.battle_request) + end + end else - # Choose random generic - ret += get_random_message.call(phone_data.generics) + # Standard messages + if messages.body1 && messages.body2 && (!messages.body || rand(100) < 75) + # Choose random pair of body messages + ret += get_random_message.call(messages.body1) + ret += "\\m" + ret += get_random_message.call(messages.body2) + else + # Choose random full body message + ret += get_random_message.call(messages.body) + end + # Choose end message + mod_call = get_random_message.call(messages.end) + ret += "\\m" + mod_call if !nil_or_empty?(mod_call) end return ret end diff --git a/Data/Scripts/016_UI/001_Non-interactive UI/003_UI_EggHatching.rb b/Data/Scripts/016_UI/001_Non-interactive UI/003_UI_EggHatching.rb index 9426c7524..2b8e75af5 100644 --- a/Data/Scripts/016_UI/001_Non-interactive UI/003_UI_EggHatching.rb +++ b/Data/Scripts/016_UI/001_Non-interactive UI/003_UI_EggHatching.rb @@ -28,9 +28,7 @@ class PokemonEggHatch_Scene @pokemon.form, @pokemon.shiny?, false, false, true) # Egg sprite # Load egg cracks bitmap - crackfilename = sprintf("Graphics/Pokemon/Eggs/%s_cracks", @pokemon.species) - crackfilename = sprintf("Graphics/Pokemon/Eggs/000_cracks") if !pbResolveBitmap(crackfilename) - crackfilename = pbResolveBitmap(crackfilename) + crackfilename = GameData::Species.egg_cracks_sprite_filename(@pokemon.species, @pokemon.form) @hatchSheet = AnimatedBitmap.new(crackfilename) # Create egg cracks sprite @sprites["hatch"] = Sprite.new(@viewport) diff --git a/Data/Scripts/016_UI/005_UI_Party.rb b/Data/Scripts/016_UI/005_UI_Party.rb index 9e432d984..6110ca77d 100644 --- a/Data/Scripts/016_UI/005_UI_Party.rb +++ b/Data/Scripts/016_UI/005_UI_Party.rb @@ -811,6 +811,7 @@ class PokemonParty_Scene currentsel = Settings::MAX_PARTY_SIZE elsif currentsel == numsprites currentsel = 0 + currentsel = numsprites - 1 if currentsel >= @party.length end when Input::UP if currentsel >= Settings::MAX_PARTY_SIZE @@ -818,6 +819,7 @@ class PokemonParty_Scene while currentsel > 0 && currentsel < Settings::MAX_PARTY_SIZE && !@party[currentsel] currentsel -= 1 end + currentsel = numsprites - 1 if currentsel >= @party.length else loop do currentsel -= 2 @@ -839,6 +841,7 @@ class PokemonParty_Scene currentsel = Settings::MAX_PARTY_SIZE elsif currentsel >= numsprites currentsel = 0 + currentsel = numsprites - 1 if currentsel >= @party.length end end return currentsel @@ -853,7 +856,7 @@ class PokemonParty_Scene @sprites["pokemon#{i}"].dispose end lastselected = @party.length - 1 if lastselected >= @party.length - lastselected = 0 if lastselected < 0 + lastselected = Settings::MAX_PARTY_SIZE if lastselected < 0 Settings::MAX_PARTY_SIZE.times do |i| if @party[i] @sprites["pokemon#{i}"] = PokemonPartyPanel.new(@party[i], i, @viewport) diff --git a/Data/Scripts/016_UI/008_UI_Pokegear.rb b/Data/Scripts/016_UI/008_UI_Pokegear.rb index 4d822d16b..4a362dff5 100644 --- a/Data/Scripts/016_UI/008_UI_Pokegear.rb +++ b/Data/Scripts/016_UI/008_UI_Pokegear.rb @@ -182,7 +182,11 @@ MenuHandlers.add(:pokegear_menu, :phone, { "order" => 20, # "condition" => proc { next $PokemonGlobal.phone && $PokemonGlobal.phone.contacts.length > 0 }, "effect" => proc { |menu| - pbFadeOutIn { PokemonPhoneScene.new.start } + pbFadeOutIn { + scene = PokemonPhone_Scene.new + screen = PokemonPhoneScreen.new(scene) + screen.pbStartScreen + } next false } }) diff --git a/Data/Scripts/016_UI/010_UI_Phone.rb b/Data/Scripts/016_UI/010_UI_Phone.rb index d6e07d2b9..43f657c3e 100644 --- a/Data/Scripts/016_UI/010_UI_Phone.rb +++ b/Data/Scripts/016_UI/010_UI_Phone.rb @@ -1,10 +1,13 @@ +# TODO: Choosable icons/marks for each contact? Add a "sort by" option for these. #=============================================================================== -# Phone screen +# Phone list of contacts #=============================================================================== class Window_PhoneList < Window_CommandPokemon + attr_accessor :switching + def drawCursor(index, rect) - selarrow = AnimatedBitmap.new("Graphics/UI/Phone/cursor") if self.index == index + selarrow = AnimatedBitmap.new("Graphics/UI/Phone/cursor") pbCopyBitmap(self.contents, selarrow.bitmap, rect.x, rect.y + 2) end return Rect.new(rect.x + 28, rect.y + 8, rect.width - 16, rect.height) @@ -12,7 +15,13 @@ class Window_PhoneList < Window_CommandPokemon def drawItem(index, count, rect) return if index >= self.top_row + self.page_item_max - super + if self.index == index && @switching + rect = drawCursor(index, rect) + pbDrawShadowText(self.contents, rect.x, rect.y + (self.contents.text_offset_y || 0), + rect.width, rect.height, @commands[index], Color.new(224, 0, 0), Color.new(224, 144, 144)) + else + super + end drawCursor(index - 1, itemRect(index - 1)) end end @@ -20,17 +29,57 @@ end #=============================================================================== # #=============================================================================== -class PokemonPhoneScene - def start - # Get list of contacts +class PokemonPhone_Scene + def pbStartScene + @sprites = {} + # Create viewport + @viewport = Viewport.new(0, 0, Graphics.width, Graphics.height) + @viewport.z = 99999 + # Background + addBackgroundPlane(@sprites, "bg", "Phone/bg", @viewport) + # List of contacts + @sprites["list"] = Window_PhoneList.newEmpty(152, 32, Graphics.width - 142, Graphics.height - 80, @viewport) + @sprites["list"].windowskin = nil + # Rematch readiness icons + if Phone.rematches_enabled + @sprites["list"].page_item_max.times do |i| + @sprites["rematch_#{i}"] = IconSprite.new(468, 62 + (i * 32), @viewport) + end + end + # Phone signal icon + @sprites["signal"] = IconSprite.new(Graphics.width - 32, 0, @viewport) + if Phone::Call.can_make? + @sprites["signal"].setBitmap("Graphics/UI/Phone/icon_signal") + else + @sprites["signal"].setBitmap("Graphics/UI/Phone/icon_nosignal") + end + # Title text + @sprites["header"] = Window_UnformattedTextPokemon.newWithSize( + _INTL("Phone"), 2, -18, 128, 64, @viewport + ) + @sprites["header"].baseColor = Color.new(248, 248, 248) + @sprites["header"].shadowColor = Color.black + @sprites["header"].windowskin = nil + # Info text about all contacts + @sprites["info"] = Window_AdvancedTextPokemon.newWithSize("", -8, 224, 180, 160, @viewport) + @sprites["info"].windowskin = nil + # Portrait of contact + @sprites["icon"] = IconSprite.new(70, 102, @viewport) + # Contact's location text + @sprites["bottom"] = Window_AdvancedTextPokemon.newWithSize( + "", 162, Graphics.height - 64, Graphics.width - 158, 64, @viewport + ) + @sprites["bottom"].windowskin = nil + # Start scene + pbRefreshList + pbFadeInAndShow(@sprites) { pbUpdate } + end + + def pbRefreshList @contacts = [] $PokemonGlobal.phone.contacts.each do |contact| @contacts.push(contact) if contact.visible? end - if @contacts.length == 0 - pbMessage(_INTL("There are no phone numbers stored.")) - return - end # Create list of commands (display names of contacts) and count rematches commands = [] rematch_count = 0 @@ -38,95 +87,186 @@ class PokemonPhoneScene commands.push(contact.display_name) rematch_count += 1 if contact.can_rematch? end - # Create viewport and sprites - @sprites = {} - @viewport = Viewport.new(0, 0, Graphics.width, Graphics.height) - @viewport.z = 99999 - addBackgroundPlane(@sprites, "bg", "Phone/bg", @viewport) - @sprites["list"] = Window_PhoneList.newEmpty(152, 32, Graphics.width - 142, Graphics.height - 80, @viewport) - @sprites["list"].windowskin = nil + # Set list's commands @sprites["list"].commands = commands - @sprites["list"].page_item_max.times do |i| - @sprites["rematch[#{i}]"] = IconSprite.new(468, 62 + (i * 32), @viewport) - j = i + @sprites["list"].top_item - if j < @contacts.length && @contacts[j].can_rematch? - @sprites["rematch[#{i}]"].setBitmap("Graphics/UI/Phone/icon_rematch") - end + @sprites["list"].index = commands.length - 1 if @sprites["list"].index >= commands.length + if @sprites["list"].top_row > @sprites["list"].itemCount - @sprites["list"].page_item_max + @sprites["list"].top_row = @sprites["list"].itemCount - @sprites["list"].page_item_max end - @sprites["header"] = Window_UnformattedTextPokemon.newWithSize( - _INTL("Phone"), 2, -18, 128, 64, @viewport - ) - @sprites["header"].baseColor = Color.new(248, 248, 248) - @sprites["header"].shadowColor = Color.black - @sprites["header"].windowskin = nil - @sprites["bottom"] = Window_AdvancedTextPokemon.newWithSize( - "", 162, Graphics.height - 64, Graphics.width - 158, 64, @viewport - ) - @sprites["bottom"].windowskin = nil - map_name = (@contacts[0].map_id > 0) ? pbGetMapNameFromId(@contacts[0].map_id) : "" - @sprites["bottom"].text = "" + map_name - @sprites["info"] = Window_AdvancedTextPokemon.newWithSize("", -8, 224, 180, 160, @viewport) - @sprites["info"].windowskin = nil + # Set info text infotext = _INTL("Registered
") infotext += _INTL(" {1}
", @sprites["list"].commands.length) infotext += _INTL("Waiting for a rematch{1}", rematch_count) @sprites["info"].text = infotext - @sprites["icon"] = IconSprite.new(70, 102, @viewport) - if @contacts[0].trainer? - filename = GameData::TrainerType.charset_filename(@contacts[0].trainer_type) - else - filename = sprintf("Graphics/Characters/phone%03d", @contacts[0].common_event_id) + pbRefreshScreen + end + + def pbRefreshScreen + @sprites["list"].refresh + # Redraw rematch readiness icons + if @sprites["rematch_0"] + @sprites["list"].page_item_max.times do |i| + @sprites["rematch_#{i}"].clearBitmaps + j = i + @sprites["list"].top_item + if j < @contacts.length && @contacts[j].can_rematch? + @sprites["rematch_#{i}"].setBitmap("Graphics/UI/Phone/icon_rematch") + end + end end - @sprites["icon"].setBitmap(filename) - charwidth = @sprites["icon"].bitmap.width - charheight = @sprites["icon"].bitmap.height - @sprites["icon"].x = 86 - (charwidth / 8) - @sprites["icon"].y = 134 - (charheight / 8) - @sprites["icon"].src_rect = Rect.new(0, 0, charwidth / 4, charheight / 4) - # Start scene - pbFadeInAndShow(@sprites) + # Get the selected contact + contact = @contacts[@sprites["list"].index] + if contact + # Redraw contact's portrait + if contact.trainer? + filename = GameData::TrainerType.charset_filename(contact.trainer_type) + else + filename = sprintf("Graphics/Characters/phone%03d", contact.common_event_id) + end + @sprites["icon"].setBitmap(filename) + charwidth = @sprites["icon"].bitmap.width + charheight = @sprites["icon"].bitmap.height + @sprites["icon"].x = 86 - (charwidth / 8) + @sprites["icon"].y = 134 - (charheight / 8) + @sprites["icon"].src_rect = Rect.new(0, 0, charwidth / 4, charheight / 4) + # Redraw contact's location text + map_name = (contact.map_id > 0) ? pbGetMapNameFromId(contact.map_id) : "" + @sprites["bottom"].text = "" + map_name + else + @sprites["icon"].setBitmap(nil) + @sprites["bottom"].text = "" + end + end + + def pbChooseContact pbActivateWindow(@sprites, "list") { - oldindex = -1 + index = -1 + switch_index = -1 loop do Graphics.update Input.update pbUpdateSpriteHash(@sprites) # Cursor moved, update display - if @sprites["list"].index != oldindex - contact = @contacts[@sprites["list"].index] - if contact.trainer? - filename = GameData::TrainerType.charset_filename(contact.trainer_type) + if @sprites["list"].index != index + if switch_index >= 0 + real_contacts = $PokemonGlobal.phone.contacts + real_contacts.insert(@sprites["list"].index, real_contacts.delete_at(index)) + pbRefreshList else - filename = sprintf("Graphics/Characters/phone%03d", contact.common_event_id) - end - @sprites["icon"].setBitmap(filename) - charwidth = @sprites["icon"].bitmap.width - charheight = @sprites["icon"].bitmap.height - @sprites["icon"].x = 86 - (charwidth / 8) - @sprites["icon"].y = 134 - (charheight / 8) - @sprites["icon"].src_rect = Rect.new(0, 0, charwidth / 4, charheight / 4) - map_name = (contact.map_id > 0) ? pbGetMapNameFromId(contact.map_id) : "" - @sprites["bottom"].text = "" + map_name - @sprites["list"].page_item_max.times do |i| - @sprites["rematch[#{i}]"].clearBitmaps - j = i + @sprites["list"].top_item - if j < @contacts.length && @contacts[j].can_rematch? - @sprites["rematch[#{i}]"].setBitmap("Graphics/UI/Phone/icon_rematch") - end + pbRefreshScreen end end + index = @sprites["list"].index # Get inputs - if Input.trigger?(Input::BACK) - pbPlayCloseMenuSE - break - elsif Input.trigger?(Input::USE) - index = @sprites["list"].index - Phone::Call.make_outgoing(@contacts[index]) if index >= 0 + if switch_index >= 0 + if Input.trigger?(Input::ACTION) || + Input.trigger?(Input::USE) + pbPlayDecisionSE + @sprites["list"].switching = false + switch_index = -1 + pbRefreshScreen + elsif Input.trigger?(Input::BACK) + pbPlayCancelSE + real_contacts = $PokemonGlobal.phone.contacts + real_contacts.insert(switch_index, real_contacts.delete_at(@sprites["list"].index)) + @sprites["list"].index = switch_index + @sprites["list"].switching = false + switch_index = -1 + pbRefreshList + end + else + if Input.trigger?(Input::ACTION) + switch_index = @sprites["list"].index + @sprites["list"].switching = true + pbRefreshScreen + elsif Input.trigger?(Input::BACK) + pbPlayCloseMenuSE + return nil + elsif Input.trigger?(Input::USE) + return @contacts[index] if index >= 0 + end end end } - pbFadeOutAndHide(@sprites) + end + + def pbEndScene + pbFadeOutAndHide(@sprites) { pbUpdate } pbDisposeSpriteHash(@sprites) @viewport.dispose end + + def pbUpdate + pbUpdateSpriteHash(@sprites) + end +end + +#=============================================================================== +# +#=============================================================================== +class PokemonPhoneScreen + def initialize(scene) + @scene = scene + end + + def pbStartScreen + if $PokemonGlobal.phone.contacts.none? { |con| con.visible? } + pbMessage(_INTL("There are no phone numbers stored.")) + return + end + @scene.pbStartScene + loop do + contact = @scene.pbChooseContact + break if !contact + commands = [] + commands.push(_INTL("Call")) + commands.push(_INTL("Delete")) if contact.can_hide? + commands.push(_INTL("Sort Contacts")) + commands.push(_INTL("Cancel")) + cmd = pbShowCommands(nil, commands, -1) + cmd += 1 if cmd >=1 && !contact.can_hide? + case cmd + when 0 # Call + Phone::Call.make_outgoing(contact) + when 1 # Delete + name = contact.display_name + if pbConfirmMessage(_INTL("Are you sure you want to delete {1} from your phone?", name)) + contact.visible = false + $PokemonGlobal.phone.sort_contacts + @scene.pbRefreshList + pbMessage(_INTL("{1} was deleted from your phone contacts.", name)) + if $PokemonGlobal.phone.contacts.none? { |con| con.visible? } + pbMessage(_INTL("There are no phone numbers stored.")) + break + end + end + when 2 # Sort Contacts + case pbMessage(_INTL("How do you want to sort the contacts?"), + [_INTL("By name"), + _INTL("By Trainer type"), + _INTL("Special contacts first"), + _INTL("Cancel")], -1, nil, 0) + when 0 # By name + $PokemonGlobal.phone.contacts.sort! { |a, b| a.name <=> b.name } + $PokemonGlobal.phone.sort_contacts + @scene.pbRefreshList + when 1 # By trainer type + $PokemonGlobal.phone.contacts.sort! { |a, b| a.display_name <=> b.display_name } + $PokemonGlobal.phone.sort_contacts + @scene.pbRefreshList + when 2 # Special contacts first + new_contacts = [] + 2.times do |i| + $PokemonGlobal.phone.contacts.each do |con| + next if (i == 0 && con.trainer?) || (i == 1 && !con.trainer?) + new_contacts.push(con) + end + end + $PokemonGlobal.phone.contacts = new_contacts + $PokemonGlobal.phone.sort_contacts + @scene.pbRefreshList + end + end + end + @scene.pbEndScene + end end diff --git a/Data/Scripts/018_Alternate battle modes/002_BugContest.rb b/Data/Scripts/018_Alternate battle modes/002_BugContest.rb index 2833e5981..8a6b890d2 100644 --- a/Data/Scripts/018_Alternate battle modes/002_BugContest.rb +++ b/Data/Scripts/018_Alternate battle modes/002_BugContest.rb @@ -79,7 +79,6 @@ class BugContestState @contestMaps.push(map) end end - echoln "contest maps: #{@contestMaps}" end # Reception map is handled separately from contest map since the reception map @@ -94,7 +93,6 @@ class BugContestState @reception.push(map) end end - echoln "reception maps: #{@reception}" end def pbOffLimits?(map) diff --git a/Data/Scripts/019_Utilities/001_Utilities.rb b/Data/Scripts/019_Utilities/001_Utilities.rb index 11ea68636..1665f7eaf 100644 --- a/Data/Scripts/019_Utilities/001_Utilities.rb +++ b/Data/Scripts/019_Utilities/001_Utilities.rb @@ -70,7 +70,32 @@ def toCelsius(fahrenheit) return ((fahrenheit - 32) * 5.0 / 9.0).round end +#=============================================================================== +# This class is designed to favor different values more than a uniform +# random generator does. +#=============================================================================== +class AntiRandom + def initialize(size) + @old = [] + @new = Array.new(size) { |i| i } + end + def get + if @new.length == 0 # No new values + @new = @old.clone + @old.clear + end + if @old.length > 0 && rand(7) == 0 # Get old value + return @old[rand(@old.length)] + end + if @new.length > 0 # Get new value + ret = @new.delete_at(rand(@new.length)) + @old.push(ret) + return ret + end + return @old[rand(@old.length)] # Get old value + end +end #=============================================================================== # Constants utilities @@ -125,8 +150,6 @@ def getConstantNameOrValue(mod, value) return value.inspect end - - #=============================================================================== # Event utilities #=============================================================================== @@ -196,8 +219,6 @@ def pbNoticePlayer(event) pbMoveTowardPlayer(event) end - - #=============================================================================== # Player-related utilities, random name generator #=============================================================================== @@ -339,8 +360,6 @@ def getRandomName(maxLength = 100) return getRandomNameEx(2, nil, nil, maxLength) end - - #=============================================================================== # Regional and National Pokédexes utilities #=============================================================================== @@ -387,8 +406,6 @@ def pbGetRegionalDexLength(region_dex) return (dex_list) ? dex_list.length : 0 end - - #=============================================================================== # Other utilities #=============================================================================== diff --git a/Data/Scripts/020_Debug/003_Debug menus/002_Debug_MenuCommands.rb b/Data/Scripts/020_Debug/003_Debug menus/002_Debug_MenuCommands.rb index 939d5fa42..115635945 100644 --- a/Data/Scripts/020_Debug/003_Debug menus/002_Debug_MenuCommands.rb +++ b/Data/Scripts/020_Debug/003_Debug menus/002_Debug_MenuCommands.rb @@ -233,7 +233,7 @@ MenuHandlers.add(:debug_menu, :test_wild_battle_advanced, { params.setCancelValue(0) level = pbMessageChooseNumber(_INTL("Set the wild {1}'s level.", GameData::Species.get(species).name), params) - pkmn.push(Pokemon.new(species, level)) if level > 0 + pkmn.push(pbGenerateWildPokemon(species, level)) if level > 0 end else # Edit a Pokémon if pbConfirmMessage(_INTL("Change this Pokémon?")) @@ -336,6 +336,7 @@ MenuHandlers.add(:debug_menu, :test_trainer_battle_advanced, { trainerdata = pbListScreen(_INTL("CHOOSE A TRAINER"), TrainerBattleLister.new(0, false)) if trainerdata tr = pbLoadTrainer(trainerdata[0], trainerdata[1], trainerdata[2]) + EventHandlers.trigger(:on_trainer_load, tr) trainers.push([0, tr]) end else # Edit a trainer @@ -344,6 +345,7 @@ MenuHandlers.add(:debug_menu, :test_trainer_battle_advanced, { TrainerBattleLister.new(trainers[trainerCmd][0], false)) if trainerdata tr = pbLoadTrainer(trainerdata[0], trainerdata[1], trainerdata[2]) + EventHandlers.trigger(:on_trainer_load, tr) trainers[trainerCmd] = [0, tr] end elsif pbConfirmMessage(_INTL("Delete this trainer?")) @@ -386,6 +388,17 @@ MenuHandlers.add(:debug_menu, :reset_trainers, { } }) +MenuHandlers.add(:debug_menu, :toggle_rematches_possible, { + "name" => _INTL("Toggle Phone Rematches Possible"), + "parent" => :battle_menu, + "description" => _INTL("Toggles whether trainers in the phone can be rebattled."), + "effect" => proc { + Phone.rematches_enabled = !Phone.rematches_enabled + pbMessage(_INTL("Trainers in the phone can now be rebattled.")) if Phone.rematches_enabled + pbMessage(_INTL("Trainers in the phone cannot be rebattled.")) if !Phone.rematches_enabled + } +}) + MenuHandlers.add(:debug_menu, :ready_rematches, { "name" => _INTL("Ready All Phone Rematches"), "parent" => :battle_menu, @@ -1115,6 +1128,8 @@ MenuHandlers.add(:debug_menu, :create_pbs_files, { "abilities.txt", "battle_facility_lists.txt", "berry_plants.txt", + "dungeon_parameters.txt", + "dungeon_tilesets.txt", "encounters.txt", "items.txt", "map_connections.txt", @@ -1140,23 +1155,25 @@ MenuHandlers.add(:debug_menu, :create_pbs_files, { when 1 then Compiler.write_abilities when 2 then Compiler.write_trainer_lists when 3 then Compiler.write_berry_plants - when 4 then Compiler.write_encounters - when 5 then Compiler.write_items - when 6 then Compiler.write_connections - when 7 then Compiler.write_map_metadata - when 8 then Compiler.write_metadata - when 9 then Compiler.write_moves - when 10 then Compiler.write_phone - when 11 then Compiler.write_pokemon - when 12 then Compiler.write_pokemon_forms - when 13 then Compiler.write_pokemon_metrics - when 14 then Compiler.write_regional_dexes - when 15 then Compiler.write_ribbons - when 16 then Compiler.write_shadow_pokemon - when 17 then Compiler.write_town_map - when 18 then Compiler.write_trainer_types - when 19 then Compiler.write_trainers - when 20 then Compiler.write_types + when 4 then Compiler.write_dungeon_parameters + when 5 then Compiler.write_dungeon_tilesets + when 6 then Compiler.write_encounters + when 7 then Compiler.write_items + when 8 then Compiler.write_connections + when 9 then Compiler.write_map_metadata + when 10 then Compiler.write_metadata + when 11 then Compiler.write_moves + when 12 then Compiler.write_phone + when 13 then Compiler.write_pokemon + when 14 then Compiler.write_pokemon_forms + when 15 then Compiler.write_pokemon_metrics + when 16 then Compiler.write_regional_dexes + when 17 then Compiler.write_ribbons + when 18 then Compiler.write_shadow_pokemon + when 19 then Compiler.write_town_map + when 20 then Compiler.write_trainer_types + when 21 then Compiler.write_trainers + when 22 then Compiler.write_types else break end pbMessage(_INTL("File written.")) diff --git a/Data/Scripts/020_Debug/003_Debug menus/007_Debug_PokemonCommands.rb b/Data/Scripts/020_Debug/003_Debug menus/007_Debug_PokemonCommands.rb index 7538a866e..242f3c8ba 100644 --- a/Data/Scripts/020_Debug/003_Debug menus/007_Debug_PokemonCommands.rb +++ b/Data/Scripts/020_Debug/003_Debug menus/007_Debug_PokemonCommands.rb @@ -773,6 +773,11 @@ MenuHandlers.add(:pokemon_debug_menu, :species_and_form, { end if formcmds[0].length <= 1 screen.pbDisplay(_INTL("Species {1} only has one form.", pkmn.speciesName)) + if pkmn.form != 0 && screen.pbConfirm(_INTL("Do you want to reset the form to 0?")) + pkmn.form = 0 + $player.pokedex.register(pkmn) if !settingUpBattle && !pkmn.egg? + screen.pbRefreshSingle(pkmnid) + end else cmd2 = screen.pbShowCommands(_INTL("Set the Pokémon's form."), formcmds[1], cmd2) next if cmd2 < 0 diff --git a/Data/Scripts/021_Compiler/001_Compiler.rb b/Data/Scripts/021_Compiler/001_Compiler.rb index 394a8b92a..0586a5d63 100644 --- a/Data/Scripts/021_Compiler/001_Compiler.rb +++ b/Data/Scripts/021_Compiler/001_Compiler.rb @@ -757,7 +757,6 @@ module Compiler modify_pbs_file_contents_before_compiling compile_town_map compile_connections - compile_phone compile_types compile_abilities compile_moves # Depends on Type @@ -775,12 +774,18 @@ module Compiler compile_trainer_lists # Depends on TrainerType compile_metadata # Depends on TrainerType compile_map_metadata + compile_dungeon_tilesets + compile_dungeon_parameters + compile_phone # Depends on TrainerType end def compile_all(mustCompile) - return if !mustCompile + if !mustCompile + Console.echo_h1(_INTL("Game did not compile data")) + return + end FileLineData.clear - Console.echo_h1 _INTL("Starting full compile") + Console.echo_h1 _INTL("Compiling all data") compile_pbs_files compile_animations compile_trainer_events(mustCompile) @@ -793,7 +798,7 @@ module Compiler System.reload_cache Console.echo_done(true) echoln "" - Console.echo_h2("Successfully fully compiled", text: :green) + Console.echo_h2("Successfully compiled all data", text: :green) end def main @@ -802,6 +807,8 @@ module Compiler dataFiles = [ "abilities.dat", "berry_plants.dat", + "dungeon_parameters.dat", + "dungeon_tilesets.dat", "encounters.dat", "items.dat", "map_connections.dat", @@ -825,6 +832,8 @@ module Compiler "abilities.txt", "battle_facility_lists.txt", "berry_plants.txt", + "dungeon_parameters.txt", + "dungeon_tilesets.txt", "encounters.txt", "items.txt", "map_connections.txt", diff --git a/Data/Scripts/021_Compiler/002_Compiler_CompilePBS.rb b/Data/Scripts/021_Compiler/002_Compiler_CompilePBS.rb index ad62d1e3f..5c4c878ff 100644 --- a/Data/Scripts/021_Compiler/002_Compiler_CompilePBS.rb +++ b/Data/Scripts/021_Compiler/002_Compiler_CompilePBS.rb @@ -102,37 +102,69 @@ module Compiler def compile_phone(path = "PBS/phone.txt") return if !safeExists?(path) compile_pbs_file_message_start(path) - database = PhoneDatabase.new - sections = [] - File.open(path, "rb") { |f| - pbEachSection(f) { |section, name| - case name - when "" - database.generics = section - sections.concat(section) - when "" - database.battleRequests = section - sections.concat(section) - when "" - database.greetingsMorning = section - sections.concat(section) - when "" - database.greetingsEvening = section - sections.concat(section) - when "" - database.greetings = section - sections.concat(section) - when "" - database.bodies1 = section - sections.concat(section) - when "" - database.bodies2 = section - sections.concat(section) + GameData::PhoneMessage::DATA.clear + schema = GameData::PhoneMessage::SCHEMA + messages = [] + contact_hash = nil + # Read each line of phone.txt at a time and compile it as a contact property + idx = 0 + pbCompilerEachPreppedLine(path) { |line, line_no| + echo "." if idx % 50 == 0 + idx += 1 + Graphics.update if idx % 250 == 0 + if line[/^\s*\[\s*(.+)\s*\]\s*$/] + # New section [trainer_type, name] or [trainer_type, name, version] + if contact_hash + # Add contact's data to records + if contact_hash[:trainer_type] == "default" + contact_hash[:id] = contact_hash[:trainer_type] + else + contact_hash[:id] = [contact_hash[:trainer_type], contact_hash[:name], contact_hash[:version]] + end + GameData::PhoneMessage.register(contact_hash) end - } + # Construct contact hash + header = $~[1] + if header.strip.downcase == "default" + contact_hash = { + :trainer_type => "default" + } + else + line_data = pbGetCsvRecord($~[1], line_no, [0, "esU", :TrainerType]) + contact_hash = { + :trainer_type => line_data[0], + :name => line_data[1], + :version => line_data[2] || 0 + } + end + elsif line[/^\s*(\w+)\s*=\s*(.*)$/] + # XXX=YYY lines + if !contact_hash + raise _INTL("Expected a section at the beginning of the file.\r\n{1}", FileLineData.linereport) + end + property_name = $~[1] + line_schema = schema[property_name] + next if !line_schema + property_value = pbGetCsvRecord($~[2], line_no, line_schema) + # Record XXX=YYY setting + contact_hash[line_schema[0]] ||= [] + contact_hash[line_schema[0]].push(property_value) + messages.push(property_value) + end } - MessageTypes.setMessagesAsHash(MessageTypes::PhoneMessages, sections) - save_data(database, "Data/phone.dat") + # Add last contact's data to records + if contact_hash + # Add contact's data to records + if contact_hash[:trainer_type] == "default" + contact_hash[:id] = contact_hash[:trainer_type] + else + contact_hash[:id] = [contact_hash[:trainer_type], contact_hash[:name], contact_hash[:version]] + end + GameData::PhoneMessage.register(contact_hash) + end + # Save all data + GameData::PhoneMessage.save + MessageTypes.setMessagesAsHash(MessageTypes::PhoneMessages, messages) process_pbs_file_message_end end @@ -1816,6 +1848,110 @@ module Compiler process_pbs_file_message_end end + #============================================================================= + # Compile dungeon tileset data + #============================================================================= + def compile_dungeon_tilesets(path = "PBS/dungeon_tilesets.txt") + compile_pbs_file_message_start(path) + GameData::DungeonTileset::DATA.clear + schema = GameData::DungeonTileset::SCHEMA + tileset_hash = nil + # Read each line of dungeon_tilesets.txt at a time and compile it as a tileset property + idx = 0 + pbCompilerEachPreppedLine(path) { |line, line_no| + echo "." if idx % 50 == 0 + idx += 1 + Graphics.update if idx % 250 == 0 + if line[/^\s*\[\s*(.+)\s*\]\s*$/] + # New section + # Add tileset's data to records + GameData::DungeonTileset.register(tileset_hash) if tileset_hash + # Construct tileset hash + tileset_hash = { + :id => $~[1].to_i, + :tile => [], + :autotile => [] + } + elsif line[/^\s*(\w+)\s*=\s*(.*)$/] + # XXX=YYY lines + if !tileset_hash + raise _INTL("Expected a section at the beginning of the file.\r\n{1}", FileLineData.linereport) + end + property_name = $~[1] + line_schema = schema[property_name] + next if !line_schema + property_value = pbGetCsvRecord($~[2], line_no, line_schema) + # Record XXX=YYY setting + case property_name + when "Tile", "Autotile" + tileset_hash[line_schema[0]].push(property_value) + else + tileset_hash[line_schema[0]] = property_value + end + end + } + # Add last tileset's data to records + GameData::DungeonTileset.register(tileset_hash) if tileset_hash + # Save all data + GameData::DungeonTileset.save + process_pbs_file_message_end + end + + #============================================================================= + # Compile dungeon parameters data + #============================================================================= + def compile_dungeon_parameters(path = "PBS/dungeon_parameters.txt") + compile_pbs_file_message_start(path) + GameData::DungeonParameters::DATA.clear + schema = GameData::DungeonParameters::SCHEMA + # Read from PBS file + File.open(path, "rb") { |f| + FileLineData.file = path # For error reporting + # Read a whole section's lines at once, then run through this code. + # contents is a hash containing all the XXX=YYY lines in that section, where + # the keys are the XXX and the values are the YYY (as unprocessed strings). + idx = 0 + pbEachFileSection(f) { |contents, section_name| + echo "." if idx % 50 == 0 + idx += 1 + Graphics.update if idx % 250 == 0 + FileLineData.setSection(section_name, "header", nil) # For error reporting + # Split section_name into an area and version number + split_section_name = section_name.split(/[-,\s]/) + if split_section_name.length == 0 || split_section_name.length > 2 + raise _INTL("Section name {1} is invalid ({2}). Expected syntax like [XXX] or [XXX,Y] (XXX=area, Y=version).", section_name, path) + end + area_symbol = split_section_name[0].downcase.to_sym + version = (split_section_name[1]) ? csvPosInt!(split_section_name[1]) : 0 + # Construct parameters hash + area_version = (version > 0) ? sprintf("%s_%d", area_symbol.to_s, version).to_sym : area_symbol + parameters_hash = { + :id => area_version, + :area => area_symbol, + :version => version + } + # Go through schema hash of compilable data and compile this section + schema.each_key do |key| + # Skip empty properties (none are required) + if nil_or_empty?(contents[key]) + contents[key] = nil + next + end + FileLineData.setSection(section_name, key, contents[key]) # For error reporting + # Compile value for key + value = pbGetCsvRecord(contents[key], key, schema[key]) + value = nil if value.is_a?(Array) && value.length == 0 + parameters_hash[schema[key][0]] = value + end + # Add parameters data to records + GameData::DungeonParameters.register(parameters_hash) + } + } + # Save all data + GameData::DungeonParameters.save + process_pbs_file_message_end + end + #============================================================================= # Compile battle animations #============================================================================= diff --git a/Data/Scripts/021_Compiler/003_Compiler_WritePBS.rb b/Data/Scripts/021_Compiler/003_Compiler_WritePBS.rb index b206e8ffa..0219d72f5 100644 --- a/Data/Scripts/021_Compiler/003_Compiler_WritePBS.rb +++ b/Data/Scripts/021_Compiler/003_Compiler_WritePBS.rb @@ -103,32 +103,26 @@ module Compiler # Save phone messages to PBS file #============================================================================= def write_phone(path = "PBS/phone.txt") - data = load_data("Data/phone.dat") rescue nil - return if !data write_pbs_file_message_start(path) + keys = GameData::PhoneMessage::SCHEMA.keys File.open(path, "wb") { |f| add_PBS_header_to_file(f) - f.write("\#-------------------------------\r\n") - f.write("[]\r\n") - f.write(data.generics.join("\r\n") + "\r\n") - f.write("\#-------------------------------\r\n") - f.write("[]\r\n") - f.write(data.battleRequests.join("\r\n") + "\r\n") - f.write("\#-------------------------------\r\n") - f.write("[]\r\n") - f.write(data.greetingsMorning.join("\r\n") + "\r\n") - f.write("\#-------------------------------\r\n") - f.write("[]\r\n") - f.write(data.greetingsEvening.join("\r\n") + "\r\n") - f.write("\#-------------------------------\r\n") - f.write("[]\r\n") - f.write(data.greetings.join("\r\n") + "\r\n") - f.write("\#-------------------------------\r\n") - f.write("[]\r\n") - f.write(data.bodies1.join("\r\n") + "\r\n") - f.write("\#-------------------------------\r\n") - f.write("[]\r\n") - f.write(data.bodies2.join("\r\n") + "\r\n") + # Write message sets + GameData::PhoneMessage.each do |contact| + f.write("\#-------------------------------\r\n") + if contact.id == "default" + f.write("[Default]\r\n") + elsif contact.version > 0 + f.write(sprintf("[%s,%s,%d]\r\n", contact.trainer_type, contact.real_name, contact.version)) + else + f.write(sprintf("[%s,%s]\r\n", contact.trainer_type, contact.real_name)) + end + keys.each do |key| + msgs = contact.property_from_string(key) + next if !msgs || msgs.length == 0 + msgs.each { |msg| f.write(key + " = " + msg + "\r\n") } + end + end } process_pbs_file_message_end end @@ -879,6 +873,91 @@ module Compiler process_pbs_file_message_end end + #============================================================================= + # Save dungeon tileset contents data to PBS file + #============================================================================= + def write_dungeon_tilesets(path = "PBS/dungeon_tilesets.txt") + write_pbs_file_message_start(path) + tilesets = load_data("Data/Tilesets.rxdata") + schema = GameData::DungeonTileset::SCHEMA + keys = schema.keys + File.open(path, "wb") { |f| + idx = 0 + add_PBS_header_to_file(f) + GameData::DungeonTileset.each do |tileset_data| + echo "." if idx % 50 == 0 + idx += 1 + Graphics.update if idx % 250 == 0 + f.write("\#-------------------------------\r\n") + tileset_name = (tilesets && tilesets[tileset_data.id]) ? tilesets[tileset_data.id].name : nil + if tileset_name + f.write(sprintf("[%d] # %s\r\n", tileset_data.id, tileset_name)) + else + f.write(sprintf("[%d]\r\n", tileset_data.id)) + end + keys.each do |key| + if ["Autotile", "Tile"].include?(key) + tiles = tileset_data.tile_type_ids + tiles.each do |tile_type, tile_ids| + tile_ids.each do |tile| + next if tile[1] # Tile was auto-generated from "walls" property + if tile[0] < 384 + next if key == "Tile" + f.write(sprintf("Autotile = %i,%s", tile[0] / 48, tile_type.to_s)) + else + next if key == "Autotile" + f.write(sprintf("Tile = %i,%s", tile[0] - 384, tile_type.to_s)) + end + f.write("\r\n") + end + end + else + record = tileset_data.property_from_string(key) + next if record.nil? || (record.is_a?(Array) && record.empty?) + next if record == false || record == 0 + f.write(sprintf("%s = ", key)) + pbWriteCsvRecord(record, f, schema[key]) + f.write("\r\n") + end + end + end + } + process_pbs_file_message_end + end + + #============================================================================= + # Save dungeon parameters to PBS file + #============================================================================= + def write_dungeon_parameters(path = "PBS/dungeon_parameters.txt") + write_pbs_file_message_start(path) + schema = GameData::DungeonParameters::SCHEMA + keys = schema.keys + # Write file + File.open(path, "wb") { |f| + idx = 0 + add_PBS_header_to_file(f) + GameData::DungeonParameters.each do |parameters_data| + echo "." if idx % 50 == 0 + idx += 1 + Graphics.update if idx % 250 == 0 + f.write("\#-------------------------------\r\n") + if parameters_data.version > 0 + f.write(sprintf("[%s,%d]\r\n", parameters_data.area, parameters_data.version)) + else + f.write(sprintf("[%s]\r\n", parameters_data.area)) + end + keys.each do |key| + value = parameters_data.property_from_string(key) + next if !value || (value.is_a?(Array) && value.length == 0) + f.write(sprintf("%s = ", key)) + pbWriteCsvRecord(value, f, schema[key]) + f.write("\r\n") + end + end + } + process_pbs_file_message_end + end + #============================================================================= # Save all data to PBS files #============================================================================= @@ -886,7 +965,6 @@ module Compiler Console.echo_h1 _INTL("Writing all PBS files") write_town_map write_connections - write_phone write_types write_abilities write_moves @@ -904,6 +982,9 @@ module Compiler write_trainer_lists write_metadata write_map_metadata + write_dungeon_tilesets + write_dungeon_parameters + write_phone echoln "" Console.echo_h2("Successfully rewrote all PBS files", text: :green) end diff --git a/Data/Scripts/021_Compiler/004_Compiler_MapsAndEvents.rb b/Data/Scripts/021_Compiler/004_Compiler_MapsAndEvents.rb index 348a7114c..9689bf171 100644 --- a/Data/Scripts/021_Compiler/004_Compiler_MapsAndEvents.rb +++ b/Data/Scripts/021_Compiler/004_Compiler_MapsAndEvents.rb @@ -506,7 +506,7 @@ module Compiler end end # Compile the trainer comments - rewriteComments = true #false # You can change this + rewriteComments = false # You can change this battles = [] trtype = nil trname = nil @@ -519,6 +519,7 @@ module Compiler endifswitch = [] vanishifswitch = [] regspeech = nil + common_event = 0 commands.each do |command| if command[/^Battle\:\s*([\s\S]+)$/i] battles.push($~[1]) @@ -558,6 +559,9 @@ module Compiler elsif command[/^RegSpeech\:\s*([\s\S]+)$/i] regspeech = $~[1].gsub(/^\s+/, "").gsub(/\s+$/, "") push_comment(firstpage.list, command) if rewriteComments + elsif command[/^CommonEvent\:\s*(\d+)$/i] + common_event = $~[1].to_i + push_comment(firstpage.list, command) if rewriteComments end end return nil if battles.length <= 0 @@ -600,10 +604,18 @@ module Compiler push_text(firstpage.list, regspeech, 2) push_choices(firstpage.list, ["Yes", "No"], 2, 2) push_choice(firstpage.list, 0, "Yes", 3) - if battleid > 0 - push_script(firstpage.list, sprintf("Phone.add(get_self,\n %s, %d, %d\n)", brieftrcombo, battles.length, battleid), 3) + if common_event > 0 + if battleid > 0 + push_script(firstpage.list, sprintf("Phone.add(get_self,\n %s, %d, %d, %d\n)", brieftrcombo, battles.length, battleid, common_event), 3) + else + push_script(firstpage.list, sprintf("Phone.add(get_self,\n %s, %d, nil, %d\n)", brieftrcombo, battles.length, common_event), 3) + end else - push_script(firstpage.list, sprintf("Phone.add(get_self,\n %s, %d\n)", brieftrcombo, battles.length), 3) + if battleid > 0 + push_script(firstpage.list, sprintf("Phone.add(get_self,\n %s, %d, %d\n)", brieftrcombo, battles.length, battleid), 3) + else + push_script(firstpage.list, sprintf("Phone.add(get_self,\n %s, %d\n)", brieftrcombo, battles.length), 3) + end end push_choice(firstpage.list, 1, "No", 3) push_choices_end(firstpage.list, 3) @@ -679,10 +691,18 @@ module Compiler push_text(lastpage.list, regspeech, 1) push_choices(lastpage.list, ["Yes", "No"], 2, 1) push_choice(lastpage.list, 0, "Yes", 2) - if battleid > 0 - push_script(lastpage.list, sprintf("Phone.add(get_self,\n %s, %d, %d\n)", brieftrcombo, battles.length, battleid), 2) + if common_event > 0 + if battleid > 0 + push_script(lastpage.list, sprintf("Phone.add(get_self,\n %s, %d, %d, %d\n)", brieftrcombo, battles.length, battleid, common_event), 2) + else + push_script(lastpage.list, sprintf("Phone.add(get_self,\n %s, %d, nil, %d\n)", brieftrcombo, battles.length, common_event), 2) + end else - push_script(lastpage.list, sprintf("Phone.add(get_self,\n %s, %d\n)", brieftrcombo, battles.length), 2) + if battleid > 0 + push_script(lastpage.list, sprintf("Phone.add(get_self,\n %s, %d, %d\n)", brieftrcombo, battles.length, battleid), 2) + else + push_script(lastpage.list, sprintf("Phone.add(get_self,\n %s, %d\n)", brieftrcombo, battles.length), 2) + end end push_choice(lastpage.list, 1, "No", 2) push_choices_end(lastpage.list, 2) diff --git a/PBS/dungeon_parameters.txt b/PBS/dungeon_parameters.txt new file mode 100644 index 000000000..8388bb4f0 --- /dev/null +++ b/PBS/dungeon_parameters.txt @@ -0,0 +1,30 @@ +# See the documentation on the wiki to learn how to edit this file. +#------------------------------- +[cave] +DungeonSize = 5,4 +CellSize = 10,10 +MinRoomSize = 5,5 +MaxRoomSize = 9,9 +CorridorWidth = 2 +ShiftCorridors = true +NodeLayout = full +RoomLayout = full +RoomChance = 70 +ExtraConnections = 2 +FloorPatches = 2,50,25 +FloorDecorations = 50,200 +VoidDecorations = 50,200 +#------------------------------- +[forest] +DungeonSize = 5,4 +CellSize = 10,10 +MinRoomSize = 4,4 +MaxRoomSize = 8,8 +CorridorWidth = 2 +NodeLayout = full +RoomLayout = full +RoomChance = 70 +ExtraConnections = 2 +FloorPatches = 3,75,25 +FloorDecorations = 50,200 +VoidDecorations = 50,200 diff --git a/PBS/dungeon_tilesets.txt b/PBS/dungeon_tilesets.txt new file mode 100644 index 000000000..c27edf6f3 --- /dev/null +++ b/PBS/dungeon_tilesets.txt @@ -0,0 +1,28 @@ +# See the documentation on the wiki to learn how to edit this file. +#------------------------------- +[7] # Dungeon cave +Autotile = 1,floor_patch +Tile = 40,void +Tile = 41,void_decoration +Tile = 48,void_decoration_large +Tile = 42,floor +Tile = 50,floor_decoration_large +Tile = 0,walls +DoubleWalls = true +FloorPatchUnderWalls = true +#------------------------------- +[23] # Dungeon forest +Autotile = 1,floor_decoration +Tile = 40,void +Tile = 42,floor +Tile = 43,floor +Tile = 44,floor +Tile = 45,floor +Tile = 46,floor +Tile = 47,floor_patch +Tile = 8,walls +Tile = 25,wall_top +SnapToLargeGrid = true +LargeVoidTiles = true +LargeWallTiles = true +ThinNorthWallOffset = -8 diff --git a/PBS/phone.txt b/PBS/phone.txt index 226a306f8..c8084c76f 100644 --- a/PBS/phone.txt +++ b/PBS/phone.txt @@ -1,34 +1,48 @@ # See the documentation on the wiki to learn how to edit this file. #------------------------------- -[] -How are your Pokémon doing?\mMy \TP's really energetic. It's a handful!\mOh yeah, I managed to beat a tough \TE.\mI need to make my party stronger. -My \TP's looking really awesome.\mI wish I could show you.\mHey, listen! I almost caught a \TE the other day.\mOh, it was sooooooo close, too! +[Default] +Intro = Hello. This is \TN. +Intro = Good day, \PN! Hi. This is \TN. +Intro = How are you doing? \PN, howdy! This is \TN. Isn't it nice out? +Intro = \PN, good day! It's me, \TN. Got a minute? +Intro = Hello, \PN. This is \TN. How are things? +IntroMorning = Good morning, \PN. This is \TN. Did I wake you? +IntroAfternoon = Good afternoon, \PN! It's \TN here. +IntroEvening = \PN, good evening! Hi. This is \TN. +Body = How are your Pokémon doing?\mMy \TP's really energetic. It's a handful!\mOh yeah, I managed to beat a tough \TE.\mI need to make my party stronger. +Body = My \TP's looking really awesome. I wish I could show you.\mHey, listen! I almost caught a \TE the other day.\mOh, it was sooooooo close, too! +Body1 = How are your Pokémon doing?\mMy \TP's really energetic. It's a handful! +Body1 = How are your Pokémon doing?\mI always keep my \TP in top shape by going to Pokémon Centers. +Body1 = My \TP's looking really awesome. I wish I could show you. +Body1 = I dressed my \TP and it looks even cuter than before. +Body2 = Oh yeah, I managed to beat a tough \TE.\mI need to make my party stronger. +Body2 = You have to hear this! I battled \TE the other day.\mIt was easy! I had a type advantage. +Body2 = Hey, listen! I almost caught \TE the other day.\mOh, it was sooooooo close, too! +Body2 = Guess what happened the other day. I missed catching \TE again.\mMaybe I'm not very good at this. +BattleRequest = Want to battle? It's not going to be a repeat of the last time we met.\mI'll be waiting for you around \TM. +BattleRequest = Do you want to battle? I'm going to win this time!\mI'll be waiting for you around \TM.\mLook for me, OK? My \TP will be expecting you. #------------------------------- -[] -Want to battle? It's not going to be a repeat of the last time we met.\mI'll be waiting for you around \TM. -Do you want to battle? I'm going to win this time!\mI'll be waiting for you around \TM.\mLook for me, OK? My \TP will be expecting you. +[CAMPER,Jeff] +Intro = Hello. This is \TN...\mHow's it going, \PN? +Body1 = My \TP is looking more and more like me. It's getting cuter! +Body2 = And you know? Now we can KO \TE easily.\mI should challenge the Cedolan Gym. +Body2 = And you know? We just failed to beat \TE by a tiny margin.\mI'm guessing my Pokémon's levels aren't high enough yet... +BattleRequest = You must be a lot better now, huh?\mHow about showing me your technique in a real battle with me?\mI'll be waiting on \TM. +BattleReminder = Where are you? Let's have our battle soon!\mI'll be waiting on \TM. +End = See you later! #------------------------------- -[] -Good morning, \PN.\mThis is \TN. Did I wake you? -#------------------------------- -[] -\PN, good evening! Hi.\mThis is \TN. -#------------------------------- -[] -Hello. This is \TN. -Good day, \PN! Hi. This is \TN. -How are you doing? \PN, howdy! This is \TN. Isn't it nice out? -\PN, good day! It's me, \TN. Got a minute? -Hello, \PN. This is \TN. How are things? -#------------------------------- -[] -How are your Pokémon doing?\mMy \TP's really energetic. It's a handful! -How are your Pokémon doing?\mI always keep my \TP in top shape by going to Pokémon Centers. -My \TP's looking really awesome. I wish I could show you. -I dressed my \TP and it looks even cuter than before. -#------------------------------- -[] -Oh yeah, I managed to beat a tough \TE.\mI need to make my party stronger. -You have to hear this! I battled \TE the other day.\mIt was easy! I had a type advantage. -Hey, listen! I almost caught \TE the other day.\mOh, it was sooooooo close, too! -Guess what happened the other day. I missed catching \TE again.\mMaybe I'm not very good at this. +[PICNICKER,Susie] +Intro = This is \TN! +Intro = Hi, this is \TN! +IntroMorning = This is \TN! Good morning, \PN! +IntroMorning = Hi, this is \TN! Good morning, \PN! +IntroAfternoon = This is \TN! Good afternoon, \PN! +IntroAfternoon = Hi, this is \TN! Good afternoon, \PN! +IntroEvening = This is \TN! Good evening, \PN! +IntroEvening = Hi, this is \TN! Good evening, \PN! +Body1 = My \TP and I are getting more in sync with each other. +Body2 = We battled a wild \TE and managed to beat it in a close match.\mWe're getting into the groove! +Body2 = But, you know what? I still haven't caught \TE.\mIt's getting beyond frustrating... +BattleRequest = Would you be my practice partner again sometime?\mI'll be waiting on \TM...\mCould you take it a little easier on me next time? +BattleReminder = How soon can I expect to see you?\mDon't forget, \TM! +End = Bye! Let's chat again!