commit ba94119d028208728637fc6d6ae64a8cd56d4f5b Author: Maruno17 Date: Fri Sep 4 22:00:59 2020 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..5bc2d02ca --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# Audio and Graphics folders +Audio/ +Graphics/ + +# Data folder, but not Data/Scripts folder +Data/* +!Data/Scripts.rxdata +!Data/Scripts/ + +# Files in the main project folder +Game.exe +Game.ini +Game.rxproj +RGSS*.dll diff --git a/Data/Scripts.rxdata b/Data/Scripts.rxdata new file mode 100644 index 000000000..b6ed00a3f Binary files /dev/null and b/Data/Scripts.rxdata differ diff --git a/Data/Scripts/001_Settings.rb b/Data/Scripts/001_Settings.rb new file mode 100644 index 000000000..60634a782 --- /dev/null +++ b/Data/Scripts/001_Settings.rb @@ -0,0 +1,353 @@ +#==============================================================================# +# Pokémon Essentials # +# Version 18 # +#==============================================================================# + +#=============================================================================== +# * The default screen width (at a zoom of 1.0; size is half this at zoom 0.5). +# * The default screen height (at a zoom of 1.0). +# * The default screen zoom. (1.0 means each tile is 32x32 pixels, 0.5 means +# each tile is 16x16 pixels, 2.0 means each tile is 64x64 pixels.) +# * Whether full-screen display lets the border graphic go outside the edges of +# the screen (true), or forces the border graphic to always be fully shown +# (false). +# * The width of each of the left and right sides of the screen border. This is +# added on to the screen width above, only if the border is turned on. +# * The height of each of the top and bottom sides of the screen border. This is +# added on to the screen height above, only if the border is turned on. +# * Map view mode (0=original, 1=custom, 2=perspective). +#=============================================================================== +SCREEN_WIDTH = 512 +SCREEN_HEIGHT = 384 +SCREEN_ZOOM = 1.0 +BORDER_FULLY_SHOWS = false +BORDER_WIDTH = 78 +BORDER_HEIGHT = 78 +MAP_VIEW_MODE = 1 +# To forbid the player from changing the screen size themselves, quote out or +# delete the relevant bit of code in the PScreen_Options script section. + +#=============================================================================== +# * The maximum level Pokémon can reach. +# * The level of newly hatched Pokémon. +# * The odds of a newly generated Pokémon being shiny (out of 65536). +# * The odds of a wild Pokémon/bred egg having Pokérus (out of 65536). +#=============================================================================== +MAXIMUM_LEVEL = 100 +EGG_LEVEL = 1 +SHINY_POKEMON_CHANCE = 8 +POKERUS_CHANCE = 3 + +#=============================================================================== +# * Whether outdoor maps should be shaded according to the time of day. +#=============================================================================== +TIME_SHADING = true + +#=============================================================================== +# * Whether poisoned Pokémon will lose HP while walking around in the field. +# * Whether poisoned Pokémon will faint while walking around in the field +# (true), or survive the poisoning with 1HP (false). +# * Whether fishing automatically hooks the Pokémon (if false, there is a +# reaction test first). +# * Whether the player can surface from anywhere while diving (true), or only in +# spots where they could dive down from above (false). +# * Whether planted berries grow according to Gen 4 mechanics (true) or Gen 3 +# mechanics (false). +# * Whether TMs can be used infinitely as in Gen 5 (true), or are one-use-only +# as in older Gens (false). +#=============================================================================== +POISON_IN_FIELD = true +POISON_FAINT_IN_FIELD = false +FISHING_AUTO_HOOK = false +DIVING_SURFACE_ANYWHERE = false +NEW_BERRY_PLANTS = true +INFINITE_TMS = true + +#=============================================================================== +# * The number of steps allowed before a Safari Zone game is over (0=infinite). +# * The number of seconds a Bug Catching Contest lasts for (0=infinite). +#=============================================================================== +SAFARI_STEPS = 600 +BUG_CONTEST_TIME = 1200 + +#=============================================================================== +# * Pairs of map IDs, where the location signpost isn't shown when moving from +# one of the maps in a pair to the other (and vice versa). Useful for +# single long routes/towns that are spread over multiple maps. +# e.g. [4,5,16,17,42,43] will be map pairs 4,5 and 16,17 and 42,43. +# Moving between two maps that have the exact same name won't show the +# location signpost anyway, so you don't need to list those maps here. +#=============================================================================== +NO_SIGNPOSTS = [] + +#=============================================================================== +# * The amount of money the player starts the game with. +# * The maximum amount of money the player can have. +# * The maximum number of Game Corner coins the player can have. +# * The maximum length, in characters, that the player's name can be. +#=============================================================================== +INITIAL_MONEY = 3000 +MAX_MONEY = 999999 +MAX_COINS = 99999 +MAX_PLAYER_NAME_SIZE = 10 + +#=============================================================================== +# * A set of arrays each containing a trainer type followed by a Global Variable +# number. If the variable isn't set to 0, then all trainers with the +# associated trainer type will be named as whatever is in that variable. +#=============================================================================== +RIVAL_NAMES = [ + [:RIVAL1,12], + [:RIVAL2,12], + [:CHAMPION,12] +] + +#=============================================================================== +# * The minimum number of badges required to boost each stat of a player's +# Pokémon by 1.1x, while using moves in battle only. +# * Whether the badge restriction on using certain hidden moves is either owning +# at least a certain number of badges (true), or owning a particular badge +# (false). +# * Depending on FIELD_MOVES_COUNT_BADGES, either the number of badges required +# to use each hidden move, or the specific badge number required to use +# each move. Remember that badge 0 is the first badge, badge 1 is the +# second badge, etc. +# e.g. To require the second badge, put false and 1. +# To require at least 2 badges, put true and 2. +#=============================================================================== +NUM_BADGES_BOOST_ATTACK = 1 +NUM_BADGES_BOOST_DEFENSE = 5 +NUM_BADGES_BOOST_SPATK = 7 +NUM_BADGES_BOOST_SPDEF = 7 +NUM_BADGES_BOOST_SPEED = 3 +FIELD_MOVES_COUNT_BADGES = true +BADGE_FOR_CUT = 1 +BADGE_FOR_FLASH = 2 +BADGE_FOR_ROCKSMASH = 3 +BADGE_FOR_SURF = 4 +BADGE_FOR_FLY = 5 +BADGE_FOR_STRENGTH = 6 +BADGE_FOR_DIVE = 7 +BADGE_FOR_WATERFALL = 8 + +#=============================================================================== +# * Whether a move's physical/special category depends on the move itself as in +# newer Gens (true), or on its type as in older Gens (false). +# * Whether the battle mechanics mimic Gen 5 (false) or Gen 7 (true). +# * Whether the Exp gained from beating a Pokémon should be scaled depending on +# the gainer's level as in Gens 5/7 (true) or not as in other Gens (false). +# * Whether the Exp gained from beating a Pokémon should be divided equally +# between each participant (true), or whether each participant should gain +# that much Exp (false). This also applies to Exp gained via the Exp Share +# (held item version) being distributed to all Exp Share holders. This is +# true in Gen 6 and false otherwise. +# * Whether the critical capture mechanic applies (true) or not (false). Note +# that it is based on a total of 600+ species (i.e. that many species need +# to be caught to provide the greatest critical capture chance of 2.5x), +# and there may be fewer species in your game. +# * Whether Pokémon gain Exp for capturing a Pokémon (true) or not (false). +# * An array of items which act as Mega Rings for the player (NPCs don't need a +# Mega Ring item, just a Mega Stone held by their Pokémon). +#=============================================================================== +MOVE_CATEGORY_PER_MOVE = true +NEWEST_BATTLE_MECHANICS = true +SCALED_EXP_FORMULA = true +SPLIT_EXP_BETWEEN_GAINERS = false +ENABLE_CRITICAL_CAPTURES = false +GAIN_EXP_FOR_CAPTURE = true +MEGA_RINGS = [:MEGARING,:MEGABRACELET,:MEGACUFF,:MEGACHARM] + +#=============================================================================== +# * The names of each pocket of the Bag. Leave the first entry blank. +# * The maximum number of slots per pocket (-1 means infinite number). Ignore +# the first number (0). +# * The maximum number of items each slot in the Bag can hold. +# * Whether each pocket in turn auto-sorts itself by item ID number. Ignore the +# first entry (the 0). +#=============================================================================== +def pbPocketNames; return ["", + _INTL("Items"), + _INTL("Medicine"), + _INTL("Poké Balls"), + _INTL("TMs & HMs"), + _INTL("Berries"), + _INTL("Mail"), + _INTL("Battle Items"), + _INTL("Key Items") +]; end +BAG_MAX_POCKET_SIZE = [0,-1,-1,-1,-1,-1,-1,-1,-1] +BAG_MAX_PER_SLOT = 999 +BAG_POCKET_AUTO_SORT = [0,false,false,false,true,true,false,false,false] + +#=============================================================================== +# * A set of arrays each containing details of a graphic to be shown on the +# region map if appropriate. The values for each array are as follows: +# - Region number. +# - Global Switch; the graphic is shown if this is ON (non-wall maps only). +# - X coordinate of the graphic on the map, in squares. +# - Y coordinate of the graphic on the map, in squares. +# - Name of the graphic, found in the Graphics/Pictures folder. +# - The graphic will always (true) or never (false) be shown on a wall map. +#=============================================================================== +REGION_MAP_EXTRAS = [ + [0,51,16,15,"mapHiddenBerth",false], + [0,52,20,14,"mapHiddenFaraday",false] +] + +#=============================================================================== +# * The name of the person who created the Pokémon storage system. +# * The number of boxes in Pokémon storage. +#=============================================================================== +def pbStorageCreator + return _INTL("Bill") +end +NUM_STORAGE_BOXES = 30 + +#=============================================================================== +# * Whether the Pokédex list shown is the one for the player's current region +# (true), or whether a menu pops up for the player to manually choose which +# Dex list to view if more than one is available (false). +# * The names of each Dex list in the game, in order and with National Dex at +# the end. This is also the order that $PokemonGlobal.pokedexUnlocked is +# in, which records which Dexes have been unlocked (first is unlocked by +# default). +# You can define which region a particular Dex list is linked to. This +# means the area map shown while viewing that Dex list will ALWAYS be that +# of the defined region, rather than whichever region the player is +# currently in. To define this, put the Dex name and the region number in +# an array, like the Kanto and Johto Dexes are. The National Dex isn't in +# an array with a region number, therefore its area map is whichever region +# the player is currently in. +# * Whether all forms of a given species will be immediately available to view +# in the Pokédex so long as that species has been seen at all (true), or +# whether each form needs to be seen specifically before that form appears +# in the Pokédex (false). +# * An array of numbers, where each number is that of a Dex list (National Dex +# is -1). All Dex lists included here have the species numbers in them +# reduced by 1, thus making the first listed species have a species number +# of 0 (e.g. Victini in Unova's Dex). +#=============================================================================== +USE_CURRENT_REGION_DEX = false +def pbDexNames; return [ + [_INTL("Kanto Pokédex"),0], + [_INTL("Johto Pokédex"),1], + _INTL("National Pokédex") +]; end +DEX_SHOWS_ALL_FORMS = false +DEXES_WITH_OFFSETS = [] + +#=============================================================================== +# * A list of maps used by roaming Pokémon. Each map has an array of other maps +# it can lead to. +# * A set of arrays each containing the details of a roaming Pokémon. The +# information within is as follows: +# - Species. +# - Level. +# - Global Switch; the Pokémon roams while this is ON. +# - Encounter type (0=any, 1=grass/walking in cave, 2=surfing, 3=fishing, +# 4=surfing/fishing). See bottom of PField_RoamingPokemon for lists. +# - Name of BGM to play for that encounter (optional). +# - Roaming areas specifically for this Pokémon (optional). +#=============================================================================== +RoamingAreas = { + 5 => [21,28,31,39,41,44,47,66,69], + 21 => [5,28,31,39,41,44,47,66,69], + 28 => [5,21,31,39,41,44,47,66,69], + 31 => [5,21,28,39,41,44,47,66,69], + 39 => [5,21,28,31,41,44,47,66,69], + 41 => [5,21,28,31,39,44,47,66,69], + 44 => [5,21,28,31,39,41,47,66,69], + 47 => [5,21,28,31,39,41,44,66,69], + 66 => [5,21,28,31,39,41,44,47,69], + 69 => [5,21,28,31,39,41,44,47,66] +} +RoamingSpecies = [ + [:LATIAS, 30, 53, 0, "Battle roaming"], + [:LATIOS, 30, 53, 0, "Battle roaming"], + [:KYOGRE, 40, 54, 2, nil, { + 2 => [21,31], + 21 => [2,31,69], + 31 => [2,21,69], + 69 => [21,31] + }], + [:ENTEI, 40, 55, 1, nil] +] + +#=============================================================================== +# * A set of arrays each containing details of a wild encounter that can only +# occur via using the Poké Radar. The information within is as follows: +# - Map ID on which this encounter can occur. +# - Probability that this encounter will occur (as a percentage). +# - Species. +# - Minimum possible level. +# - Maximum possible level (optional). +#=============================================================================== +POKE_RADAR_ENCOUNTERS = [ + [5, 20, :STARLY, 12, 15], + [21, 10, :STANTLER, 14], + [28, 20, :BUTTERFREE, 15, 18], + [28, 20, :BEEDRILL, 15, 18] +] + +#=============================================================================== +# * The Global Switch that is set to ON when the player whites out. +# * The Global Switch that is set to ON when the player has seen Pokérus in the +# Poké Center, and doesn't need to be told about it again. +# * The Global Switch which, while ON, makes all wild Pokémon created be +# shiny. +# * The Global Switch which, while ON, makes all Pokémon created considered to +# be met via a fateful encounter. +# * The Global Switch which determines whether the player will lose money if +# they lose a battle (they can still gain money from trainers for winning). +# * The Global Switch which, while ON, prevents all Pokémon in battle from Mega +# Evolving even if they otherwise could. +#=============================================================================== +STARTING_OVER_SWITCH = 1 +SEEN_POKERUS_SWITCH = 2 +SHINY_WILD_POKEMON_SWITCH = 31 +FATEFUL_ENCOUNTER_SWITCH = 32 +NO_MONEY_LOSS = 33 +NO_MEGA_EVOLUTION = 34 + +#=============================================================================== +# * The ID of the common event that runs when the player starts fishing (runs +# instead of showing the casting animation). +# * The ID of the common event that runs when the player stops fishing (runs +# instead of showing the reeling in animation). +#=============================================================================== +FISHING_BEGIN_COMMON_EVENT = -1 +FISHING_END_COMMON_EVENT = -1 + +#=============================================================================== +# * The ID of the animation played when the player steps on grass (shows grass +# rustling). +# * The ID of the animation played when the player lands on the ground after +# hopping over a ledge (shows a dust impact). +# * The ID of the animation played when a trainer notices the player (an +# exclamation bubble). +# * The ID of the animation played when a patch of grass rustles due to using +# the Poké Radar. +# * The ID of the animation played when a patch of grass rustles vigorously due +# to using the Poké Radar. (Rarer species) +# * The ID of the animation played when a patch of grass rustles and shines due +# to using the Poké Radar. (Shiny encounter) +# * The ID of the animation played when a berry tree grows a stage while the +# player is on the map (for new plant growth mechanics only). +#=============================================================================== +GRASS_ANIMATION_ID = 1 +DUST_ANIMATION_ID = 2 +EXCLAMATION_ANIMATION_ID = 3 +RUSTLE_NORMAL_ANIMATION_ID = 1 +RUSTLE_VIGOROUS_ANIMATION_ID = 5 +RUSTLE_SHINY_ANIMATION_ID = 6 +PLANT_SPARKLE_ANIMATION_ID = 7 + +#=============================================================================== +# * An array of available languages in the game, and their corresponding +# message file in the Data folder. Edit only if you have 2 or more +# languages to choose from. +#=============================================================================== +LANGUAGES = [ +# ["English","english.dat"], +# ["Deutsch","deutsch.dat"] +] \ No newline at end of file diff --git a/Data/Scripts/001_Technical/001_Ruby Utilities.rb b/Data/Scripts/001_Technical/001_Ruby Utilities.rb new file mode 100644 index 000000000..64142d1fc --- /dev/null +++ b/Data/Scripts/001_Technical/001_Ruby Utilities.rb @@ -0,0 +1,211 @@ +#=============================================================================== +# class Class +#=============================================================================== +class Class + def to_sym + return self.to_s.to_sym + end +end + + + +#=============================================================================== +# module Comparable +#=============================================================================== +unless Comparable.method_defined? :clamp + module Comparable + def clamp(min, max) + if max-min<0 + raise ArgumentError("min argument must be smaller than max argument") + end + return (self>max) ? max : (self= str.length + return proc ? proc : false + end + + def ends_with?(str) + e = self.length - 1 + proc = (self[(e-str.length)...e] == str) if self.length >= str.length + return proc ? proc : false + end + + def starts_with_vowel? + return ['a','e','i','o','u'].include?(self[0,1].downcase) + end + + def first(n=1) + return self[0...n] + end + + def last(n=1) + return self[-n..-1] || self + end + + def bytesize + return self.size + end + + def capitalize + proc = self.scan(/./) + proc[0] = proc[0].upcase + string = "" + for letter in proc + string += letter + end + return string + end + + def capitalize! + self.replace(self.capitalize) + end + + def blank? + blank = true + s = self.scan(/./) + for l in s + blank = false if l != "" + end + return blank + end + + def cut(bitmap,width) + string = self + width -= bitmap.text_size("...").width + string_width = 0 + text = [] + for char in string.scan(/./) + wdh = bitmap.text_size(char).width + next if (wdh+string_width) > width + string_width += wdh + text.push(char) + end + text.push("...") if text.length < string.length + new_string = "" + for char in text + new_string += char + end + return new_string + end +end + + + +#=============================================================================== +# class Numeric +#=============================================================================== +class Numeric + # Turns a number into a string formatted like 12,345,678. + def to_s_formatted + return self.to_s.reverse.gsub(/(\d{3})(?=\d)/, '\1,').reverse + end +end + + + +#=============================================================================== +# class Integer +#=============================================================================== +class Integer + # Returns an array containing each digit of the number in turn. + def digits(base=10) + quotient, remainder = divmod(base) + (quotient==0) ? [remainder] : quotient.digits(base).push(remainder) + end +end + + + +#=============================================================================== +# class Array +#=============================================================================== +class Array + def first + return self[0] + end + + def last + return self[self.length-1] + end + + def ^(other) # xor of two arrays + return (self|other)-(self&other) + end + + def shuffle + dup.shuffle! + end unless method_defined? :shuffle + + def shuffle! + (size-1).times do |i| + r = i+rand(size-i) + self[i], self[r] = self[r], self[i] + end + self + end unless method_defined? :shuffle! +end + + +#=============================================================================== +# module Enumerable +#=============================================================================== +module Enumerable + def transform + ret = [] + self.each { |item| ret.push(yield(item)) } + return ret + end +end + + + +#=============================================================================== +# Kernel methods +#=============================================================================== +def rand(*args) + Kernel.rand(*args) +end + +class << Kernel + alias oldRand rand unless method_defined?(:oldRand) + def rand(a = nil, b = nil) + if a.is_a?(Range) + lo = a.min + hi = a.max + return lo + oldRand(hi - lo + 1) + elsif a.is_a?(Numeric) + if b.is_a?(Numeric) + return a + oldRand(b - a + 1) + else + return oldRand(a) + end + elsif a.nil? + return (b) ? oldRand(b) : oldRand(2) + end + end +end + +def nil_or_empty?(string) + return string.nil? || !string.is_a?(String) || string.size == 0 +end \ No newline at end of file diff --git a/Data/Scripts/001_Technical/002_RGSS2Compatibility.rb b/Data/Scripts/001_Technical/002_RGSS2Compatibility.rb new file mode 100644 index 000000000..e1fdcc4c2 --- /dev/null +++ b/Data/Scripts/001_Technical/002_RGSS2Compatibility.rb @@ -0,0 +1,608 @@ +$TEST = true if $DEBUG +$DEBUG = true if $TEST +$scene = nil +Font.default_shadow = false if Font.respond_to?(:default_shadow) +Graphics.frame_rate = 40 + + + +=begin +class Win32API + class << self + unless defined?(debug_new) + alias debug_new new + end + + def new(*args) + File.open("winapi.txt","ab") { |f| f.write("new(#{args[0]},#{args[1]})\r\n") } + b=debug_new(*args) + b.setDllName(args[0],args[1]) + return b + end + end + + unless defined?(debug_call) + alias debug_call call + end + + def setDllName(a,b) + @w32dll=a + @w32name=b + end + + def call(*args) + if @w32name!="GetAsyncKeyState" + File.open("winapi.txt","ab") { |f| + f.write("call(#{@w32dll},#{@w32name},#{args.inspect})\r\n") + } + end + debug_call(*args) + end +end + +class Bitmap + class << self + unless defined?(debug_new) + alias debug_new new + end + + def new(*args) + if args.length==1 + File.open("winapib.txt","ab") { |f| f.write("new(#{args[0]})\r\n") } + end + debug_new(*args) + end + end +end + +alias debug_load_data load_data + +def load_data(*args) + File.open("winapif.txt","ab") { |f| f.write("load(#{args[0]})\r\n") } + debug_load_data(*args) +end +=end + + + +class Hangup < Exception; end + + + +if false + p (Tilemap.instance_methods-Kernel.instance_methods-Object.instance_methods).sort +# no changes + p (Plane.instance_methods-Kernel.instance_methods-Object.instance_methods).sort +# no changes + p (Viewport.instance_methods-Kernel.instance_methods-Object.instance_methods).sort + p (Bitmap.instance_methods-Kernel.instance_methods-Object.instance_methods).sort +# openness(=) + p (Window.instance_methods-Kernel.instance_methods-Object.instance_methods).sort + p (Sprite.instance_methods-Kernel.instance_methods-Object.instance_methods).sort +end + + + +module RPG + class Animation + attr_accessor :id + attr_accessor :name + attr_accessor :animation_name + attr_accessor :animation_hue + attr_accessor :position + attr_accessor :frame_max + attr_accessor :frames + attr_accessor :timings + + def initialize + @id = 0 + @name = "" + @animation_name = "" + @animation_hue = 0 + @position = 1 + @frame_max = 1 + @frames = [RPG::Animation::Frame.new] + @timings = [] + end + end +end + + + +module RPG + class Animation + class Frame + attr_accessor :cell_max + attr_accessor :cell_data + + def initialize + @cell_max = 0 + @cell_data = Table.new(0, 0) + end + end + end +end + + + +module RPG + class Animation + class Timing + attr_accessor :frame + attr_accessor :se + attr_accessor :flash_scope + attr_accessor :flash_color + attr_accessor :flash_duration + attr_accessor :condition + + def initialize + @frame = 0 + @se = RPG::AudioFile.new("", 80) + @flash_scope = 0 + @flash_color = Color.new(255,255,255,255) + @flash_duration = 5 + @condition = 0 + end + end + end +end + + + +module RPG + class System + attr_accessor :magic_number + attr_accessor :party_members + attr_accessor :elements + attr_accessor :switches + attr_accessor :variables + attr_accessor :windowskin_name + attr_accessor :title_name + attr_accessor :gameover_name + attr_accessor :battle_transition + attr_accessor :title_bgm + attr_accessor :battle_bgm + attr_accessor :battle_end_me + attr_accessor :gameover_me + attr_accessor :cursor_se + attr_accessor :decision_se + attr_accessor :cancel_se + attr_accessor :buzzer_se + attr_accessor :equip_se + attr_accessor :shop_se + attr_accessor :save_se + attr_accessor :load_se + attr_accessor :battle_start_se + attr_accessor :escape_se + attr_accessor :actor_collapse_se + attr_accessor :enemy_collapse_se + attr_accessor :words + attr_accessor :test_battlers + attr_accessor :test_troop_id + attr_accessor :start_map_id + attr_accessor :start_x + attr_accessor :start_y + attr_accessor :battleback_name + attr_accessor :battler_name + attr_accessor :battler_hue + attr_accessor :edit_map_id + + def initialize + @magic_number = 0 + @party_members = [1] + @elements = [nil, ""] + @switches = [nil, ""] + @variables = [nil, ""] + @windowskin_name = "" + @title_name = "" + @gameover_name = "" + @battle_transition = "" + @title_bgm = RPG::AudioFile.new + @battle_bgm = RPG::AudioFile.new + @battle_end_me = RPG::AudioFile.new + @gameover_me = RPG::AudioFile.new + @cursor_se = RPG::AudioFile.new("", 80) + @decision_se = RPG::AudioFile.new("", 80) + @cancel_se = RPG::AudioFile.new("", 80) + @buzzer_se = RPG::AudioFile.new("", 80) + @equip_se = RPG::AudioFile.new("", 80) + @shop_se = RPG::AudioFile.new("", 80) + @save_se = RPG::AudioFile.new("", 80) + @load_se = RPG::AudioFile.new("", 80) + @battle_start_se = RPG::AudioFile.new("", 80) + @escape_se = RPG::AudioFile.new("", 80) + @actor_collapse_se = RPG::AudioFile.new("", 80) + @enemy_collapse_se = RPG::AudioFile.new("", 80) + @words = RPG::System::Words.new + @test_battlers = [] + @test_troop_id = 1 + @start_map_id = 1 + @start_x = 0 + @start_y = 0 + @battleback_name = "" + @battler_name = "" + @battler_hue = 0 + @edit_map_id = 1 + end + end +end + + + +module RPG + class Tileset + attr_accessor :id + attr_accessor :name + attr_accessor :tileset_name + attr_accessor :autotile_names + attr_accessor :panorama_name + attr_accessor :panorama_hue + attr_accessor :fog_name + attr_accessor :fog_hue + attr_accessor :fog_opacity + attr_accessor :fog_blend_type + attr_accessor :fog_zoom + attr_accessor :fog_sx + attr_accessor :fog_sy + attr_accessor :battleback_name + attr_accessor :passages + attr_accessor :priorities + attr_accessor :terrain_tags + + def initialize + @id = 0 + @name = "" + @tileset_name = "" + @autotile_names = [""]*7 + @panorama_name = "" + @panorama_hue = 0 + @fog_name = "" + @fog_hue = 0 + @fog_opacity = 64 + @fog_blend_type = 0 + @fog_zoom = 200 + @fog_sx = 0 + @fog_sy = 0 + @battleback_name = "" + @passages = Table.new(384) + @priorities = Table.new(384) + @priorities[0] = 5 + @terrain_tags = Table.new(384) + end + end +end + + + +module RPG + class CommonEvent + attr_accessor :id + attr_accessor :name + attr_accessor :trigger + attr_accessor :switch_id + attr_accessor :list + + def initialize + @id = 0 + @name = "" + @trigger = 0 + @switch_id = 1 + @list = [RPG::EventCommand.new] + end + end +end + + + +module RPG + class Map + attr_accessor :tileset_id + attr_accessor :width + attr_accessor :height + attr_accessor :autoplay_bgm + attr_accessor :bgm + attr_accessor :autoplay_bgs + attr_accessor :bgs + attr_accessor :encounter_list + attr_accessor :encounter_step + attr_accessor :data + attr_accessor :events + + def initialize(width, height) + @tileset_id = 1 + @width = width + @height = height + @autoplay_bgm = false + @bgm = RPG::AudioFile.new + @autoplay_bgs = false + @bgs = RPG::AudioFile.new("", 80) + @encounter_list = [] + @encounter_step = 30 + @data = Table.new(width, height, 3) + @events = {} + end + end +end + + + +module RPG + class MapInfo + attr_accessor :name + attr_accessor :parent_id + attr_accessor :order + attr_accessor :expanded + attr_accessor :scroll_x + attr_accessor :scroll_y + + def initialize + @name = "" + @parent_id = 0 + @order = 0 + @expanded = false + @scroll_x = 0 + @scroll_y = 0 + end + end +end + + + +module RPG + class Event + attr_accessor :id + attr_accessor :name + attr_accessor :x + attr_accessor :y + attr_accessor :pages + + def initialize(x, y) + @id = 0 + @name = "" + @x = x + @y = y + @pages = [RPG::Event::Page.new] + end + end +end + + + +module RPG + class Event + class Page + attr_accessor :condition + attr_accessor :graphic + attr_accessor :move_type + attr_accessor :move_speed + attr_accessor :move_frequency + attr_accessor :move_route + attr_accessor :walk_anime + attr_accessor :step_anime + attr_accessor :direction_fix + attr_accessor :through + attr_accessor :always_on_top + attr_accessor :trigger + attr_accessor :list + + def initialize + @condition = RPG::Event::Page::Condition.new + @graphic = RPG::Event::Page::Graphic.new + @move_type = 0 + @move_speed = 3 + @move_frequency = 3 + @move_route = RPG::MoveRoute.new + @walk_anime = true + @step_anime = false + @direction_fix = false + @through = false + @always_on_top = false + @trigger = 0 + @list = [RPG::EventCommand.new] + end + end + end +end + + + +module RPG + class Event + class Page + class Condition + attr_accessor :switch1_valid + attr_accessor :switch2_valid + attr_accessor :variable_valid + attr_accessor :self_switch_valid + attr_accessor :switch1_id + attr_accessor :switch2_id + attr_accessor :variable_id + attr_accessor :variable_value + attr_accessor :self_switch_ch + + def initialize + @switch1_valid = false + @switch2_valid = false + @variable_valid = false + @self_switch_valid = false + @switch1_id = 1 + @switch2_id = 1 + @variable_id = 1 + @variable_value = 0 + @self_switch_ch = "A" + end + end + end + end +end + + + +module RPG + class Event + class Page + class Graphic + attr_accessor :tile_id + attr_accessor :character_name + attr_accessor :character_hue + attr_accessor :direction + attr_accessor :pattern + attr_accessor :opacity + attr_accessor :blend_type + + def initialize + @tile_id = 0 + @character_name = "" + @character_hue = 0 + @direction = 2 + @pattern = 0 + @opacity = 255 + @blend_type = 0 + end + end + end + end +end + + + +module RPG + class EventCommand + attr_accessor :code + attr_accessor :indent + attr_accessor :parameters + + def initialize(code = 0, indent = 0, parameters = []) + @code = code + @indent = indent + @parameters = parameters + end + end +end + + + +module RPG + class MoveRoute + attr_accessor :repeat + attr_accessor :skippable + attr_accessor :list + + def initialize + @repeat = true + @skippable = false + @list = [RPG::MoveCommand.new] + end + end +end + + + +module RPG + class MoveCommand + attr_accessor :code + attr_accessor :parameters + + def initialize(code = 0, parameters = []) + @code = code + @parameters = parameters + end + end +end + + + +module RPG + class System + class Words + attr_accessor :gold + attr_accessor :hp + attr_accessor :sp + attr_accessor :str + attr_accessor :dex + attr_accessor :agi + attr_accessor :int + attr_accessor :atk + attr_accessor :pdef + attr_accessor :mdef + attr_accessor :weapon + attr_accessor :armor1 + attr_accessor :armor2 + attr_accessor :armor3 + attr_accessor :armor4 + attr_accessor :attack + attr_accessor :skill + attr_accessor :guard + attr_accessor :item + attr_accessor :equip + + def initialize + @gold = "" + @hp = "" + @sp = "" + @str = "" + @dex = "" + @agi = "" + @int = "" + @atk = "" + @pdef = "" + @mdef = "" + @weapon = "" + @armor1 = "" + @armor2 = "" + @armor3 = "" + @armor4 = "" + @attack = "" + @skill = "" + @guard = "" + @item = "" + @equip = "" + end + end + end +end + + + +module RPG + class System + class TestBattler + attr_accessor :actor_id + attr_accessor :level + attr_accessor :weapon_id + attr_accessor :armor1_id + attr_accessor :armor2_id + attr_accessor :armor3_id + attr_accessor :armor4_id + + def initialize + @actor_id = 1 + @level = 1 + @weapon_id = 0 + @armor1_id = 0 + @armor2_id = 0 + @armor3_id = 0 + @armor4_id = 0 + end + end + end +end + + + +module RPG + class AudioFile + attr_accessor :name + attr_accessor :volume + attr_accessor :pitch + + def initialize(name = "", volume = 100, pitch = 100) + @name = name + @volume = volume + @pitch = pitch + end + +# def play +# end + end +end \ No newline at end of file diff --git a/Data/Scripts/001_Technical/003_RPG__Sprite.rb b/Data/Scripts/001_Technical/003_RPG__Sprite.rb new file mode 100644 index 000000000..f40b815dd --- /dev/null +++ b/Data/Scripts/001_Technical/003_RPG__Sprite.rb @@ -0,0 +1,534 @@ +class SpriteAnimation + @@_animations = [] + @@_reference_count = {} + + def initialize(sprite) + @sprite = sprite + end + + %w[ + x y ox oy viewport flash src_rect opacity tone + ].each_with_index do |s, i| + eval <<-__END__ + + def #{s}(*arg) + @sprite.#{s}(*arg) + end + + __END__ + end + + def self.clear + @@_animations.clear + end + + def dispose + dispose_animation + dispose_loop_animation + end + + def animation(animation, hit, height = 3) + dispose_animation + @_animation = animation + return if @_animation == nil + @_animation_hit = hit + @_animation_height = height + @_animation_duration = @_animation.frame_max + fr = 20 + if @_animation.name[/\[\s*(\d+?)\s*\]\s*$/] + fr = $~[1].to_i + end + @_animation_frame_skip = Graphics.frame_rate / fr + animation_name = @_animation.animation_name + animation_hue = @_animation.animation_hue + bitmap = pbGetAnimation(animation_name, animation_hue) + if @@_reference_count.include?(bitmap) + @@_reference_count[bitmap] += 1 + else + @@_reference_count[bitmap] = 1 + end + @_animation_sprites = [] + if @_animation.position != 3 or not @@_animations.include?(animation) + for i in 0..15 + sprite = ::Sprite.new(self.viewport) + sprite.bitmap = bitmap + sprite.visible = false + @_animation_sprites.push(sprite) + end + unless @@_animations.include?(animation) + @@_animations.push(animation) + end + end + update_animation + end + + def loop_animation(animation) + return if animation == @_loop_animation + dispose_loop_animation + @_loop_animation = animation + return if @_loop_animation == nil + @_loop_animation_index = 0 + fr = 20 + if @_animation.name[/\[\s*(\d+?)\s*\]\s*$/] + fr = $~[1].to_i + end + @_loop_animation_frame_skip = Graphics.frame_rate / fr + animation_name = @_loop_animation.animation_name + animation_hue = @_loop_animation.animation_hue + bitmap = pbGetAnimation(animation_name, animation_hue) + if @@_reference_count.include?(bitmap) + @@_reference_count[bitmap] += 1 + else + @@_reference_count[bitmap] = 1 + end + @_loop_animation_sprites = [] + for i in 0..15 + sprite = ::Sprite.new(self.viewport) + sprite.bitmap = bitmap + sprite.visible = false + @_loop_animation_sprites.push(sprite) + end + update_loop_animation + end + + def dispose_animation + return if @_animation_sprites == nil + sprite = @_animation_sprites[0] + if sprite != nil + @@_reference_count[sprite.bitmap] -= 1 + if @@_reference_count[sprite.bitmap] == 0 + sprite.bitmap.dispose + end + end + for sprite in @_animation_sprites + sprite.dispose + end + @_animation_sprites = nil + @_animation = nil + end + + def dispose_loop_animation + return if @_loop_animation_sprites == nil + sprite = @_loop_animation_sprites[0] + if sprite != nil + @@_reference_count[sprite.bitmap] -= 1 + if @@_reference_count[sprite.bitmap] == 0 + sprite.bitmap.dispose + end + end + for sprite in @_loop_animation_sprites + sprite.dispose + end + @_loop_animation_sprites = nil + @_loop_animation = nil + end + + def active? + return @_loop_animation_sprites != nil || @_animation_sprites != nil + end + + def effect? + return @_animation_duration > 0 + end + + def update + if @_animation != nil + quick_update = true + if Graphics.frame_count % @_animation_frame_skip == 0 + @_animation_duration -= 1 + quick_update = false + end + update_animation(quick_update) + end + if @_loop_animation != nil + quick_update = (Graphics.frame_count % @_loop_animation_frame_skip != 0) + update_loop_animation(quick_update) + if !quick_update + @_loop_animation_index += 1 + @_loop_animation_index %= @_loop_animation.frame_max + end + end + end + + def update_animation(quick_update = false) + if @_animation_duration <= 0 + dispose_animation + return + end + frame_index = @_animation.frame_max - @_animation_duration + cell_data = @_animation.frames[frame_index].cell_data + position = @_animation.position + animation_set_sprites(@_animation_sprites, cell_data, position, quick_update) + return if quick_update + for timing in @_animation.timings + next if timing.frame != frame_index + animation_process_timing(timing, @_animation_hit) + end + end + + def update_loop_animation(quick_update = false) + frame_index = @_loop_animation_index + cell_data = @_loop_animation.frames[frame_index].cell_data + position = @_loop_animation.position + animation_set_sprites(@_loop_animation_sprites, cell_data, position, quick_update) + return if quick_update + for timing in @_loop_animation.timings + next if timing.frame != frame_index + animation_process_timing(timing, true) + end + end + + def animation_set_sprites(sprites, cell_data, position, quick_update = false) + sprite_x = 320 + sprite_y = 240 + if position == 3 + if self.viewport != nil + sprite_x = self.viewport.rect.width / 2 + sprite_y = self.viewport.rect.height - 160 + end + else + sprite_x = self.x - self.ox + self.src_rect.width / 2 + sprite_y = self.y - self.oy + self.src_rect.height / 2 + sprite_y -= self.src_rect.height / 4 if position == 0 + sprite_y += self.src_rect.height / 4 if position == 2 + end + for i in 0..15 + sprite = sprites[i] + pattern = cell_data[i, 0] + if sprite == nil or pattern == nil or pattern == -1 + sprite.visible = false if sprite != nil + next + end + sprite.x = sprite_x + cell_data[i, 1] + sprite.y = sprite_y + cell_data[i, 2] + next if quick_update + sprite.visible = true + sprite.src_rect.set(pattern % 5 * 192, pattern / 5 * 192, 192, 192) + case @_animation_height + when 0; sprite.z = 1 + when 1; sprite.z = sprite.y+32+15 + when 2; sprite.z = sprite.y+32+32+17 + else; sprite.z = 2000 + end + sprite.ox = 96 + sprite.oy = 96 + sprite.zoom_x = cell_data[i, 3] / 100.0 + sprite.zoom_y = cell_data[i, 3] / 100.0 + sprite.angle = cell_data[i, 4] + sprite.mirror = (cell_data[i, 5] == 1) + sprite.tone = self.tone + sprite.opacity = cell_data[i, 6] * self.opacity / 255.0 + sprite.blend_type = cell_data[i, 7] + end + end + + def animation_process_timing(timing, hit) + if (timing.condition == 0) or + (timing.condition == 1 and hit == true) or + (timing.condition == 2 and hit == false) + if timing.se.name != "" + se = timing.se + pbSEPlay(se) + end + case timing.flash_scope + when 1 + self.flash(timing.flash_color, timing.flash_duration * 2) + when 2 + if self.viewport != nil + self.viewport.flash(timing.flash_color, timing.flash_duration * 2) + end + when 3 + self.flash(nil, timing.flash_duration * 2) + end + end + end + + def x=(x) + sx = x - self.x + return if sx == 0 + if @_animation_sprites != nil + for i in 0..15 + @_animation_sprites[i].x += sx + end + end + if @_loop_animation_sprites != nil + for i in 0..15 + @_loop_animation_sprites[i].x += sx + end + end + end + + def y=(y) + sy = y - self.y + return if sy == 0 + if @_animation_sprites != nil + for i in 0..15 + @_animation_sprites[i].y += sy + end + end + if @_loop_animation_sprites != nil + for i in 0..15 + @_loop_animation_sprites[i].y += sy + end + end + end +end + + + +module RPG + class Sprite < ::Sprite + def initialize(viewport = nil) + super(viewport) + @_whiten_duration = 0 + @_appear_duration = 0 + @_escape_duration = 0 + @_collapse_duration = 0 + @_damage_duration = 0 + @_animation_duration = 0 + @_blink = false + @animations = [] + @loopAnimations = [] + end + + def dispose + dispose_damage + dispose_animation + dispose_loop_animation + super + end + + def whiten + self.blend_type = 0 + self.color.set(255, 255, 255, 128) + self.opacity = 255 + @_whiten_duration = 16 + @_appear_duration = 0 + @_escape_duration = 0 + @_collapse_duration = 0 + end + + def appear + self.blend_type = 0 + self.color.set(0, 0, 0, 0) + self.opacity = 0 + @_appear_duration = 16 + @_whiten_duration = 0 + @_escape_duration = 0 + @_collapse_duration = 0 + end + + def escape + self.blend_type = 0 + self.color.set(0, 0, 0, 0) + self.opacity = 255 + @_escape_duration = 32 + @_whiten_duration = 0 + @_appear_duration = 0 + @_collapse_duration = 0 + end + + def collapse + self.blend_type = 1 + self.color.set(255, 64, 64, 255) + self.opacity = 255 + @_collapse_duration = 48 + @_whiten_duration = 0 + @_appear_duration = 0 + @_escape_duration = 0 + end + + def damage(value, critical) + dispose_damage + damage_string = (value.is_a?(Numeric)) ? value.abs.to_s : value.to_s + bitmap = Bitmap.new(160, 48) + bitmap.font.name = "Arial Black" + bitmap.font.size = 32 + bitmap.font.color.set(0, 0, 0) + bitmap.draw_text(-1, 12-1, 160, 36, damage_string, 1) + bitmap.draw_text(+1, 12-1, 160, 36, damage_string, 1) + bitmap.draw_text(-1, 12+1, 160, 36, damage_string, 1) + bitmap.draw_text(+1, 12+1, 160, 36, damage_string, 1) + if value.is_a?(Numeric) and value < 0 + bitmap.font.color.set(176, 255, 144) + else + bitmap.font.color.set(255, 255, 255) + end + bitmap.draw_text(0, 12, 160, 36, damage_string, 1) + if critical + bitmap.font.size = 20 + bitmap.font.color.set(0, 0, 0) + bitmap.draw_text(-1, -1, 160, 20, "CRITICAL", 1) + bitmap.draw_text(+1, -1, 160, 20, "CRITICAL", 1) + bitmap.draw_text(-1, +1, 160, 20, "CRITICAL", 1) + bitmap.draw_text(+1, +1, 160, 20, "CRITICAL", 1) + bitmap.font.color.set(255, 255, 255) + bitmap.draw_text(0, 0, 160, 20, "CRITICAL", 1) + end + @_damage_sprite = ::Sprite.new(self.viewport) + @_damage_sprite.bitmap = bitmap + @_damage_sprite.ox = 80 + @_damage_sprite.oy = 20 + @_damage_sprite.x = self.x + @_damage_sprite.y = self.y - self.oy / 2 + @_damage_sprite.z = 3000 + @_damage_duration = 40 + end + + def pushAnimation(array, anim) + for i in 0...array.length + next if array[i] && array[i].active? + array[i] = anim + return + end + array.push(anim) + end + + def animation(animation, hit, height = 3) + anim = SpriteAnimation.new(self) + anim.animation(animation,hit,height) + pushAnimation(@animations,anim) + end + + def loop_animation(animation) + anim = SpriteAnimation.new(self) + anim.loop_animation(animation) + pushAnimation(@loopAnimations,anim) + end + + def dispose_damage + return if @_damage_sprite == nil + @_damage_sprite.bitmap.dispose + @_damage_sprite.dispose + @_damage_sprite = nil + @_damage_duration = 0 + end + + def dispose_animation + for a in @animations + a.dispose_animation if a + end + @animations.clear + end + + def dispose_loop_animation + for a in @loopAnimations + a.dispose_loop_animation if a + end + @loopAnimations.clear + end + + def blink_on + return if @_blink + @_blink = true + @_blink_count = 0 + end + + def blink_off + return unless @_blink + @_blink = false + self.color.set(0, 0, 0, 0) + end + + def blink? + return @_blink + end + + def effect? + return true if @_whiten_duration > 0 + return true if @_appear_duration > 0 + return true if @_escape_duration > 0 + return true if @_collapse_duration > 0 + return true if @_damage_duration > 0 + for a in @animations + return true if a.effect? + end + return false + end + + def update + super + if @_whiten_duration > 0 + @_whiten_duration -= 1 + self.color.alpha = 128 - (16 - @_whiten_duration) * 10 + end + if @_appear_duration > 0 + @_appear_duration -= 1 + self.opacity = (16 - @_appear_duration) * 16 + end + if @_escape_duration > 0 + @_escape_duration -= 1 + self.opacity = 256 - (32 - @_escape_duration) * 10 + end + if @_collapse_duration > 0 + @_collapse_duration -= 1 + self.opacity = 256 - (48 - @_collapse_duration) * 6 + end + if @_damage_duration > 0 + @_damage_duration -= 1 + case @_damage_duration + when 38..39 + @_damage_sprite.y -= 4 + when 36..37 + @_damage_sprite.y -= 2 + when 34..35 + @_damage_sprite.y += 2 + when 28..33 + @_damage_sprite.y += 4 + end + @_damage_sprite.opacity = 256 - (12 - @_damage_duration) * 32 + if @_damage_duration == 0 + dispose_damage + end + end + for a in @animations + a.update + end + for a in @loopAnimations + a.update + end + if @_blink + @_blink_count = (@_blink_count + 1) % 32 + if @_blink_count < 16 + alpha = (16 - @_blink_count) * 6 + else + alpha = (@_blink_count - 16) * 6 + end + self.color.set(255, 255, 255, alpha) + end + SpriteAnimation.clear + end + + def update_animation + for a in @animations + a.update_animation if a && a.active? + end + end + + def update_loop_animation + for a in @loopAnimations + a.update_loop_animation if a && a.active? + end + end + + def x=(x) + for a in @animations + a.x = x if a + end + for a in @loopAnimations + a.x = x if a + end + super + end + + def y=(y) + for a in @animations + a.y = y if a + end + for a in @loopAnimations + a.y = y if a + end + super + end + end +end diff --git a/Data/Scripts/001_Technical/004_Win32API.rb b/Data/Scripts/001_Technical/004_Win32API.rb new file mode 100644 index 000000000..495aa517f --- /dev/null +++ b/Data/Scripts/001_Technical/004_Win32API.rb @@ -0,0 +1,104 @@ +class Win32API + @@RGSSWINDOW = nil + @@GetCurrentThreadId = Win32API.new('kernel32','GetCurrentThreadId', '%w()','l') + @@GetWindowThreadProcessId = Win32API.new('user32','GetWindowThreadProcessId', '%w(l p)','l') + @@FindWindowEx = Win32API.new('user32','FindWindowEx', '%w(l l p p)','l') + + def Win32API.SetWindowText(text) + hWnd = pbFindRgssWindow + swp = Win32API.new('user32','SetWindowTextA',%(l, p),'i') + swp.call(hWnd, text.to_s) + end + + # Added by Peter O. as a more reliable way to get the RGSS window + def Win32API.pbFindRgssWindow + return @@RGSSWINDOW if @@RGSSWINDOW + processid = [0].pack('l') + threadid = @@GetCurrentThreadId.call + nextwindow = 0 + begin + nextwindow = @@FindWindowEx.call(0,nextwindow,"RGSS Player",0) + if nextwindow!=0 + wndthreadid = @@GetWindowThreadProcessId.call(nextwindow,processid) + if wndthreadid==threadid + @@RGSSWINDOW = nextwindow + return @@RGSSWINDOW + end + end + end until nextwindow==0 + raise "Can't find RGSS player window" + return 0 + end + + def Win32API.SetWindowPos(w, h) + hWnd = pbFindRgssWindow + windowrect = Win32API.GetWindowRect + clientsize = Win32API.client_size + xExtra = windowrect.width-clientsize[0] + yExtra = windowrect.height-clientsize[1] + swp = Win32API.new('user32','SetWindowPos',%(l,l,i,i,i,i,i),'i') + win = swp.call(hWnd,0,windowrect.x,windowrect.y,w+xExtra,h+yExtra,0) + return win + end + + def Win32API.client_size + hWnd = pbFindRgssWindow + rect = [0,0,0,0].pack('l4') + Win32API.new('user32','GetClientRect',%w(l p),'i').call(hWnd,rect) + width,height = rect.unpack('l4')[2..3] + return width,height + end + + def Win32API.GetWindowRect + hWnd = pbFindRgssWindow + rect = [0,0,0,0].pack('l4') + Win32API.new('user32','GetWindowRect',%w(l p),'i').call(hWnd,rect) + x,y,width,height = rect.unpack('l4') + return Rect.new(x,y,width-x,height-y) + end + + def Win32API.focusWindow + window = Win32API.new('user32','ShowWindow','LL','L') + hWnd = pbFindRgssWindow + window.call(hWnd,9) + end + + def Win32API.fillScreen + setWindowLong = Win32API.new('user32','SetWindowLong','LLL','L') + setWindowPos = Win32API.new('user32','SetWindowPos','LLIIIII','I') + metrics = Win32API.new('user32', 'GetSystemMetrics', 'I', 'I') + hWnd = pbFindRgssWindow + width = metrics.call(0) + height = metrics.call(1) + setWindowLong.call(hWnd,-16,0x00000000) + setWindowPos.call(hWnd,0,0,0,width,height,0) + Win32API.focusWindow + return [width,height] + end + + def Win32API.restoreScreen + setWindowLong = Win32API.new('user32','SetWindowLong','LLL','L') + setWindowPos = Win32API.new('user32','SetWindowPos','LLIIIII','I') + metrics = Win32API.new('user32','GetSystemMetrics','I','I') + hWnd = pbFindRgssWindow + width = SCREEN_WIDTH*$ResizeFactor + height = SCREEN_HEIGHT*$ResizeFactor + if $PokemonSystem && $PokemonSystem.border==1 + width += BORDER_WIDTH*2*$ResizeFactor + height += BORDER_HEIGHT*2*$ResizeFactor + end + x = [(metrics.call(0)-width)/2,0].max + y = [(metrics.call(1)-height)/2,0].max + setWindowLong.call(hWnd,-16,0x14CA0000) + setWindowPos.call(hWnd,0,x,y,width+6,height+29,0) + Win32API.focusWindow + return [width,height] + end +end + + + +# Well done for finding this place. +# DO NOT EDIT THESE +ESSENTIALS_VERSION = "18" +ERROR_TEXT = "" \ No newline at end of file diff --git a/Data/Scripts/001_Technical/005_Sockets.rb b/Data/Scripts/001_Technical/005_Sockets.rb new file mode 100644 index 000000000..57ad18ce6 --- /dev/null +++ b/Data/Scripts/001_Technical/005_Sockets.rb @@ -0,0 +1,696 @@ +module Win32 + def copymem(len) + buf = "\0" * len + Win32API.new("kernel32", "RtlMoveMemory", "ppl", "").call(buf, self, len) + buf + end +end + + + +# Extends the numeric class. +class Numeric + include Win32 +end + + + +# Extends the string class. +class String + include Win32 +end + + + +module Winsock + DLL = "ws2_32" + #----------------------------------------------------------------------------- + # * Accept Connection + #----------------------------------------------------------------------------- + def self.accept(*args) + Win32API.new(DLL, "accept", "ppl", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Bind + #----------------------------------------------------------------------------- + def self.bind(*args) + Win32API.new(DLL, "bind", "ppl", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Close Socket + #----------------------------------------------------------------------------- + def self.closesocket(*args) + Win32API.new(DLL, "closesocket", "p", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Connect + #----------------------------------------------------------------------------- + def self.connect(*args) + Win32API.new(DLL, "connect", "ppl", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Get host (Using Adress) + #----------------------------------------------------------------------------- + def self.gethostbyaddr(*args) + Win32API.new(DLL, "gethostbyaddr", "pll", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Get host (Using Name) + #----------------------------------------------------------------------------- + def self.gethostbyname(*args) + Win32API.new(DLL, "gethostbyname", "p", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Get host's Name + #----------------------------------------------------------------------------- + def self.gethostname(*args) + Win32API.new(DLL, "gethostname", "pl", "").call(*args) + end + #----------------------------------------------------------------------------- + # * Get Server (Using Name) + #----------------------------------------------------------------------------- + def self.getservbyname(*args) + Win32API.new(DLL, "getservbyname", "pp", "p").call(*args) + end + #----------------------------------------------------------------------------- + # * Convert Host Long To Network Long + #----------------------------------------------------------------------------- + def self.htonl(*args) + Win32API.new(DLL, "htonl", "l", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Convert Host Short To Network Short + #----------------------------------------------------------------------------- + def self.htons(*args) + Win32API.new(DLL, "htons", "l", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Inet Adress + #----------------------------------------------------------------------------- + def self.inet_addr(*args) + Win32API.new(DLL, "inet_addr", "p", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Inet N To A + #----------------------------------------------------------------------------- + def self.inet_ntoa(*args) + Win32API.new(DLL, "inet_ntoa", "l", "p").call(*args) + end + #----------------------------------------------------------------------------- + # * Listen + #----------------------------------------------------------------------------- + def self.listen(*args) + Win32API.new(DLL, "listen", "pl", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Recieve + #----------------------------------------------------------------------------- + def self.recv(*args) + Win32API.new(DLL, "recv", "ppll", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Select + #----------------------------------------------------------------------------- + def self.select(*args) + Win32API.new(DLL, "select", "lpppp", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Send + #----------------------------------------------------------------------------- + def self.send(*args) + Win32API.new(DLL, "send", "ppll", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Set Socket Options + #----------------------------------------------------------------------------- + def self.setsockopt(*args) + Win32API.new(DLL, "setsockopt", "pllpl", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Shutdown + #----------------------------------------------------------------------------- + def self.shutdown(*args) + Win32API.new(DLL, "shutdown", "pl", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Socket + #----------------------------------------------------------------------------- + def self.socket(*args) + Win32API.new(DLL, "socket", "lll", "l").call(*args) + end + #----------------------------------------------------------------------------- + # * Get Last Error + #----------------------------------------------------------------------------- + def self.WSAGetLastError(*args) + Win32API.new(DLL, "WSAGetLastError", "", "l").call(*args) + end +end + + + +if !Object.const_defined?(:Socket) # for compatibility + + + +#=============================================================================== +# ** Socket - Creates and manages sockets. +#------------------------------------------------------------------------------- +# Author Ruby +# Version 1.8.1 +#=============================================================================== +class Socket + #----------------------------------------------------------------------------- + # * Constants + #----------------------------------------------------------------------------- + AF_UNSPEC = 0 + AF_UNIX = 1 + AF_INET = 2 + AF_IPX = 6 + AF_APPLETALK = 16 + PF_UNSPEC = 0 + PF_UNIX = 1 + PF_INET = 2 + PF_IPX = 6 + PF_APPLETALK = 16 + SOCK_STREAM = 1 + SOCK_DGRAM = 2 + SOCK_RAW = 3 + SOCK_RDM = 4 + SOCK_SEQPACKET = 5 + IPPROTO_IP = 0 + IPPROTO_ICMP = 1 + IPPROTO_IGMP = 2 + IPPROTO_GGP = 3 + IPPROTO_TCP = 6 + IPPROTO_PUP = 12 + IPPROTO_UDP = 17 + IPPROTO_IDP = 22 + IPPROTO_ND = 77 + IPPROTO_RAW = 255 + IPPROTO_MAX = 256 + SOL_SOCKET = 65535 + SO_DEBUG = 1 + SO_REUSEADDR = 4 + SO_KEEPALIVE = 8 + SO_DONTROUTE = 16 + SO_BROADCAST = 32 + SO_LINGER = 128 + SO_OOBINLINE = 256 + SO_RCVLOWAT = 4100 + SO_SNDTIMEO = 4101 + SO_RCVTIMEO = 4102 + SO_ERROR = 4103 + SO_TYPE = 4104 + SO_SNDBUF = 4097 + SO_RCVBUF = 4098 + SO_SNDLOWAT = 4099 + TCP_NODELAY = 1 + MSG_OOB = 1 + MSG_PEEK = 2 + MSG_DONTROUTE = 4 + IP_OPTIONS = 1 + IP_DEFAULT_MULTICAST_LOOP = 1 + IP_DEFAULT_MULTICAST_TTL = 1 + IP_MULTICAST_IF = 2 + IP_MULTICAST_TTL = 3 + IP_MULTICAST_LOOP = 4 + IP_ADD_MEMBERSHIP = 5 + IP_DROP_MEMBERSHIP = 6 + IP_TTL = 7 + IP_TOS = 8 + IP_MAX_MEMBERSHIPS = 20 + EAI_ADDRFAMILY = 1 + EAI_AGAIN = 2 + EAI_BADFLAGS = 3 + EAI_FAIL = 4 + EAI_FAMILY = 5 + EAI_MEMORY = 6 + EAI_NODATA = 7 + EAI_NONAME = 8 + EAI_SERVICE = 9 + EAI_SOCKTYPE = 10 + EAI_SYSTEM = 11 + EAI_BADHINTS = 12 + EAI_PROTOCOL = 13 + EAI_MAX = 14 + AI_PASSIVE = 1 + AI_CANONNAME = 2 + AI_NUMERICHOST = 4 + AI_MASK = 7 + AI_ALL = 256 + AI_V4MAPPED_CFG = 512 + AI_ADDRCONFIG = 1024 + AI_DEFAULT = 1536 + AI_V4MAPPED = 2048 + #-------------------------------------------------------------------------- + # * Returns the associated IP address for the given hostname. + #-------------------------------------------------------------------------- + def self.getaddress(host) + gethostbyname(host)[3].unpack("C4").join(".") + end + #-------------------------------------------------------------------------- + # * Returns the associated IP address for the given hostname. + #-------------------------------------------------------------------------- + def self.getservice(serv) + case serv + when Numeric + return serv + when String + return getservbyname(serv) + else + raise "Please use an integer or string for services." + end + end + #-------------------------------------------------------------------------- + # * Returns information about the given hostname. + #-------------------------------------------------------------------------- + def self.gethostbyname(name) + raise SocketError::ENOASSOCHOST if (ptr = Winsock.gethostbyname(name)) == 0 + host = ptr.copymem(16).unpack("iissi") + [host[0].copymem(64).split("\0")[0], [], host[2], host[4].copymem(4).unpack("l")[0].copymem(4)] + end + #-------------------------------------------------------------------------- + # * Returns the user's hostname. + #-------------------------------------------------------------------------- + def self.gethostname + buf = "\0" * 256 + Winsock.gethostname(buf, 256) + buf.strip + end + #-------------------------------------------------------------------------- + # * Returns information about the given service. + #-------------------------------------------------------------------------- + def self.getservbyname(name) + case name + when /echo/i + return 7 + when /daytime/i + return 13 + when /ftp/i + return 21 + when /telnet/i + return 23 + when /smtp/i + return 25 + when /time/i + return 37 + when /http/i + return 80 + when /pop/i + return 110 + else + #Network.testing? != 0 ? (Network.testresult(true)) : (raise "Service not recognized.") + #return if Network.testing? == 2 + end + end + #-------------------------------------------------------------------------- + # * Creates an INET-sockaddr struct. + #-------------------------------------------------------------------------- + def self.sockaddr_in(port, host) + begin + [AF_INET, getservice(port)].pack("sn") + gethostbyname(host)[3] + [].pack("x8") + rescue + #Network.testing? != 0 ? (Network.testresult(true)): (nil) + #return if Network.testing? == 2 + rescue Hangup + #Network.testing? != 0 ? (Network.testresult(true)): (nil) + #return if Network.testing? == 2 + end + end + #-------------------------------------------------------------------------- + # * Creates a new socket and connects it to the given host and port. + #-------------------------------------------------------------------------- + def self.open(*args) + socket = new(*args) + if block_given? + begin + yield socket + ensure + socket.close + end + end + nil + end + #-------------------------------------------------------------------------- + # * Creates a new socket. + #-------------------------------------------------------------------------- + def initialize(domain, type, protocol) + SocketError.check if (@fd = Winsock.socket(domain, type, protocol)) == -1 + @fd + end + #-------------------------------------------------------------------------- + # * Accepts incoming connections. + #-------------------------------------------------------------------------- + def accept(flags = 0) + buf = "\0" * 16 + SocketError.check if Winsock.accept(@fd, buf, flags) == -1 + buf + end + #-------------------------------------------------------------------------- + # * Binds a socket to the given sockaddr. + #-------------------------------------------------------------------------- + def bind(sockaddr) + SocketError.check if (ret = Winsock.bind(@fd, sockaddr, sockaddr.size)) == -1 + ret + end + #-------------------------------------------------------------------------- + # * Closes a socket. + #-------------------------------------------------------------------------- + def close + SocketError.check if (ret = Winsock.closesocket(@fd)) == -1 + ret + end + #-------------------------------------------------------------------------- + # * Connects a socket to the given sockaddr. + #-------------------------------------------------------------------------- + def connect(sockaddr) + #return if Network.testing? == 2 + SocketError.check if (ret = Winsock.connect(@fd, sockaddr, sockaddr.size)) == -1 + ret + end + #-------------------------------------------------------------------------- + # * Listens for incoming connections. + #-------------------------------------------------------------------------- + def listen(backlog) + SocketError.check if (ret = Winsock.listen(@fd, backlog)) == -1 + ret + end + #-------------------------------------------------------------------------- + # * Checks waiting data's status. + #-------------------------------------------------------------------------- + def select(timeout) # timeout in seconds + SocketError.check if (ret = Winsock.select(1, [1, @fd].pack("ll"), 0, 0, [timeout.to_i, + (timeout * 1000000).to_i].pack("ll"))) == -1 + ret + end + #-------------------------------------------------------------------------- + # * Checks if data is waiting. + #-------------------------------------------------------------------------- + def ready? + not select(0) == 0 + end + #-------------------------------------------------------------------------- + # * Reads data from socket. + #-------------------------------------------------------------------------- + def read(len) + buf = "\0" * len + Win32API.new("msvcrt", "_read", "lpl", "l").call(@fd, buf, len) + buf + end + #-------------------------------------------------------------------------- + # * Returns received data. + #-------------------------------------------------------------------------- + def recv(len, flags = 0) + retString="" + remainLen=len + while remainLen > 0 + buf = "\0" * remainLen + retval=Winsock.recv(@fd, buf, buf.size, flags) + SocketError.check if retval == -1 + # Note: Return value may not equal requested length + remainLen-=retval + retString+=buf[0,retval] + end + return retString + end + #-------------------------------------------------------------------------- + # * Sends data to a host. + #-------------------------------------------------------------------------- + def send(data, flags = 0) + SocketError.check if (ret = Winsock.send(@fd, data, data.size, flags)) == -1 + ret + end + #-------------------------------------------------------------------------- + # * Recieves file from a socket + # size : file size + # scene : update scene boolean + #-------------------------------------------------------------------------- + def recv_file(size,scene=false,file="") + data = [] + size.times do |i| + if scene == true + $scene.recv_update(size,i,file) if i%((size/1000)+1)== 0 + else + Graphics.update if i%1024 == 0 + end + data << recv(1) + end + return data + end + + def recvTimeout + if select(10)==0 + raise Hangup.new("Timeout") + end + return recv(1) + end + #-------------------------------------------------------------------------- + # * Gets + #-------------------------------------------------------------------------- + def gets + # Create buffer + message = "" + # Loop Until "end of line" + count=0 + while true + x=select(0.05) + if x==0 + count+=1 + Graphics.update if count%10==0 + raise Errno::ETIMEOUT if count>200 + next + end + ch = recv(1) + break if ch == "\n" + message += ch + end + # Return recieved data + return message + end + #-------------------------------------------------------------------------- + # * Writes data to socket. + #-------------------------------------------------------------------------- + def write(data) + Win32API.new("msvcrt", "_write", "lpl", "l").call(@fd, data, 1) + end +end + + + +#=============================================================================== +# ** TCPSocket - Creates and manages TCP sockets. +#------------------------------------------------------------------------------- +# Author Ruby +# Version 1.8.1 +#=============================================================================== + +#------------------------------------------------------------------------------- +# Begin SDK Enabled Check +#------------------------------------------------------------------------------- +class TCPSocket < Socket + #-------------------------------------------------------------------------- + # * Creates a new socket and connects it to the given host and port. + #-------------------------------------------------------------------------- + def self.open(*args) + socket = new(*args) + if block_given? + begin + yield socket + ensure + socket.close + end + end + nil + end + #-------------------------------------------------------------------------- + # * Creates a new socket and connects it to the given host and port. + #-------------------------------------------------------------------------- + def initialize(host, port) + super(AF_INET, SOCK_STREAM, IPPROTO_TCP) + connect(Socket.sockaddr_in(port, host)) + end +end + + + +#============================================================================== +# ** SocketError +#------------------------------------------------------------------------------ +# Default exception class for sockets. +#============================================================================== +class SocketError < StandardError + ENOASSOCHOST = "getaddrinfo: no address associated with hostname." + + def self.check + errno = Winsock.WSAGetLastError + #if not Network.testing? == 1 + raise Errno.const_get(Errno.constants.detect { |c| Errno.const_get(c).new.errno == errno }) + #else + # errno != 0 ? (Network.testresult(true)) : (Network.testresult(false)) + #end + end +end + + + +end # !Object.const_defined?(:Socket) + + +############################# +# +# HTTP utility functions +# +############################# +def pbPostData(url, postdata, filename=nil, depth=0) + if url[/^http:\/\/([^\/]+)(.*)$/] + host = $1 + path = $2 + path = "/" if path.length==0 + userAgent = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.14) Gecko/2009082707 Firefox/3.0.14" + body = postdata.map { |key, value| + keyString = key.to_s + valueString = value.to_s + keyString.gsub!(/[^a-zA-Z0-9_\.\-]/n) { |s| sprintf('%%%02x', s[0]) } + valueString.gsub!(/[^a-zA-Z0-9_\.\-]/n) { |s| sprintf('%%%02x', s[0]) } + next "#{keyString}=#{valueString}" + }.join('&') + request = "POST #{path} HTTP/1.1\r\n" + request += "Host: #{host}\r\n" + request += "Proxy-Connection: Close\r\n" + request += "Content-Length: #{body.length}\r\n" + request += "Pragma: no-cache\r\n" + request += "User-Agent: #{userAgent}\r\n" + request += "Content-Type: application/x-www-form-urlencoded\r\n" + request += "\r\n" + request += body + return pbHttpRequest(host, request, filename, depth) + end + return "" +end + +def pbDownloadData(url, filename=nil, depth=0) + raise "Redirection level too deep" if depth>10 + if url[/^http:\/\/([^\/]+)(.*)$/] + host = $1 + path = $2 + path = "/" if path.length==0 + userAgent = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.14) Gecko/2009082707 Firefox/3.0.14" + request = "GET #{path} HTTP/1.1\r\n" + request += "User-Agent: #{userAgent}\r\n" + request += "Pragma: no-cache\r\n" + request += "Host: #{host}\r\n" + request += "Proxy-Connection: Close\r\n" + request += "\r\n" + return pbHttpRequest(host, request, filename, depth) + end + return "" +end + +def pbHttpRequest(host, request, filename=nil, depth=0) + raise "Redirection level too deep" if depth>10 + socket = ::TCPSocket.new(host, 80) + time = Time.now.to_i + begin + socket.send(request) + result = socket.gets + data = "" + # Get the HTTP result + if result[/^HTTP\/1\.[01] (\d+).*/] + errorcode = $1.to_i + raise "HTTP Error #{errorcode}" if errorcode>=400 && errorcode<500 + headers = {} + # Get the response headers + while true + result = socket.gets.sub(/\r$/,"") + break if result=="" + if result[/^([^:]+):\s*(.*)/] + headers[$1] = $2 + end + end + length = -1 + chunked = false + if headers["Content-Length"] + length = headers["Content-Length"].to_i + end + if headers["Transfer-Encoding"]=="chunked" + chunked = true + end + if headers["Location"] && errorcode>=300 && errorcode<400 + socket.close rescue socket = nil + return pbDownloadData(headers["Location"],filename,depth+1) + end + if chunked + # Chunked content + while true + lengthline = socket.gets.sub(/\r$/,"") + length = lengthline.to_i(16) + break if length==0 + while Time.now.to_i-time>=5 || socket.select(10)==0 + time = Time.now.to_i + Graphics.update + end + data += socket.recv(length) + socket.gets + end + elsif length==-1 + # No content length specified + while true + break if socket.select(500)==0 + while Time.now.to_i-time>=5 || socket.select(10)==0 + time = Time.now.to_i + Graphics.update + end + data += socket.recv(1) + end + else + # Content length specified + while length>0 + chunk = [length,4096].min + while Time.now.to_i-time>=5 || socket.select(10)==0 + time = Time.now.to_i + Graphics.update + end + data += socket.recv(chunk) + length -= chunk + end + end + end + return data if !filename + File.open(filename,"wb") { |f| f.write(data) } + ensure + socket.close rescue socket = nil + end + return "" +end + +def pbDownloadToString(url) + begin + data = pbDownloadData(url) + return data + rescue + return "" + end +end + +def pbDownloadToFile(url, file) + begin + pbDownloadData(url,file) + rescue + end +end + +def pbPostToString(url, postdata) + begin + data = pbPostData(url, postdata) + return data + rescue + return "" + end +end + +def pbPostToFile(url, postdata, file) + begin + pbPostData(url, postdata,file) + rescue + end +end \ No newline at end of file diff --git a/Data/Scripts/001_Technical/006_DebugConsole.rb b/Data/Scripts/001_Technical/006_DebugConsole.rb new file mode 100644 index 000000000..1672d7f25 --- /dev/null +++ b/Data/Scripts/001_Technical/006_DebugConsole.rb @@ -0,0 +1,158 @@ +module Console + attr_reader :bufferHandle + GENERIC_READ = 0x80000000 + GENERIC_WRITE = 0x40000000 + FILE_SHARE_READ = 0x00000001 + FILE_SHARE_WRITE = 0x00000002 + CONSOLE_TEXTMODE_BUFFER = 0x00000001 + + def Console::AllocConsole + return @apiAllocConsole.call + end + + def Console::CreateConsoleScreenBuffer(dwDesiredAccess,dwShareMode,dwFlags) + return @apiCreateConsoleScreenBuffer.call(dwDesiredAccess,dwShareMode,nil,dwFlags,nil) + end + + def Console::WriteConsole(lpBuffer) + hFile = @bufferHandle + return if !hFile + return @apiWriteConsole.call(hFile,lpBuffer,lpBuffer.size,0,0) + end + + def Console::ReadConsole(lpBuffer) + hFile = @bufferHandle + return @apiReadConsole.call(hFile,lpBuffer,lpBuffer.size,0,0) + end + + def Console::SetConsoleActiveScreenBuffer(hScreenBuffer) + return @apiSetConsoleActiveScreenBuffer.call(hScreenBuffer) + end + + def Console::SetConsoleScreenBufferSize(hScreenBuffer,x,y) + return @apiSetConsoleScreenBufferSize.call(hScreenBuffer,[x,y].pack("vv")) + end + + def Console::SetConsoleTitle(title) + return @apiSetConsoleTitle.call(title) + end + + def self.setup_console + return unless $DEBUG + @apiAllocConsole = Win32API.new("kernel32","AllocConsole","","l") + @apiCreateConsoleScreenBuffer = Win32API.new("kernel32","CreateConsoleScreenBuffer","nnpnp","l") + @apiSetConsoleActiveScreenBuffer = Win32API.new("kernel32","SetConsoleActiveScreenBuffer","l","s") + @apiWriteConsole = Win32API.new("kernel32","WriteConsole","lpnnn","S") + @apiReadConsole = Win32API.new("kernel32","ReadConsole","lpnnn","S") + @apiSetConsoleScreenBufferSize = Win32API.new("kernel32","SetConsoleScreenBufferSize","lp","S") + @apiSetConsoleTitle = Win32API.new("kernel32","SetConsoleTitle","p","s") + access = (GENERIC_READ | GENERIC_WRITE) + sharemode = (FILE_SHARE_READ | FILE_SHARE_WRITE) + returnCode = AllocConsole() + @bufferHandle = CreateConsoleScreenBuffer(access,sharemode,CONSOLE_TEXTMODE_BUFFER) + f = File.open("Game.ini") + lines = f.readlines() + s = lines[3] + len = s.size + title = (s[6,len - 7]) + SetConsoleScreenBufferSize(@bufferHandle,100,2000) + SetConsoleTitle("Debug Console -- #{title}") + echo "#{title} Output Window\n" + echo "-------------------------------\n" + echo "If you are seeing this window, you are running\n" + echo "#{title} in Debug Mode. This means\n" + echo "that you're either playing a Debug Version, or\n" + echo "you are playing from within RPG Maker XP.\n" + echo "\n" + echo "Closing this window will close the game. If \n" + echo "you want to get rid of this window, run the\n" + echo "program from the Shell, or download a Release\n" + echo "version.\n" + echo "\n" + echo "Gameplay will be paused while the console has\n" + echo "focus. To resume playing, switch to the Game\n" + echo "Window.\n" + echo "-------------------------------\n" + echo "Debug Output:\n" + echo "-------------------------------\n\n" + SetConsoleActiveScreenBuffer(@bufferHandle) + end + + def self.readInput + length=20 + buffer=0.chr*length + eventsread=0.chr*4 + done=false + input="" + while !done + echo("waiting for input") + begin + @apiReadConsole.call(@bufferHandle,buffer,1,eventsread) + rescue Hangup + return + end + offset=0 + events=eventsread.unpack("V") + echo("got input [eventsread #{events}") + for i in 0...events[0] + keyevent=buffer[offset,20] + keyevent=keyevent.unpack("vCvvvvV") + if keyevent[0]==1 && keyevent[1]>0 + input+=keyevent[4].chr + if keyevent[4].chr=="\n" + done=true + break + end + end + offset+=20 + end + end + return input + end + + def self.readInput2 + buffer=0.chr + done=false + input="" + eventsread=0.chr*4 + while !done + if ReadConsole(buffer)==0 + getlast = Win32API.new("kernel32","GetLastError","","n") + echo(sprintf("failed (%d)\r\n",getlast.call())) + break + end + offset=0 + events=eventsread.unpack("V") + if events[0]>0 + echo("got input [eventsread #{events}][buffer #{buffer}]\r\n") + key=buffer[0,events[0]] + input+=key + if key=="\n" + break + end + Graphics.update + end + end + return input + end + + def self.get_input + echo self.readInput2 + end +end + + + +module Kernel + def echo(string) + unless $DEBUG + return + end + Console::WriteConsole(string.is_a?(String) ? string : string.inspect) + end + + def echoln(string) + echo(string) + echo("\r\n") + end +end \ No newline at end of file diff --git a/Data/Scripts/001_Technical/007_Sprite_Resizer.rb b/Data/Scripts/001_Technical/007_Sprite_Resizer.rb new file mode 100644 index 000000000..da9e2a094 --- /dev/null +++ b/Data/Scripts/001_Technical/007_Sprite_Resizer.rb @@ -0,0 +1,757 @@ +#=============================================================================== +# Overriding Sprite, Viewport, and Plane to support resizing +# By Peter O. +# Modified by Luka S.J. and Maruno to support fullscreen and more sizes. +# -- This is a stand-alone RGSS script. -- +#=============================================================================== +$ResizeFactor = 1.0 +$ResizeFactorMul = 100 +$ResizeOffsetX = 0 +$ResizeOffsetY = 0 +$ResizeFactorSet = false +$HaveResizeBorder = false + +if true # Disables using Alt+Enter to go fullscreen + regHotKey = Win32API.new('user32', 'RegisterHotKey', 'LIII', 'I') + regHotKey.call(0, 1, 1, 0x0D) +end + +def pbSetResizeFactor(factor=1,norecalc=false) + factor = [0.5,1.0,2.0,-1][factor] if !norecalc + (factor<0) ? pbConfigureFullScreen : pbConfigureWindowedScreen(factor) +end + +def pbSetResizeFactor2(factor,force=false) + if $ResizeFactor!=factor || force + $ResizeFactor = factor + $ResizeFactorMul = (factor*100).to_i + pbRefreshResizeFactor if $ResizeFactorSet + end + $ResizeFactorSet = true + $ResizeBorder.refresh if $HaveResizeBorder + begin + if Graphics.haveresizescreen + Graphics.oldresizescreen( + (Graphics.width+$ResizeOffsetX*2)*factor, + (Graphics.height+$ResizeOffsetY*2)*factor + ) + end + Win32API.SetWindowPos( + (Graphics.width+$ResizeOffsetX*2)*factor, + (Graphics.height+$ResizeOffsetY*2)*factor + ) + rescue + end +end + +def pbRefreshResizeFactor + ObjectSpace.each_object(Sprite) { |o| + next if o.disposed? + o.x = o.x + o.y = o.y + o.ox = o.ox + o.oy = o.oy + o.zoom_x = o.zoom_x + o.zoom_y = o.zoom_y + } + ObjectSpace.each_object(Viewport) { |o| + begin + o.rect = o.rect + o.ox = o.ox + o.oy = o.oy + rescue RGSSError + end + } + ObjectSpace.each_object(Plane) { |o| + next if o.disposed? + o.zoom_x = o.zoom_x + o.zoom_y = o.zoom_y + } +end + +def pbConfigureFullScreen + params = Win32API.fillScreen + fullgamew = gamew = SCREEN_WIDTH + fullgameh = gameh = SCREEN_HEIGHT + if !BORDER_FULLY_SHOWS && $PokemonSystem && $PokemonSystem.border==1 + fullgamew += BORDER_WIDTH * 2 + fullgameh += BORDER_HEIGHT * 2 + end +# factor_x = ((2*params[0])/fullgamew).floor +# factor_y = ((2*params[1])/fullgameh).floor +# factor = [factor_x,factor_y].min/2.0 + factor_x = (params[0]/fullgamew).floor + factor_y = (params[1]/fullgameh).floor + factor = [factor_x,factor_y].min + offset_x = (params[0]-gamew*factor)/(2*factor) + offset_y = (params[1]-gameh*factor)/(2*factor) + $ResizeOffsetX = offset_x + $ResizeOffsetY = offset_y + ObjectSpace.each_object(Viewport) { |o| + begin + next if o.rect.nil? + ox = o.rect.x-$ResizeOffsetX + oy = o.rect.y-$ResizeOffsetY + o.rect.x = ox+offset_x + o.rect.y = oy+offset_y + rescue RGSSError + end + } + pbSetResizeFactor2(factor,true) +end + +def pbConfigureWindowedScreen(value) + border = $PokemonSystem ? $PokemonSystem.border : 0 + $ResizeOffsetX = [0,BORDER_WIDTH][border] + $ResizeOffsetY = [0,BORDER_HEIGHT][border] + pbSetResizeFactor2(value,true) + Win32API.restoreScreen +end + +def setScreenBorderName(border) + if !$HaveResizeBorder + $ResizeBorder = ScreenBorder.new + $HaveResizeBorder = true + end + $ResizeBorder.bordername = border if $ResizeBorder +end + + + +module Graphics + ## Nominal screen size + @@width = SCREEN_WIDTH + @@height = SCREEN_HEIGHT + + def self.width + return @@width.to_i + end + + def self.height + return @@height.to_i + end + + @@fadeoutvp = Viewport.new(0,0,640,480) + @@fadeoutvp.z = 0x3FFFFFFF + @@fadeoutvp.color = Color.new(0,0,0,0) + + def self.brightness + return 255-@@fadeoutvp.color.alpha + end + + def self.brightness=(value) + value = 0 if value<0 + value = 255 if value>255 + @@fadeoutvp.color.alpha = 255-value + end + + def self.fadein(frames) + return if frames<=0 + curvalue = self.brightness + count = (255-self.brightness) + frames.times do |i| + self.brightness = curvalue+(count*i/frames) + self.update + end + end + + def self.wait(frames) + return if frames<=0 + frames.times do |i| + self.update + end + end + + def self.fadeout(frames) + return if frames<=0 + curvalue = self.brightness + count = self.brightness + frames.times do |i| + self.brightness = curvalue-(count*i/frames) + self.update + end + end + + class << self + begin + x = @@haveresizescreen + rescue NameError # If exception is caught, the class + if !method_defined?(:oldresizescreen) # variable wasn't defined yet + begin + alias oldresizescreen resize_screen + @@haveresizescreen = true + rescue + @@haveresizescreen = false + end + else + @@haveresizescreen = false + end + end + + def haveresizescreen + @@haveresizescreen + end + end + + def self.resize_screen(w,h) + @@width = w + @@height = h + pbSetResizeFactor($ResizeFactor,true) + end + + @@deletefailed = false + + def self.snap_to_bitmap(resize=true) + tempPath = ENV["TEMP"]+"\\tempscreen.bmp" + if safeExists?(tempPath) && @@deletefailed + begin + File.delete(tempPath) + @@deletefailed = false + rescue Errno::EACCES + @@deletefailed = true + return nil + end + end + if safeExists?("./rubyscreen.dll") + takescreen = Win32API.new("rubyscreen.dll","TakeScreenshot","p","i") + takescreen.call(tempPath) + end + bm = nil + if safeExists?(tempPath) + bm = Bitmap.new(tempPath) + begin + File.delete(tempPath) + @@deletefailed = false + rescue Errno::EACCES + @@deletefailed = true + end + end + bm.asOpaque if bm && bm.get_pixel(0,0).alpha==0 + if resize + if bm && $ResizeOffsetX && $ResizeOffsetY && ($ResizeOffsetX!=0 || $ResizeOffsetY!=0) + tmpbitmap = Bitmap.new(Graphics.width*$ResizeFactor,Graphics.height*$ResizeFactor) + tmpbitmap.blt(0,0,bm,Rect.new( + $ResizeOffsetX*$ResizeFactor,$ResizeOffsetY*$ResizeFactor,tmpbitmap.width,tmpbitmap.height)) + bm.dispose + bm = tmpbitmap + end + if bm && (bm.width!=Graphics.width || bm.height!=Graphics.height) + newbitmap = Bitmap.new(Graphics.width,Graphics.height) + newbitmap.stretch_blt(newbitmap.rect,bm,Rect.new(0,0,bm.width,bm.height)) + bm.dispose + bm = newbitmap + end + else + # Thise code is used only for taking screenshots with F8. + # Doesn't crop out the screen border, doesn't normalise to 1x zoom. + # Fixes screenshots being 1 pixel too tall. + fullw = (Graphics.width+$ResizeOffsetX*2)*$ResizeFactor + fullh = (Graphics.height+$ResizeOffsetY*2)*$ResizeFactor + if bm && $ResizeOffsetX && $ResizeOffsetY && $ResizeFactor && + (bm.width!=fullw || bm.height!=fullh) + tmpbitmap = Bitmap.new(fullw,fullh) + tmpbitmap.blt(0,0,bm,Rect.new(0,0,fullw,fullh)) + bm.dispose + bm = tmpbitmap + end + end + return bm + end +end + + + +class Sprite + unless @SpriteResizerMethodsAliased + alias _initialize_SpriteResizer initialize + alias _x_SpriteResizer x + alias _y_SpriteResizer y + alias _ox_SpriteResizer ox + alias _oy_SpriteResizer oy + alias _zoomx_SpriteResizer zoom_x + alias _zoomy_SpriteResizer zoom_y + alias _xeq_SpriteResizer x= + alias _yeq_SpriteResizer y= + alias _oxeq_SpriteResizer ox= + alias _oyeq_SpriteResizer oy= + alias _zoomxeq_SpriteResizer zoom_x= + alias _zoomyeq_SpriteResizer zoom_y= + alias _bushdeptheq_SpriteResizer bush_depth= + @SpriteResizerMethodsAliased = true + end + + def initialize(viewport=nil) + _initialize_SpriteResizer(viewport) + @resizedX=0 + @resizedY=0 + @resizedOx=0 + @resizedOy=0 + @resizedBushDepth=0 + @resizedZoomX=1.0 + @resizedZoomY=1.0 + if $ResizeOffsetX!=0 && $ResizeOffsetY!=0 && !viewport + _xeq_SpriteResizer($ResizeOffsetX.to_f*$ResizeFactorMul/100) + _yeq_SpriteResizer($ResizeOffsetY.to_f*$ResizeFactorMul/100) + end + _zoomxeq_SpriteResizer(@resizedZoomX*$ResizeFactorMul/100) + _zoomyeq_SpriteResizer(@resizedZoomY*$ResizeFactorMul/100) + end + + def x + return @resizedX + end + + def y + return @resizedY + end + + def x=(val) + if $ResizeFactorMul!=100 + offset=(self.viewport) ? 0 : $ResizeOffsetX + value=(val+offset).to_f*$ResizeFactorMul/100 + _xeq_SpriteResizer(value.to_i) + @resizedX=val.to_i + elsif self.viewport + _xeq_SpriteResizer(val) + @resizedX=val + else + _xeq_SpriteResizer(val + $ResizeOffsetX) + @resizedX=val + end + end + + def y=(val) + if $ResizeFactorMul!=100 + offset=(self.viewport) ? 0 : $ResizeOffsetY + value=(val+offset).to_f*$ResizeFactorMul/100 + _yeq_SpriteResizer(value.to_i) + @resizedY=val.to_i + elsif self.viewport + _yeq_SpriteResizer(val) + @resizedY=val + else + _yeq_SpriteResizer(val + $ResizeOffsetY) + @resizedY=val + end + end + + def ox + return @resizedOx + end + + def oy + return @resizedOy + end + + def ox=(val) + @resizedOx=val + _oxeq_SpriteResizer(val) + end + + def oy=(val) + @resizedOy=val + _oyeq_SpriteResizer(val) + end + + def zoom_x + return @resizedZoomX + end + + def zoom_y + return @resizedZoomY + end + + def zoom_x=(val) + value=val + if $ResizeFactorMul!=100 + value=(val.to_f*$ResizeFactorMul/100) + if (value-0.5).abs<=0.001 + value=0.5 + elsif (value-1.0).abs<=0.001 + value=1.0 + elsif (value-1.5).abs<=0.001 + value=1.5 + elsif (value-2.0).abs<=0.001 + value=2.0 + end + end + _zoomxeq_SpriteResizer(value) + @resizedZoomX=val + end + + def zoom_y=(val) + value=val + if $ResizeFactorMul!=100 + value=(val.to_f*$ResizeFactorMul/100) + if (value-0.5).abs<=0.001 + value=0.5 + elsif (value-1.0).abs<=0.001 + value=1.0 + elsif (value-1.5).abs<=0.001 + value=1.5 + elsif (value-2.0).abs<=0.001 + value=2.0 + end + end + _zoomyeq_SpriteResizer(value) + @resizedZoomY=val + end + + def bush_depth + return @resizedBushDepth + end + + def bush_depth=(val) + value=((val.to_i)*$ResizeFactorMul/100) + _bushdeptheq_SpriteResizer(value.to_i) + @resizedBushDepth=val.to_i + end +end + + + +class NotifiableRect < Rect + def setNotifyProc(proc) + @notifyProc = proc + end + + def set(x,y,width,height) + super + @notifyProc.call(self) if @notifyProc + end + + def x=(value) + super + @notifyProc.call(self) if @notifyProc + end + + def y=(value) + super + @notifyProc.call(self) if @notifyProc + end + + def width=(value) + super + @notifyProc.call(self) if @notifyProc + end + + def height=(value) + super + @notifyProc.call(self) if @notifyProc + end +end + + + +class Viewport + unless @SpriteResizerMethodsAliased + alias _initialize_SpriteResizer initialize + alias _rect_ViewportResizer rect + alias _recteq_SpriteResizer rect= + alias _oxeq_SpriteResizer ox= + alias _oyeq_SpriteResizer oy= + @SpriteResizerMethodsAliased=true + end + + def initialize(*arg) + args=arg.clone + @oldrect=Rect.new(0,0,100,100) + _initialize_SpriteResizer(@oldrect) + newRect=NotifiableRect.new(0,0,0,0) + @resizedRectProc=Proc.new { |r| + if $ResizeFactorMul==100 + @oldrect.set( + r.x.to_i+$ResizeOffsetX, + r.y.to_i+$ResizeOffsetY, + r.width.to_i, + r.height.to_i + ) + self._recteq_SpriteResizer(@oldrect) + else + @oldrect.set( + ((r.x+$ResizeOffsetX)*$ResizeFactorMul/100).to_i, + ((r.y+$ResizeOffsetY)*$ResizeFactorMul/100).to_i, + (r.width*$ResizeFactorMul/100).to_i, + (r.height*$ResizeFactorMul/100).to_i + ) + self._recteq_SpriteResizer(@oldrect) + end + } + newRect.setNotifyProc(@resizedRectProc) + if arg.length==1 + newRect.set(args[0].x,args[0].y,args[0].width,args[0].height) + else + newRect.set(args[0],args[1],args[2],args[3]) + end + @resizedRect=newRect + @resizedOx=0 + @resizedOy=0 + end + + def ox + return @resizedOx + end + + def ox=(val) + return if !val + _oxeq_SpriteResizer(val.to_f*$ResizeFactorMul/100) + @resizedOx=val + end + + def oy + return @resizedOy + end + + def oy=(val) + return if !val + _oyeq_SpriteResizer(val.to_f*$ResizeFactorMul/100) + @resizedOy=val + end + + def rect + return @resizedRect + end + + def rect=(val) + if val + newRect=NotifiableRect.new(0,0,100,100) + newRect.setNotifyProc(@resizedRectProc) + newRect.set(val.x.to_i,val.y.to_i,val.width.to_i,val.height.to_i) + @resizedRect=newRect + end + end +end + + + +class Plane + unless @SpriteResizerMethodsAliased + alias _initialize_SpriteResizer initialize + alias _zoomxeq_SpriteResizer zoom_x= + alias _zoomyeq_SpriteResizer zoom_y= + alias _oxeq_SpriteResizer ox= + alias _oyeq_SpriteResizer oy= + @SpriteResizerMethodsAliased=true + end + + def initialize(viewport=nil) + _initialize_SpriteResizer(viewport) + @resizedZoomX=1.0 + @resizedZoomY=1.0 + @resizedOx=0 + @resizedOy=0 + _zoomxeq_SpriteResizer(@resizedZoomX*$ResizeFactorMul/100) + _zoomyeq_SpriteResizer(@resizedZoomY*$ResizeFactorMul/100) + end + + def ox + return @resizedOx + end + + def ox=(val) + return if !val + _oxeq_SpriteResizer(val.to_f*$ResizeFactorMul/100) + @resizedOx=val + end + + def oy + return @resizedOy + end + + def oy=(val) + return if !val + _oyeq_SpriteResizer(val.to_f*$ResizeFactorMul/100) + @resizedOy=val + end + + def zoom_x + return @resizedZoomX + end + + def zoom_x=(val) + return if !val + _zoomxeq_SpriteResizer(val*$ResizeFactorMul/100) + @resizedZoomX=val + end + + def zoom_y + return @resizedZoomY + end + + def zoom_y=(val) + return if !val + _zoomyeq_SpriteResizer(val*$ResizeFactorMul/100) + @resizedZoomY=val + end +end + + + + +class ScreenBorder + def initialize + initializeInternal + refresh + end + + def initializeInternal + @maximumZ=500000 + @bordername="" + @sprite=IconSprite.new(0,0) rescue Sprite.new + @defaultwidth=640 + @defaultheight=480 + @defaultbitmap=Bitmap.new(@defaultwidth,@defaultheight) + end + + def dispose + @borderbitmap.dispose if @borderbitmap + @defaultbitmap.dispose + @sprite.dispose + end + + def adjustZ(z) + if z>=@maximumZ + @maximumZ=z+1 + @sprite.z=@maximumZ + end + end + + def bordername=(value) + @bordername=value + refresh + end + + def refresh + @sprite.z=@maximumZ + @sprite.x=-BORDER_WIDTH + @sprite.y=-BORDER_HEIGHT + @sprite.visible=($PokemonSystem && $PokemonSystem.border==1) + @sprite.bitmap=nil + if @sprite.visible + if @bordername!=nil && @bordername!="" + setSpriteBitmap("Graphics/Pictures/"+@bordername) + else + setSpriteBitmap(nil) + @sprite.bitmap=@defaultbitmap + end + end + @defaultbitmap.clear + @defaultbitmap.fill_rect(0,0,@defaultwidth,$ResizeOffsetY,Color.new(0,0,0)) + @defaultbitmap.fill_rect(0,$ResizeOffsetY, + $ResizeOffsetX,@defaultheight-$ResizeOffsetY,Color.new(0,0,0)) + @defaultbitmap.fill_rect(@defaultwidth-$ResizeOffsetX,$ResizeOffsetY, + $ResizeOffsetX,@defaultheight-$ResizeOffsetY,Color.new(0,0,0)) + @defaultbitmap.fill_rect($ResizeOffsetX,@defaultheight-$ResizeOffsetY, + @defaultwidth-$ResizeOffsetX*2,$ResizeOffsetY,Color.new(0,0,0)) + end + + private + + def setSpriteBitmap(x) + if (@sprite.is_a?(IconSprite) rescue false) + @sprite.setBitmap(x) + else + @sprite.bitmap=x ? RPG::Cache.load_bitmap("",x) : nil + end + end +end + + + +class Bitmap + # Fast methods for retrieving bitmap data + RtlMoveMemory_pi = Win32API.new('kernel32', 'RtlMoveMemory', 'pii', 'i') + RtlMoveMemory_ip = Win32API.new('kernel32', 'RtlMoveMemory', 'ipi', 'i') + SwapRgb = Win32API.new('./rubyscreen.dll', 'SwapRgb', 'pi', '') rescue nil + + def setData(x) + RtlMoveMemory_ip.call(self.address, x, x.length) + end + + def getData + data = "rgba" * width * height + RtlMoveMemory_pi.call(data, self.address, data.length) + return data + end + + def swap32(x) + return ((x>>24)&0x000000FF)| + ((x>>8)&0x0000FF00)| + ((x<<8)&0x00FF0000)| + ((x<<24)&0xFF000000) + end + + def asOpaque + data=getData + j=3 + for i in 0...width*height + data[j]=0xFF + j+=4 + end + setData(data) + end + + def saveToPng(filename) + bytes=[ + 0x89,0x50,0x4E,0x47,0x0D,0x0A,0x1A,0x0A,0x00,0x00,0x00,0x0D + ].pack("CCCCCCCCCCCC") + ihdr=[ + 0x49,0x48,0x44,0x52,swap32(self.width),swap32(self.height), + 0x08,0x06,0x00,0x00,0x00 + ].pack("CCCCVVCCCCC") + crc=Zlib::crc32(ihdr) + ihdr+=[swap32(crc)].pack("V") + bytesPerScan=self.width*4 + row=(self.height-1)*bytesPerScan + data=self.getData + data2=data.clone + width=self.width + x="" + len=bytesPerScan*self.height + ttt=Time.now + if SwapRgb + SwapRgb.call(data2,data2.length) + else + # the following is considerably slower + b=0;c=2;while b!=len + data2[b]=data[c] + data2[c]=data[b] + b+=4;c+=4; + end + end + #$times.push(Time.now-ttt) + filter="\0" + while row>=0 + thisRow=data2[row,bytesPerScan] + x.concat(filter) + x.concat(thisRow) + row-=bytesPerScan + end + x=Zlib::Deflate.deflate(x) + length=x.length + x="IDAT"+x + crc=Zlib::crc32(x) + idat=[swap32(length)].pack("V") + idat.concat(x) + idat.concat([swap32(crc)].pack("V")) + idat.concat([0,0x49,0x45,0x4E,0x44,0xAE,0x42,0x60,0x82].pack("VCCCCCCCC")) + File.open(filename,"wb") { |f| + f.write(bytes) + f.write(ihdr) + f.write(idat) + } + end + + def address + if !@address + buffer, ad = "rgba", object_id * 2 + 16 + RtlMoveMemory_pi.call(buffer, ad, 4) + ad = buffer.unpack("L")[0] + 8 + RtlMoveMemory_pi.call(buffer, ad, 4) + ad = buffer.unpack("L")[0] + 16 + RtlMoveMemory_pi.call(buffer, ad, 4) + @address=buffer.unpack("L")[0] + end + return @address + end +end \ No newline at end of file diff --git a/Data/Scripts/001_Technical/008_Plugin_Manager.rb b/Data/Scripts/001_Technical/008_Plugin_Manager.rb new file mode 100644 index 000000000..a8e4a2278 --- /dev/null +++ b/Data/Scripts/001_Technical/008_Plugin_Manager.rb @@ -0,0 +1,376 @@ +#==============================================================================# +# Plugin Manager # +# by Marin # +#------------------------------------------------------------------------------# +# Provides a simple interface that allows plugins to require dependencies # +# at specific versions, and to specify incompatibilities between plugins. # +#------------------------------------------------------------------------------# +# Usage: # +# # +# A Pokémon Essentials plugin should register itself using the PluginManager. # +# The simplest way to do so, for a plugin without dependencies, is as follows: # +# # +# PluginManager.register({ # +# :name => "Basic Plugin", # +# :version => "1.0", # +# :link => "https://reliccastle.com/link-to-the-plugin/", # +# :credits => "Marin" # +# }) # +# # +# The link portion here is optional, but recommended. This will be shown in # +# the error message if the PluginManager detects that this plugin needs to be # +# updated. # +# # +# A plugin's version is typically in the format X.Y.Z, but the number of # +# digits does not matter. You can also use Xa, Xb, Xc, Ya, etc. # +# What matters is that you use it consistently, so that it can be compared. # +# # +# # +# # +# Now let's say we create a new plugin titled "Simple Extension", which # +# requires our previously created "Basic Plugin" to work. # +# # +# PluginManager.register({ # +# :name => "Simple Extension", # +# :version => "1.0", # +# :link => "https://reliccastle.com/link-to-the-plugin/", # +# :credits => ["Marin", "Maruno"], # +# :dependencies => ["Basic Plugin"] # +# }) # +# # +# This plugin has two credits as an array, instead of one string. Furthermore, # +# this code will ensure that "Basic Plugin" is installed, ignoring its # +# version. If you have only one dependency, you can omit the array brackets # +# like so: # +# # +# :dependencies => "Basic Plugin" # +# # +# # +# # +# To require a minimum version of a dependency plugin, you should turn the # +# dependency's name into an array which contains the name and the version # +# (both as strings). For example, to require "Basic Plugin" version 1.2 or # +# higher, you would write: # +# # +# PluginManager.register({ # +# :name => "Simple Extension", # +# :version => "1.0", # +# :link => "https://reliccastle.com/link-to-the-plugin/", # +# :credits => "Marin", # +# :dependencies => [ # +# ["Basic Plugin", "1.2"] # +# ] # +# }) # +# # +# # +# # +# To require a specific version (no higher and no lower) of a dependency # +# plugin, you should add the :exact flag as the first thing in the array for # +# that dependency: # +# # +# PluginManager.register({ # +# :name => "Simple Extension", # +# :version => "1.0", # +# :link => "https://reliccastle.com/link-to-the-plugin/", # +# :credits => "Marin", # +# :dependencies => [ # +# [:exact, "Basic Plugin", "1.2"] # +# ] # +# }) # +# # +# # +# # +# If your plugin is known to be incompatible with another plugin, you should # +# list that other plugin as such. Only one of the two plugins needs to list # +# that it is incompatible with the other. # +# # +# PluginManager.register({ # +# :name => "QoL Improvements", # +# :version => "1.0", # +# :link => "https://reliccastle.com/link-to-the-plugin/", # +# :credits => "Marin", # +# :incompatibilities => [ # +# "Simple Extension" # +# ] # +# }) # +# # +# # +# # +# If your plugin can work without another plugin, but is known to be # +# incompatible with an old version of that other plugin, you should list it as # +# an optional dependency. If that other plugin is present in a game, then this # +# optional dependency will ensure it meets the minimum version required for # +# your plugin. Write it in the same way as any other dependency as described # +# above, but use the :optional flag instead. # +# You do not need to list a plugin as an optional dependency at all if all # +# versions of that other plugin are compatible with your plugin. # +# # +# PluginManager.register({ # +# :name => "Other Plugin", # +# :version => "1.0", # +# :link => "https://reliccastle.com/link-to-the-plugin/", # +# :credits => "Marin", # +# :dependencies => [ # +# [:optional, "QoL Improvements", "1.1"] # +# ] # +# }) # +# # +# The :optional_exact flag is a combination of :optional and :exact. # +#------------------------------------------------------------------------------# +# Please give credit when using this. # +#==============================================================================# + +module PluginManager + # Win32API MessageBox function for custom errors. + MBOX = Win32API.new('user32', 'MessageBox', ['I','P','P','I'], 'I') + # Holds all registered plugin data. + @@Plugins = {} + + # Registers a plugin and tests its dependencies and incompatibilities. + def self.register(options) + name = nil + version = nil + link = nil + dependencies = nil + incompats = nil + credits = [] + order = [:name, :version, :link, :dependencies, :incompatibilities, :credits] + # Ensure it first reads the plugin's name, which is used in error reporting, + # by sorting the keys + keys = options.keys.sort do |a, b| + idx_a = order.index(a) + idx_a = order.size if idx_a == -1 + idx_b = order.index(b) + idx_b = order.size if idx_b == -1 + next idx_a <=> idx_b + end + for key in keys + value = options[key] + case key + when :name # Plugin name + if nil_or_empty?(value) + self.error("Plugin name must be a non-empty string.") + end + if !@@Plugins[value].nil? + self.error("A plugin called '#{value}' already exists.") + end + name = value + when :version # Plugin version + if nil_or_empty?(value) + self.error("Plugin version must be a string.") + end + version = value + when :link # Plugin website + if nil_or_empty?(value) + self.error("Plugin link must be a non-empty string.") + end + link = value + when :dependencies # Plugin dependencies + dependencies = value + dependencies = [dependencies] if !dependencies.is_a?(Array) || !dependencies[0].is_a?(Array) + for dep in value + if dep.is_a?(String) # "plugin name" + if !self.installed?(dep) + self.error("Plugin '#{name}' requires plugin '#{dep}' to be installed above it.") + end + elsif dep.is_a?(Array) + case dep.size + when 1 # ["plugin name"] + if dep[0].is_a?(String) + dep_name = dep[0] + if !self.installed?(dep_name) + self.error("Plugin '#{name}' requires plugin '#{dep_name}' to be installed above it.") + end + else + self.error("Expected the plugin name as a string, but got #{dep[0].inspect}.") + end + when 2 # ["plugin name", "version"] + if dep[0].is_a?(Symbol) + self.error("A plugin version comparator symbol was given but no version was given.") + elsif dep[0].is_a?(String) && dep[1].is_a?(String) + dep_name = dep[0] + dep_version = dep[1] + next if self.installed?(dep_name, dep_version) + if self.installed?(dep_name) # Have plugin but lower version + msg = "Plugin '#{name}' requires plugin '#{dep_name}' version #{dep_version} or higher, " + + "but the installed version is #{self.version(dep_name)}." + if dep_link = self.link(dep_name) + msg += "\r\nCheck #{dep_link} for an update to plugin '#{dep_name}'." + end + self.error(msg) + else # Don't have plugin + self.error("Plugin '#{name}' requires plugin '#{dep_name}' version #{dep_version} " + + "or higher to be installed above it.") + end + end + when 3 # [:optional/:exact/:optional_exact, "plugin name", "version"] + if !dep[0].is_a?(Symbol) + self.error("Expected first dependency argument to be a symbol, but got #{dep[0].inspect}.") + end + if !dep[1].is_a?(String) + self.error("Expected second dependency argument to be a plugin name, but got #{dep[1].inspect}.") + end + if !dep[2].is_a?(String) + self.error("Expected third dependency argument to be the plugin version, but got #{dep[2].inspect}.") + end + dep_arg = dep[0] + dep_name = dep[1] + dep_version = dep[2] + optional = false + exact = false + case def_arg + when :optional; optional = true + when :exact; exact = true + when :optional_exact; optional = true; exact = true + else + self.error("Expected first dependency argument to be one of " + + ":optional, :exact or :optional_exact, but got #{dep_arg.inspect}.") + end + if optional + if self.installed?(dep_name) && # Have plugin but lower version + !self.installed?(dep_name, dep_version, exact) + msg = "Plugin '#{name}' requires plugin '#{dep_name}', if installed, to be version #{dep_version}" + msg << " or higher" if !exact + msg << ", but the installed version was #{self.version(dep_name)}." + if dep_link = self.link(dep_name) + msg << "\r\nCheck #{dep_link} for an update to plugin '#{dep_name}'." + end + self.error(msg) + end + elsif !self.installed?(dep_name, dep_version, exact) + if self.installed?(dep_name) # Have plugin but lower version + msg = "Plugin '#{name}' requires plugin '#{dep_name}' to be version #{dep_version}" + msg << " or later" if !exact + msg << ", but the installed version was #{self.version(dep_name)}." + if dep_link = self.link(dep_name) + msg << "\r\nCheck #{dep_link} for an update to plugin '#{dep_name}'." + end + self.error(msg) + else # Don't have plugin + msg = "Plugin '#{name}' requires plugin '#{dep_name}' version #{dep_version} " + msg << "or later" if !exact + msg << "to be installed above it." + self.error(msg) + end + end + end + end + end + when :incompatibilities # Plugin incompatibilities + incompats = value + incompats = [incompats] if !incompats.is_a?(Array) + for incompat in incompats + if self.installed?(incompat) + self.error("Plugin '#{name}' is incompatible with '#{incompat}'. " + + "They cannot both be used at the same time.") + end + end + when :credits # Plugin credits + value = [value] if value.is_a?(String) + if value.is_a?(Array) + for entry in value + if !entry.is_a?(String) + self.error("Plugin '#{name}'s credits array contains a non-string value.") + else + credits << entry + end + end + else + self.error("Plugin '#{name}'s credits field must contain a string, or a string array.") + end + else + self.error("Invalid plugin registry key '#{key}'.") + end + end + for plugin in @@Plugins.values + if plugin[:incompatibilities] && plugin[:incompatibilities].include?(name) + self.error("Plugin '#{plugin[:name]}' is incompatible with '#{name}'. " + + "They cannot both be used at the same time.") + end + end + # Add plugin to class variable + @@Plugins[name] = { + :name => name, + :version => version, + :link => link, + :dependencies => dependencies, + :incompatibilities => incompats, + :credits => credits + } + end + + # Throws a pure error message without stack trace or any other useless info. + def self.error(msg) + Graphics.update + t = Thread.new do + MBOX.call(Win32API.pbFindRgssWindow, msg, "Plugin Error", 0x10) + Thread.exit + end + while t.status + Graphics.update + end + Kernel.exit! true + end + + # Returns true if the specified plugin is installed. + # If the version is specified, this version is taken into account. + # If mustequal is true, the version must be a match with the specified version. + def self.installed?(plugin_name, plugin_version = nil, mustequal = false) + plugin = @@Plugins[plugin_name] + return false if plugin.nil? + return true if plugin_version.nil? + comparison = compare_versions(plugin[:version], plugin_version) + return true if !mustequal && comparison >= 0 + return true if mustequal && comparison == 0 + end + + # Returns the string names of all installed plugins. + def self.plugins + return @@Plugins.keys + end + + # Returns the installed version of the specified plugin. + def self.version(plugin_name) + return if !installed?(plugin_name) + return @@Plugins[plugin_name][:version] + end + + # Returns the link of the specified plugin. + def self.link(plugin_name) + return if !installed?(plugin_name) + return @@Plugins[plugin_name][:link] + end + + # Returns the credits of the specified plugin. + def self.credits(plugin_name) + return if !installed?(plugin_name) + return @@Plugins[plugin_name][:credits] + end + + # Compares two versions given in string form. v1 should be the plugin version + # you actually have, and v2 should be the minimum/desired plugin version. + # Return values: + # 1 if v1 is higher than v2 + # 0 if v1 is equal to v2 + # -1 if v1 is lower than v2 + def self.compare_versions(v1, v2) + d1 = v1.split("") + d1.insert(0, "0") if d1[0] == "." # Turn ".123" into "0.123" + while d1[-1] == "."; d1 = d1[0..-2]; end # Turn "123." into "123" + d2 = v2.split("") + d2.insert(0, "0") if d2[0] == "." # Turn ".123" into "0.123" + while d2[-1] == "."; d2 = d2[0..-2]; end # Turn "123." into "123" + for i in 0...[d1.size, d2.size].max # Compare each digit in turn + c1 = d1[i] + c2 = d2[i] + if c1 + return 1 if !c2 + return 1 if c1.to_i(16) > c2.to_i(16) + return -1 if c1.to_i(16) < c2.to_i(16) + else + return -1 if c2 + end + end + return 0 + end +end \ No newline at end of file diff --git a/Data/Scripts/002_Switches and Variables/001_Game_Temp.rb b/Data/Scripts/002_Switches and Variables/001_Game_Temp.rb new file mode 100644 index 000000000..727043c2b --- /dev/null +++ b/Data/Scripts/002_Switches and Variables/001_Game_Temp.rb @@ -0,0 +1,104 @@ +#=============================================================================== +# ** Game_Temp +#------------------------------------------------------------------------------- +# This class handles temporary data that is not included with save data. +# Refer to "$game_temp" for the instance of this class. +#=============================================================================== +class Game_Temp + attr_accessor :map_bgm # map music (for battle memory) + attr_accessor :message_text # message text + attr_accessor :message_proc # message callback (Proc) + attr_accessor :choice_start # show choices: opening line + attr_accessor :choice_max # show choices: number of items + attr_accessor :choice_cancel_type # show choices: cancel + attr_accessor :choice_proc # show choices: callback (Proc) + attr_accessor :num_input_start # input number: opening line + attr_accessor :num_input_variable_id # input number: variable ID + attr_accessor :num_input_digits_max # input number: digit amount + attr_accessor :message_window_showing # message window showing + attr_accessor :common_event_id # common event ID + attr_accessor :in_battle # in-battle flag + attr_accessor :battle_calling # battle calling flag + attr_accessor :battle_troop_id # battle troop ID + attr_accessor :battle_can_escape # battle flag: escape possible + attr_accessor :battle_can_lose # battle flag: losing possible + attr_accessor :battle_proc # battle callback (Proc) + attr_accessor :battle_turn # number of battle turns + attr_accessor :battle_event_flags # battle event flags: completed + attr_accessor :battle_abort # battle flag: interrupt + attr_accessor :battle_main_phase # battle flag: main phase + attr_accessor :battleback_name # battleback file name + attr_accessor :forcing_battler # battler being forced into action + attr_accessor :shop_calling # shop calling flag + attr_accessor :shop_goods # list of shop goods + attr_accessor :name_calling # name input: calling flag + attr_accessor :name_actor_id # name input: actor ID + attr_accessor :name_max_char # name input: max character count + attr_accessor :menu_calling # menu calling flag + attr_accessor :menu_beep # menu: play sound effect flag + attr_accessor :in_menu # menu is open + attr_accessor :save_calling # save calling flag + attr_accessor :debug_calling # debug calling flag + attr_accessor :player_transferring # player place movement flag + attr_accessor :player_new_map_id # player destination: map ID + attr_accessor :player_new_x # player destination: x-coordinate + attr_accessor :player_new_y # player destination: y-coordinate + attr_accessor :player_new_direction # player destination: direction + attr_accessor :transition_processing # transition processing flag + attr_accessor :transition_name # transition file name + attr_accessor :gameover # game over flag + attr_accessor :to_title # return to title screen flag + attr_accessor :last_file_index # last save file no. + attr_accessor :map_refresh # map needs redrawing + #----------------------------------------------------------------------------- + # * Object Initialization + #----------------------------------------------------------------------------- + def initialize + @map_bgm = nil + @message_text = nil + @message_proc = nil + @choice_start = 99 + @choice_max = 0 + @choice_cancel_type = 0 + @choice_proc = nil + @num_input_start = 99 + @num_input_variable_id = 0 + @num_input_digits_max = 0 + @message_window_showing = false + @common_event_id = 0 + @in_battle = false + @battle_calling = false + @battle_troop_id = 0 + @battle_can_escape = false + @battle_can_lose = false + @battle_proc = nil + @battle_turn = 0 + @battle_event_flags = {} + @battle_abort = false + @battle_main_phase = false + @battleback_name = '' + @forcing_battler = nil + @shop_calling = false + @shop_id = 0 + @name_calling = false + @name_actor_id = 0 + @name_max_char = 0 + @menu_calling = false + @menu_beep = false + @in_menu = false + @save_calling = false + @debug_calling = false + @player_transferring = false + @player_new_map_id = 0 + @player_new_x = 0 + @player_new_y = 0 + @player_new_direction = 0 + @transition_processing = false + @transition_name = "" + @gameover = false + @to_title = false + @last_file_index = 0 + @debug_top_row = 0 + @debug_index = 0 + end +end diff --git a/Data/Scripts/002_Switches and Variables/002_Game_Switches.rb b/Data/Scripts/002_Switches and Variables/002_Game_Switches.rb new file mode 100644 index 000000000..29b66871e --- /dev/null +++ b/Data/Scripts/002_Switches and Variables/002_Game_Switches.rb @@ -0,0 +1,36 @@ +#=============================================================================== +# ** Game_Switches +#------------------------------------------------------------------------------- +# This class handles switches. It's a wrapper for the built-in class "Array." +# Refer to "$game_switches" for the instance of this class. +#=============================================================================== + +class Game_Switches + #----------------------------------------------------------------------------- + # * Object Initialization + #----------------------------------------------------------------------------- + def initialize + @data = [] + end + #----------------------------------------------------------------------------- + # * Get Switch + # switch_id : switch ID + #----------------------------------------------------------------------------- + def [](switch_id) + if switch_id<=5000 and @data[switch_id]!=nil + return @data[switch_id] + else + return false + end + end + #----------------------------------------------------------------------------- + # * Set Switch + # switch_id : switch ID + # value : ON (true) / OFF (false) + #----------------------------------------------------------------------------- + def []=(switch_id, value) + if switch_id<=5000 + @data[switch_id] = value + end + end +end \ No newline at end of file diff --git a/Data/Scripts/002_Switches and Variables/003_Game_Variables.rb b/Data/Scripts/002_Switches and Variables/003_Game_Variables.rb new file mode 100644 index 000000000..0ae70171c --- /dev/null +++ b/Data/Scripts/002_Switches and Variables/003_Game_Variables.rb @@ -0,0 +1,36 @@ +#=============================================================================== +# ** Game_Variables +#------------------------------------------------------------------------------- +# This class handles variables. It's a wrapper for the built-in class "Array." +# Refer to "$game_variables" for the instance of this class. +#=============================================================================== + +class Game_Variables + #----------------------------------------------------------------------------- + # * Object Initialization + #----------------------------------------------------------------------------- + def initialize + @data = [] + end + #----------------------------------------------------------------------------- + # * Get Variable + # variable_id : variable ID + #----------------------------------------------------------------------------- + def [](variable_id) + if variable_id<=5000 and @data[variable_id]!=nil + return @data[variable_id] + else + return 0 + end + end + #----------------------------------------------------------------------------- + # * Set Variable + # variable_id : variable ID + # value : the variable's value + #----------------------------------------------------------------------------- + def []=(variable_id, value) + if variable_id<=5000 + @data[variable_id] = value + end + end +end \ No newline at end of file diff --git a/Data/Scripts/002_Switches and Variables/004_Game_SelfSwitches.rb b/Data/Scripts/002_Switches and Variables/004_Game_SelfSwitches.rb new file mode 100644 index 000000000..a65708df7 --- /dev/null +++ b/Data/Scripts/002_Switches and Variables/004_Game_SelfSwitches.rb @@ -0,0 +1,30 @@ +#=============================================================================== +# ** Game_SelfSwitches +#------------------------------------------------------------------------------- +# This class handles self switches. It's a wrapper for the built-in class +# "Hash." Refer to "$game_self_switches" for the instance of this class. +#=============================================================================== + +class Game_SelfSwitches + #----------------------------------------------------------------------------- + # * Object Initialization + #----------------------------------------------------------------------------- + def initialize + @data = {} + end + #----------------------------------------------------------------------------- + # * Get Self Switch + # key : key + #----------------------------------------------------------------------------- + def [](key) + return (@data[key]==true) ? true : false + end + #----------------------------------------------------------------------------- + # * Set Self Switch + # key : key + # value : ON (true) / OFF (false) + #----------------------------------------------------------------------------- + def []=(key, value) + @data[key] = value + end +end \ No newline at end of file diff --git a/Data/Scripts/003_Game classes/001_Game_Screen.rb b/Data/Scripts/003_Game classes/001_Game_Screen.rb new file mode 100644 index 000000000..6c659c6e9 --- /dev/null +++ b/Data/Scripts/003_Game classes/001_Game_Screen.rb @@ -0,0 +1,156 @@ +#=============================================================================== +# ** Game_Screen +#------------------------------------------------------------------------------- +# This class handles screen maintenance data, such as change in color tone, +# flashing, etc. Refer to "$game_screen" for the instance of this class. +#=============================================================================== + +class Game_Screen + #----------------------------------------------------------------------------- + # * Public Instance Variables + #----------------------------------------------------------------------------- + attr_reader :brightness # brightness + attr_reader :tone # color tone + attr_reader :flash_color # flash color + attr_reader :shake # shake positioning + attr_reader :pictures # pictures + attr_reader :weather_type # weather type + attr_reader :weather_max # max number of weather sprites + #----------------------------------------------------------------------------- + # * Object Initialization + #----------------------------------------------------------------------------- + def initialize + @brightness = 255 + @fadeout_duration = 0 + @fadein_duration = 0 + @tone = Tone.new(0, 0, 0, 0) + @tone_target = Tone.new(0, 0, 0, 0) + @tone_duration = 0 + @flash_color = Color.new(0, 0, 0, 0) + @flash_duration = 0 + @shake_power = 0 + @shake_speed = 0 + @shake_duration = 0 + @shake_direction = 1 + @shake = 0 + @pictures = [nil] + for i in 1..100 + @pictures.push(Game_Picture.new(i)) + end + @weather_type = 0 + @weather_max = 0.0 + @weather_type_target = 0 + @weather_max_target = 0.0 + @weather_duration = 0 + end + #----------------------------------------------------------------------------- + # * Start Changing Color Tone + # tone : color tone + # duration : time + #----------------------------------------------------------------------------- + def start_tone_change(tone, duration) + @tone_target = tone.clone + @tone_duration = duration + if @tone_duration == 0 + @tone = @tone_target.clone + end + end + #----------------------------------------------------------------------------- + # * Start Flashing + # color : color + # duration : time + #----------------------------------------------------------------------------- + def start_flash(color, duration) + @flash_color = color.clone + @flash_duration = duration + end + #----------------------------------------------------------------------------- + # * Start Shaking + # power : strength + # speed : speed + # duration : time + #----------------------------------------------------------------------------- + def start_shake(power, speed, duration) + @shake_power = power + @shake_speed = speed + @shake_duration = duration + end + #----------------------------------------------------------------------------- + # * Set Weather + # type : type + # power : strength + # duration : time + #----------------------------------------------------------------------------- + def weather(type, power, duration) + @weather_type_target = type + if @weather_type_target!=0 + @weather_type = @weather_type_target + end + if @weather_type_target==0 + @weather_max_target = 0.0 + else + @weather_max_target = (power + 1) * 4.0 + end + @weather_duration = duration + if @weather_duration==0 + @weather_type = @weather_type_target + @weather_max = @weather_max_target + end + end + #----------------------------------------------------------------------------- + # * Frame Update + #----------------------------------------------------------------------------- + def update + if @fadeout_duration && @fadeout_duration>=1 + d = @fadeout_duration + @brightness = (@brightness*(d-1))/d + @fadeout_duration -= 1 + end + if @fadein_duration && @fadein_duration>=1 + d = @fadein_duration + @brightness = (@brightness*(d-1)+255)/d + @fadein_duration -= 1 + end + if @tone_duration>=1 + d = @tone_duration + @tone.red = (@tone.red*(d-1)+@tone_target.red)/d + @tone.green = (@tone.green*(d-1)+@tone_target.green)/d + @tone.blue = (@tone.blue*(d-1)+@tone_target.blue)/d + @tone.gray = (@tone.gray*(d-1)+@tone_target.gray)/d + @tone_duration -= 1 + end + if @flash_duration>=1 + d = @flash_duration + @flash_color.alpha = @flash_color.alpha*(d-1)/d + @flash_duration -= 1 + end + if @shake_duration>=1 or @shake!=0 + delta = (@shake_power*@shake_speed*@shake_direction)/10.0 + if @shake_duration<=1 and @shake*(@shake+delta)<0 + @shake = 0 + else + @shake += delta + end + @shake_direction = -1 if @shake>@shake_power*2 + @shake_direction = 1 if @shake<-@shake_power*2 + @shake_duration -= 1 if @shake_duration>=1 + end + if @weather_duration>=1 + d = @weather_duration + @weather_max = (@weather_max*(d-1)+@weather_max_target)/d + @weather_duration -= 1 + if @weather_duration==0 + @weather_type = @weather_type_target + end + end + if $game_temp.in_battle + for i in 51..100 + @pictures[i].update + end + else + for i in 1..50 + @pictures[i].update + end + end + end +end \ No newline at end of file diff --git a/Data/Scripts/003_Game classes/002_Game_System.rb b/Data/Scripts/003_Game classes/002_Game_System.rb new file mode 100644 index 000000000..8637bdba4 --- /dev/null +++ b/Data/Scripts/003_Game classes/002_Game_System.rb @@ -0,0 +1,289 @@ +#============================================================================== +# ** Game_System +#------------------------------------------------------------------------------ +# This class handles data surrounding the system. Backround music, etc. +# is managed here as well. Refer to "$game_system" for the instance of +# this class. +#============================================================================== + +class Game_System + attr_reader :map_interpreter # map event interpreter + attr_reader :battle_interpreter # battle event interpreter + attr_accessor :timer # timer + attr_accessor :timer_working # timer working flag + attr_accessor :save_disabled # save forbidden + attr_accessor :menu_disabled # menu forbidden + attr_accessor :encounter_disabled # encounter forbidden + attr_accessor :message_position # text option: positioning + attr_accessor :message_frame # text option: window frame + attr_accessor :save_count # save count + attr_accessor :magic_number # magic number + attr_accessor :autoscroll_x_speed + attr_accessor :autoscroll_y_speed + attr_accessor :bgm_position + + def initialize + if $RPGVX + @map_interpreter = Game_Interpreter.new(0,true) + @battle_interpreter = Game_Interpreter.new(0,false) + else + @map_interpreter = Interpreter.new(0,true) + @battle_interpreter = Interpreter.new(0,false) + end + @timer = 0 + @timer_working = false + @save_disabled = false + @menu_disabled = false + @encounter_disabled = false + @message_position = 2 + @message_frame = 0 + @save_count = 0 + @magic_number = 0 + @autoscroll_x_speed = 0 + @autoscroll_y_speed = 0 + @bgm_position = 0 + @bgs_position = 0 + end + +################################################################################ + + def bgm_play(bgm) + bgm_play_internal(bgm,0) + end + + def bgm_play_internal2(name,volume,pitch,position) # :nodoc: + vol = volume + vol *= $PokemonSystem.bgmvolume/100.0 + vol = vol.to_i + begin + Audio.bgm_play(name,vol,pitch,position) + rescue ArgumentError + Audio.bgm_play(name,vol,pitch) + end + end + + def bgm_play_internal(bgm,position) # :nodoc: + @bgm_position = position if !@bgm_paused + @playing_bgm = (bgm==nil) ? nil : bgm.clone + if bgm!=nil and bgm.name!="" + if FileTest.audio_exist?("Audio/BGM/"+bgm.name) + bgm_play_internal2("Audio/BGM/"+bgm.name, + bgm.volume,bgm.pitch,@bgm_position) if !@defaultBGM + end + else + @bgm_position = position if !@bgm_paused + @playing_bgm = nil + Audio.bgm_stop if !@defaultBGM + end + if @defaultBGM + bgm_play_internal2("Audio/BGM/"+@defaultBGM.name, + @defaultBGM.volume,@defaultBGM.pitch,@bgm_position) + end + Graphics.frame_reset + end + + def bgm_pause(fadetime=0.0) # :nodoc: + pos = Audio.bgm_position rescue 0 + self.bgm_fade(fadetime) if fadetime>0.0 + @bgm_position = pos + @bgm_paused = true + end + + def bgm_unpause # :nodoc: + @bgm_position = 0 + @bgm_paused = false + end + + def bgm_resume(bgm) # :nodoc: + if @bgm_paused + self.bgm_play_internal(bgm,@bgm_position) + @bgm_position = 0 + @bgm_paused = false + end + end + + def bgm_stop # :nodoc: + @bgm_position = 0 if !@bgm_paused + @playing_bgm = nil + Audio.bgm_stop if !@defaultBGM + end + + def bgm_fade(time) # :nodoc: + @bgm_position = 0 if !@bgm_paused + @playing_bgm = nil + Audio.bgm_fade((time*1000).floor) if !@defaultBGM + end + + def playing_bgm + return @playing_bgm + end + + # Saves the currently playing background music for later playback. + def bgm_memorize + @memorized_bgm = @playing_bgm + end + + # Plays the currently memorized background music + def bgm_restore + bgm_play(@memorized_bgm) + end + + # Returns an RPG::AudioFile object for the currently playing background music + def getPlayingBGM + return (@playing_bgm) ? @playing_bgm.clone : nil + end + + def setDefaultBGM(bgm,volume=80,pitch=100) + bgm = RPG::AudioFile.new(bgm,volume,pitch) if bgm.is_a?(String) + if bgm!=nil and bgm.name!="" + @defaultBGM = nil + self.bgm_play(bgm) + @defaultBGM = bgm.clone + else + @defaultBGM = nil + self.bgm_play(@playing_bgm) + end + end + +################################################################################ + + def me_play(me) + me = RPG::AudioFile.new(me) if me.is_a?(String) + if me!=nil and me.name!="" + if FileTest.audio_exist?("Audio/ME/"+me.name) + vol = me.volume + vol *= $PokemonSystem.bgmvolume/100.0 + vol = vol.to_i + Audio.me_play("Audio/ME/"+me.name,vol,me.pitch) + end + else + Audio.me_stop + end + Graphics.frame_reset + end + +################################################################################ + + def bgs_play(bgs) + @playing_bgs = (bgs==nil) ? nil : bgs.clone + if bgs!=nil and bgs.name!="" + if FileTest.audio_exist?("Audio/BGS/"+bgs.name) + vol = bgs.volume + vol *= $PokemonSystem.sevolume/100.0 + vol = vol.to_i + Audio.bgs_play("Audio/BGS/"+bgs.name,vol,bgs.pitch) + end + else + @bgs_position = 0 + @playing_bgs = nil + Audio.bgs_stop + end + Graphics.frame_reset + end + + def bgs_pause(fadetime=0.0) # :nodoc: + if fadetime>0.0 + self.bgs_fade(fadetime) + else + self.bgs_stop + end + @bgs_paused = true + end + + def bgs_unpause # :nodoc: + @bgs_paused = false + end + + def bgs_resume(bgs) # :nodoc: + if @bgs_paused + self.bgs_play(bgs) + @bgs_paused = false + end + end + + def bgs_stop + @bgs_position = 0 + @playing_bgs = nil + Audio.bgs_stop + end + + def bgs_fade(time) + @bgs_position = 0 + @playing_bgs = nil + Audio.bgs_fade((time*1000).floor) + end + + def playing_bgs + return @playing_bgs + end + + def bgs_memorize + @memorized_bgs = @playing_bgs + end + + def bgs_restore + bgs_play(@memorized_bgs) + end + + def getPlayingBGS + return (@playing_bgs) ? @playing_bgs.clone : nil + end + +################################################################################ + + def se_play(se) + se = RPG::AudioFile.new(se) if se.is_a?(String) + if se!=nil and se.name!="" && FileTest.audio_exist?("Audio/SE/"+se.name) + vol = se.volume + vol *= $PokemonSystem.sevolume/100.0 + vol = vol.to_i + Audio.se_play("Audio/SE/"+se.name,vol,se.pitch) + end + end + + def se_stop + Audio.se_stop + end + +################################################################################ + + def battle_bgm + return (@battle_bgm) ? @battle_bgm : $data_system.battle_bgm + end + + def battle_bgm=(battle_bgm) + @battle_bgm = battle_bgm + end + + def battle_end_me + return (@battle_end_me) ? @battle_end_me : $data_system.battle_end_me + end + + def battle_end_me=(battle_end_me) + @battle_end_me = battle_end_me + end + +################################################################################ + + def windowskin_name + if @windowskin_name==nil + return $data_system.windowskin_name + else + return @windowskin_name + end + end + + def windowskin_name=(windowskin_name) + @windowskin_name = windowskin_name + end + + def update + @timer -= 1 if @timer_working and @timer>0 + if Input.trigger?(Input::F5) && pbCurrentEventCommentInput(1,"Cut Scene") + event = @map_interpreter.get_character(0) + @map_interpreter.pbSetSelfSwitch(event.id,"A",true) + @map_interpreter.command_end + event.start + end + end +end \ No newline at end of file diff --git a/Data/Scripts/003_Game classes/003_Game_Picture.rb b/Data/Scripts/003_Game classes/003_Game_Picture.rb new file mode 100644 index 000000000..6e30c3d13 --- /dev/null +++ b/Data/Scripts/003_Game classes/003_Game_Picture.rb @@ -0,0 +1,156 @@ +#=============================================================================== +# ** Game_Picture +#------------------------------------------------------------------------------- +# This class handles the picture. It's used within the Game_Screen class +# ($game_screen). +#=============================================================================== + +class Game_Picture + #----------------------------------------------------------------------------- + # * Public Instance Variables + #----------------------------------------------------------------------------- + attr_reader :number # picture number + attr_reader :name # file name + attr_reader :origin # starting point + attr_reader :x # x-coordinate + attr_reader :y # y-coordinate + attr_reader :zoom_x # x directional zoom rate + attr_reader :zoom_y # y directional zoom rate + attr_reader :opacity # opacity level + attr_reader :blend_type # blend method + attr_reader :tone # color tone + attr_reader :angle # rotation angle + #----------------------------------------------------------------------------- + # * Object Initialization + # number : picture number + #----------------------------------------------------------------------------- + def initialize(number) + @number = number + @name = "" + @origin = 0 + @x = 0.0 + @y = 0.0 + @zoom_x = 100.0 + @zoom_y = 100.0 + @opacity = 255.0 + @blend_type = 1 + @duration = 0 + @target_x = @x + @target_y = @y + @target_zoom_x = @zoom_x + @target_zoom_y = @zoom_y + @target_opacity = @opacity + @tone = Tone.new(0, 0, 0, 0) + @tone_target = Tone.new(0, 0, 0, 0) + @tone_duration = 0 + @angle = 0 + @rotate_speed = 0 + end + #----------------------------------------------------------------------------- + # * Show Picture + # name : file name + # origin : starting point + # x : x-coordinate + # y : y-coordinate + # zoom_x : x directional zoom rate + # zoom_y : y directional zoom rate + # opacity : opacity level + # blend_type : blend method + #----------------------------------------------------------------------------- + def show(name, origin, x, y, zoom_x, zoom_y, opacity, blend_type) + @name = name + @origin = origin + @x = x.to_f + @y = y.to_f + @zoom_x = zoom_x.to_f + @zoom_y = zoom_y.to_f + @opacity = opacity.to_f + @blend_type = blend_type ? blend_type : 0 + @duration = 0 + @target_x = @x + @target_y = @y + @target_zoom_x = @zoom_x + @target_zoom_y = @zoom_y + @target_opacity = @opacity + @tone = Tone.new(0, 0, 0, 0) + @tone_target = Tone.new(0, 0, 0, 0) + @tone_duration = 0 + @angle = 0 + @rotate_speed = 0 + end + #----------------------------------------------------------------------------- + # * Move Picture + # duration : time + # origin : starting point + # x : x-coordinate + # y : y-coordinate + # zoom_x : x directional zoom rate + # zoom_y : y directional zoom rate + # opacity : opacity level + # blend_type : blend method + #----------------------------------------------------------------------------- + def move(duration, origin, x, y, zoom_x, zoom_y, opacity, blend_type) + @duration = duration + @origin = origin + @target_x = x.to_f + @target_y = y.to_f + @target_zoom_x = zoom_x.to_f + @target_zoom_y = zoom_y.to_f + @target_opacity = opacity.to_f + @blend_type = blend_type ? blend_type : 0 + end + #----------------------------------------------------------------------------- + # * Change Rotation Speed + # speed : rotation speed + #----------------------------------------------------------------------------- + def rotate(speed) + @rotate_speed = speed + end + #----------------------------------------------------------------------------- + # * Start Change of Color Tone + # tone : color tone + # duration : time + #----------------------------------------------------------------------------- + def start_tone_change(tone, duration) + @tone_target = tone.clone + @tone_duration = duration + if @tone_duration == 0 + @tone = @tone_target.clone + end + end + #----------------------------------------------------------------------------- + # * Erase Picture + #----------------------------------------------------------------------------- + def erase + @name = "" + end + #----------------------------------------------------------------------------- + # * Frame Update + #----------------------------------------------------------------------------- + def update + if @duration >= 1 + d = @duration + @x = (@x * (d - 1) + @target_x) / d + @y = (@y * (d - 1) + @target_y) / d + @zoom_x = (@zoom_x * (d - 1) + @target_zoom_x) / d + @zoom_y = (@zoom_y * (d - 1) + @target_zoom_y) / d + @opacity = (@opacity * (d - 1) + @target_opacity) / d + @duration -= 1 + end + if @tone_duration >= 1 + d = @tone_duration + @tone.red = (@tone.red * (d - 1) + @tone_target.red) / d + @tone.green = (@tone.green * (d - 1) + @tone_target.green) / d + @tone.blue = (@tone.blue * (d - 1) + @tone_target.blue) / d + @tone.gray = (@tone.gray * (d - 1) + @tone_target.gray) / d + @tone_duration -= 1 + end + if @rotate_speed != 0 + @angle += @rotate_speed / 2.0 + while @angle < 0 + @angle += 360 + end + @angle %= 360 + end + end +end \ No newline at end of file diff --git a/Data/Scripts/003_Game classes/004_Game_CommonEvent.rb b/Data/Scripts/003_Game classes/004_Game_CommonEvent.rb new file mode 100644 index 000000000..978291c46 --- /dev/null +++ b/Data/Scripts/003_Game classes/004_Game_CommonEvent.rb @@ -0,0 +1,81 @@ +#=============================================================================== +# ** Game_CommonEvent +#------------------------------------------------------------------------------- +# This class handles common events. It includes execution of parallel process +# event. This class is used within the Game_Map class ($game_map). +#=============================================================================== +class Game_CommonEvent + #----------------------------------------------------------------------------- + # * Object Initialization + # common_event_id : common event ID + #----------------------------------------------------------------------------- + def initialize(common_event_id) + @common_event_id = common_event_id + @interpreter = nil + refresh + end + #----------------------------------------------------------------------------- + # * Get Name + #----------------------------------------------------------------------------- + def name + return $data_common_events[@common_event_id].name + end + #----------------------------------------------------------------------------- + # * Get Trigger + #----------------------------------------------------------------------------- + def trigger + return $data_common_events[@common_event_id].trigger + end + #----------------------------------------------------------------------------- + # * Get Condition Switch ID + #----------------------------------------------------------------------------- + def switch_id + return $data_common_events[@common_event_id].switch_id + end + #----------------------------------------------------------------------------- + # * Get List of Event Commands + #----------------------------------------------------------------------------- + def list + return $data_common_events[@common_event_id].list + end + #----------------------------------------------------------------------------- + # * Checks if switch is on + #----------------------------------------------------------------------------- + def switchIsOn?(id) + switchName = $data_system.switches[id] + return false if !switchName + if switchName[/^s\:/] + return eval($~.post_match) + else + return $game_switches[id] + end + end + #----------------------------------------------------------------------------- + # * Refresh + #----------------------------------------------------------------------------- + def refresh + # Create an interpreter for parallel process if necessary + if self.trigger == 2 and switchIsOn?(self.switch_id) + if @interpreter == nil + @interpreter = Interpreter.new + end + else + @interpreter = nil + end + end + #----------------------------------------------------------------------------- + # * Frame Update + #----------------------------------------------------------------------------- + def update + # If parallel process is valid + if @interpreter != nil + # If not running + unless @interpreter.running? + # Set up event + @interpreter.setup(self.list, 0) + end + # Update interpreter + @interpreter.update + end + end +end \ No newline at end of file diff --git a/Data/Scripts/003_Game classes/005_Game_Character.rb b/Data/Scripts/003_Game classes/005_Game_Character.rb new file mode 100644 index 000000000..1ec45ca10 --- /dev/null +++ b/Data/Scripts/003_Game classes/005_Game_Character.rb @@ -0,0 +1,869 @@ +class Game_Character + attr_reader :id + attr_reader :original_x + attr_reader :original_y + attr_reader :x + attr_reader :y + attr_reader :real_x + attr_reader :real_y + attr_accessor :sprite_size + attr_reader :tile_id + attr_accessor :character_name + attr_accessor :character_hue + attr_reader :opacity + attr_reader :blend_type + attr_reader :direction + attr_accessor :pattern + attr_reader :pattern_surf + attr_accessor :lock_pattern + attr_reader :move_route_forcing + attr_accessor :through + attr_accessor :animation_id + attr_accessor :transparent + attr_reader :move_speed + attr_accessor :walk_anime + attr_accessor :bob_height + + def initialize(map=nil) + @map = map + @id = 0 + @original_x = 0 + @original_y = 0 + @x = 0 + @y = 0 + @real_x = 0 + @real_y = 0 + @sprite_size = [Game_Map::TILE_WIDTH,Game_Map::TILE_HEIGHT] + @tile_id = 0 + @character_name = "" + @character_hue = 0 + @opacity = 255 + @blend_type = 0 + @direction = 2 + @pattern = 0 + @pattern_surf = 0 + @lock_pattern = false + @move_route_forcing = false + @through = false + @animation_id = 0 + @transparent = false + @original_direction = 2 + @original_pattern = 0 + @move_type = 0 + self.move_speed = 4 + self.move_frequency = 6 + @move_route = nil + @move_route_index = 0 + @original_move_route = nil + @original_move_route_index = 0 + @walk_anime = true # Whether character should animate while moving + @step_anime = false # Whether character should animate while still + @direction_fix = false + @always_on_top = false + @anime_count = 0 + @stop_count = 0 + @jump_count = 0 + @jump_peak = 0 + @bob_height = 0 + @wait_count = 0 + @moved_this_frame = false + @locked = false + @prelock_direction = 0 + end + + def move_speed=(val) + return if val==@move_speed + @move_speed = val + # @move_speed_real is the number of quarter-pixels to move each frame. There + # are 128 quarter-pixels per tile. By default, it is calculated from + # @move_speed and has these values (assuming 40 fps): + # 1 => 1.6 # 80 frames per tile + # 2 => 3.2 # 40 frames per tile + # 3 => 6.4 # 20 frames per tile + # 4 => 12.8 # 10 frames per tile - walking speed + # 5 => 25.6 # 5 frames per tile - running speed (2x walking speed) + # 6 => 32 # 4 frames per tile - cycling speed (1.25x running speed) + self.move_speed_real = (val == 6) ? 32 : (1 << val) * 0.8 + end + + def move_speed_real + self.move_speed = @move_speed if !@move_speed_real + return @move_speed_real + end + + def move_speed_real=(val) + @move_speed_real = val * 40.0 / Graphics.frame_rate + end + + def move_frequency=(val) + return if val==@move_frequency + @move_frequency = val + # @move_frequency_real is the number of frames to wait between each action + # in a move route (not forced). Specifically, this is the number of frames + # to wait after the character stops moving because of the previous action. + # By default, it is calculated from @move_frequency and has these values + # (assuming 40 fps): + # 1 => 190 # 4.75 seconds + # 2 => 144 # 3.6 seconds + # 3 => 102 # 2.55 seconds + # 4 => 64 # 1.6 seconds + # 5 => 30 # 0.75 seconds + # 6 => 0 # 0 seconds, i.e. continuous movement + self.move_frequency_real = (40 - val * 2) * (6 - val) + end + + def move_frequency_real + self.move_frequency = @move_frequency if !@move_frequency_real + return @move_frequency_real + end + + def move_frequency_real=(val) + @move_frequency_real = val * Graphics.frame_rate / 40.0 + end + + def bob_height + @bob_height = 0 if !@bob_height + return @bob_height + end + + def lock + return if @locked + @prelock_direction = 0 # Was @direction but disabled + turn_toward_player + @locked = true + end + + def minilock + @prelock_direction = 0 # Was @direction but disabled + @locked = true + end + + def lock? + return @locked + end + + def unlock + return unless @locked + @locked = false + @direction = @prelock_direction if !@direction_fix and @prelock_direction != 0 + end + + #============================================================================= + # Information from map data + #============================================================================= + def map + return (@map) ? @map : $game_map + end + + def terrain_tag + return self.map.terrain_tag(@x, @y) + end + + def bush_depth + return 0 if @tile_id > 0 or @always_on_top or @jump_count > 0 + xbehind = @x + (@direction==4 ? 1 : @direction==6 ? -1 : 0) + ybehind = @y + (@direction==8 ? 1 : @direction==2 ? -1 : 0) + return Game_Map::TILE_HEIGHT if self.map.deepBush?(@x, @y) and self.map.deepBush?(xbehind, ybehind) + return 12 if !moving? and self.map.bush?(@x, @y) + return 0 + end + + #============================================================================= + # Passability + #============================================================================= + def passableEx?(x, y, d, strict=false) + new_x = x + (d == 6 ? 1 : d == 4 ? -1 : 0) + new_y = y + (d == 2 ? 1 : d == 8 ? -1 : 0) + return false unless self.map.valid?(new_x, new_y) + return true if @through + if strict + return false unless self.map.passableStrict?(x, y, d, self) + return false unless self.map.passableStrict?(new_x, new_y, 10 - d, self) + else + return false unless self.map.passable?(x, y, d, self) + return false unless self.map.passable?(new_x, new_y, 10 - d, self) + end + for event in self.map.events.values + next if event.x != new_x || event.y != new_y || event.through + return false if self != $game_player || event.character_name != "" + end + if $game_player.x == new_x and $game_player.y == new_y + return false if !$game_player.through && @character_name != "" + end + return true + end + + def passable?(x,y,d) + return passableEx?(x,y,d,false) + end + + def passableStrict?(x,y,d) + return passableEx?(x,y,d,true) + end + + #============================================================================= + # Screen position of the character + #============================================================================= + def screen_x + ret = ((@real_x - self.map.display_x) / Game_Map::X_SUBPIXELS).round + ret += Game_Map::TILE_WIDTH/2 + return ret + end + + def screen_y_ground + ret = ((@real_y - self.map.display_y) / Game_Map::Y_SUBPIXELS).round + ret += Game_Map::TILE_HEIGHT + return ret + end + + def screen_y + ret = screen_y_ground + if jumping? + n = ((2 * @jump_count * 20 / Graphics.frame_rate) - @jump_peak).abs + return ret - (@jump_peak * @jump_peak - n * n) / 2 + end + return ret + end + + def screen_z(height = 0) + return 999 if @always_on_top + z = screen_y_ground + if @tile_id > 0 + begin + return z + self.map.priorities[@tile_id] * 32 + rescue + raise "Event's graphic is an out-of-range tile (event #{@id}, map #{self.map.map_id})" + end + end + # Add z if height exceeds 32 + return z + ((height > Game_Map::TILE_HEIGHT) ? Game_Map::TILE_HEIGHT - 1 : 0) + end + + #============================================================================= + # Movement + #============================================================================= + def moving? + return @real_x != @x * Game_Map::REAL_RES_X || + @real_y != @y * Game_Map::REAL_RES_Y + end + + def jumping? + return @jump_count > 0 + end + + def straighten + @pattern = 0 if @walk_anime or @step_anime + @anime_count = 0 + @prelock_direction = 0 + end + + def force_move_route(move_route) + if @original_move_route == nil + @original_move_route = @move_route + @original_move_route_index = @move_route_index + end + @move_route = move_route + @move_route_index = 0 + @move_route_forcing = true + @prelock_direction = 0 + @wait_count = 0 + move_type_custom + end + + def moveto(x, y) + @x = x % self.map.width + @y = y % self.map.height + @real_x = @x * Game_Map::REAL_RES_X + @real_y = @y * Game_Map::REAL_RES_Y + @prelock_direction = 0 + triggerLeaveTile + end + + def triggerLeaveTile + if @oldX && @oldY && @oldMap && + (@oldX!=self.x || @oldY!=self.y || @oldMap!=self.map.map_id) + Events.onLeaveTile.trigger(self,self,@oldMap,@oldX,@oldY) + end + @oldX = self.x + @oldY = self.y + @oldMap = self.map.map_id + end + + def increase_steps + @stop_count = 0 + triggerLeaveTile + end + + #============================================================================= + # Movement commands + #============================================================================= + def move_type_random + case rand(6) + when 0..3; move_random + when 4; move_forward + when 5; @stop_count = 0 + end + end + + def move_type_toward_player + sx = @x - $game_player.x + sy = @y - $game_player.y + if sx.abs + sy.abs >= 20 + move_random + return + end + case rand(6) + when 0..3; move_toward_player + when 4; move_random + when 5; move_forward + end + end + + def move_type_custom + return if jumping? or moving? + while @move_route_index < @move_route.list.size + command = @move_route.list[@move_route_index] + if command.code == 0 + if @move_route.repeat + @move_route_index = 0 + else + if @move_route_forcing + @move_route_forcing = false + @move_route = @original_move_route + @move_route_index = @original_move_route_index + @original_move_route = nil + end + @stop_count = 0 + end + return + end + if command.code <= 14 + case command.code + when 1; move_down + when 2; move_left + when 3; move_right + when 4; move_up + when 5; move_lower_left + when 6; move_lower_right + when 7; move_upper_left + when 8; move_upper_right + when 9; move_random + when 10; move_toward_player + when 11; move_away_from_player + when 12; move_forward + when 13; move_backward + when 14; jump(command.parameters[0], command.parameters[1]) + end + @move_route_index += 1 if @move_route.skippable or moving? or jumping? + return + end + if command.code == 15 # Wait + @wait_count = (command.parameters[0] * Graphics.frame_rate / 20) - 1 + @move_route_index += 1 + return + end + if command.code >= 16 and command.code <= 26 + case command.code + when 16; turn_down + when 17; turn_left + when 18; turn_right + when 19; turn_up + when 20; turn_right_90 + when 21; turn_left_90 + when 22; turn_180 + when 23; turn_right_or_left_90 + when 24; turn_random + when 25; turn_toward_player + when 26; turn_away_from_player + end + @move_route_index += 1 + return + end + if command.code >= 27 + case command.code + when 27 + $game_switches[command.parameters[0]] = true + self.map.need_refresh = true + when 28 + $game_switches[command.parameters[0]] = false + self.map.need_refresh = true + when 29; self.move_speed = command.parameters[0] + when 30; self.move_frequency = command.parameters[0] + when 31; @walk_anime = true + when 32; @walk_anime = false + when 33; @step_anime = true + when 34; @step_anime = false + when 35; @direction_fix = true + when 36; @direction_fix = false + when 37; @through = true + when 38; @through = false + when 39; @always_on_top = true + when 40; @always_on_top = false + when 41 + @tile_id = 0 + @character_name = command.parameters[0] + @character_hue = command.parameters[1] + if @original_direction != command.parameters[2] + @direction = command.parameters[2] + @original_direction = @direction + @prelock_direction = 0 + end + if @original_pattern != command.parameters[3] + @pattern = command.parameters[3] + @original_pattern = @pattern + end + when 42; @opacity = command.parameters[0] + when 43; @blend_type = command.parameters[0] + when 44; pbSEPlay(command.parameters[0]) + when 45; result = eval(command.parameters[0]) + end + @move_route_index += 1 + end + end + end + + def move_up(turn_enabled = true) + turn_up if turn_enabled + if passable?(@x, @y, 8) + turn_up + @y -= 1 + increase_steps + else + check_event_trigger_touch(@x, @y-1) + end + end + + def move_down(turn_enabled = true) + turn_down if turn_enabled + if passable?(@x, @y, 2) + turn_down + @y += 1 + increase_steps + else + check_event_trigger_touch(@x, @y+1) + end + end + + def move_left(turn_enabled = true) + turn_left if turn_enabled + if passable?(@x, @y, 4) + turn_left + @x -= 1 + increase_steps + else + check_event_trigger_touch(@x-1, @y) + end + end + + def move_right(turn_enabled = true) + turn_right if turn_enabled + if passable?(@x, @y, 6) + turn_right + @x += 1 + increase_steps + else + check_event_trigger_touch(@x+1, @y) + end + end + + def move_upper_left + unless @direction_fix + @direction = (@direction == 6 ? 4 : @direction == 2 ? 8 : @direction) + end + if (passable?(@x, @y, 8) and passable?(@x, @y - 1, 4)) or + (passable?(@x, @y, 4) and passable?(@x - 1, @y, 8)) + @x -= 1 + @y -= 1 + increase_steps + end + end + + def move_upper_right + unless @direction_fix + @direction = (@direction == 4 ? 6 : @direction == 2 ? 8 : @direction) + end + if (passable?(@x, @y, 8) and passable?(@x, @y - 1, 6)) or + (passable?(@x, @y, 6) and passable?(@x + 1, @y, 8)) + @x += 1 + @y -= 1 + increase_steps + end + end + + def move_lower_left + unless @direction_fix + @direction = (@direction == 6 ? 4 : @direction == 8 ? 2 : @direction) + end + if (passable?(@x, @y, 2) and passable?(@x, @y + 1, 4)) or + (passable?(@x, @y, 4) and passable?(@x - 1, @y, 2)) + @x -= 1 + @y += 1 + increase_steps + end + end + + def move_lower_right + unless @direction_fix + @direction = (@direction == 4 ? 6 : @direction == 8 ? 2 : @direction) + end + if (passable?(@x, @y, 2) and passable?(@x, @y + 1, 6)) or + (passable?(@x, @y, 6) and passable?(@x + 1, @y, 2)) + @x += 1 + @y += 1 + increase_steps + end + end + + def moveLeft90 # anticlockwise + case self.direction + when 2; move_right # down + when 4; move_down # left + when 6; move_up # right + when 8; move_left # up + end + end + + def moveRight90 # clockwise + case self.direction + when 2; move_left # down + when 4; move_up # left + when 6; move_down # right + when 8; move_right # up + end + end + + def move_random + case rand(4) + when 0; move_down(false) + when 1; move_left(false) + when 2; move_right(false) + when 3; move_up(false) + end + end + + def move_random_range(xrange=-1,yrange=-1) + dirs = [] # 0=down, 1=left, 2=right, 3=up + if xrange<0 + dirs.push(1); dirs.push(2) + elsif xrange>0 + dirs.push(1) if @x > @original_x - xrange + dirs.push(2) if @x < @original_x + xrange + end + if yrange<0 + dirs.push(0); dirs.push(3) + elsif yrange>0 + dirs.push(0) if @y < @original_y + yrange + dirs.push(3) if @y > @original_y - yrange + end + return if dirs.length==0 + case dirs[rand(dirs.length)] + when 0; move_down(false) + when 1; move_left(false) + when 2; move_right(false) + when 3; move_up(false) + end + end + + def move_random_UD(range=-1) + move_random_range(0,range) + end + + def move_random_LR(range=-1) + move_random_range(range,0) + end + + def move_toward_player + sx = @x - $game_player.x + sy = @y - $game_player.y + return if sx == 0 and sy == 0 + abs_sx = sx.abs + abs_sy = sy.abs + if abs_sx == abs_sy + (rand(2) == 0) ? abs_sx += 1 : abs_sy += 1 + end + if abs_sx > abs_sy + (sx > 0) ? move_left : move_right + if not moving? and sy != 0 + (sy > 0) ? move_up : move_down + end + else + (sy > 0) ? move_up : move_down + if not moving? and sx != 0 + (sx > 0) ? move_left : move_right + end + end + end + + def move_away_from_player + sx = @x - $game_player.x + sy = @y - $game_player.y + return if sx == 0 and sy == 0 + abs_sx = sx.abs + abs_sy = sy.abs + if abs_sx == abs_sy + (rand(2) == 0) ? abs_sx += 1 : abs_sy += 1 + end + if abs_sx > abs_sy + (sx > 0) ? move_right : move_left + if not moving? and sy != 0 + (sy > 0) ? move_down : move_up + end + else + (sy > 0) ? move_down : move_up + if not moving? and sx != 0 + (sx > 0) ? move_right : move_left + end + end + end + + def move_forward + case @direction + when 2; move_down(false) + when 4; move_left(false) + when 6; move_right(false) + when 8; move_up(false) + end + end + + def move_backward + last_direction_fix = @direction_fix + @direction_fix = true + case @direction + when 2; move_up(false) + when 4; move_right(false) + when 6; move_left(false) + when 8; move_down(false) + end + @direction_fix = last_direction_fix + end + + def jump(x_plus, y_plus) + if x_plus != 0 or y_plus != 0 + if x_plus.abs > y_plus.abs + (x_plus < 0) ? turn_left : turn_right + else + (y_plus < 0) ? turn_up : turn_down + end + end + new_x = @x + x_plus + new_y = @y + y_plus + if (x_plus == 0 and y_plus == 0) || passable?(new_x, new_y, 0) + straighten + @x = new_x + @y = new_y + distance = [4, x_plus * x_plus + y_plus * y_plus].max + @jump_peak = (6 + distance - move_speed).floor + @jump_count = @jump_peak * Graphics.frame_rate / 20 + @stop_count = 0 + if self.is_a?(Game_Player) + $PokemonTemp.dependentEvents.pbMoveDependentEvents + end + triggerLeaveTile + end + end + + def jumpForward + case self.direction + when 2; jump(0,1) # down + when 4; jump(-1,0) # left + when 6; jump(1,0) # right + when 8; jump(0,-1) # up + end + end + + def jumpBackward + case self.direction + when 2; jump(0,-1) # down + when 4; jump(1,0) # left + when 6; jump(-1,0) # right + when 8; jump(0,1) # up + end + end + + def turnGeneric(dir) + return if @direction_fix + oldDirection = @direction + @direction = dir + @stop_count = 0 + pbCheckEventTriggerAfterTurning if dir != oldDirection + end + + def turn_up; turnGeneric(8); end + def turn_down; turnGeneric(2); end + def turn_left; turnGeneric(4); end + def turn_right; turnGeneric(6); end + + def turn_right_90 + case @direction + when 2; turn_left + when 4; turn_up + when 6; turn_down + when 8; turn_right + end + end + + def turn_left_90 + case @direction + when 2; turn_right + when 4; turn_down + when 6; turn_up + when 8; turn_left + end + end + + def turn_180 + case @direction + when 2; turn_up + when 4; turn_right + when 6; turn_left + when 8; turn_down + end + end + + def turn_right_or_left_90 + (rand(2) == 0) ? turn_right_90 : turn_left_90 + end + + def turn_random + case rand(4) + when 0; turn_up + when 1; turn_right + when 2; turn_left + when 3; turn_down + end + end + + def turn_toward_player + sx = @x - $game_player.x + sy = @y - $game_player.y + return if sx == 0 and sy == 0 + if sx.abs > sy.abs + (sx > 0) ? turn_left : turn_right + else + (sy > 0) ? turn_up : turn_down + end + end + + def turn_away_from_player + sx = @x - $game_player.x + sy = @y - $game_player.y + return if sx == 0 and sy == 0 + if sx.abs > sy.abs + (sx > 0) ? turn_right : turn_left + else + (sy > 0) ? turn_down : turn_up + end + end + + #============================================================================= + # Updating + #============================================================================= + def update + @moved_last_frame = @moved_this_frame + if !$game_temp.in_menu + # Update command + update_command + # Update movement + if jumping?; update_jump + elsif moving?; update_move + else; update_stop + end + end + # Update animation + update_pattern + end + + def update_command + if @wait_count > 0 + @wait_count -= 1 + elsif @move_route_forcing + move_type_custom + elsif !@starting && !lock? && !moving? && !jumping? + update_command_new + end + end + + def update_command_new + # @stop_count is the number of frames since the last movement finished. + # @move_frequency has these values: + # 1 => @stop_count > 190 # 4.75 seconds + # 2 => @stop_count > 144 # 3.6 seconds + # 3 => @stop_count > 102 # 2.55 seconds + # 4 => @stop_count > 64 # 1.6 seconds + # 5 => @stop_count > 30 # 0.75 seconds + # 6 => @stop_count > 0 # 0 seconds + if @stop_count >= self.move_frequency_real + case @move_type + when 1; move_type_random + when 2; move_type_toward_player + when 3; move_type_custom + end + end + end + + def update_jump + @jump_count -= 1 + @real_x = (@real_x * @jump_count + @x * Game_Map::REAL_RES_X) / (@jump_count + 1) + @real_y = (@real_y * @jump_count + @y * Game_Map::REAL_RES_Y) / (@jump_count + 1) + @moved_this_frame = true + # End of a jump, so perform events that happen at this time + Events.onStepTakenFieldMovement.trigger(self,self) if !jumping? && !moving? + end + + def update_move + # Move the character (the 0.1 catches rounding errors) + distance = move_speed_real + dest_x = @x * Game_Map::REAL_RES_X + dest_y = @y * Game_Map::REAL_RES_Y + if @real_x < dest_x + @real_x += distance + @real_x = dest_x if @real_x > dest_x - 0.1 + else + @real_x -= distance + @real_x = dest_x if @real_x < dest_x + 0.1 + end + if @real_y < dest_y + @real_y += distance + @real_y = dest_y if @real_y > dest_y - 0.1 + else + @real_y -= distance + @real_y = dest_y if @real_y < dest_y + 0.1 + end + # End of a step, so perform events that happen at this time + Events.onStepTakenFieldMovement.trigger(self,self) if !jumping? && !moving? + # Increment animation counter + @anime_count += 1 if @walk_anime || @step_anime + @moved_this_frame = true + end + + def update_stop + @anime_count += 1 if @step_anime + @stop_count += 1 if !@starting && !lock? + @moved_this_frame = false + end + + def update_pattern + return if @lock_pattern + # Character has stopped moving, return to original pattern + if @moved_last_frame && !@moved_this_frame && !@step_anime + @pattern = @original_pattern + @anime_count = 0 + return + end + # Character has started to move, change pattern immediately + if !@moved_last_frame && @moved_this_frame && !jumping? && !@step_anime + @pattern = (@pattern + 1) % 4 if @walk_anime + @anime_count = 0 + return + end + # Calculate how many frames each pattern should display for, i.e. the time + # it takes to move half a tile (or a whole tile if cycling). We assume the + # game uses square tiles. + frames_per_pattern = Game_Map::REAL_RES_X / (move_speed_real * 2.0) + frames_per_pattern *= 2 if move_speed == 6 # Cycling/fastest speed + return if @anime_count < frames_per_pattern + # Advance to the next animation frame + @pattern = (@pattern + 1) % 4 + @anime_count -= frames_per_pattern + end +end \ No newline at end of file diff --git a/Data/Scripts/003_Game classes/006_Game_Event.rb b/Data/Scripts/003_Game classes/006_Game_Event.rb new file mode 100644 index 000000000..52d8c9ad9 --- /dev/null +++ b/Data/Scripts/003_Game classes/006_Game_Event.rb @@ -0,0 +1,247 @@ +class Game_Event < Game_Character + attr_reader :map_id + attr_reader :trigger + attr_reader :list + attr_reader :starting + attr_reader :tempSwitches # Temporary self-switches + attr_accessor :need_refresh + + def initialize(map_id, event, map=nil) + super(map) + @map_id = map_id + @event = event + @id = @event.id + @original_x = @event.x + @original_y = @event.y + @erased = false + @starting = false + @need_refresh = false + @route_erased = false + @through = true + @to_update = true + @tempSwitches = {} + moveto(@event.x, @event.y) if map + refresh + end + + def id; return @event.id; end + def name; return @event.name; end + + def clear_starting + @starting = false + end + + def start + @starting = true if @list.size > 1 + end + + def erase + @erased = true + refresh + end + + def erase_route + @route_erased = true + refresh + end + + def tsOn?(c) + return @tempSwitches && @tempSwitches[c]==true + end + + def tsOff?(c) + return !@tempSwitches || !@tempSwitches[c] + end + + def setTempSwitchOn(c) + @tempSwitches[c]=true + refresh + end + + def setTempSwitchOff(c) + @tempSwitches[c]=false + refresh + end + + def isOff?(c) + return !$game_self_switches[[@map_id,@event.id,c]] + end + + def switchIsOn?(id) + switchname = $data_system.switches[id] + return false if !switchname + if switchname[/^s\:/] + return eval($~.post_match) + else + return $game_switches[id] + end + end + + def variable + return nil if !$PokemonGlobal.eventvars + return $PokemonGlobal.eventvars[[@map_id,@event.id]] + end + + def setVariable(variable) + $PokemonGlobal.eventvars[[@map_id,@event.id]]=variable + end + + def varAsInt + return 0 if !$PokemonGlobal.eventvars + return $PokemonGlobal.eventvars[[@map_id,@event.id]].to_i + end + + def expired?(secs=86400) + ontime=self.variable + time=pbGetTimeNow + return ontime && (time.to_i>ontime+secs) + end + + def expiredDays?(days=1) + ontime=self.variable.to_i + return false if !ontime + now=pbGetTimeNow + elapsed=(now.to_i-ontime)/86400 + elapsed+=1 if (now.to_i-ontime)%86400>(now.hour*3600+now.min*60+now.sec) + return elapsed>=days + end + + def onEvent? + return @map_id==$game_map.map_id && + $game_player.x==self.x && $game_player.y==self.y + end + + def over_trigger? + return false if @character_name!="" and not @through + return false if @event.name[/hiddenitem/i] + return false if !self.map.passable?(@x, @y, 0, $game_player) + return true + end + + def pbCheckEventTriggerAfterTurning + return if $game_system.map_interpreter.running? || @starting + if @event.name[/trainer\((\d+)\)/i] + distance = $~[1].to_i + if @trigger==2 && pbEventCanReachPlayer?(self,$game_player,distance) + start if !jumping? && !over_trigger? + end + end + end + + def check_event_trigger_touch(x, y) + return if $game_system.map_interpreter.running? + return if @trigger!=2 + return if x != $game_player.x || y != $game_player.y + return if jumping? || over_trigger? + start + end + + def check_event_trigger_auto + if @trigger == 2 # Event touch + if @x == $game_player.x and @y == $game_player.y + start if not jumping? and over_trigger? + end + elsif @trigger == 3 # Autorun + start + end + end + + def refresh + new_page = nil + unless @erased + for page in @event.pages.reverse + c = page.condition + next if c.switch1_valid && !switchIsOn?(c.switch1_id) + next if c.switch2_valid && !switchIsOn?(c.switch2_id) + next if c.variable_valid && $game_variables[c.variable_id] < c.variable_value + if c.self_switch_valid + key = [@map_id, @event.id, c.self_switch_ch] + next if $game_self_switches[key] != true + end + new_page = page + break + end + end + return if new_page == @page + @page = new_page + clear_starting + if @page == nil + @tile_id = 0 + @character_name = "" + @character_hue = 0 + @move_type = 0 + @through = true + @trigger = nil + @list = nil + @interpreter = nil + return + end + @tile_id = @page.graphic.tile_id + @character_name = @page.graphic.character_name + @character_hue = @page.graphic.character_hue + if @original_direction != @page.graphic.direction + @direction = @page.graphic.direction + @original_direction = @direction + @prelock_direction = 0 + end + if @original_pattern != @page.graphic.pattern + @pattern = @page.graphic.pattern + @original_pattern = @pattern + end + @opacity = @page.graphic.opacity + @blend_type = @page.graphic.blend_type + @move_type = @page.move_type + self.move_speed = @page.move_speed + self.move_frequency = @page.move_frequency + @move_route = (@route_erased) ? RPG::MoveRoute.new : @page.move_route + @move_route_index = 0 + @move_route_forcing = false + @walk_anime = @page.walk_anime + @step_anime = @page.step_anime + @direction_fix = @page.direction_fix + @through = @page.through + @always_on_top = @page.always_on_top + @trigger = @page.trigger + @list = @page.list + @interpreter = nil + if @trigger == 4 # Parallel Process + @interpreter = Interpreter.new + end + check_event_trigger_auto + end + + def should_update?(recalc=false) + return @to_update if !recalc + return true if $PokemonSystem.tilemap==2 + return true if @trigger && (@trigger == 3 || @trigger == 4) + return true if @move_route_forcing + return true if @event.name[/update/i] + range = 2 # Number of tiles + return false if self.screen_x - @sprite_size[0]/2 > Graphics.width + range * Game_Map::TILE_WIDTH + return false if self.screen_x + @sprite_size[0]/2 < -range * Game_Map::TILE_WIDTH + return false if self.screen_y_ground - @sprite_size[1] > Graphics.height + range * Game_Map::TILE_HEIGHT + return false if self.screen_y_ground < -range * Game_Map::TILE_HEIGHT + return true + end + + def update + @to_update = should_update?(true) + return if !@to_update + last_moving = moving? + super + if !moving? && last_moving + $game_player.pbCheckEventTriggerFromDistance([2]) + end + if @need_refresh + @need_refresh = false + refresh + end + check_event_trigger_auto + if @interpreter != nil + unless @interpreter.running? + @interpreter.setup(@list, @event.id, @map_id) + end + @interpreter.update + end + end +end \ No newline at end of file diff --git a/Data/Scripts/003_Game classes/007_Game_Player.rb b/Data/Scripts/003_Game classes/007_Game_Player.rb new file mode 100644 index 000000000..e21709a56 --- /dev/null +++ b/Data/Scripts/003_Game classes/007_Game_Player.rb @@ -0,0 +1,505 @@ +#=============================================================================== +# ** Game_Player +#------------------------------------------------------------------------------- +# This class handles the player. Its functions include event starting +# determinants and map scrolling. Refer to "$game_player" for the one +# instance of this class. +#=============================================================================== +class Game_Player < Game_Character + attr_accessor :bump_se + attr_accessor :charsetData + attr_accessor :encounter_count + + def initialize(*arg) + super(*arg) + @lastdir=0 + @lastdirframe=0 + @bump_se=0 + end + + def map + @map = nil + return $game_map + end + + def bush_depth + return 0 if @tile_id > 0 or @always_on_top + xbehind = (@direction==4) ? @x+1 : (@direction==6) ? @x-1 : @x + ybehind = (@direction==8) ? @y+1 : (@direction==2) ? @y-1 : @y + # Both current tile and previous tile are on the same map; just return super + return super if $game_map.valid?(@x,@y) && $game_map.valid?(xbehind,ybehind) + # The current or the previous tile is on a different map; consult MapFactory + return 0 if !$MapFactory + # Get map and coordinates of the current tile + if $game_map.valid?(@x,@y) + heremap = self.map; herex = @x; herey = @y + else + newhere = $MapFactory.getNewMap(@x,@y) + return 0 unless newhere && newhere[0] # Map not found + heremap = newhere[0]; herex = newhere[1]; herey = newhere[2] + end + # Get map and coordinates of the previous tile + newbehind = $MapFactory.getNewMap(xbehind,ybehind) + if $game_map.valid?(xbehind,ybehind) + behindmap = self.map; behindx = xbehind; behindy = ybehind + else + return 0 unless newbehind && newbehind[0] # Map not found + behindmap = newbehind[0]; behindx = newbehind[1]; behindy = newbehind[2] + end + # Return bush depth + if @jump_count <= 0 + return 32 if heremap.deepBush?(herex, herey) && behindmap.deepBush?(behindx, behindy) + return 12 if heremap.bush?(herex, herey) && !moving? + end + return 0 + end + + def pbHasDependentEvents? + return $PokemonGlobal.dependentEvents.length>0 + end + + def bump_into_object + return if @bump_se && @bump_se>0 + pbSEPlay("Player bump") + @bump_se = Graphics.frame_rate/4 + end + + def move_down(turn_enabled = true) + turn_down if turn_enabled + if passable?(@x, @y, 2) + return if pbLedge(0,1) + return if pbEndSurf(0,1) + turn_down + @y += 1 + $PokemonTemp.dependentEvents.pbMoveDependentEvents + increase_steps + else + if !check_event_trigger_touch(@x, @y+1) + bump_into_object + end + end + end + + def move_left(turn_enabled = true) + turn_left if turn_enabled + if passable?(@x, @y, 4) + return if pbLedge(-1,0) + return if pbEndSurf(-1,0) + turn_left + @x -= 1 + $PokemonTemp.dependentEvents.pbMoveDependentEvents + increase_steps + else + if !check_event_trigger_touch(@x-1, @y) + bump_into_object + end + end + end + + def move_right(turn_enabled = true) + turn_right if turn_enabled + if passable?(@x, @y, 6) + return if pbLedge(1,0) + return if pbEndSurf(1,0) + turn_right + @x += 1 + $PokemonTemp.dependentEvents.pbMoveDependentEvents + increase_steps + else + if !check_event_trigger_touch(@x+1, @y) + bump_into_object + end + end + end + + def move_up(turn_enabled = true) + turn_up if turn_enabled + if passable?(@x, @y, 8) + return if pbLedge(0,-1) + return if pbEndSurf(0,-1) + turn_up + @y -= 1 + $PokemonTemp.dependentEvents.pbMoveDependentEvents + increase_steps + else + if !check_event_trigger_touch(@x, @y-1) + bump_into_object + end + end + end + + def pbTriggeredTrainerEvents(triggers,checkIfRunning=true) + result = [] + # If event is running + return result if checkIfRunning && $game_system.map_interpreter.running? + # All event loops + for event in $game_map.events.values + next if !event.name[/trainer\((\d+)\)/i] + distance = $~[1].to_i + # If event coordinates and triggers are consistent + if pbEventCanReachPlayer?(event,self,distance) and triggers.include?(event.trigger) + # If starting determinant is front event (other than jumping) + result.push(event) if not event.jumping? and not event.over_trigger? + end + end + return result + end + + def pbTriggeredCounterEvents(triggers,checkIfRunning=true) + result = [] + # If event is running + return result if checkIfRunning && $game_system.map_interpreter.running? + # All event loops + for event in $game_map.events.values + next if !event.name[/counter\((\d+)\)/i] + distance = $~[1].to_i + # If event coordinates and triggers are consistent + if pbEventFacesPlayer?(event,self,distance) and triggers.include?(event.trigger) + # If starting determinant is front event (other than jumping) + result.push(event) if not event.jumping? and not event.over_trigger? + end + end + return result + end + + def pbCheckEventTriggerAfterTurning + end + + def pbCheckEventTriggerFromDistance(triggers) + ret = pbTriggeredTrainerEvents(triggers) + ret.concat(pbTriggeredCounterEvents(triggers)) + return false if ret.length==0 + for event in ret + event.start + end + return true + end + + def pbFacingEvent(ignoreInterpreter=false) + return nil if $game_system.map_interpreter.running? && !ignoreInterpreter + new_x = @x + (@direction == 6 ? 1 : @direction == 4 ? -1 : 0) + new_y = @y + (@direction == 2 ? 1 : @direction == 8 ? -1 : 0) + return nil if !$game_map.valid?(new_x, new_y) + for event in $game_map.events.values + next if event.x != new_x || event.y != new_y + next if event.jumping? || event.over_trigger? + return event + end + if $game_map.counter?(new_x, new_y) + new_x += (@direction == 6 ? 1 : @direction == 4 ? -1 : 0) + new_y += (@direction == 2 ? 1 : @direction == 8 ? -1 : 0) + for event in $game_map.events.values + next if event.x != new_x || event.y != new_y + next if event.jumping? || event.over_trigger? + return event + end + end + return nil + end + + #----------------------------------------------------------------------------- + # * Passable Determinants + # x : x-coordinate + # y : y-coordinate + # d : direction (0,2,4,6,8) + # * 0 = Determines if all directions are impassable (for jumping) + #----------------------------------------------------------------------------- + def passable?(x, y, d) + # Get new coordinates + new_x = x + (d == 6 ? 1 : d == 4 ? -1 : 0) + new_y = y + (d == 2 ? 1 : d == 8 ? -1 : 0) + # If coordinates are outside of map + return false if !$game_map.validLax?(new_x, new_y) + if !$game_map.valid?(new_x, new_y) + return false if !$MapFactory + return $MapFactory.isPassableFromEdge?(new_x, new_y) + end + # If debug mode is ON and Ctrl key was pressed + return true if $DEBUG and Input.press?(Input::CTRL) + return super + end + + #----------------------------------------------------------------------------- + # * Set Map Display Position to Center of Screen + #----------------------------------------------------------------------------- + def center(x, y) + center_x = (Graphics.width/2 - Game_Map::TILE_WIDTH/2) * Game_Map::X_SUBPIXELS + center_y = (Graphics.height/2 - Game_Map::TILE_HEIGHT/2) * Game_Map::Y_SUBPIXELS + max_x = (self.map.width - Graphics.width*1.0/Game_Map::TILE_WIDTH) * Game_Map::REAL_RES_X + max_y = (self.map.height - Graphics.height*1.0/Game_Map::TILE_HEIGHT) * Game_Map::REAL_RES_Y + dispx = x * Game_Map::REAL_RES_X - center_x + dispy = y * Game_Map::REAL_RES_Y - center_y + self.map.display_x = dispx + self.map.display_y = dispy + end + + #----------------------------------------------------------------------------- + # * Move to Designated Position + # x : x-coordinate + # y : y-coordinate + #----------------------------------------------------------------------------- + def moveto(x, y) + super + # Centering + center(x, y) + # Make encounter count + make_encounter_count + end + + #----------------------------------------------------------------------------- + # * Make Encounter Count + #----------------------------------------------------------------------------- + def make_encounter_count + # Image of two dice rolling + if $game_map.map_id != 0 + n = $game_map.encounter_step + @encounter_count = rand(n) + rand(n) + 1 + end + end + + #----------------------------------------------------------------------------- + # * Refresh + #----------------------------------------------------------------------------- + def refresh + @opacity = 255 + @blend_type = 0 + end + + #----------------------------------------------------------------------------- + # * Same Position Starting Determinant + #----------------------------------------------------------------------------- + def check_event_trigger_here(triggers) + result = false + # If event is running + return result if $game_system.map_interpreter.running? + # All event loops + for event in $game_map.events.values + # If event coordinates and triggers are consistent + next if event.x != @x || event.y != @y + next if !triggers.include?(event.trigger) + # If starting determinant is same position event (other than jumping) + next if event.jumping? || !event.over_trigger? + event.start + result = true + end + return result + end + + #----------------------------------------------------------------------------- + # * Front Event Starting Determinant + #----------------------------------------------------------------------------- + def check_event_trigger_there(triggers) + result = false + # If event is running + return result if $game_system.map_interpreter.running? + # Calculate front event coordinates + new_x = @x + (@direction == 6 ? 1 : @direction == 4 ? -1 : 0) + new_y = @y + (@direction == 2 ? 1 : @direction == 8 ? -1 : 0) + return false if !$game_map.valid?(new_x, new_y) + # All event loops + for event in $game_map.events.values + # If event coordinates and triggers are consistent + next if event.x != new_x || event.y != new_y + next if !triggers.include?(event.trigger) + # If starting determinant is front event (other than jumping) + next if event.jumping? || event.over_trigger? + event.start + result = true + end + # If fitting event is not found + if result == false + # If front tile is a counter + if $game_map.counter?(new_x, new_y) + # Calculate coordinates of 1 tile further away + new_x += (@direction == 6 ? 1 : @direction == 4 ? -1 : 0) + new_y += (@direction == 2 ? 1 : @direction == 8 ? -1 : 0) + return false if !$game_map.valid?(new_x, new_y) + # All event loops + for event in $game_map.events.values + # If event coordinates and triggers are consistent + next if event.x != new_x || event.y != new_y + next if !triggers.include?(event.trigger) + # If starting determinant is front event (other than jumping) + next if event.jumping? || event.over_trigger? + event.start + result = true + end + end + end + return result + end + + #----------------------------------------------------------------------------- + # * Touch Event Starting Determinant + #----------------------------------------------------------------------------- + def check_event_trigger_touch(x, y) + result = false + # If event is running + return result if $game_system.map_interpreter.running? + # All event loops + for event in $game_map.events.values + # If event coordinates and triggers are consistent + next if event.x != x || event.y != y + if event.name[/trainer\((\d+)\)/i] + distance = $~[1].to_i + next if !pbEventCanReachPlayer?(event,self,distance) + elsif event.name[/counter\((\d+)\)/i] + distance = $~[1].to_i + next if !pbEventFacesPlayer?(event,self,distance) + end + next if ![1,2].include?(event.trigger) + # If starting determinant is front event (other than jumping) + next if event.jumping? || event.over_trigger? + event.start + result = true + end + return result + end + + #----------------------------------------------------------------------------- + # * Frame Update + #----------------------------------------------------------------------------- + def update + last_real_x = @real_x + last_real_y = @real_y + super + update_screen_position(last_real_x, last_real_y) + # Update dependent events + $PokemonTemp.dependentEvents.updateDependentEvents + # Count down the time between allowed bump sounds + @bump_se -= 1 if @bump_se && @bump_se>0 + # Finish up dismounting from surfing + if $PokemonTemp.endSurf && !moving? + pbCancelVehicles + $PokemonTemp.surfJump = nil + $PokemonTemp.endSurf = false + end + update_event_triggering + end + + def update_command_new + dir = Input.dir4 + unless pbMapInterpreterRunning? or $game_temp.message_window_showing or + $PokemonTemp.miniupdate or $game_temp.in_menu + # Move player in the direction the directional button is being pressed + if dir==@lastdir && Graphics.frame_count-@lastdirframe>Graphics.frame_rate/20 + case dir + when 2; move_down + when 4; move_left + when 6; move_right + when 8; move_up + end + elsif dir!=@lastdir + case dir + when 2; turn_down + when 4; turn_left + when 6; turn_right + when 8; turn_up + end + end + end + # Record last direction input + @lastdirframe = Graphics.frame_count if dir!=@lastdir + @lastdir = dir + end + + # Center player on-screen + def update_screen_position(last_real_x, last_real_y) + return if !@moved_this_frame + center_x = (Graphics.width/2 - Game_Map::TILE_WIDTH/2) * Game_Map::X_SUBPIXELS + center_y = (Graphics.height/2 - Game_Map::TILE_HEIGHT/2) * Game_Map::Y_SUBPIXELS + if @real_y < last_real_y and @real_y - $game_map.display_y < center_y + $game_map.scroll_up(last_real_y - @real_y) + end + if @real_y > last_real_y and @real_y - $game_map.display_y > center_y + $game_map.scroll_down(@real_y - last_real_y) + end + if @real_x < last_real_x and @real_x - $game_map.display_x < center_x + $game_map.scroll_left(last_real_x - @real_x) + end + if @real_x > last_real_x and @real_x - $game_map.display_x > center_x + $game_map.scroll_right(@real_x - last_real_x) + end + end + + def update_event_triggering + return if moving? + # Try triggering events upon walking into them/in front of them + if @moved_this_frame + $PokemonTemp.dependentEvents.pbTurnDependentEvents + result = pbCheckEventTriggerFromDistance([2]) + # Event determinant is via touch of same position event + result |= check_event_trigger_here([1,2]) + # No events triggered, try other event triggers upon finishing a step + pbOnStepTaken(result) + end + # If C button was pressed, try to manually interact with events + if Input.trigger?(Input::C) && !$PokemonTemp.miniupdate + # Same position and front event determinant + check_event_trigger_here([0]) + check_event_trigger_there([0,2]) + end + end +end + + + +def pbGetPlayerCharset(meta,charset,trainer=nil,force=false) + trainer = $Trainer if !trainer + outfit = (trainer) ? trainer.outfit : 0 + if $game_player && $game_player.charsetData && !force + return nil if $game_player.charsetData[0]==$PokemonGlobal.playerID && + $game_player.charsetData[1]==charset && + $game_player.charsetData[2]==outfit + end + $game_player.charsetData = [$PokemonGlobal.playerID,charset,outfit] if $game_player + ret = meta[charset] + ret = meta[1] if !ret || ret=="" + if pbResolveBitmap("Graphics/Characters/"+ret+"_"+outfit.to_s) + ret = ret+"_"+outfit.to_s + end + return ret +end + +def pbUpdateVehicle + meta = pbGetMetadata(0,MetadataPlayerA+$PokemonGlobal.playerID) + if meta + newCharName = nil + charset = 1 # Regular graphic + if $PokemonGlobal.diving; charset = 5 # Diving graphic + elsif $PokemonGlobal.surfing; charset = 3 # Surfing graphic + elsif $PokemonGlobal.bicycle; charset = 2 # Bicycle graphic + end + newCharName = pbGetPlayerCharset(meta,charset) + $game_player.character_name = newCharName if newCharName + end +end + +def pbCancelVehicles(destination=nil) + $PokemonGlobal.surfing = false + $PokemonGlobal.diving = false + $PokemonGlobal.bicycle = false if !destination || !pbCanUseBike?(destination) + pbUpdateVehicle +end + +def pbCanUseBike?(mapid) + return true if pbGetMetadata(mapid,MetadataBicycleAlways) + val = pbGetMetadata(mapid,MetadataBicycle) + val = pbGetMetadata(mapid,MetadataOutdoor) if val==nil + return (val) ? true : false +end + +def pbMountBike + return if $PokemonGlobal.bicycle + $PokemonGlobal.bicycle = true + pbUpdateVehicle + bikebgm = pbGetMetadata(0,MetadataBicycleBGM) + pbCueBGM(bikebgm,0.5) if bikebgm +end + +def pbDismountBike + return if !$PokemonGlobal.bicycle + $PokemonGlobal.bicycle = false + pbUpdateVehicle + $game_map.autoplayAsCue +end \ No newline at end of file diff --git a/Data/Scripts/003_Game classes/008_Game_Player_Visuals.rb b/Data/Scripts/003_Game classes/008_Game_Player_Visuals.rb new file mode 100644 index 000000000..371254968 --- /dev/null +++ b/Data/Scripts/003_Game classes/008_Game_Player_Visuals.rb @@ -0,0 +1,103 @@ +class Game_Player < Game_Character + @@bobFrameSpeed = 1.0/15 + + def fullPattern + case self.direction + when 2; return self.pattern + when 4; return 4+self.pattern + when 6; return 8+self.pattern + when 8; return 12+self.pattern + end + return 0 + end + + def setDefaultCharName(chname,pattern,lockpattern=false) + return if pattern<0 || pattern>=16 + @defaultCharacterName = chname + @direction = [2,4,6,8][pattern/4] + @pattern = pattern%4 + @lock_pattern = lockpattern + end + + def pbCanRun? + return false if $game_temp.in_menu || $game_temp.in_battle || + @move_route_forcing || $game_temp.message_window_showing || + pbMapInterpreterRunning? + terrain = pbGetTerrainTag + input = ($PokemonSystem.runstyle==1) ? $PokemonGlobal.runtoggle : Input.press?(Input::A) + return input && $PokemonGlobal.runningShoes && + !$PokemonGlobal.diving && !$PokemonGlobal.surfing && + !$PokemonGlobal.bicycle && !PBTerrain.onlyWalk?(terrain) + end + + def pbIsRunning? + return moving? && !@move_route_forcing && pbCanRun? + end + + def character_name + @defaultCharacterName = "" if !@defaultCharacterName + return @defaultCharacterName if @defaultCharacterName!="" + if !@move_route_forcing && $PokemonGlobal.playerID>=0 + meta = pbGetMetadata(0,MetadataPlayerA+$PokemonGlobal.playerID) + if meta && !$PokemonGlobal.bicycle && !$PokemonGlobal.diving && !$PokemonGlobal.surfing + charset = 1 # Display normal character sprite + if pbCanRun? && (moving? || @wasmoving) && Input.dir4!=0 && meta[4] && meta[4]!="" + charset = 4 # Display running character sprite + end + newCharName = pbGetPlayerCharset(meta,charset) + @character_name = newCharName if newCharName + @wasmoving = moving? + end + end + return @character_name + end + + def update_command + if PBTerrain.isIce?(pbGetTerrainTag) + self.move_speed = 5 # Sliding on ice + elsif !moving? && !@move_route_forcing && $PokemonGlobal + if $PokemonGlobal.bicycle + self.move_speed = 6 # Cycling + elsif pbCanRun? || $PokemonGlobal.surfing || $PokemonGlobal.diving + self.move_speed = 5 # Running, surfing or diving + else + self.move_speed = 4 # Walking + end + end + super + end + + def update_pattern + if $PokemonGlobal.surfing || $PokemonGlobal.diving + p = ((Graphics.frame_count%60)*@@bobFrameSpeed).floor + @pattern = p if !@lock_pattern + @pattern_surf = p + @bob_height = (p>=2) ? 2 : 0 + else + @bob_height = 0 + super + end + end +end + + +=begin +class Game_Character + alias update_old2 update + + def update + if self.is_a?(Game_Event) + if @dependentEvents + for i in 0...@dependentEvents.length + if @dependentEvents[i][0]==$game_map.map_id && + @dependentEvents[i][1]==self.id + self.move_speed_real = $game_player.move_speed_real + break + end + end + end + end + update_old2 + end +end +=end \ No newline at end of file diff --git a/Data/Scripts/003_Game classes/009_Game_Map.rb b/Data/Scripts/003_Game classes/009_Game_Map.rb new file mode 100644 index 000000000..a069cf087 --- /dev/null +++ b/Data/Scripts/003_Game classes/009_Game_Map.rb @@ -0,0 +1,438 @@ +#============================================================================== +# ** Game_Map +#------------------------------------------------------------------------------ +# This class handles the map. It includes scrolling and passable determining +# functions. Refer to "$game_map" for the instance of this class. +#============================================================================== +class Game_Map + attr_accessor :map_id + attr_accessor :tileset_name # tileset file name + attr_accessor :autotile_names # autotile file name + attr_reader :passages # passage table + attr_reader :priorities # prioroty table + attr_reader :terrain_tags # terrain tag table + attr_reader :events # events + attr_accessor :panorama_name # panorama file name + attr_accessor :panorama_hue # panorama hue + attr_accessor :fog_name # fog file name + attr_accessor :fog_hue # fog hue + attr_accessor :fog_opacity # fog opacity level + attr_accessor :fog_blend_type # fog blending method + attr_accessor :fog_zoom # fog zoom rate + attr_accessor :fog_sx # fog sx + attr_accessor :fog_sy # fog sy + attr_reader :fog_ox # fog x-coordinate starting point + attr_reader :fog_oy # fog y-coordinate starting point + attr_reader :fog_tone # fog color tone + attr_accessor :battleback_name # battleback file name + attr_accessor :display_x # display x-coordinate * 128 + attr_accessor :display_y # display y-coordinate * 128 + attr_accessor :need_refresh # refresh request flag + + TILE_WIDTH = 32 + TILE_HEIGHT = 32 + X_SUBPIXELS = ($RPGVX) ? 8 : 4 + Y_SUBPIXELS = ($RPGVX) ? 8 : 4 + REAL_RES_X = TILE_WIDTH * X_SUBPIXELS + REAL_RES_Y = TILE_HEIGHT * Y_SUBPIXELS + + def initialize + @map_id = 0 + @display_x = 0 + @display_y = 0 + end + + def setup(map_id) + @map_id = map_id + @map = load_data(sprintf("Data/Map%03d.%s",map_id,($RPGVX) ? "rvdata" : "rxdata")) + tileset = $data_tilesets[@map.tileset_id] + updateTileset + @fog_ox = 0 + @fog_oy = 0 + @fog_tone = Tone.new(0, 0, 0, 0) + @fog_tone_target = Tone.new(0, 0, 0, 0) + @fog_tone_duration = 0 + @fog_opacity_duration = 0 + @fog_opacity_target = 0 + self.display_x = 0 + self.display_y = 0 + @need_refresh = false + Events.onMapCreate.trigger(self,map_id,@map,tileset) + @events = {} + for i in @map.events.keys + @events[i] = Game_Event.new(@map_id, @map.events[i],self) + end + @common_events = {} + for i in 1...$data_common_events.size + @common_events[i] = Game_CommonEvent.new(i) + end + @scroll_direction = 2 + @scroll_rest = 0 + @scroll_speed = 4 + end + + def updateTileset + tileset = $data_tilesets[@map.tileset_id] + @tileset_name = tileset.tileset_name + @autotile_names = tileset.autotile_names + @panorama_name = tileset.panorama_name + @panorama_hue = tileset.panorama_hue + @fog_name = tileset.fog_name + @fog_hue = tileset.fog_hue + @fog_opacity = tileset.fog_opacity + @fog_blend_type = tileset.fog_blend_type + @fog_zoom = tileset.fog_zoom + @fog_sx = tileset.fog_sx + @fog_sy = tileset.fog_sy + @battleback_name = tileset.battleback_name + @passages = tileset.passages + @priorities = tileset.priorities + @terrain_tags = tileset.terrain_tags + end + + def width; return @map.width; end + def height; return @map.height; end + def encounter_list; return @map.encounter_list; end + def encounter_step; return @map.encounter_step; end + def data; return @map.data; end + + def name + ret = pbGetMessage(MessageTypes::MapNames,@map_id) + ret.gsub!(/\\PN/,$Trainer.name) if $Trainer + return ret + end + #----------------------------------------------------------------------------- + # * Autoplays background music + # Plays music called "[normal BGM]n" if it's night time and it exists + #----------------------------------------------------------------------------- + def autoplayAsCue + if @map.autoplay_bgm + if PBDayNight.isNight? && FileTest.audio_exist?("Audio/BGM/"+ @map.bgm.name+ "_n") + pbCueBGM(@map.bgm.name+"_n",1.0,@map.bgm.volume,@map.bgm.pitch) + else + pbCueBGM(@map.bgm,1.0) + end + end + if @map.autoplay_bgs + pbBGSPlay(@map.bgs) + end + end + #----------------------------------------------------------------------------- + # * Plays background music + # Plays music called "[normal BGM]n" if it's night time and it exists + #----------------------------------------------------------------------------- + def autoplay + if @map.autoplay_bgm + if PBDayNight.isNight? && FileTest.audio_exist?("Audio/BGM/"+ @map.bgm.name+ "n") + pbBGMPlay(@map.bgm.name+"n",@map.bgm.volume,@map.bgm.pitch) + else + pbBGMPlay(@map.bgm) + end + end + if @map.autoplay_bgs + pbBGSPlay(@map.bgs) + end + end + + def valid?(x, y) + return (x>=0 and x=0 and y=-10 and x<=width+10 and y>=-10 and y<=height+10) + end + + def passable?(x, y, d, self_event = nil) + return false if !valid?(x, y) + bit = (1 << (d / 2 - 1)) & 0x0f + for event in events.values + next if event.tile_id <= 0 + terrain = @terrain_tags[event.tile_id] + next if terrain == PBTerrain::Neutral + next if event == self_event + next if event.x != x || event.y != y + next if event.through + passage = @passages[event.tile_id] + return false if passage & bit != 0 + return false if passage & 0x0f == 0x0f + return true if @priorities[event.tile_id] == 0 + end + return playerPassable?(x, y, d, self_event) if self_event==$game_player + # All other events + newx = x; newy = y + case d + when 1; newx -= 1; newy += 1 + when 2; newy += 1 + when 3; newx += 1; newy += 1 + when 4; newx -= 1 + when 6; newx += 1 + when 7; newx -= 1; newy -= 1 + when 8; newy -= 1 + when 9; newx += 1; newy -= 1 + end + return false if !valid?(newx, newy) + for i in [2, 1, 0] + tile_id = data[x, y, i] + terrain = @terrain_tags[tile_id] + passage = @passages[tile_id] + # If already on water, only allow movement to another water tile + if self_event!=nil && PBTerrain.isJustWater?(terrain) + for j in [2, 1, 0] + facing_tile_id = data[newx, newy, j] + return false if facing_tile_id==nil + facing_terrain = @terrain_tags[facing_tile_id] + if facing_terrain!=0 && facing_terrain!=PBTerrain::Neutral + return PBTerrain.isJustWater?(facing_terrain) + end + end + return false + # Can't walk onto ice + elsif PBTerrain.isIce?(terrain) + return false + elsif self_event!=nil && self_event.x==x && self_event.y==y + # Can't walk onto ledges + for j in [2, 1, 0] + facing_tile_id = data[newx, newy, j] + return false if facing_tile_id==nil + facing_terrain = @terrain_tags[facing_tile_id] + if facing_terrain!=0 && facing_terrain!=PBTerrain::Neutral + return false if PBTerrain.isLedge?(facing_terrain) + break + end + end + # Regular passability checks + if terrain!=PBTerrain::Neutral + if passage & bit != 0 || passage & 0x0f == 0x0f + return false + elsif @priorities[tile_id] == 0 + return true + end + end + # Regular passability checks + elsif terrain!=PBTerrain::Neutral + if passage & bit != 0 || passage & 0x0f == 0x0f + return false + elsif @priorities[tile_id] == 0 + return true + end + end + end + return true + end + + def playerPassable?(x, y, d, self_event = nil) + bit = (1 << (d / 2 - 1)) & 0x0f + for i in [2, 1, 0] + tile_id = data[x, y, i] + terrain = @terrain_tags[tile_id] + passage = @passages[tile_id] + # Ignore bridge tiles if not on a bridge + next if PBTerrain.isBridge?(terrain) && $PokemonGlobal.bridge==0 + # Make water tiles passable if player is surfing + if $PokemonGlobal.surfing && PBTerrain.isPassableWater?(terrain) + return true + # Prevent cycling in really tall grass/on ice + elsif $PokemonGlobal.bicycle && PBTerrain.onlyWalk?(terrain) + return false + # Depend on passability of bridge tile if on bridge + elsif PBTerrain.isBridge?(terrain) && $PokemonGlobal.bridge>0 + return (passage & bit == 0 && passage & 0x0f != 0x0f) + # Regular passability checks + elsif terrain!=PBTerrain::Neutral + if passage & bit != 0 || passage & 0x0f == 0x0f + return false + elsif @priorities[tile_id] == 0 + return true + end + end + end + return true + end + + # Returns whether the position x,y is fully passable (there is no blocking + # event there, and the tile is fully passable in all directions) + def passableStrict?(x, y, d, self_event = nil) + return false if !valid?(x, y) + for event in events.values + next if event == self_event || event.tile_id < 0 || event.through + next if event.x != x || event.y != y + terrain = @terrain_tags[event.tile_id] + next if terrain == PBTerrain::Neutral + return false if @passages[event.tile_id] & 0x0f != 0 + return true if @priorities[event.tile_id] == 0 + end + for i in [2, 1, 0] + tile_id = data[x, y, i] + terrain = @terrain_tags[tile_id] + next if terrain == PBTerrain::Neutral + return false if @passages[tile_id] & 0x0f != 0 + return true if @priorities[tile_id] == 0 + end + return true + end + + def bush?(x,y) + for i in [2, 1, 0] + tile_id = data[x, y, i] + return false if PBTerrain.isBridge?(@terrain_tags[tile_id]) && $PokemonGlobal.bridge>0 + return true if @passages[tile_id] & 0x40 == 0x40 + end + return false + end + + def deepBush?(x,y) + for i in [2, 1, 0] + tile_id = data[x, y, i] + terrain = @terrain_tags[tile_id] + return false if $PokemonGlobal.bridge>0 && PBTerrain.isBridge?(terrain) + return true if terrain==PBTerrain::TallGrass && @passages[tile_id] & 0x40 == 0x40 + end + return false + end + + def counter?(x,y) + for i in [2, 1, 0] + tile_id = data[x, y, i] + passage = @passages[tile_id] + return true if passage & 0x80 == 0x80 + end + return false + end + + def terrain_tag(x,y,countBridge=false) + return 0 if !valid?(x, y) + for i in [2, 1, 0] + tile_id = data[x, y, i] + terrain = @terrain_tags[tile_id] + next if !countBridge && PBTerrain.isBridge?(terrain) && $PokemonGlobal.bridge==0 + return terrain if terrain > 0 && terrain!=PBTerrain::Neutral + end + return 0 + end + + def check_event(x,y) + for event in self.events.values + return event.id if event.x == x and event.y == y + end + end + + def display_x=(value) + @display_x = value + if pbGetMetadata(self.map_id,MetadataSnapEdges) + max_x = (self.width - Graphics.width*1.0/TILE_WIDTH) * REAL_RES_X + @display_x = [0, [@display_x, max_x].min].max + end + $MapFactory.setMapsInRange if $MapFactory + end + + def display_y=(value) + @display_y = value + if pbGetMetadata(self.map_id,MetadataSnapEdges) + max_y = (self.height - Graphics.height*1.0/TILE_HEIGHT) * REAL_RES_Y + @display_y = [0, [@display_y, max_y].min].max + end + $MapFactory.setMapsInRange if $MapFactory + end + + def scroll_up(distance) + self.display_y -= distance + end + def scroll_down(distance) + self.display_y += distance + end + + def scroll_left(distance) + self.display_x -= distance + end + + def scroll_right(distance) + self.display_x += distance + end + + def start_scroll(direction, distance, speed) + @scroll_direction = direction + if direction==2 || direction==8 # down or up + @scroll_rest = distance * REAL_RES_Y + else + @scroll_rest = distance * REAL_RES_X + end + @scroll_speed = speed + end + + def scrolling? + return @scroll_rest > 0 + end + + def start_fog_tone_change(tone,duration) + @fog_tone_target = tone.clone + @fog_tone_duration = duration + if @fog_tone_duration == 0 + @fog_tone = @fog_tone_target.clone + end + end + + def start_fog_opacity_change(opacity,duration) + @fog_opacity_target = opacity*1.0 + @fog_opacity_duration = duration + if @fog_opacity_duration==0 + @fog_opacity = @fog_opacity_target + end + end + + def refresh + for event in @events.values + event.refresh + end + for common_event in @common_events.values + common_event.refresh + end + @need_refresh = false + end + + def update + # refresh maps if necessary + if $MapFactory + for i in $MapFactory.maps + i.refresh if i.need_refresh + end + $MapFactory.setCurrentMap + end + # If scrolling + if @scroll_rest>0 + distance = (1<<@scroll_speed)*40.0/Graphics.frame_rate + distance = @scroll_rest if distance>@scroll_rest + case @scroll_direction + when 2; scroll_down(distance) + when 4; scroll_left(distance) + when 6; scroll_right(distance) + when 8; scroll_up(distance) + end + @scroll_rest -= distance + end + # Only update events that are on-screen + for event in @events.values + event.update + end + # Update common events + for common_event in @common_events.values + common_event.update + end + # Update fog + @fog_ox -= @fog_sx/8.0 + @fog_oy -= @fog_sy/8.0 + if @fog_tone_duration>=1 + d = @fog_tone_duration + target = @fog_tone_target + @fog_tone.red = (@fog_tone.red * (d - 1) + target.red) / d + @fog_tone.green = (@fog_tone.green * (d - 1) + target.green) / d + @fog_tone.blue = (@fog_tone.blue * (d - 1) + target.blue) / d + @fog_tone.gray = (@fog_tone.gray * (d - 1) + target.gray) / d + @fog_tone_duration -= 1 + end + if @fog_opacity_duration >= 1 + d = @fog_opacity_duration + @fog_opacity = (@fog_opacity * (d - 1) + @fog_opacity_target) / d + @fog_opacity_duration -= 1 + end + end +end \ No newline at end of file diff --git a/Data/Scripts/003_Game classes/010_Game_Map_Autoscroll.rb b/Data/Scripts/003_Game classes/010_Game_Map_Autoscroll.rb new file mode 100644 index 000000000..00c8e0e40 --- /dev/null +++ b/Data/Scripts/003_Game classes/010_Game_Map_Autoscroll.rb @@ -0,0 +1,194 @@ +#=============================================================================== +# ** Map Autoscroll +#------------------------------------------------------------------------------- +# Wachunga +# Version 1.02 +# 2005-12-18 +#=============================================================================== +=begin + + This script supplements the built-in "Scroll Map" event command with the + aim of simplifying cutscenes (and map scrolling in general). Whereas the + normal event command requires a direction and number of tiles to scroll, + Map Autoscroll scrolls the map to center on the tile whose x and y + coordinates are given. + + FEATURES + - automatic map scrolling to given x,y coordinate (or player) + - destination is fixed, so it's possible to scroll to same place even if + origin is variable (e.g. moving NPC) + - variable speed (just like "Scroll Map" event command) + - diagonal scrolling supported + + SETUP + Instead of a "Scroll Map" event command, use the "Call Script" command + and enter on the following on the first line: + + autoscroll(x,y) + + (replacing "x" and "y" with the x and y coordinates of the tile to scroll to) + + To specify a scroll speed other than the default (4), use: + + autoscroll(x,y,speed) + + (now also replacing "speed" with the scroll speed from 1-6) + + Diagonal scrolling happens automatically when the destination is diagonal + relative to the starting point (i.e., not directly up, down, left or right). + + To scroll to the player, instead use the following: + + autoscroll_player(speed) + + Note: because of how the interpreter and the "Call Script" event command + are setup, the call to autoscroll(...) can only be on the first line of + the "Call Script" event command (and not flowing down to subsequent lines). + + For example, the following call may not work as expected: + + autoscroll($game_variables[1], + $game_variables[2]) + + (since the long argument names require dropping down to a second line) + A work-around is to setup new variables with shorter names in a preceding + (separate) "Call Script" event command: + + @x = $game_variables[1] + @y = $game_variables[2] + + and then use those as arguments: + + autoscroll(@x,@y) + + The renaming must be in a separate "Call Script" because otherwise + the call to autoscroll(...) isn't on the first line. + + Originally requested by militantmilo80: + http://www.rmxp.net/forums/index.php?showtopic=29519 + +=end + +class Interpreter + SCROLL_SPEED_DEFAULT = 4 + #----------------------------------------------------------------------------- + # * Map Autoscroll to Coordinates + # x : x coordinate to scroll to and center on + # y : y coordinate to scroll to and center on + # speed : (optional) scroll speed (from 1-6, default being 4) + #----------------------------------------------------------------------------- + def autoscroll(x,y,speed=SCROLL_SPEED_DEFAULT) + if $game_map.scrolling? + return false + elsif not $game_map.valid?(x,y) + print 'Map Autoscroll: given x,y is invalid' + return command_skip + elsif not (1..6).include?(speed) + print 'Map Autoscroll: invalid speed (1-6 only)' + return command_skip + end + center_x = (Graphics.width/2 - Game_Map::TILE_WIDTH/2) * 4 # X coordinate in the center of the screen + center_y = (Graphics.height/2 - Game_Map::TILE_HEIGHT/2) * 4 # Y coordinate in the center of the screen + max_x = ($game_map.width - Graphics.width*1.0/Game_Map::TILE_WIDTH) * 4 * Game_Map::TILE_WIDTH + max_y = ($game_map.height - Graphics.height*1.0/Game_Map::TILE_HEIGHT) * 4 * Game_Map::TILE_HEIGHT + count_x = ($game_map.display_x - [0,[x*Game_Map::REAL_RES_X-center_x,max_x].min].max)/Game_Map::REAL_RES_X + count_y = ($game_map.display_y - [0,[y*Game_Map::REAL_RES_Y-center_y,max_y].min].max)/Game_Map::REAL_RES_Y + if not @diag + @diag = true + dir = nil + if count_x > 0 + if count_y > 0 + dir = 7 + elsif count_y < 0 + dir = 1 + end + elsif count_x < 0 + if count_y > 0 + dir = 9 + elsif count_y < 0 + dir = 3 + end + end + count = [count_x.abs,count_y.abs].min + else + @diag = false + dir = nil + if count_x != 0 and count_y != 0 + return false + elsif count_x > 0 + dir = 4 + elsif count_x < 0 + dir = 6 + elsif count_y > 0 + dir = 8 + elsif count_y < 0 + dir = 2 + end + count = count_x != 0 ? count_x.abs : count_y.abs + end + $game_map.start_scroll(dir, count, speed) if dir != nil + if @diag + return false + else + return true + end + end + + #----------------------------------------------------------------------------- + # * Map Autoscroll (to Player) + # speed : (optional) scroll speed (from 1-6, default being 4) + #----------------------------------------------------------------------------- + def autoscroll_player(speed=SCROLL_SPEED_DEFAULT) + autoscroll($game_player.x,$game_player.y,speed) + end +end + + + +class Game_Map + def scroll_downright(distance) + @display_x = [@display_x + distance, + (self.width - Graphics.width*1.0/TILE_WIDTH) * REAL_RES_X].min + @display_y = [@display_y + distance, + (self.height - Graphics.height*1.0/TILE_HEIGHT) * REAL_RES_Y].min + end + + def scroll_downleft(distance) + @display_x = [@display_x - distance, 0].max + @display_y = [@display_y + distance, + (self.height - Graphics.height*1.0/TILE_HEIGHT) * REAL_RES_Y].min + end + + def scroll_upright(distance) + @display_x = [@display_x + distance, + (self.width - Graphics.width*1.0/TILE_WIDTH) * REAL_RES_X].min + @display_y = [@display_y - distance, 0].max + end + + def scroll_upleft(distance) + @display_x = [@display_x - distance, 0].max + @display_y = [@display_y - distance, 0].max + end + + def update_scrolling + # If scrolling + if @scroll_rest > 0 + # Change from scroll speed to distance in map coordinates + distance = (1<<@scroll_speed)*40/Graphics.frame_rate + distance = @scroll_rest if distance>@scroll_rest + # Execute scrolling + case @scroll_direction + when 1; scroll_downleft(distance) + when 2; scroll_down(distance) + when 3; scroll_downright(distance) + when 4; scroll_left(distance) + when 6; scroll_right(distance) + when 7; scroll_upleft(distance) + when 8; scroll_up(distance) + when 9; scroll_upright(distance) + end + # Subtract distance scrolled + @scroll_rest -= distance + end + end +end \ No newline at end of file diff --git a/Data/Scripts/003_Game classes/011_MapFactory.rb b/Data/Scripts/003_Game classes/011_MapFactory.rb new file mode 100644 index 000000000..121d142e5 --- /dev/null +++ b/Data/Scripts/003_Game classes/011_MapFactory.rb @@ -0,0 +1,488 @@ +#=============================================================================== +# Map Factory (allows multiple maps to be loaded at once and connected) +#=============================================================================== +class PokemonMapFactory + attr_reader :maps + + def initialize(id) + @maps = [] + @fixup = false + @mapChanged = false # transient instance variable + setup(id) + end + + # Clears all maps and sets up the current map with id. This function also sets + # the positions of neighboring maps and notifies the game system of a map + # change. + def setup(id) + @maps.clear + @maps[0] = Game_Map.new + @mapIndex = 0 + oldID = ($game_map) ? $game_map.map_id : 0 + setMapChanging(id,@maps[0]) if oldID!=0 && oldID!=@maps[0].map_id + $game_map = @maps[0] + @maps[0].setup(id) + setMapsInRange + setMapChanged(oldID) + end + + def map + @mapIndex = 0 if !@mapIndex || @mapIndex<0 + return @maps[@mapIndex] if @maps[@mapIndex] + raise "No maps in save file... (mapIndex=#{@mapIndex})" if @maps.length==0 + for i in 0...@maps.length + if @maps[i] + echo("Using next map, may be incorrect (mapIndex=#{@mapIndex}, length=#{@maps.length})") + return @maps[i] + end + raise "No maps in save file... (all maps empty; mapIndex=#{@mapIndex})" + end + end + + def hasMap?(id) + for map in @maps + return true if map.map_id==id + end + return false + end + + def getMapIndex(id) + for i in 0...@maps.length + return i if @maps[i].map_id==id + end + return -1 + end + + def getMap(id,add=true) + for map in @maps + return map if map.map_id==id + end + map = Game_Map.new + map.setup(id) + @maps.push(map) if add + return map + end + + def getMapNoAdd(id) + return getMap(id,false) + end + + def getNewMap(playerX,playerY) + id = $game_map.map_id + conns = MapFactoryHelper.getMapConnections + for conn in conns + next if conn[0]!=id && conn[3]!=id + mapidB = nil + newx = 0 + newy = 0 + if conn[0]==id + mapidB = conn[3] + mapB = MapFactoryHelper.getMapDims(conn[3]) + newx = conn[4] - conn[1] + playerX + newy = conn[5] - conn[2] + playerY + else + mapidB = conn[0] + mapB = MapFactoryHelper.getMapDims(conn[0]) + newx = conn[1] - conn[4] + playerX + newy = conn[2] - conn[5] + playerY + end + if newx>=0 && newx=0 && newy=dims[0] || newY>=dims[1] + return [conn[3],newX,newY] + elsif conn[3]==id + newX = x + conn[1] - conn[4] + newY = y + conn[2] - conn[5] + next if newX<0 || newY<0 + dims = MapFactoryHelper.getMapDims(conn[0]) + next if newX>=dims[0] || newY>=dims[1] + return [conn[0],newX,newY] + end + end + return nil + end + + def getFacingCoords(x,y,direction=0,steps=1) + case direction + when 1; x -= steps; y += steps + when 2; y += steps + when 3; x += steps; y += steps + when 4; x -= steps + when 6; x += steps + when 7; x -= steps; y -= steps + when 8; y -= steps + when 9; x += steps; y -= steps + end + return [x,y] + end + + def updateMaps(scene) + updateMapsInternal + $MapFactory.setSceneStarted(scene) if @mapChanged + end + + def updateMapsInternal + return if $game_player.moving? + if !MapFactoryHelper.hasConnections?($game_map.map_id) + return if @maps.length==1 + for i in 0...@maps.length + @maps[i] = nil if $game_map.map_id!=@maps[i].map_id + end + @maps.compact! + @mapIndex = getMapIndex($game_map.map_id) + return + end + setMapsInRange + deleted = false + for i in 0...@maps.length + next if MapFactoryHelper.mapInRange?(@maps[i]) + @maps[i] = nil + deleted = true + end + if deleted + @maps.compact! + @mapIndex = getMapIndex($game_map.map_id) + end + end +end + + + +#=============================================================================== +# Map Factory Helper (stores map connection and size data and calculations +# involving them) +#=============================================================================== +module MapFactoryHelper + @@MapConnections = nil + @@MapDims = nil + + def self.clear + @@MapConnections = nil + @@MapDims = nil + end + + def self.getMapConnections + if !@@MapConnections + @@MapConnections = [] + begin + conns = load_data("Data/map_connections.dat") + rescue + conns = [] + end + for i in 0...conns.length + conn = conns[i] + v = getMapEdge(conn[0],conn[1]) + dims = getMapDims(conn[0]) + next if dims[0]==0 || dims[1]==0 + if conn[1]=="N" || conn[1]=="S" + conn[1] = conn[2] + conn[2] = v + elsif conn[1]=="E" || conn[1]=="W" + conn[1] = v + end + v = getMapEdge(conn[3],conn[4]) + dims = getMapDims(conn[3]) + next if dims[0]==0 || dims[1]==0 + if conn[4]=="N" || conn[4]=="S" + conn[4] = conn[5] + conn[5] = v + elsif conn[4]=="E" || conn[4]=="W" + conn[4] = v + end + @@MapConnections.push(conn) + end + end + return @@MapConnections + end + + def self.hasConnections?(id) + conns = MapFactoryHelper.getMapConnections + for conn in conns + return true if conn[0]==id || conn[3]==id + end + return false + end + + # Gets the height and width of the map with id + def self.getMapDims(id) + # Create cache if doesn't exist + @@MapDims = [] if !@@MapDims + # Add map to cache if can't be found + if !@@MapDims[id] + begin + map = pbLoadRxData(sprintf("Data/Map%03d", id)) + @@MapDims[id] = [map.width,map.height] + rescue + @@MapDims[id] = [0,0] + end + end + # Return map in cache + return @@MapDims[id] + end + + # Returns the X or Y coordinate of an edge on the map with id. + # Considers the special strings "N","W","E","S" + def self.getMapEdge(id,edge) + return 0 if edge=="N" || edge=="W" + dims = getMapDims(id) # Get dimensions + return dims[0] if edge=="E" + return dims[1] if edge=="S" + return dims[0] # real dimension (use width) + end + + def self.mapInRange?(map) + range = 6 # Number of tiles + dispx = map.display_x + dispy = map.display_y + return false if dispx >= (map.width + range) * Game_Map::REAL_RES_X + return false if dispy >= (map.height + range) * Game_Map::REAL_RES_Y + return false if dispx <= -(Graphics.width + range * Game_Map::TILE_WIDTH) * Game_Map::X_SUBPIXELS + return false if dispy <= -(Graphics.height + range * Game_Map::TILE_HEIGHT) * Game_Map::Y_SUBPIXELS + return true + end + + def self.mapInRangeById?(id,dispx,dispy) + range = 6 # Number of tiles + dims = MapFactoryHelper.getMapDims(id) + return false if dispx >= (dims[0] + range) * Game_Map::REAL_RES_X + return false if dispy >= (dims[1] + range) * Game_Map::REAL_RES_Y + return false if dispx <= -(Graphics.width + range * Game_Map::TILE_WIDTH) * Game_Map::X_SUBPIXELS + return false if dispy <= -(Graphics.height + range * Game_Map::TILE_HEIGHT) * Game_Map::Y_SUBPIXELS + return true + end +end + + + +def updateTilesets + maps = $MapFactory.maps + for map in maps + map.updateTileset if map + end +end \ No newline at end of file diff --git a/Data/Scripts/004_Sprites/001_Sprite_Picture.rb b/Data/Scripts/004_Sprites/001_Sprite_Picture.rb new file mode 100644 index 000000000..afd0d43e1 --- /dev/null +++ b/Data/Scripts/004_Sprites/001_Sprite_Picture.rb @@ -0,0 +1,58 @@ +class Sprite_Picture + def initialize(viewport, picture) + @viewport = viewport + @picture = picture + @sprite = nil + update + end + + def dispose + @sprite.dispose if @sprite + end + + def update + @sprite.update if @sprite + # If picture file name is different from current one + if @picture_name != @picture.name + # Remember file name to instance variables + @picture_name = @picture.name + # If file name is not empty + if @picture_name != "" + # Get picture graphic + @sprite=IconSprite.new(0,0,@viewport) if !@sprite + @sprite.setBitmap("Graphics/Pictures/"+@picture_name) + end + end + # If file name is empty + if @picture_name == "" + # Set sprite to invisible + if @sprite + @sprite.dispose if @sprite + @sprite=nil + end + return + end + # Set sprite to visible + @sprite.visible = true + # Set transfer starting point + if @picture.origin == 0 + @sprite.ox = 0 + @sprite.oy = 0 + else + @sprite.ox = @sprite.bitmap.width / 2 + @sprite.oy = @sprite.bitmap.height / 2 + end + # Set sprite coordinates + @sprite.x = @picture.x + @sprite.y = @picture.y + @sprite.z = @picture.number + # Set zoom rate, opacity level, and blend method + @sprite.zoom_x = @picture.zoom_x / 100.0 + @sprite.zoom_y = @picture.zoom_y / 100.0 + @sprite.opacity = @picture.opacity + @sprite.blend_type = @picture.blend_type + # Set rotation angle and color tone + @sprite.angle = @picture.angle + @sprite.tone = @picture.tone + end +end \ No newline at end of file diff --git a/Data/Scripts/004_Sprites/002_Sprite_Timer.rb b/Data/Scripts/004_Sprites/002_Sprite_Timer.rb new file mode 100644 index 000000000..ae37ac026 --- /dev/null +++ b/Data/Scripts/004_Sprites/002_Sprite_Timer.rb @@ -0,0 +1,44 @@ +class Sprite_Timer + def initialize(viewport=nil) + @viewport=viewport + @timer=nil + @total_sec=nil + @disposed=false + end + + def dispose + @timer.dispose if @timer + @timer=nil + @disposed=true + end + + def disposed? + @disposed + end + + def update + return if disposed? + if $game_system.timer_working + if !@timer + @timer=Window_AdvancedTextPokemon.newWithSize("",Graphics.width-120,0,120,64) + @timer.width=@timer.borderX+96 + @timer.x=Graphics.width-@timer.width + @timer.viewport=@viewport + @timer.z=99998 + end + curtime=$game_system.timer / Graphics.frame_rate + curtime=0 if curtime<0 + if curtime != @total_sec + # Calculate total number of seconds + @total_sec = curtime + # Make a string for displaying the timer + min = @total_sec / 60 + sec = @total_sec % 60 + @timer.text = _ISPRINTF("{1:02d}:{2:02d}", min, sec) + end + @timer.update + else + @timer.visible=false if @timer + end + end +end \ No newline at end of file diff --git a/Data/Scripts/004_Sprites/003_Sprite_Character.rb b/Data/Scripts/004_Sprites/003_Sprite_Character.rb new file mode 100644 index 000000000..6f21809d8 --- /dev/null +++ b/Data/Scripts/004_Sprites/003_Sprite_Character.rb @@ -0,0 +1,174 @@ +class BushBitmap + def initialize(bitmap,isTile,depth) + @bitmaps = [] + @bitmap = bitmap + @isTile = isTile + @isBitmap = @bitmap.is_a?(Bitmap) + @depth = depth + end + + def dispose + for b in @bitmaps + b.dispose if b + end + end + + def bitmap + thisBitmap = (@isBitmap) ? @bitmap : @bitmap.bitmap + current = (@isBitmap) ? 0 : @bitmap.currentIndex + if !@bitmaps[current] + if @isTile + @bitmaps[current] = pbBushDepthTile(thisBitmap,@depth) + else + @bitmaps[current] = pbBushDepthBitmap(thisBitmap,@depth) + end + end + return @bitmaps[current] + end + + def pbBushDepthBitmap(bitmap,depth) + ret = Bitmap.new(bitmap.width,bitmap.height) + charheight = ret.height/4 + cy = charheight-depth-2 + for i in 0...4 + y = i*charheight + if cy>=0 + ret.blt(0,y,bitmap,Rect.new(0,y,ret.width,cy)) + ret.blt(0,y+cy,bitmap,Rect.new(0,y+cy,ret.width,2),170) + end + ret.blt(0,y+cy+2,bitmap,Rect.new(0,y+cy+2,ret.width,2),85) if cy+2>=0 + end + return ret + end + + def pbBushDepthTile(bitmap,depth) + ret = Bitmap.new(bitmap.width,bitmap.height) + charheight = ret.height + cy = charheight-depth-2 + y = charheight + if cy>=0 + ret.blt(0,y,bitmap,Rect.new(0,y,ret.width,cy)) + ret.blt(0,y+cy,bitmap,Rect.new(0,y+cy,ret.width,2),170) + end + ret.blt(0,y+cy+2,bitmap,Rect.new(0,y+cy+2,ret.width,2),85) if cy+2>=0 + return ret + end +end + + + +class Sprite_Character < RPG::Sprite + attr_accessor :character + + def initialize(viewport, character = nil) + super(viewport) + @character = character + @oldbushdepth = 0 + @spriteoffset = false + if !character || character==$game_player || (character.name[/reflection/i] rescue false) + @reflection = Sprite_Reflection.new(self,character,viewport) + end + @surfbase = Sprite_SurfBase.new(self,character,viewport) if character==$game_player + update + end + + def groundY + return @character.screen_y_ground + end + + def visible=(value) + super(value) + @reflection.visible = value if @reflection + end + + def dispose + @bushbitmap.dispose if @bushbitmap + @bushbitmap = nil + @charbitmap.dispose if @charbitmap + @charbitmap = nil + @reflection.dispose if @reflection + @reflection = nil + @surfbase.dispose if @surfbase + @surfbase = nil + super + end + + def update + return if @character.is_a?(Game_Event) && !@character.should_update? + super + if @tile_id!=@character.tile_id or + @character_name!=@character.character_name or + @character_hue!=@character.character_hue or + @oldbushdepth!=@character.bush_depth + @tile_id = @character.tile_id + @character_name = @character.character_name + @character_hue = @character.character_hue + @oldbushdepth = @character.bush_depth + if @tile_id>=384 + @charbitmap.dispose if @charbitmap + @charbitmap = pbGetTileBitmap(@character.map.tileset_name,@tile_id,@character.character_hue) + @charbitmapAnimated = false + @bushbitmap.dispose if @bushbitmap + @bushbitmap = nil + @spriteoffset = false + @cw = Game_Map::TILE_WIDTH + @ch = Game_Map::TILE_HEIGHT + self.src_rect.set(0,0,@cw,@ch) + self.ox = @cw/2 + self.oy = @ch + @character.sprite_size = [@cw,@ch] + else + @charbitmap.dispose if @charbitmap + @charbitmap = AnimatedBitmap.new( + "Graphics/Characters/"+@character.character_name,@character.character_hue) + @charbitmapAnimated = true + @bushbitmap.dispose if @bushbitmap + @bushbitmap = nil + @spriteoffset = @character_name[/offset/i] + @cw = @charbitmap.width/4 + @ch = @charbitmap.height/4 + self.ox = @cw/2 + @character.sprite_size = [@cw,@ch] + end + end + @charbitmap.update if @charbitmapAnimated + bushdepth = @character.bush_depth + if bushdepth==0 + self.bitmap = (@charbitmapAnimated) ? @charbitmap.bitmap : @charbitmap + else + @bushbitmap = BushBitmap.new(@charbitmap,(@tile_id>=384),bushdepth) if !@bushbitmap + self.bitmap = @bushbitmap.bitmap + end + self.visible = !@character.transparent + if @tile_id==0 + sx = @character.pattern*@cw + sy = ((@character.direction-2)/2)*@ch + self.src_rect.set(sx,sy,@cw,@ch) + self.oy = (@spriteoffset rescue false) ? @ch-16 : @ch + self.oy -= @character.bob_height + end + if self.visible + if $PokemonSystem.tilemap==0 || + (@character.is_a?(Game_Event) && @character.name[/regulartone/i]) + self.tone.set(0,0,0,0) + else + pbDayNightTint(self) + end + end + self.x = @character.screen_x + self.y = @character.screen_y + self.z = @character.screen_z(@ch) +# self.zoom_x = Game_Map::TILE_WIDTH/32.0 +# self.zoom_y = Game_Map::TILE_HEIGHT/32.0 + self.opacity = @character.opacity + self.blend_type = @character.blend_type +# self.bush_depth = @character.bush_depth + if @character.animation_id!=0 + animation = $data_animations[@character.animation_id] + animation(animation,true) + @character.animation_id = 0 + end + @reflection.update if @reflection + @surfbase.update if @surfbase + end +end \ No newline at end of file diff --git a/Data/Scripts/004_Sprites/004_Sprite_WaterReflection.rb b/Data/Scripts/004_Sprites/004_Sprite_WaterReflection.rb new file mode 100644 index 000000000..7645c7eba --- /dev/null +++ b/Data/Scripts/004_Sprites/004_Sprite_WaterReflection.rb @@ -0,0 +1,90 @@ +class Sprite_Reflection + attr_reader :visible + attr_accessor :event + + def initialize(sprite,event,viewport=nil) + @rsprite = sprite + @sprite = nil + @event = event + @height = 0 + @fixedheight = false + if @event && @event!=$game_player + if @event.name[/reflection\((\d+)\)/i] + @height = $~[1].to_i || 0 + @fixedheight = true + end + end + @viewport = viewport + @disposed = false + update + end + + def dispose + if !@disposed + @sprite.dispose if @sprite + @sprite = nil + @disposed = true + end + end + + def disposed? + @disposed + end + + def visible=(value) + @visible = value + @sprite.visible = value if @sprite && !@sprite.disposed? + end + + def update + return if disposed? + shouldShow = @rsprite.visible + if !shouldShow + # Just-in-time disposal of sprite + if @sprite + @sprite.dispose + @sprite = nil + end + return + end + # Just-in-time creation of sprite + @sprite = Sprite.new(@viewport) if !@sprite + if @sprite + x = @rsprite.x-@rsprite.ox + y = @rsprite.y-@rsprite.oy + y -= 32 if @rsprite.character.character_name[/offset/i] + @height = $PokemonGlobal.bridge if !@fixedheight + y += @height*16 + width = @rsprite.src_rect.width + height = @rsprite.src_rect.height + @sprite.x = x+width/2 + @sprite.y = y+height+height/2 + @sprite.ox = width/2 + @sprite.oy = height/2-2 # Hard-coded 2 pixel shift up + @sprite.oy -= @rsprite.character.bob_height*2 + @sprite.z = -50 # Still water is -100, map is 0 and above + @sprite.zoom_x = @rsprite.zoom_x + @sprite.zoom_y = @rsprite.zoom_y + frame = (Graphics.frame_count%40)/10 + case frame + when 1; @sprite.zoom_x *= 0.95 + when 3; @sprite.zoom_x *= 1.05 + else; @sprite.zoom_x *= 1.0 + end + @sprite.angle = 180.0 + @sprite.mirror = true + @sprite.bitmap = @rsprite.bitmap + @sprite.tone = @rsprite.tone + if @height>0 + @sprite.color = Color.new(48,96,160,255) # Dark still water + @sprite.opacity = @rsprite.opacity + @sprite.visible = !TIME_SHADING # Can't time-tone a colored sprite + else + @sprite.color = Color.new(224,224,224,96) + @sprite.opacity = @rsprite.opacity*3/4 + @sprite.visible = true + end + @sprite.src_rect = @rsprite.src_rect + end + end +end \ No newline at end of file diff --git a/Data/Scripts/004_Sprites/005_Sprite_SurfBase.rb b/Data/Scripts/004_Sprites/005_Sprite_SurfBase.rb new file mode 100644 index 000000000..1b22a4616 --- /dev/null +++ b/Data/Scripts/004_Sprites/005_Sprite_SurfBase.rb @@ -0,0 +1,77 @@ +class Sprite_SurfBase + attr_reader :visible + attr_accessor :event + + def initialize(sprite,event,viewport=nil) + @rsprite = sprite + @sprite = nil + @event = event + @viewport = viewport + @disposed = false + @surfbitmap = AnimatedBitmap.new("Graphics/Characters/base_surf") + @divebitmap = AnimatedBitmap.new("Graphics/Characters/base_dive") + @cws = @surfbitmap.width/4 + @chs = @surfbitmap.height/4 + @cwd = @divebitmap.width/4 + @chd = @divebitmap.height/4 + update + end + + def dispose + if !@disposed + @sprite.dispose if @sprite + @sprite = nil + @surfbitmap.dispose + @disposed = true + end + end + + def disposed? + @disposed + end + + def visible=(value) + @visible = value + @sprite.visible = value if @sprite && !@sprite.disposed? + end + + def update + return if disposed? + if !$PokemonGlobal.surfing && !$PokemonGlobal.diving + # Just-in-time disposal of sprite + if @sprite + @sprite.dispose + @sprite = nil + end + return + end + # Just-in-time creation of sprite + @sprite = Sprite.new(@viewport) if !@sprite + if @sprite + if $PokemonGlobal.surfing + @sprite.bitmap = @surfbitmap.bitmap; cw = @cws; ch = @chs + elsif $PokemonGlobal.diving + @sprite.bitmap = @divebitmap.bitmap; cw = @cwd; ch = @chd + end + sx = @event.pattern_surf*cw + sy = ((@event.direction-2)/2)*ch + @sprite.src_rect.set(sx,sy,cw,ch) + if $PokemonTemp.surfJump + @sprite.x = ($PokemonTemp.surfJump[0]*Game_Map::REAL_RES_X-@event.map.display_x+3)/4+(Game_Map::TILE_WIDTH/2) + @sprite.y = ($PokemonTemp.surfJump[1]*Game_Map::REAL_RES_Y-@event.map.display_y+3)/4+(Game_Map::TILE_HEIGHT/2)+16 + else + @sprite.x = @rsprite.x + @sprite.y = @rsprite.y + end + @sprite.ox = cw/2 + @sprite.oy = ch-16 # Assume base needs offsetting + @sprite.oy -= @event.bob_height + @sprite.z = @event.screen_z(ch)-1 + @sprite.zoom_x = @rsprite.zoom_x + @sprite.zoom_y = @rsprite.zoom_y + @sprite.tone = @rsprite.tone + @sprite.color = @rsprite.color + @sprite.opacity = @rsprite.opacity + end + end +end \ No newline at end of file diff --git a/Data/Scripts/004_Sprites/006_Spriteset_Map.rb b/Data/Scripts/004_Sprites/006_Spriteset_Map.rb new file mode 100644 index 000000000..4578f7111 --- /dev/null +++ b/Data/Scripts/004_Sprites/006_Spriteset_Map.rb @@ -0,0 +1,167 @@ +class ClippableSprite < Sprite_Character + def initialize(viewport,event,tilemap) + @tilemap = tilemap + @_src_rect = Rect.new(0,0,0,0) + super(viewport,event) + end + + def update + super + @_src_rect = self.src_rect + tmright = @tilemap.map_data.xsize*Game_Map::TILE_WIDTH-@tilemap.ox + echoln("x=#{self.x},ox=#{self.ox},tmright=#{tmright},tmox=#{@tilemap.ox}") + if @tilemap.ox-self.ox<-self.x + # clipped on left + diff = -self.x-@tilemap.ox+self.ox + self.src_rect = Rect.new(@_src_rect.x+diff,@_src_rect.y, + @_src_rect.width-diff,@_src_rect.height) + echoln("clipped out left: #{diff} #{@tilemap.ox-self.ox} #{self.x}") + elsif tmright-self.ox0 + @weather.max -= 2 + if @weather.max<=0 + @weather.max = 0 + @weather.type = 0 + @weather.ox = 0 + @weather.oy = 0 + end + end + else + @weather.type = $game_screen.weather_type + @weather.max = $game_screen.weather_max + @weather.ox = tmox + @weather.oy = tmoy + end + @weather.update + @@viewport1.tone = $game_screen.tone + @@viewport3.color = $game_screen.flash_color + @@viewport1.update + @@viewport3.update + end +end \ No newline at end of file diff --git a/Data/Scripts/004_Sprites/007_Spriteset_Global.rb b/Data/Scripts/004_Sprites/007_Spriteset_Global.rb new file mode 100644 index 000000000..111434113 --- /dev/null +++ b/Data/Scripts/004_Sprites/007_Spriteset_Global.rb @@ -0,0 +1,34 @@ +class Spriteset_Global + attr_reader :playersprite + @@viewport2 = Viewport.new(0,0,Graphics.width,Graphics.height) + @@viewport2.z = 200 + + def initialize + @playersprite = Sprite_Character.new(Spriteset_Map.viewport,$game_player) + @picture_sprites = [] + for i in 1..100 + @picture_sprites.push(Sprite_Picture.new(@@viewport2,$game_screen.pictures[i])) + end + @timer_sprite = Sprite_Timer.new + update + end + + def dispose + @playersprite.dispose + for sprite in @picture_sprites + sprite.dispose + end + @timer_sprite.dispose + @playersprite = nil + @picture_sprites.clear + @timer_sprite = nil + end + + def update + @playersprite.update + for sprite in @picture_sprites + sprite.update + end + @timer_sprite.update + end +end \ No newline at end of file diff --git a/Data/Scripts/004_Sprites/008_Sprite_AnimationSprite.rb b/Data/Scripts/004_Sprites/008_Sprite_AnimationSprite.rb new file mode 100644 index 000000000..432c10621 --- /dev/null +++ b/Data/Scripts/004_Sprites/008_Sprite_AnimationSprite.rb @@ -0,0 +1,94 @@ +=begin +A sprite whose sole purpose is to display an animation. This sprite +can be displayed anywhere on the map and is disposed +automatically when its animation is finished. +Used for grass rustling and so forth. +=end +class AnimationSprite < RPG::Sprite + def initialize(animID,map,tileX,tileY,viewport=nil,tinting=false,height=3) + super(viewport) + @tileX = tileX + @tileY = tileY + self.bitmap = Bitmap.new(1, 1) + self.bitmap.clear + @map = map + setCoords + pbDayNightTint(self) if tinting + self.animation($data_animations[animID],true,height) + end + + def setCoords + self.x = ((@tileX * Game_Map::REAL_RES_X - @map.display_x) / Game_Map::X_SUBPIXELS).ceil + self.x += Game_Map::TILE_WIDTH / 2 + self.y = ((@tileY * Game_Map::REAL_RES_Y - @map.display_y) / Game_Map::Y_SUBPIXELS).ceil + self.y += Game_Map::TILE_HEIGHT + end + + def dispose + self.bitmap.dispose + super + end + + def update + if !self.disposed? + setCoords + super + self.dispose if !self.effect? + end + end +end + + + +class Spriteset_Map + alias _animationSprite_initialize initialize + alias _animationSprite_update update + alias _animationSprite_dispose dispose + + def initialize(map=nil) + @usersprites=[] + _animationSprite_initialize(map) + end + + def addUserAnimation(animID,x,y,tinting=false,height=3) + sprite=AnimationSprite.new(animID,$game_map,x,y,@@viewport1,tinting,height) + addUserSprite(sprite) + return sprite + end + + def addUserSprite(sprite) + for i in 0...@usersprites.length + if @usersprites[i]==nil || @usersprites[i].disposed? + @usersprites[i]=sprite + return + end + end + @usersprites.push(sprite) + end + + def dispose + _animationSprite_dispose + for i in 0...@usersprites.length + @usersprites[i].dispose + end + @usersprites.clear + end + + def update + return if @tilemap.disposed? + if $RPGVX || $PokemonSystem.tilemap==0 + if self.map==$game_map + pbDayNightTint(@@viewport3) + else + @@viewport3.tone.set(0,0,0,0) + end + else + pbDayNightTint(@tilemap) + @@viewport3.tone.set(0,0,0,0) + end + _animationSprite_update + for i in 0...@usersprites.length + @usersprites[i].update if !@usersprites[i].disposed? + end + end +end \ No newline at end of file diff --git a/Data/Scripts/004_Sprites/009_Sprite_DynamicShadows.rb b/Data/Scripts/004_Sprites/009_Sprite_DynamicShadows.rb new file mode 100644 index 000000000..7a2c8d242 --- /dev/null +++ b/Data/Scripts/004_Sprites/009_Sprite_DynamicShadows.rb @@ -0,0 +1,255 @@ +#=============================================================================== +# Sprite_Shadow (Sprite_Ombre ) +# Based on Genzai Kawakami's shadows, dynamisme & features by Rataime, extra +# features Boushy +# Modified by Peter O. to be compatible with Pokémon Essentials +#=============================================================================== +SHADOW_WARN = true + +class Sprite_Shadow < RPG::Sprite + attr_accessor :character + + def initialize(viewport, character = nil,params=[]) + super(viewport) + @source = params[0] + @anglemin = (params.size>1) ? params[1] : 0 + @anglemax = (params.size>2) ? params[2] : 0 + @self_opacity = (params.size>4) ? params[4] : 100 + @distancemax = (params.size>3) ? params[3] : 350 + @character = character + update + end + + def dispose + @chbitmap.dispose if @chbitmap + super + end + + def update + if !in_range?(@character, @source, @distancemax) + self.opacity = 0 + return + end + super + if @tile_id != @character.tile_id or + @character_name != @character.character_name or + @character_hue != @character.character_hue + @tile_id = @character.tile_id + @character_name = @character.character_name + @character_hue = @character.character_hue + if @tile_id >= 384 + @chbitmap.dispose if @chbitmap + @chbitmap = pbGetTileBitmap(@character.map.tileset_name, + @tile_id, @character.character_hue) + self.src_rect.set(0, 0, 32, 32) + @ch = 32 + @cw = 32 + self.ox = 16 + self.oy = 32 + else + @chbitmap.dispose if @chbitmap + @chbitmap = AnimatedBitmap.new( + "Graphics/Characters/"+@character.character_name,@character.character_hue) + @cw = @chbitmap.width / 4 + @ch = @chbitmap.height / 4 + self.ox = @cw / 2 + self.oy = @ch + end + end + if @chbitmap.is_a?(AnimatedBitmap) + @chbitmap.update + self.bitmap = @chbitmap.bitmap + else + self.bitmap = @chbitmap + end + self.visible = (not @character.transparent) + if @tile_id == 0 + sx = @character.pattern * @cw + sy = (@character.direction - 2) / 2 * @ch + if self.angle > 90 or angle < -90 + case @character.direction + when 2; sy = (8- 2) / 2 * @ch + when 4; sy = (6- 2) / 2 * @ch + when 6; sy = (4- 2) / 2 * @ch + when 8; sy = (2- 2) / 2 * @ch + end + end + self.src_rect.set(sx, sy, @cw, @ch) + end + self.x = ScreenPosHelper.pbScreenX(@character) + self.y = ScreenPosHelper.pbScreenY(@character)-5 + self.z = ScreenPosHelper.pbScreenZ(@character,@ch)-1 + self.zoom_x = ScreenPosHelper.pbScreenZoomX(@character) + self.zoom_y = ScreenPosHelper.pbScreenZoomY(@character) + self.blend_type = @character.blend_type + self.bush_depth = @character.bush_depth + if @character.animation_id != 0 + animation = $data_animations[@character.animation_id] + animation(animation, true) + @character.animation_id = 0 + end + @deltax = ScreenPosHelper.pbScreenX(@source) - self.x + @deltay = ScreenPosHelper.pbScreenY(@source) - self.y + self.color = Color.new(0, 0, 0) + @distance = ((@deltax ** 2) + (@deltay ** 2)) + self.opacity = @self_opacity * 13000 / ((@distance * 370 / @distancemax) + 6000) + self.angle = 57.3 * Math.atan2(@deltax, @deltay) + @angle_trigo = self.angle+90 + @angle_trigo += 360 if @angle_trigo < 0 + if @anglemin != 0 or @anglemax != 0 + if (@angle_trigo < @anglemin or @angle_trigo > @anglemax) and @anglemin < @anglemax + self.opacity = 0 + return + end + if (@angle_trigo < @anglemin and @angle_trigo > @anglemax) and @anglemin > @anglemax + self.opacity = 0 + return + end + end + end + + def in_range?(element, object, range) # From Near's Anti Lag Script, edited + elemScreenX = ScreenPosHelper.pbScreenX(element) + elemScreenY = ScreenPosHelper.pbScreenY(element) + objScreenX = ScreenPosHelper.pbScreenX(object) + objScreenY = ScreenPosHelper.pbScreenY(object) + x = (elemScreenX - objScreenX) * (elemScreenX - objScreenX) + y = (elemScreenY - objScreenY) * (elemScreenY - objScreenY) + r = x + y + return r <= range * range + end +end + + + +#=================================================== +# ? CLASS Sprite_Character edit +#=================================================== +class Sprite_Character < RPG::Sprite + alias :shadow_initialize :initialize + + def initialize(viewport, character = nil) + @ombrelist = [] + @character = character + shadow_initialize(viewport, @character) + end + + def setShadows(map,shadows) + if character.is_a?(Game_Event) and shadows.length > 0 + params = XPML_read(map,"Shadow",@character,4) + if params != nil + for i in 0...shadows.size + @ombrelist.push(Sprite_Shadow.new(viewport, @character, shadows[i])) + end + end + end + if character.is_a?(Game_Player) and shadows.length > 0 + for i in 0...shadows.size + @ombrelist.push(Sprite_Shadow.new(viewport, $game_player, shadows[i])) + end + end + update + end + + alias shadow_update update + + def update + shadow_update + if @ombrelist.length>0 + for i in 0...@ombrelist.size + @ombrelist[i].update + end + end + end +end + + + +#=================================================== +# ? CLASS Game_Event edit +#=================================================== +class Game_Event + attr_accessor :id +end + + + +#=================================================== +# ? CLASS Spriteset_Map edit +#=================================================== +class Spriteset_Map + attr_accessor :shadows + + alias shadow_initialize initialize + def initialize(map=nil) + @shadows = [] + warn = false + map = $game_map if !map + for k in map.events.keys.sort + ev = map.events[k] + warn = true if (ev.list != nil and ev.list.length > 0 and + ev.list[0].code == 108 and + (ev.list[0].parameters == ["s"] or ev.list[0].parameters == ["o"])) + params = XPML_read(map,"Shadow Source", ev, 4) + @shadows.push([ev] + params) if params != nil + end + if warn == true and SHADOW_WARN + p "Warning : At least one event on this map uses the obsolete way to add shadows" + end + shadow_initialize(map) + for sprite in @character_sprites + sprite.setShadows(map, @shadows) + end + $scene.spritesetGlobal.playersprite.setShadows(map, @shadows) + end +end + + + +#=================================================== +# ? XPML Definition, by Rataime, using ideas from Near Fantastica +# +# Returns nil if the markup wasn't present at all, +# returns [] if there wasn't any parameters, else +# returns a parameters list with "int" converted as int +# eg : +# begin first +# begin second +# param1 1 +# param2 two +# begin third +# anything 3 +# +# p XPML_read("first", event_id) -> [] +# p XPML_read("second", event_id) -> [1, "two"] +# p XPML_read("third", event_id) -> [3] +# p XPML_read("forth", event_id) -> nil +#=================================================== +def XPML_read(map,markup,event,max_param_number=0) + parameter_list = nil + return nil if !event || event.list == nil + for i in 0...event.list.size + if event.list[i].code == 108 and + event.list[i].parameters[0].downcase == "begin " + markup.downcase + parameter_list = [] if parameter_list == nil + for j in i+1...event.list.size + if event.list[j].code == 108 + parts = event.list[j].parameters[0].split + if parts.size != 1 and parts[0].downcase != "begin" + if parts[1].to_i != 0 or parts[1] == "0" + parameter_list.push(parts[1].to_i) + else + parameter_list.push(parts[1]) + end + else + return parameter_list + end + else + return parameter_list + end + return parameter_list if max_param_number != 0 and j == i + max_param_number + end + end + end + return parameter_list +end \ No newline at end of file diff --git a/Data/Scripts/004_Sprites/010_ParticleEngine.rb b/Data/Scripts/004_Sprites/010_ParticleEngine.rb new file mode 100644 index 000000000..61b348dad --- /dev/null +++ b/Data/Scripts/004_Sprites/010_ParticleEngine.rb @@ -0,0 +1,586 @@ +# Particle Engine, Peter O., 2007-11-03 +# Based on version 2 by Near Fantastica, 04.01.06 +# In turn based on the Particle Engine designed by PinkMan +class Particle_Engine + def initialize(viewport=nil,map=nil) + @map = (map) ? map : $game_map + @viewport = viewport + @effect = [] + @disposed = false + @firsttime = true + @effects = { + # PinkMan's Effects + "fire" => Particle_Engine::Fire, + "smoke" => Particle_Engine::Smoke, + "teleport" => Particle_Engine::Teleport, + "spirit" => Particle_Engine::Spirit, + "explosion" => Particle_Engine::Explosion, + "aura" => Particle_Engine::Aura, + # BlueScope's Effects + "soot" => Particle_Engine::Soot, + "sootsmoke" => Particle_Engine::SootSmoke, + "rocket" => Particle_Engine::Rocket, + "fixteleport" => Particle_Engine::FixedTeleport, + "smokescreen" => Particle_Engine::Smokescreen, + "flare" => Particle_Engine::Flare, + "splash" => Particle_Engine::Splash, + # By Peter O. + "starteleport" => Particle_Engine::StarTeleport + } + end + + def dispose + return if disposed? + for particle in @effect + next if particle.nil? + particle.dispose + end + @effect.clear + @map = nil + @disposed = true + end + + def disposed? + return @disposed + end + + def add_effect(event) + @effect[event.id] = pbParticleEffect(event) + end + + def remove_effect(event) + return if @effect[event.id].nil? + @effect[event.id].dispose + @effect.delete_at(event.id) + end + + def realloc_effect(event,particle) + type = pbEventCommentInput(event, 1, "Particle Engine Type") + if type.nil? + particle.dispose if particle + return nil + end + type = type[0].downcase + cls = @effects[type] + if cls.nil? + particle.dispose if particle + return nil + end + if !particle || !particle.is_a?(cls) + particle.dispose if particle + particle = cls.new(event,@viewport) + end + return particle + end + + def pbParticleEffect(event) + return realloc_effect(event,nil) + end + + def update + if @firsttime + @firsttime = false + for event in @map.events.values + remove_effect(event) + add_effect(event) + end + end + for i in 0...@effect.length + particle = @effect[i] + next if particle.nil? + if particle.event.pe_refresh + event = particle.event + event.pe_refresh = false + particle = realloc_effect(event,particle) + @effect[i] = particle + end + particle.update if particle + end + end +end + + + +class ParticleEffect + attr_accessor :x, :y, :z + + def initialize + @x = 0 + @y = 0 + @z = 0 + end + + def update; end + def dispose; end +end + + + +class ParticleSprite + attr_accessor :x, :y, :z, :ox, :oy, :opacity, :bitmap, :blend_type + + def initialize(viewport) + @viewport = viewport + @sprite = nil + @x = 0 + @y = 0 + @z = 0 + @ox = 0 + @oy = 0 + @opacity = 255 + @bitmap = nil + @blend_type = 0 + @minleft = 0 + @mintop = 0 + end + + def dispose + @sprite.dispose if @sprite + end + + def bitmap=(value) + @bitmap = value + if value + @minleft = -value.width + @mintop = -value.height + else + @minleft = 0 + @mintop = 0 + end + end + + def update + w = Graphics.width + h = Graphics.height + if !@sprite && @x>=@minleft && @y>=@mintop && @x=w || @y>=h) + @sprite.dispose + @sprite = nil + end + if @sprite + @sprite.x = @x if @sprite.x!=@x + @sprite.x -= @ox + @sprite.y = @y if @sprite.y!=@y + @sprite.y -= @oy + @sprite.z = @z if @sprite.z!=@z + @sprite.opacity = @opacity if @sprite.opacity!=@opacity + @sprite.blend_type = @blend_type if @sprite.blend_type!=@blend_type + @sprite.bitmap = @bitmap if @sprite.bitmap!=@bitmap + end + end +end + + + +class ParticleEffect_Event < ParticleEffect + attr_accessor :event + + def initialize(event,viewport=nil) + @event = event + @viewport = viewport + @particles = [] + @bitmaps = {} + end + + def setParameters(params) + @randomhue,@leftright,@fade, + @maxparticless,@hue,@slowdown, + @ytop,@ybottom,@xleft,@xright, + @xgravity,@ygravity,@xoffset,@yoffset, + @opacityvar,@originalopacity = params + end + + def loadBitmap(filename,hue) + key = [filename,hue] + bitmap = @bitmaps[key] + if !bitmap || bitmap.disposed? + bitmap = AnimatedBitmap.new("Graphics/Fogs/"+filename,hue).deanimate + @bitmaps[key] = bitmap + end + return bitmap + end + + def initParticles(filename,opacity,zOffset=0,blendtype=1) + @particles = [] + @particlex = [] + @particley = [] + @opacity = [] + @startingx = self.x + @xoffset + @startingy = self.y + @yoffset + @screen_x = self.x + @screen_y = self.y + @real_x = @event.real_x + @real_y = @event.real_y + @filename = filename + @zoffset = zOffset + @bmwidth = 32 + @bmheight = 32 + for i in 0...@maxparticless + @particlex[i] = -@xoffset + @particley[i] = -@yoffset + @particles[i] = ParticleSprite.new(@viewport) + @particles[i].bitmap = loadBitmap(filename, @hue) if filename + if i==0 && @particles[i].bitmap + @bmwidth = @particles[i].bitmap.width + @bmheight = @particles[i].bitmap.height + end + @particles[i].blend_type = blendtype + @particles[i].y = @startingy + @particles[i].x = @startingx + @particles[i].z = self.z+zOffset + @opacity[i] = rand(opacity/4) + @particles[i].opacity = @opacity[i] + @particles[i].update + end + end + + def x; return ScreenPosHelper.pbScreenX(@event); end + def y; return ScreenPosHelper.pbScreenY(@event); end + def z; return ScreenPosHelper.pbScreenZ(@event); end + + def update + if @viewport && + (@viewport.rect.x >= Graphics.width || + @viewport.rect.y >= Graphics.height) + return + end + selfX = self.x + selfY = self.y + selfZ = self.z + newRealX = @event.real_x + newRealY = @event.real_y + @startingx = selfX + @xoffset + @startingy = selfY + @yoffset + @__offsetx = (@real_x==newRealX) ? 0 : selfX-@screen_x + @__offsety = (@real_y==newRealY) ? 0 : selfY-@screen_y + @screen_x = selfX + @screen_y = selfY + @real_x = newRealX + @real_y = newRealY + if @opacityvar>0 && @viewport + opac = 255.0/@opacityvar + minX = opac*(-@xgravity*1.0 / @slowdown).floor + @startingx + maxX = opac*(@xgravity*1.0 / @slowdown).floor + @startingx + minY = opac*(-@ygravity*1.0 / @slowdown).floor + @startingy + maxY = @startingy + minX -= @bmwidth + minY -= @bmheight + maxX += @bmwidth + maxY += @bmheight + if maxX<0 || maxY<0 || minX>=Graphics.width || minY>=Graphics.height +# echo "skipped" + return + end + end + particleZ = selfZ+@zoffset + for i in 0...@maxparticless + @particles[i].z = particleZ + if @particles[i].y <= @ytop + @particles[i].y = @startingy + @yoffset + @particles[i].x = @startingx + @xoffset + @particlex[i] = 0.0 + @particley[i] = 0.0 + end + if @particles[i].x <= @xleft + @particles[i].y = @startingy + @yoffset + @particles[i].x = @startingx + @xoffset + @particlex[i] = 0.0 + @particley[i] = 0.0 + end + if @particles[i].y >= @ybottom + @particles[i].y = @startingy + @yoffset + @particles[i].x = @startingx + @xoffset + @particlex[i] = 0.0 + @particley[i] = 0.0 + end + if @particles[i].x >= @xright + @particles[i].y = @startingy + @yoffset + @particles[i].x = @startingx + @xoffset + @particlex[i] = 0.0 + @particley[i] = 0.0 + end + if @fade == 0 + if @opacity[i] <= 0 + @opacity[i] = @originalopacity + @particles[i].y = @startingy + @yoffset + @particles[i].x = @startingx + @xoffset + @particlex[i] = 0.0 + @particley[i] = 0.0 + end + else + if @opacity[i] <= 0 + @opacity[i] = 250 + @particles[i].y = @startingy + @yoffset + @particles[i].x = @startingx + @xoffset + @particlex[i] = 0.0 + @particley[i] = 0.0 + end + end + calcParticlePos(i) + if @randomhue == 1 + @hue += 0.5 + @hue = 0 if @hue >= 360 + @particles[i].bitmap = loadBitmap(@filename, @hue) if @filename + end + @opacity[i] = @opacity[i] - rand(@opacityvar) + @particles[i].opacity = @opacity[i] + @particles[i].update + end + end + + def calcParticlePos(i) + @leftright = rand(2) + if @leftright == 1 + xo = -@xgravity*1.0 / @slowdown + else + xo = @xgravity*1.0 / @slowdown + end + yo = -@ygravity*1.0 / @slowdown + @particlex[i] += xo + @particley[i] += yo + @particlex[i] -= @__offsetx + @particley[i] -= @__offsety + @particlex[i] = @particlex[i].floor + @particley[i] = @particley[i].floor + @particles[i].x = @particlex[i]+@startingx+@xoffset + @particles[i].y = @particley[i]+@startingy+@yoffset + end + + def dispose + for particle in @particles + particle.dispose + end + for bitmap in @bitmaps.values + bitmap.dispose + end + @particles.clear + @bitmaps.clear + end +end + + + +class Particle_Engine::Fire < ParticleEffect_Event + def initialize(event,viewport) + super + setParameters([0,0,1,20,40,0.5,-64, + Graphics.height,-64,Graphics.width,0.5,0.10,-5,-13,30,0]) + initParticles("particle",250) + end +end + + + +class Particle_Engine::Smoke < ParticleEffect_Event + def initialize(event,viewport) + super + setParameters([0,0,0,80,20,0.5,-64, + Graphics.height,-64,Graphics.width,0.5,0.10,-5,-15,5,80]) + initParticles("smoke",250) + end +end + + + +class Particle_Engine::Teleport < ParticleEffect_Event + def initialize(event,viewport) + super + setParameters([1,1,1,10,rand(360),1,-64, + Graphics.height,-64,Graphics.width,0,3,-8,-15,20,0]) + initParticles("wideportal",250) + for i in 0...@maxparticless + @particles[i].ox = 16 + @particles[i].oy = 16 + end + end +end + + + +class Particle_Engine::Spirit < ParticleEffect_Event + def initialize(event,viewport) + super + setParameters([1,0,1,20,rand(360),0.5,-64, + Graphics.height,-64,Graphics.width,0.5,0.10,-5,-13,30,0]) + initParticles("particle",250) + end +end + + + +class Particle_Engine::Explosion < ParticleEffect_Event + def initialize(event,viewport) + super + setParameters([0,0,1,20,0,0.5,-64, + Graphics.height,-64,Graphics.width,0.5,0.10,-5,-13,30,0]) + initParticles("explosion",250) + end +end + + + +class Particle_Engine::Aura < ParticleEffect_Event + def initialize(event,viewport) + super + setParameters([0,0,1,20,0,1,-64, + Graphics.height,-64,Graphics.width,2,2,-5,-13,30,0]) + initParticles("particle",250) + end +end + + + +class Particle_Engine::Soot < ParticleEffect_Event + def initialize(event,viewport) + super + setParameters([0,0,0,20,0,0.5,-64, + Graphics.height,-64,Graphics.width,0.5,0.10,-5,-15,5,80]) + initParticles("smoke",100,0,2) + end +end + + + +class Particle_Engine::SootSmoke < ParticleEffect_Event + def initialize(event,viewport) + super + setParameters([0,0,0,30,0,0.5,-64, + Graphics.height,-64,Graphics.width,0.5,0.10,-5,-15,5,80]) + initParticles("smoke",100,0) + for i in 0...@maxparticless + @particles[i].blend_type = rand(6) < 3 ? 1 : 2 + end + end +end + + + +class Particle_Engine::Rocket < ParticleEffect_Event + def initialize(event,viewport) + super + setParameters([0,0,0,60,0,0.5,-64, + Graphics.height,-64,Graphics.width,0.5,0,-5,-15,5,80]) + initParticles("smoke",100,-1) + end +end + + + +class Particle_Engine::FixedTeleport < ParticleEffect_Event + def initialize(event,viewport) + super + setParameters([1,0,1,10,rand(360),1, + -Graphics.height,Graphics.height,0,Graphics.width,0,3,-8,-15,20,0]) + initParticles("wideportal",250) + for i in 0...@maxparticless + @particles[i].ox = 16 + @particles[i].oy = 16 + end + end +end + + + +# By Peter O. +class Particle_Engine::StarTeleport < ParticleEffect_Event + def initialize(event,viewport) + super + setParameters([0,0,1,10,0,1, + -Graphics.height,Graphics.height,0,Graphics.width,0,3,-8,-15,10,0]) + initParticles("star",250) + for i in 0...@maxparticless + @particles[i].ox = 48 + @particles[i].oy = 48 + end + end +end + + + +class Particle_Engine::Smokescreen < ParticleEffect_Event + def initialize(event,viewport) + super + setParameters([0,0,0,250,0,0.2,-64, + Graphics.height,-64,Graphics.width,0.8,0.8,-5,-15,5,80]) + initParticles(nil,100) + for i in 0...@maxparticless + rnd = rand(3) + @opacity[i] = (rnd==0) ? 1 : 100 + filename = (rnd==0) ? "explosionsmoke" : "smoke" + @particles[i].bitmap = loadBitmap(filename, @hue) + end + end + + def calcParticlePos(i) + if @randomhue==1 + filename = (rand(3)==0) ? "explosionsmoke" : "smoke" + @particles[i].bitmap = loadBitmap(filename, @hue) + end + multiple = 1.7 + xgrav = @xgravity*multiple/@slowdown + xgrav = -xgrav if (rand(2)==1) + ygrav = @ygravity*multiple/@slowdown + ygrav = -ygrav if (rand(2)==1) + @particlex[i] += xgrav + @particley[i] += ygrav + @particlex[i] -= @__offsetx + @particley[i] -= @__offsety + @particlex[i] = @particlex[i].floor + @particley[i] = @particley[i].floor + @particles[i].x = @particlex[i]+@startingx+@xoffset + @particles[i].y = @particley[i]+@startingy+@yoffset + end +end + + + +class Particle_Engine::Flare < ParticleEffect_Event + def initialize(event,viewport) + super + setParameters([0,0,1,30,10,1,-64, + Graphics.height,-64,Graphics.width,2,2,-5,-12,30,0]) + initParticles("particle",255) + end +end + + + +class Particle_Engine::Splash < ParticleEffect_Event + def initialize(event,viewport) + super + setParameters([0,0,1,30,255,1,-64, + Graphics.height,-64,Graphics.width,4,2,-5,-12,30,0]) + initParticles("smoke",50) + end + + def update + super + for i in 0...@maxparticless + @particles[i].opacity = 50 + @particles[i].update + end + end +end + + + +class Game_Event < Game_Character + attr_accessor :pe_refresh + + alias nf_particles_game_map_initialize initialize + def initialize(map_id,event,map=nil) + @pe_refresh = false + begin + nf_particles_game_map_initialize(map_id, event, map) + rescue ArgumentError + nf_particles_game_map_initialize(map_id, event) + end + end + + alias nf_particles_game_map_refresh refresh + def refresh + nf_particles_game_map_refresh + @pe_refresh = true + end +end \ No newline at end of file diff --git a/Data/Scripts/005_Map renderer/001_Tilemap_XP.rb b/Data/Scripts/005_Map renderer/001_Tilemap_XP.rb new file mode 100644 index 000000000..c027af1b8 --- /dev/null +++ b/Data/Scripts/005_Map renderer/001_Tilemap_XP.rb @@ -0,0 +1,947 @@ +#=============================================================================== +# +#=============================================================================== +class CustomTilemapAutotiles + attr_accessor :changed + + def initialize + @changed = true + @tiles = [nil,nil,nil,nil,nil,nil,nil] + end + + def [](i) + return @tiles[i] + end + + def []=(i,value) + @tiles[i] = value + @changed = true + end +end + + + +#Console::setup_console +class CustomTilemapSprite < Sprite +end + + + +#=============================================================================== +# +#=============================================================================== +class CustomTilemap + attr_reader :tileset + attr_reader :autotiles + attr_reader :map_data + attr_reader :flash_data + attr_reader :priorities + attr_reader :terrain_tags + attr_reader :visible + attr_reader :viewport + attr_reader :graphicsWidth + attr_reader :graphicsHeight + attr_accessor :ox + attr_accessor :oy + attr_accessor :tone + attr_accessor :color + + Autotiles = [ + [ [27, 28, 33, 34], [ 5, 28, 33, 34], [27, 6, 33, 34], [ 5, 6, 33, 34], + [27, 28, 33, 12], [ 5, 28, 33, 12], [27, 6, 33, 12], [ 5, 6, 33, 12] ], + [ [27, 28, 11, 34], [ 5, 28, 11, 34], [27, 6, 11, 34], [ 5, 6, 11, 34], + [27, 28, 11, 12], [ 5, 28, 11, 12], [27, 6, 11, 12], [ 5, 6, 11, 12] ], + [ [25, 26, 31, 32], [25, 6, 31, 32], [25, 26, 31, 12], [25, 6, 31, 12], + [15, 16, 21, 22], [15, 16, 21, 12], [15, 16, 11, 22], [15, 16, 11, 12] ], + [ [29, 30, 35, 36], [29, 30, 11, 36], [ 5, 30, 35, 36], [ 5, 30, 11, 36], + [39, 40, 45, 46], [ 5, 40, 45, 46], [39, 6, 45, 46], [ 5, 6, 45, 46] ], + [ [25, 30, 31, 36], [15, 16, 45, 46], [13, 14, 19, 20], [13, 14, 19, 12], + [17, 18, 23, 24], [17, 18, 11, 24], [41, 42, 47, 48], [ 5, 42, 47, 48] ], + [ [37, 38, 43, 44], [37, 6, 43, 44], [13, 18, 19, 24], [13, 14, 43, 44], + [37, 42, 43, 48], [17, 18, 47, 48], [13, 18, 43, 48], [ 1, 2, 7, 8] ] + ] + Animated_Autotiles_Frames = 5*Graphics.frame_rate/20 # Frequency of updating animated autotiles + FlashOpacity = [100,90,80,70,80,90] + + def initialize(viewport) + @tileset = nil # Refers to Map Tileset Name + @autotiles = CustomTilemapAutotiles.new + @map_data = nil # Refers to 3D Array Of Tile Settings + @flash_data = nil # Refers to 3D Array of Tile Flashdata + @priorities = nil # Refers to Tileset Priorities + @terrain_tags = nil # Refers to Tileset Terrain Tags + @visible = true # Refers to Tileset Visibleness + @ox = 0 # Bitmap Offsets + @oy = 0 # Bitmap Offsets + @plane = false + @haveGraphicsWH = (Graphics.width!=nil rescue false) + if @haveGraphicsWH + @graphicsWidth = Graphics.width + @graphicsHeight = Graphics.height + else + @graphicsWidth = 640 + @graphicsHeight = 480 + end + @tileWidth = Game_Map::TILE_WIDTH rescue 32 + @tileHeight = Game_Map::TILE_HEIGHT rescue 32 + @tileSrcWidth = 32 + @tileSrcHeight = 32 + @diffsizes = (@tileWidth!=@tileSrcWidth) || (@tileHeight!=@tileSrcHeight) + @tone = Tone.new(0,0,0,0) + @oldtone = Tone.new(0,0,0,0) + @color = Color.new(0,0,0,0) + @oldcolor = Color.new(0,0,0,0) + @selfviewport = Viewport.new(0,0,graphicsWidth,graphicsHeight) + @viewport = (viewport) ? viewport : @selfviewport + @tiles = [] + @autotileInfo = [] + @regularTileInfo = [] + @oldOx = 0 + @oldOy = 0 + @oldViewportOx = 0 + @oldViewportOy = 0 + @layer0 = CustomTilemapSprite.new(viewport) + @layer0.visible = true + @nowshown = false + @layer0.bitmap = Bitmap.new([graphicsWidth+320,1].max,[graphicsHeight+320,1].max) + @layer0.z = 0 + @layer0.ox = 0 + @layer0.oy = 0 + @oxLayer0 = 0 + @oyLayer0 = 0 + @flash = nil + @oxFlash = 0 + @oyFlash = 0 + @priotiles = [] + @priotilesfast = [] + @prioautotiles = [] + @autosprites = [] + @framecount = [0,0,0,0,0,0,0,0] # For autotiles + @tilesetChanged = true + @flashChanged = false + @firsttime = true + @disposed = false + @usedsprites = false + @layer0clip = true + @firsttimeflash = true + @fullyrefreshed = false + @fullyrefreshedautos = false + end + + def dispose + return if disposed? + @help.dispose if @help + @help = nil + i = 0; len = @autotileInfo.length; while i=xsize + xEnd = (@ox+@viewport.rect.width)/@tileWidth + 1 + xEnd = 0 if xEnd<0 + xEnd = xsize-1 if xEnd>=xsize + return false if xStart>=xEnd + ysize = @map_data.ysize + yStart = @oy/@tileHeight - 1 + yStart = 0 if yStart<0 + yStart = ysize-1 if yStart>=ysize + yEnd = (@oy+@viewport.rect.height)/@tileHeight + 1 + yEnd = 0 if yEnd<0 + yEnd = ysize-1 if yEnd>=ysize + return false if yStart>=yEnd + return true + end + + def autotileNumFrames(id) + autotile = @autotiles[id/48-1] + return 0 if !autotile || autotile.disposed? + frames = 1 + if autotile.height==@tileHeight + frames = autotile.width/@tileWidth + else + frames = autotile.width/(3*@tileWidth) + end + return frames + end + + def autotileFrame(id) + autotile = @autotiles[id/48-1] + return -1 if !autotile || autotile.disposed? + frames = 1 + if autotile.height==@tileHeight + frames = autotile.width/@tileWidth + else + frames = autotile.width/(3*@tileWidth) + end + return (Graphics.frame_count/Animated_Autotiles_Frames)%frames + end + + def repaintAutotiles + for i in 0...@autotileInfo.length + next if !@autotileInfo[i] + frame = autotileFrame(i) + @autotileInfo[i].clear + bltAutotile(@autotileInfo[i],0,0,i,frame) + end + end + + def bltAutotile(bitmap,x,y,id,frame) + return if frame<0 + autotile = @autotiles[id/48-1] + return if !autotile || autotile.disposed? + if autotile.height==@tileSrcHeight + anim = frame*@tileSrcWidth + src_rect = Rect.new(anim,0,@tileSrcWidth,@tileSrcHeight) + if @diffsizes + bitmap.stretch_blt(Rect.new(x,y,@tileWidth,@tileHeight),autotile,src_rect) + else + bitmap.blt(x,y,autotile,src_rect) + end + else + anim = frame*3*@tileSrcWidth + id %= 48 + tiles = Autotiles[id>>3][id&7] + src = Rect.new(0,0,0,0) + halfTileWidth = @tileWidth>>1 + halfTileHeight = @tileHeight>>1 + halfTileSrcWidth = @tileSrcWidth>>1 + halfTileSrcHeight = @tileSrcHeight>>1 + for i in 0...4 + tile_position = tiles[i] - 1 + src.set( (tile_position % 6)*halfTileSrcWidth + anim, + (tile_position / 6)*halfTileSrcHeight, halfTileSrcWidth, halfTileSrcHeight) + if @diffsizes + bitmap.stretch_blt( + Rect.new(i%2*halfTileWidth+x,i/2*halfTileHeight+y,halfTileWidth,halfTileHeight), + autotile,src) + else + bitmap.blt(i%2*halfTileWidth+x,i/2*halfTileHeight+y, autotile, src) + end + end + end + end + + def getAutotile(sprite,id) + frames = @framecount[id/48-1] + if frames<=1 + anim = 0 + else + anim = (Graphics.frame_count/Animated_Autotiles_Frames)%frames + end + return if anim<0 + bitmap = @autotileInfo[id] + if !bitmap + bitmap = Bitmap.new(@tileWidth,@tileHeight) + bltAutotile(bitmap,0,0,id,anim) + @autotileInfo[id] = bitmap + end + sprite.bitmap = bitmap if sprite.bitmap!=bitmap + end + + def getRegularTile(sprite,id) + if @diffsizes + bitmap = @regularTileInfo[id] + if !bitmap + bitmap = Bitmap.new(@tileWidth,@tileHeight) + rect = Rect.new(((id - 384)&7)*@tileSrcWidth,((id - 384)>>3)*@tileSrcHeight, + @tileSrcWidth,@tileSrcHeight) + bitmap.stretch_blt(Rect.new(0,0,@tileWidth,@tileHeight),@tileset,rect) + @regularTileInfo[id] = bitmap + end + sprite.bitmap = bitmap if sprite.bitmap!=bitmap + else + sprite.bitmap = @tileset if sprite.bitmap!=@tileset + sprite.src_rect.set(((id - 384)&7)*@tileSrcWidth,((id - 384)>>3)*@tileSrcHeight, + @tileSrcWidth,@tileSrcHeight) + end + end + + def addTile(tiles,count,xpos,ypos,id) + terrain = @terrain_tags[id] + priority = @priorities[id] + if id>=384 + if count>=tiles.length + sprite = CustomTilemapSprite.new(@viewport) + tiles.push(sprite,0) + else + sprite = tiles[count] + tiles[count+1] = 0 + end + sprite.visible = @visible + sprite.x = xpos + sprite.y = ypos + sprite.tone = @tone + sprite.color = @color + getRegularTile(sprite,id) + else + if count>=tiles.length + sprite = CustomTilemapSprite.new(@viewport) + tiles.push(sprite,1) + else + sprite = tiles[count] + tiles[count+1] = 1 + end + sprite.visible = @visible + sprite.x = xpos + sprite.y = ypos + sprite.tone = @tone + sprite.color = @color + getAutotile(sprite,id) + end + if PBTerrain.hasReflections?(terrain) + spriteZ = -100 + elsif $PokemonGlobal.bridge>0 && PBTerrain.isBridge?(terrain) + spriteZ = 1 + else + spriteZ = (priority==0) ? 0 : ypos+priority*32+32 + end + sprite.z = spriteZ + count += 2 + return count + end + + def refresh_flash + if @flash_data && !@flash + @flash = CustomTilemapSprite.new(viewport) + @flash.visible = true + @flash.z = 1 + @flash.tone = tone + @flash.color = color + @flash.blend_type = 1 + @flash.bitmap = Bitmap.new([graphicsWidth*2,1].max,[graphicsHeight*2,1].max) + @firsttimeflash = true + elsif !@flash_data && @flash + @flash.bitmap.dispose if @flash.bitmap + @flash.dispose + @flash = nil + @firsttimeflash = false + end + end + + def refreshFlashSprite + return if !@flash || @flash_data.nil? + ptX = @ox-@oxFlash + ptY = @oy-@oyFlash + if !@firsttimeflash && !@usedsprites && + ptX>=0 && ptX+@viewport.rect.width<=@flash.bitmap.width && + ptY>=0 && ptY+@viewport.rect.height<=@flash.bitmap.height + @flash.ox = 0 + @flash.oy = 0 + @flash.src_rect.set(ptX.round,ptY.round, + @viewport.rect.width,@viewport.rect.height) + return + end + width = @flash.bitmap.width + height = @flash.bitmap.height + bitmap = @flash.bitmap + ysize = @map_data.ysize + xsize = @map_data.xsize + zsize = @map_data.zsize + @firsttimeflash = false + @oxFlash = @ox-(width>>2) + @oyFlash = @oy-(height>>2) + @flash.ox = 0 + @flash.oy = 0 + @flash.src_rect.set(width>>2,height>>2, + @viewport.rect.width,@viewport.rect.height) + @flash.bitmap.clear + @oxFlash = @oxFlash.floor + @oyFlash = @oyFlash.floor + xStart = @oxFlash/@tileWidth + xStart = 0 if xStart<0 + yStart = @oyFlash/@tileHeight + yStart = 0 if yStart<0 + xEnd = xStart+(width/@tileWidth)+1 + yEnd = yStart+(height/@tileHeight)+1 + xEnd = xsize if xEnd>=xsize + yEnd = ysize if yEnd>=ysize + if xStart>8)&15 + g = (id>>4)&15 + b = (id)&15 + tmpcolor.set(r<<4,g<<4,b<<4) + bitmap.fill_rect(xpos,ypos,@tileWidth,@tileHeight,tmpcolor) + end + end + end + end + + def refresh_tileset + i = 0; len = @regularTileInfo.length; while i100 || ysize>100 + @fullyrefreshed = false + else + for z in 0...zsize + for y in 0...ysize + for x in 0...xsize + id = @map_data[x, y, z] + next if id==0 + next if @priorities[id]==0 && !PBTerrain.hasReflections?(@terrain_tags[id]) + @priotiles.push([x,y,z,id]) + end + end + end + @fullyrefreshed = true + end + end + + def refresh_autotiles + i = 0; len = @autotileInfo.length; while i=2 + @framecount[i] = numframes + end + if hasanimated + ysize = @map_data.ysize + xsize = @map_data.xsize + zsize = @map_data.zsize + if xsize>100 || ysize>100 + @fullyrefreshedautos = false + else + for y in 0...ysize + for x in 0...xsize + haveautotile = false + for z in 0...zsize + id = @map_data[x, y, z] + next if id==0 || id>=384 + next if @priorities[id]!=0 || PBTerrain.hasReflections?(@terrain_tags[id]) + next if @framecount[id/48-1]<2 + haveautotile = true + break + end + @prioautotiles.push([x,y]) if haveautotile + end + end + @fullyrefreshedautos = true + end + else + @fullyrefreshedautos = true + end + end + + def refreshLayer0(autotiles=false) + return true if autotiles && !shown? + ptX = @ox-@oxLayer0 + ptY = @oy-@oyLayer0 + if !autotiles && !@firsttime && !@usedsprites && + ptX>=0 && ptX+@viewport.rect.width<=@layer0.bitmap.width && + ptY>=0 && ptY+@viewport.rect.height<=@layer0.bitmap.height + if @layer0clip && @viewport.ox==0 && @viewport.oy==0 + @layer0.ox = 0 + @layer0.oy = 0 + @layer0.src_rect.set(ptX.round,ptY.round, + @viewport.rect.width,@viewport.rect.height) + else + @layer0.ox = ptX.round + @layer0.oy = ptY.round + @layer0.src_rect.set(0,0,@layer0.bitmap.width,@layer0.bitmap.height) + end + return true + end + width = @layer0.bitmap.width + height = @layer0.bitmap.height + bitmap = @layer0.bitmap + ysize = @map_data.ysize + xsize = @map_data.xsize + zsize = @map_data.zsize + twidth = @tileWidth + theight = @tileHeight + mapdata = @map_data + if autotiles + return true if @fullyrefreshedautos && @prioautotiles.length==0 + xStart = @oxLayer0/twidth + xStart = 0 if xStart<0 + yStart = @oyLayer0/theight + yStart = 0 if yStart<0 + xEnd = xStart+(width/twidth)+1 + yEnd = yStart+(height/theight)+1 + xEnd = xsize if xEnd>xsize + yEnd = ysize if yEnd>ysize + return true if xStart>=xEnd || yStart>=yEnd + trans = Color.new(0,0,0,0) + temprect = Rect.new(0,0,0,0) + tilerect = Rect.new(0,0,twidth,theight) + zrange = 0...zsize + overallcount = 0 + count = 0 + if !@fullyrefreshedautos + for y in yStart..yEnd + for x in xStart..xEnd + haveautotile = false + for z in zrange + id = mapdata[x, y, z] + next if !id || id<48 || id>=384 + prioid = @priorities[id] + next if prioid!=0 || PBTerrain.hasReflections?(@terrain_tags[id]) + fcount = @framecount[id/48-1] + next if !fcount || fcount<2 + if !haveautotile + haveautotile = true + overallcount += 1 + xpos = (x*twidth)-@oxLayer0 + ypos = (y*theight)-@oyLayer0 + bitmap.fill_rect(xpos,ypos,twidth,theight,trans) if overallcount<=2000 + break + end + end + for z in zrange + id = mapdata[x,y,z] + next if !id || id<48 + prioid = @priorities[id] + next if prioid!=0 || PBTerrain.hasReflections?(@terrain_tags[id]) + if overallcount>2000 + xpos = (x*twidth)-@oxLayer0 + ypos = (y*theight)-@oyLayer0 + count = addTile(@autosprites,count,xpos,ypos,id) + next + elsif id>=384 + temprect.set(((id - 384)&7)*@tileSrcWidth,((id - 384)>>3)*@tileSrcHeight, + @tileSrcWidth,@tileSrcHeight) + xpos = (x*twidth)-@oxLayer0 + ypos = (y*theight)-@oyLayer0 + if @diffsizes + bitmap.stretch_blt(Rect.new(xpos,ypos,twidth,theight),@tileset,temprect) + else + bitmap.blt(xpos,ypos,@tileset,temprect) + end + else + tilebitmap = @autotileInfo[id] + if !tilebitmap + anim = autotileFrame(id) + next if anim<0 + tilebitmap = Bitmap.new(twidth,theight) + bltAutotile(tilebitmap,0,0,id,anim) + @autotileInfo[id] = tilebitmap + end + xpos = (x*twidth)-@oxLayer0 + ypos = (y*theight)-@oyLayer0 + bitmap.blt(xpos,ypos,tilebitmap,tilerect) + end + end + end + end + Graphics.frame_reset + else + if !@priorect || !@priorectautos || + @priorect[0]!=xStart || @priorect[1]!=yStart || + @priorect[2]!=xEnd || @priorect[3]!=yEnd + @priorectautos = @prioautotiles.find_all { |tile| + x = tile[0] + y = tile[1] + # "next" means "return" here + next !(xxEnd || yyEnd) + } + @priorect = [xStart,yStart,xEnd,yEnd] + end +# echoln ["autos",@priorect,@priorectautos.length,@prioautotiles.length] + for tile in @priorectautos + x = tile[0] + y = tile[1] + overallcount+=1 + xpos = (x*twidth)-@oxLayer0 + ypos = (y*theight)-@oyLayer0 + bitmap.fill_rect(xpos,ypos,twidth,theight,trans) + z = 0 + while z=384 + temprect.set(((id - 384)&7)*@tileSrcWidth,((id - 384)>>3)*@tileSrcHeight, + @tileSrcWidth,@tileSrcHeight) + if @diffsizes + bitmap.stretch_blt(Rect.new(xpos,ypos,twidth,theight),@tileset,temprect) + else + bitmap.blt(xpos,ypos,@tileset,temprect) + end + else + tilebitmap = @autotileInfo[id] + if !tilebitmap + anim = autotileFrame(id) + next if anim<0 + tilebitmap = Bitmap.new(twidth,theight) + bltAutotile(tilebitmap,0,0,id,anim) + @autotileInfo[id] = tilebitmap + end + bitmap.blt(xpos,ypos,tilebitmap,tilerect) + end + end + end + Graphics.frame_reset if overallcount>500 + end + @usedsprites = false + return true + end + return false if @usedsprites + @firsttime = false + @oxLayer0 = @ox-(width>>2) + @oyLayer0 = @oy-(height>>2) + if @layer0clip + @layer0.ox = 0 + @layer0.oy = 0 + @layer0.src_rect.set(width>>2,height>>2, + @viewport.rect.width,@viewport.rect.height) + else + @layer0.ox = (width>>2) + @layer0.oy = (height>>2) + end + @layer0.bitmap.clear + @oxLayer0 = @oxLayer0.round + @oyLayer0 = @oyLayer0.round + xStart = @oxLayer0/twidth + xStart = 0 if xStart<0 + yStart = @oyLayer0/theight + yStart = 0 if yStart<0 + xEnd = xStart+(width/twidth)+1 + yEnd = yStart+(height/theight)+1 + xEnd = xsize if xEnd>=xsize + yEnd = ysize if yEnd>=ysize + if xStart=384 + tmprect.set( ((id - 384)&7)*@tileSrcWidth,((id - 384)>>3)*@tileSrcHeight, + @tileSrcWidth,@tileSrcHeight) + if @diffsizes + bitmap.stretch_blt(Rect.new(xpos,ypos,twidth,theight),@tileset,tmprect) + else + bitmap.blt(xpos,ypos,@tileset,tmprect) + end + else + frames = @framecount[id/48-1] + if frames<=1 + frame = 0 + else + frame = (Graphics.frame_count/Animated_Autotiles_Frames)%frames + end + bltAutotile(bitmap,xpos,ypos,id,frame) + end + end + end + end + Graphics.frame_reset + end + return true + end + + def refresh(autotiles=false) + @oldOx = @ox + @oldOy = @oy + usesprites = false + if @layer0 + @layer0.visible = @visible + usesprites = !refreshLayer0(autotiles) + return if autotiles && !usesprites + else + usesprites = true + end + refreshFlashSprite + vpx = @viewport.rect.x + vpy = @viewport.rect.y + vpr = @viewport.rect.width+vpx + vpb = @viewport.rect.height+vpy + xsize = @map_data.xsize + ysize = @map_data.ysize + minX = (@ox/@tileWidth)-1 + minX = 0 if minX<0 + minX = xsize-1 if minX>=xsize + maxX = ((@ox+@viewport.rect.width)/@tileWidth)+1 + maxX = 0 if maxX<0 + maxX = xsize-1 if maxX>=xsize + minY = (@oy/@tileHeight)-1 + minY = 0 if minY<0 + minY = ysize-1 if minY>=ysize + maxY = ((@oy+@viewport.rect.height)/@tileHeight)+1 + maxY = 0 if maxY<0 + maxY = ysize-1 if maxY>=ysize + count = 0 + if minXmaxX || ymaxY) + } + @priotilesrect = [minX,minY,maxX,maxY] + end + # echoln [minX,minY,maxX,maxY,@priotilesfast.length,@priotiles.length] + for prio in @priotilesfast + xpos = (prio[0]*@tileWidth)-@ox + ypos = (prio[1]*@tileHeight)-@oy + count = addTile(@tiles,count,xpos,ypos,prio[3]) + end + else + if !@priotilesrect || !@priotilesfast || + @priotilesrect[0]!=minX || + @priotilesrect[1]!=minY || + @priotilesrect[2]!=maxX || + @priotilesrect[3]!=maxY + @priotilesfast=[] + for z in 0...@map_data.zsize + for y in minY..maxY + for x in minX..maxX + id = @map_data[x, y, z] + next if id==0 + next if @priorities[id]==0 && !PBTerrain.hasReflections?(@terrain_tags[id]) + @priotilesfast.push([x,y,z,id]) + end + end + end + @priotilesrect = [minX,minY,maxX,maxY] + end + for prio in @priotilesfast + xpos = (prio[0]*@tileWidth)-@ox + ypos = (prio[1]*@tileHeight)-@oy + count = addTile(@tiles,count,xpos,ypos,prio[3]) + end + end + end + if count<@tiles.length + bigchange = (count<=(@tiles.length*2/3)) && (@tiles.length*2/3)>25 + j = count; len = @tiles.length; while j=48 || !srcBitmap || srcBitmap.disposed? + anim=0 + cxTile=3 + cyTile=3 + tiles = TileDrawingHelper::Autotiles[id>>3][id&7] + src=Rect.new(0,0,0,0) + for i in 0...4 + tile_position = tiles[i] - 1 + src.set( + tile_position % 6 * cxTile + anim, + tile_position / 6 * cyTile, cxTile, cyTile) + dstBitmap.blt(i%2*cxTile+x,i/2*cyTile+y, srcBitmap, src) + end +end + +def passable?(passages,tile_id) + return false if tile_id == nil + passage = passages[tile_id] + return (passage && passage<15) +end + +def getPassabilityMinimap(mapid) + map = load_data(sprintf("Data/Map%03d.rxdata",mapid)) + tileset = $data_tilesets[map.tileset_id] + minimap = AnimatedBitmap.new("Graphics/Pictures/minimap_tiles") + ret = Bitmap.new(map.width*6,map.height*6) + passtable = Table.new(map.width,map.height) + passages = tileset.passages + for i in 0...map.width + for j in 0...map.height + pass=true + for z in [2,1,0] + if !passable?(passages,map.data[i,j,z]) + pass=false + break + end + end + passtable[i,j]=pass ? 1 : 0 + end + end + neighbors=TileDrawingHelper::NeighborsToTiles + for i in 0...map.width + for j in 0...map.height + if passtable[i,j]==0 + nb=TileDrawingHelper.tableNeighbors(passtable,i,j) + tile=neighbors[nb] + bltMinimapAutotile(ret,i*6,j*6,minimap.bitmap,tile) + end + end + end + minimap.disposes + return ret +end + + + +module ScreenPosHelper + def self.pbScreenZoomX(ch) + zoom=1.0 + if $PokemonSystem.tilemap==2 + zoom=((ch.screen_y - 16) - (Graphics.height / 2)) * + (Draw_Tilemap::Pitch*1.0 / (Graphics.height * 25)) + 1 + end + return zoom*Game_Map::TILE_WIDTH/32.0 + end + + def self.pbScreenZoomY(ch) + zoom=1.0 + if $PokemonSystem.tilemap==2 + zoom=((ch.screen_y - 16) - (Graphics.height / 2)) * + (Draw_Tilemap::Pitch*1.0 / (Graphics.height * 25)) + 1 + end + return zoom*Game_Map::TILE_HEIGHT/32.0 + end + + def self.pbScreenX(ch) + ret=ch.screen_x + if $PokemonSystem.tilemap==2 + widthdiv2=(Graphics.width / 2) + ret=widthdiv2+(ret-widthdiv2)*pbScreenZoomX(ch) + end + return ret + end + + def self.pbScreenY(ch) + ret=ch.screen_y + if $PokemonSystem.tilemap==2 && Draw_Tilemap::Curve && Draw_Tilemap::Pitch != 0 + zoomy=pbScreenZoomY(ch) + oneMinusZoomY=1-zoomy + ret += (8 * oneMinusZoomY * (oneMinusZoomY / + (2 * ((Draw_Tilemap::Pitch*1.0 / 100) / (Graphics.height*1.0 / 16.0))) + 0.5)) + end + return ret + end + + @heightcache={} + + def self.bmHeight(bm) + h=@heightcache[bm] + if !h + bmap=AnimatedBitmap.new("Graphics/Characters/"+bm,0) + h=bmap.height + @heightcache[bm]=h + bmap.dispose + end + return h + end + + def self.pbScreenZ(ch,height=nil) + if height==nil + height=0 + if ch.tile_id > 0 + height=32 + elsif ch.character_name!="" + height=bmHeight(ch.character_name)/4 + end + end + ret=ch.screen_z(height) + if $PokemonSystem.tilemap==2 + ret-=(pbScreenZoomY(ch) < 0.5 ? 1000 : 0) + end + return ret + end +end + +############################################### + + + +class Draw_Tilemap # This class controls a set of sprites, with + attr_reader :tileset # different Z values, arranged into horizontal bars + attr_reader :map_data + attr_reader :flash_data + attr_reader :priorities + attr_reader :terrain_tags + attr_reader :autotiles + attr_accessor :bitmaps + attr_accessor :pitch + attr_accessor :ox + attr_accessor :oy + attr_accessor :visible + attr_reader :viewport + attr_accessor :color + attr_accessor :tone + StripSize = 16 + Curve = true + Pitch = 3 + FlashOpacity = [100,90,80,70,80,90] + + def initialize(viewport=nil) + @tileset=nil + @map_data=nil + @priorities=nil + @terrain_tags=nil + @autotiles=[nil,nil,nil,nil,nil,nil,nil] + @viewport=viewport + @visible=true + @helper=TileDrawingHelper.new(nil,@autotiles) + @drawnstrips=[] + @contentstrips=[] + @disposed=false + @bitmaps=[] + @sprites=[] + @ox=0 + @oy=0 + @tone=Tone.new(0,0,0,0) + @color=Color.new(0,0,0,0) + @flash_data=nil + @numsprites=0 + end + + def tileset=(value) + @tileset=value + @helper.tileset=value + @doredraw=true + end + + def map_data=(value) + @map_data=value + @doredraw=true + end + + def flash_data=(value) + @flash_data=value + @doredraw=true + end + + def priorities=(value) + @priorities=value + @doredraw=true + end + + def terrain_tags=(value) + @terrain_tags=value + @doredraw=true + end + + def redrawmap + # Provide blank data in proper object form + self.clear + xsize=@map_data.xsize + ysize=@map_data.ysize + # Bitmaps used for each priority's drawing. Priorities 2-5 are combined. + @bitmaps = [Bitmap.new(xsize*32, ysize*32+StripSize), + Bitmap.new(xsize*32, ysize*32+StripSize), + Bitmap.new(xsize*32, ysize*32+StripSize)] + for i in @bitmaps + i.clear + end + if @flash_data + @bitmaps.push(Bitmap.new(xsize*32, ysize*32+StripSize)) + end + @drawnstrips.clear + @contentstrips.clear + # Generate blank sprites + @sprites.clear + @numsprites=ysize * (32 / StripSize) + for i in 0...@map_data.zsize # For each layer + @sprites.push([]) + @contentstrips.push([]) + end + if @flash_data + @sprites.push([]) + @contentstrips.push([]) + end + end + + def update + oyunchanged=false + if !@flash_data.nil? && @sprites.length>0 + flashindex=@sprites.length-1 + for j in 0...@numsprites + sprite=@sprites[flashindex][j] + next if !sprite.is_a?(Sprite) + sprite.opacity=FlashOpacity[(Graphics.frame_count/2) % 6] + end + end + for s in @sprites + for sprite in s + next if !sprite.is_a?(Sprite) + # sprite.tone=@tone + # sprite.color=@color + end + end + if @doredraw + @drawnstrips=[] + redrawmap + @doredraw=false + elsif @oldOx==@ox && @oldOy==@oy + return + elsif @oldOy==@oy + oyunchanged=true + end + @oldOx=@ox + @oldOy=@oy + @pitch = Pitch + minvalue=[0, ((Graphics.height / 2) - + ((Graphics.height * 60) / @pitch) + @oy) / StripSize].max.to_i + maxvalue=[@numsprites - 1,(@oy + Graphics.height) / StripSize].min.to_i + return if minvalue>maxvalue + for j in 0...@numsprites + if jmaxvalue + for i in 0...@sprites.length + sprite=@sprites[i][j] + if sprite + sprite.dispose if sprite.is_a?(Sprite) + @sprites[i][j]=nil + end + end + else + drawStrip(j) + end + end + vpy=@viewport.rect.y + vpr=@viewport.rect.x+@viewport.rect.width + vpb=@viewport.rect.y+@viewport.rect.height + numsprites=0 + for i in @sprites + numsprites+=i.compact.length + end + for j in minvalue..maxvalue + # For each strip within the visible screen, update OX/Y + x=Graphics.width/2 + sox=@ox+x + y = (j * StripSize - @oy) + zoom_x=1.0 + zoom_y=1.0 + unless @pitch == 0 # Apply X Zoom + zoom_x = (y - Graphics.height*1.0 / 2) * (@pitch*1.0 / (Graphics.height * 25)) + 1 + if Curve # Zoom Y values same as X, and compensate + zoom_y = zoom_x + yadd = StripSize*1.0 * (1 - zoom_y) * ((1 - zoom_y) / + (2 * ((@pitch*1.0 / 100) / (Graphics.height*1.0 / (StripSize * 2)))) + 0.5) + y+=yadd + end + end + xstart=(x-sox*zoom_x) + yend=(y+(StripSize*2)*zoom_y) + if xstart>vpr || yend<=vpy + for i in 0...@sprites.length + sprite=@sprites[i][j] + if sprite.is_a?(Sprite) + sprite.dispose + @sprites[i][j]=nil + end + end + else + for i in 0...@sprites.length + sprite=@sprites[i][j] + next if !sprite + if sprite==true + sprite=newSprite(i,j) + @sprites[i][j]=sprite + end + sprite.visible=@visible + sprite.x = x + sprite.ox = sox + sprite.y = y + sprite.zoom_x = zoom_x + sprite.zoom_y = zoom_y + end + end + end + end + + def clear + for i in @bitmaps + i.dispose + end + @bitmaps.clear + for i in 0...@sprites.length + for j in 0...@sprites[i].length + @sprites[i][j].dispose if @sprites[i][j].is_a?(Sprite) + end + @sprites[i].clear + end + @sprites.clear + end + + def dispose + return if @disposed + self.clear + for i in 0...7 + self.autotiles[i]=nil + end + @helper=nil + @sprites=nil + @bitmaps=nil + @disposed = true + end + + def disposed? + return @disposed + end + + def newSprite(i,j) + sprite=Sprite.new(@viewport) + sprite.bitmap=@bitmaps[i] + sprite.src_rect.set(0, j * StripSize, @map_data.xsize * 32, StripSize * 2) + sprite.x = Graphics.width / 2 + sprite.y = -64 + sprite.z = (i * 32) + sprite.tone=@tone + sprite.color=@color + if i==@bitmaps.length-1 && !@flash_data.nil? + sprite.blend_type=1 + sprite.z=1 + sprite.opacity=FlashOpacity[(Graphics.frame_count/2) % 6] + end + return sprite + end + + def drawStrip(j) + minY=(j*StripSize)/32 + maxY=(j*StripSize+StripSize*2)/32 + minY=0 if minY<0 + minY=@map_data.ysize-1 if minY>@map_data.ysize-1 + maxY=0 if maxY<0 + maxY=@map_data.ysize-1 if maxY>@map_data.ysize-1 + for y in minY..maxY + if !@drawnstrips[y] + for x in 0...@map_data.xsize + draw_position(x, y) + end + @drawnstrips[y]=true + end + end + for i in 0...@sprites.length # For each priority + sprite=@sprites[i][j] + if !sprite || (sprite!=true && sprite.disposed?) + havecontent=false + for y in minY..maxY + havecontent=havecontent||@contentstrips[i][y] + end + sprite=(havecontent) ? true : nil + @sprites[i][j]=sprite + end + end + end + + def draw_position(x, y) + for layer in 0...@map_data.zsize + pos = @map_data[x, y, layer] + priopos=@priorities[pos] + priopos=0 if !priopos + prio=(20 + @helper.bltTile(@bitmaps[prio],x*32,y*32,pos,0) + end + if !@flash_data.nil? + lastlayer=@bitmaps.length-1 + id=@flash_data[x,y,0] + r=(id>>8)&15 + g=(id>>4)&15 + b=(id)&15 + @contentstrips[lastlayer][y]=true + color=Color.new(r*16,g*16,b*16) + @bitmaps[lastlayer].fill_rect(x*32,y*32,32,32,color) + end + end +end + + + +class Sprite_Character + alias perspectivetilemap_initialize initialize + attr_accessor :character + + def initialize(viewport, character = nil) + @character = character + perspectivetilemap_initialize(viewport,character) + end + + alias update_or :update + + def update + update_or + if $PokemonSystem.tilemap==2 + self.zoom_y=ScreenPosHelper.pbScreenZoomY(@character) + self.zoom_x=ScreenPosHelper.pbScreenZoomX(@character) + self.x=ScreenPosHelper.pbScreenX(@character) + self.y=ScreenPosHelper.pbScreenY(@character) + self.z=ScreenPosHelper.pbScreenZ(@character,@ch) + end + end +end \ No newline at end of file diff --git a/Data/Scripts/005_Map renderer/003_Tilemap_Original.rb b/Data/Scripts/005_Map renderer/003_Tilemap_Original.rb new file mode 100644 index 000000000..47cdc2c3c --- /dev/null +++ b/Data/Scripts/005_Map renderer/003_Tilemap_Original.rb @@ -0,0 +1,118 @@ +#=============================================================================== +# +#=============================================================================== +class SynchronizedTilemapAutotilesInternal + def initialize(oldat) + @atdisposables = [[],[],[],[],[],[],[]] + @atframes = [[],[],[],[],[],[],[]] + @atframe = [-1,-1,-1,-1,-1,-1,-1] + @autotiles = [] + @oldat = oldat + end + + def dispose + for i in 0...7 + for bitmap in @atdisposables[i] + bitmap.dispose + end + @atdisposables[i].clear + @atframes[i].clear + end + end + + def [](i) + return @autotiles[i] + end + + def []=(i,value) + for frame in @atdisposables[i] + frame.dispose + end + @atframe[i] = -1 + @atframes[i].clear + @atdisposables[i].clear + if value && !value.disposed? + if value.height==32 + frames = value.width/32 + for j in 0...frames + @atdisposables[i][j] = Bitmap.new(32,32) + @atdisposables[i][j].blt(0,0,value,Rect.new(j*32,0,32,32)) + @atframes[i][j] = @atdisposables[i][j] + end + elsif value.height==128 + frames = value.width/96 + for j in 0...frames + @atdisposables[i][j] = Bitmap.new(96,128) + @atdisposables[i][j].blt(0,0,value,Rect.new(j*96,0,96,128)) + @atframes[i][j] = @atdisposables[i][j] + end + else + @atframes[i][0] = value + end + else + @atframes[i][0] = value + end + @autotiles[i] = value + sync + end + + def sync + frameused = [] + for i in 0...7 + frames = [1,@atframes[i].length].max + frame = (Graphics.frame_count/15)%frames + if frames>1 && @atframe[i]!=frame + @oldat[i] = @atframes[i][frame] + @atframe[i] = frame + end + end + end +end + + + +class SynchronizedTilemapAutotiles + def initialize(autotiles) + @autotiles = autotiles + end + + def [](i) + return @autotiles[i] + end + + def []=(i,value) + @autotiles[i] = value + end +end + + + +class SynchronizedTilemap < Tilemap + # This class derives from Tilemap just to synchronize + # the tilemap animation. + attr_accessor :numupdates + + def initialize(viewport=nil) + super(viewport) + @updating = true + @autotiles = SynchronizedTilemapAutotilesInternal.new(self.autotiles) + @autos = SynchronizedTilemapAutotiles.new(@autotiles) + @updating = false + end + + def dispose + @autotiles.dispose + super + end + + def autotiles + return @autos if !@updating + super + end + + def update + return if disposed? + @autotiles.sync + super + end +end \ No newline at end of file diff --git a/Data/Scripts/005_Map renderer/004_TilemapLoader.rb b/Data/Scripts/005_Map renderer/004_TilemapLoader.rb new file mode 100644 index 000000000..14d0a4720 --- /dev/null +++ b/Data/Scripts/005_Map renderer/004_TilemapLoader.rb @@ -0,0 +1,70 @@ +class TilemapLoader + def initialize(viewport) + @viewport = viewport + @tilemap = nil + @color = Color.new(0,0,0,0) + @tone = Tone.new(0,0,0,0) + updateClass + end + + def updateClass + case $PokemonSystem.tilemap + when 1 # Custom (recommended) + setClass(CustomTilemap) + when 2 # Perspective + setClass(Draw_Tilemap) + else # Original (SynchronizedTilemap) or custom (CustomTilemap) + if Tilemap.method_defined?(:passages) + setClass(CustomTilemap) + else + setClass(($ResizeFactor==1.0) ? SynchronizedTilemap : CustomTilemap) + end + end + end + + def setClass(cls) + newtilemap = cls.new(@viewport) + if @tilemap + newtilemap.tileset = @tilemap.tileset + newtilemap.map_data = @tilemap.map_data + newtilemap.flash_data = @tilemap.flash_data + newtilemap.priorities = @tilemap.priorities + newtilemap.terrain_tags = @tilemap.terrain_tags + newtilemap.visible = @tilemap.visible + newtilemap.ox = @tilemap.ox + newtilemap.oy = @tilemap.oy + for i in 0...7 + newtilemap.autotiles[i] = @tilemap.autotiles[i] + end + @tilemap.dispose + @tilemap = newtilemap + newtilemap.update if cls!=SynchronizedTilemap + else + @tilemap = newtilemap + end + end + + def dispose; @tilemap.dispose; end + def disposed?; @tilemap && @tilemap.disposed?; end + def update; @tilemap.update; end + def viewport; @tilemap.viewport; end + def autotiles; @tilemap.autotiles; end + def tileset; @tilemap.tileset; end + def tileset=(v); @tilemap.tileset = v; end + def map_data; @tilemap.map_data; end + def map_data=(v); @tilemap.map_data = v; end + def flash_data; @tilemap.flash_data; end + def flash_data=(v); @tilemap.flash_data = v; end + def priorities; @tilemap.priorities; end + def priorities=(v); @tilemap.priorities = v; end + def terrain_tags; (@tilemap.terrain_tags rescue nil); end + def terrain_tags=(v); (@tilemap.terrain_tags = v rescue nil); end + def visible; @tilemap.visible; end + def visible=(v); @tilemap.visible = v; end + def tone; (@tilemap.tone rescue @tone); end + def tone=(value); (@tilemap.tone = value rescue nil); end + def ox; @tilemap.ox; end + def ox=(v); @tilemap.ox = v; end + def oy; @tilemap.oy; end + def oy=(v); @tilemap.oy = v; end +end \ No newline at end of file diff --git a/Data/Scripts/005_Map renderer/005_TileDrawingHelper.rb b/Data/Scripts/005_Map renderer/005_TileDrawingHelper.rb new file mode 100644 index 000000000..3ebf87843 --- /dev/null +++ b/Data/Scripts/005_Map renderer/005_TileDrawingHelper.rb @@ -0,0 +1,136 @@ +class TileDrawingHelper + attr_accessor :tileset + attr_accessor :autotiles + + Autotiles = [ + [ [27, 28, 33, 34], [ 5, 28, 33, 34], [27, 6, 33, 34], [ 5, 6, 33, 34], + [27, 28, 33, 12], [ 5, 28, 33, 12], [27, 6, 33, 12], [ 5, 6, 33, 12] ], + [ [27, 28, 11, 34], [ 5, 28, 11, 34], [27, 6, 11, 34], [ 5, 6, 11, 34], + [27, 28, 11, 12], [ 5, 28, 11, 12], [27, 6, 11, 12], [ 5, 6, 11, 12] ], + [ [25, 26, 31, 32], [25, 6, 31, 32], [25, 26, 31, 12], [25, 6, 31, 12], + [15, 16, 21, 22], [15, 16, 21, 12], [15, 16, 11, 22], [15, 16, 11, 12] ], + [ [29, 30, 35, 36], [29, 30, 11, 36], [ 5, 30, 35, 36], [ 5, 30, 11, 36], + [39, 40, 45, 46], [ 5, 40, 45, 46], [39, 6, 45, 46], [ 5, 6, 45, 46] ], + [ [25, 30, 31, 36], [15, 16, 45, 46], [13, 14, 19, 20], [13, 14, 19, 12], + [17, 18, 23, 24], [17, 18, 11, 24], [41, 42, 47, 48], [ 5, 42, 47, 48] ], + [ [37, 38, 43, 44], [37, 6, 43, 44], [13, 18, 19, 24], [13, 14, 43, 44], + [37, 42, 43, 48], [17, 18, 47, 48], [13, 18, 43, 48], [ 1, 2, 7, 8] ] + ] + + # converts neighbors returned from tableNeighbors to tile indexes + NeighborsToTiles = [ + 46, 44, 46, 44, 43, 41, 43, 40, 46, 44, 46, 44, 43, 41, 43, 40, + 42, 32, 42, 32, 35, 19, 35, 18, 42, 32, 42, 32, 34, 17, 34, 16, + 46, 44, 46, 44, 43, 41, 43, 40, 46, 44, 46, 44, 43, 41, 43, 40, + 42, 32, 42, 32, 35, 19, 35, 18, 42, 32, 42, 32, 34, 17, 34, 16, + 45, 39, 45, 39, 33, 31, 33, 29, 45, 39, 45, 39, 33, 31, 33, 29, + 37, 27, 37, 27, 23, 15, 23, 13, 37, 27, 37, 27, 22, 11, 22, 9, + 45, 39, 45, 39, 33, 31, 33, 29, 45, 39, 45, 39, 33, 31, 33, 29, + 36, 26, 36, 26, 21, 7, 21, 5, 36, 26, 36, 26, 20, 3, 20, 1, + 46, 44, 46, 44, 43, 41, 43, 40, 46, 44, 46, 44, 43, 41, 43, 40, + 42, 32, 42, 32, 35, 19, 35, 18, 42, 32, 42, 32, 34, 17, 34, 16, + 46, 44, 46, 44, 43, 41, 43, 40, 46, 44, 46, 44, 43, 41, 43, 40, + 42, 32, 42, 32, 35, 19, 35, 18, 42, 32, 42, 32, 34, 17, 34, 16, + 45, 38, 45, 38, 33, 30, 33, 28, 45, 38, 45, 38, 33, 30, 33, 28, + 37, 25, 37, 25, 23, 14, 23, 12, 37, 25, 37, 25, 22, 10, 22, 8, + 45, 38, 45, 38, 33, 30, 33, 28, 45, 38, 45, 38, 33, 30, 33, 28, + 36, 24, 36, 24, 21, 6, 21, 4, 36, 24, 36, 24, 20, 2, 20, 0 + ] + + def self.tableNeighbors(data,x,y) + return 0 if x < 0 || x >= data.xsize + return 0 if y < 0 || y >= data.ysize + t = data[x,y] + 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 + return i + end + + def self.fromTileset(tileset) + bmtileset=pbGetTileset(tileset.tileset_name) + bmautotiles=[] + for i in 0...7 + bmautotiles.push(pbGetAutotile(tileset.autotile_names[i])) + end + return self.new(bmtileset,bmautotiles) + end + + def initialize(tileset,autotiles) + @tileset = tileset + @autotiles = autotiles + end + + def dispose + @tileset.dispose if @tileset + @tileset = nil + for i in 0...@autotiles.length + @autotiles[i].dispose + @autotiles[i] = nil + end + end + + def bltSmallAutotile(bitmap,x,y,cxTile,cyTile,id,frame) + return if id >= 384 || frame < 0 || !@autotiles + autotile = @autotiles[id / 48 - 1] + return if !autotile || autotile.disposed? + cxTile = [cxTile / 2, 1].max + cyTile = [cyTile / 2, 1].max + if autotile.height == 32 + anim = frame * 32 + src_rect = Rect.new(anim, 0, 32, 32) + bitmap.stretch_blt(Rect.new(x, y, cxTile * 2, cyTile * 2), autotile, src_rect) + else + anim = frame * 96 + id %= 48 + tiles = TileDrawingHelper::Autotiles[id >> 3][id & 7] + src = Rect.new(0, 0, 0, 0) + for i in 0...4 + tile_position = tiles[i] - 1 + src.set(tile_position % 6 * 16 + anim, tile_position / 6 * 16, 16, 16) + bitmap.stretch_blt(Rect.new(i % 2 * cxTile + x, i / 2 * cyTile + y, cxTile, cyTile), + autotile, src) + end + end + end + + def bltSmallRegularTile(bitmap,x,y,cxTile,cyTile,id) + return if id < 384 || !@tileset || @tileset.disposed? + rect = Rect.new((id - 384) % 8 * 32, (id - 384) / 8 * 32, 32, 32) + bitmap.stretch_blt(Rect.new(x, y, cxTile, cyTile), @tileset, rect) + end + + def bltSmallTile(bitmap,x,y,cxTile,cyTile,id,frame=0) + if id >= 384 + bltSmallRegularTile(bitmap, x, y, cxTile, cyTile, id) + elsif id > 0 + bltSmallAutotile(bitmap, x, y, cxTile, cyTile, id, frame) + end + end + + def bltAutotile(bitmap,x,y,id,frame) + bltSmallAutotile(bitmap, x, y, 32, 32, id, frame) + end + + def bltRegularTile(bitmap,x,y,id) + bltSmallRegularTile(bitmap, x, y, 32, 32, id) + end + + def bltTile(bitmap,x,y,id,frame=0) + if id >= 384 + bltRegularTile(bitmap, x, y, id) + elsif id > 0 + bltAutotile(bitmap, x, y, id, frame) + end + end +end \ No newline at end of file diff --git a/Data/Scripts/006_Events and files/001_Interpreter.rb b/Data/Scripts/006_Events and files/001_Interpreter.rb new file mode 100644 index 000000000..7f87747ca --- /dev/null +++ b/Data/Scripts/006_Events and files/001_Interpreter.rb @@ -0,0 +1,1469 @@ +#=============================================================================== +# ** Interpreter +#------------------------------------------------------------------------------- +# This interpreter runs event commands. This class is used within the +# Game_System class and the Game_Event class. +#=============================================================================== +class Interpreter + #----------------------------------------------------------------------------- + # * Object Initialization + # depth : nest depth + # main : main flag + #----------------------------------------------------------------------------- + def initialize(depth = 0, main = false) + @depth = depth + @main = main + # Depth goes up to level 100 + if depth > 100 + print("Common event call has exceeded maximum limit.") + exit + end + # Clear inner situation of interpreter + clear + end + + def clear + @map_id = 0 # map ID when starting up + @event_id = 0 # event ID + @message_waiting = false # waiting for message to end + @move_route_waiting = false # waiting for move completion + @button_input_variable_id = 0 # button input variable ID + @wait_count = 0 # wait count + @child_interpreter = nil # child interpreter + @branch = {} # branch data + end + #----------------------------------------------------------------------------- + # * Event Setup + # list : list of event commands + # event_id : event ID + #----------------------------------------------------------------------------- + def setup(list, event_id, map_id=nil) + # Clear inner situation of interpreter + clear + # Remember map ID + @map_id = map_id ? map_id : $game_map.map_id + # Remember event ID + @event_id = event_id + # Remember list of event commands + @list = list + # Initialize index + @index = 0 + # Clear branch data hash + @branch.clear + end + + def running? + return @list != nil + end + + def setup_starting_event + $game_map.refresh if $game_map.need_refresh + # If common event call is reserved + if $game_temp.common_event_id > 0 + # Set up event + setup($data_common_events[$game_temp.common_event_id].list, 0) + # Release reservation + $game_temp.common_event_id = 0 + return + end + # Loop (map events) + for event in $game_map.events.values + # If running event is found + next if !event.starting + # If not auto run + if event.trigger < 3 + # Lock + event.lock + # Clear starting flag + event.clear_starting + end + # Set up event + setup(event.list, event.id, event.map.map_id) #### CHANGED + return + end + # Loop (common events) + for common_event in $data_common_events.compact + # If trigger is auto run, and condition switch is ON + if common_event.trigger == 1 and + $game_switches[common_event.switch_id] == true + # Set up event + setup(common_event.list, 0) + return + end + end + end + #----------------------------------------------------------------------------- + # * Frame Update + #----------------------------------------------------------------------------- + def update + # Initialize loop count + @loop_count = 0 + # Loop + loop do + @loop_count += 1 + if @loop_count > 100 + # Call Graphics.update for freeze prevention + Graphics.update + @loop_count = 0 + end + # If map is different than event startup time + if $game_map.map_id != @map_id && + (!$MapFactory || !$MapFactory.areConnected?($game_map.map_id,@map_id)) + @event_id = 0 + end + if @child_interpreter != nil + @child_interpreter.update + unless @child_interpreter.running? + @child_interpreter = nil + end + return if @child_interpreter != nil + end + return if @message_waiting + if @move_route_waiting + return if $game_player.move_route_forcing + for event in $game_map.events.values + return if event.move_route_forcing + end + @move_route_waiting = false + end + # If waiting for button input + if @button_input_variable_id > 0 + input_button + return + end + if @wait_count > 0 + @wait_count -= 1 + return + end + # If an action forcing battler exists + return if $game_temp.forcing_battler != nil + # If a call flag is set for each type of screen + if $game_temp.battle_calling or + $game_temp.shop_calling or + $game_temp.name_calling or + $game_temp.menu_calling or + $game_temp.save_calling or + $game_temp.gameover + return + end + # If list of event commands is empty + if @list == nil + setup_starting_event if @main + return if @list == nil + end + # If return value is false when trying to execute event command + return if execute_command == false + # Advance index + @index += 1 + end + end + #----------------------------------------------------------------------------- + # * Button Input + #----------------------------------------------------------------------------- + def input_button + # Determine pressed button + n = 0 + for i in 1..18 + n = i if Input.trigger?(i) + end + # If button was pressed + if n > 0 + # Change value of variables + $game_variables[@button_input_variable_id] = n + $game_map.need_refresh = true + # End button input + @button_input_variable_id = 0 + end + end + #----------------------------------------------------------------------------- + # * Setup Choices + #----------------------------------------------------------------------------- + def setup_choices(parameters) + # Set choice item count to choice_max + $game_temp.choice_max = parameters[0].size + # Set choice to message_text + for text in parameters[0] + $game_temp.message_text += text + "\n" + end + # Set cancel processing + $game_temp.choice_cancel_type = parameters[1] + # Set callback + current_indent = @list[@index].indent + $game_temp.choice_proc = Proc.new { |n| @branch[current_indent] = n } + end + + def command_dummy + return true + end + + def pbExecuteScript(script) + begin + result = eval(script) + return result + rescue Exception + e = $! + raise if e.is_a?(SystemExit) || "#{e.class}"=="Reset" + event = get_character(0) + s = "Backtrace:\r\n" + message = pbGetExceptionMessage(e) + if e.is_a?(SyntaxError) + script.each_line { |line| + line.gsub!(/\s+$/,"") + if line[/\:\:\s*$/] + message += "\r\n***Line '#{line}' can't begin with '::'. Try putting\r\n" + message += "the next word on the same line, e.g. 'PBSpecies:"+":MEW'" + end + if line[/^\s*\(/] + message += "\r\n***Line '#{line}' shouldn't begin with '('. Try\r\n" + message += "putting the '(' at the end of the previous line instead,\r\n" + message += "or using 'extendtext.exe'." + end + } + else + for bt in e.backtrace[0,10] + s += bt+"\r\n" + end + s.gsub!(/Section(\d+)/) { $RGSS_SCRIPTS[$1.to_i][1] } + end + message = "Exception: #{e.class}\r\nMessage: "+message+"\r\n" + message += "\r\n***Full script:\r\n#{script}\r\n" + if event && $game_map + mapname = ($game_map.name rescue nil) || "???" + err = "Script error within event #{event.id} (coords #{event.x},#{event.y}), " + err += "map #{$game_map.map_id} (#{mapname}):\r\n#{message}\r\n#{s}" + if e.is_a?(Hangup) + $EVENTHANGUPMSG = err; raise + end + raise err + elsif $game_map + mapname = ($game_map.name rescue nil) || "???" + err = "Script error within map #{$game_map.map_id} " + err += "(#{mapname}):\r\n#{message}\r\n#{s}" + if e.is_a?(Hangup) + $EVENTHANGUPMSG = err; raise + end + raise err + else + err = "Script error in interpreter:\r\n#{message}\r\n#{s}" + if e.is_a?(Hangup) + $EVENTHANGUPMSG = err; raise + end + raise err + end + return false + end + end + #----------------------------------------------------------------------------- + # * Event Command Execution + #----------------------------------------------------------------------------- + def execute_command + # If last to arrive for list of event commands + if @index >= @list.size - 1 + # End event + command_end + # Continue + return true + end + # Make event command parameters available for reference via @parameters + @parameters = @list[@index].parameters + # Branch by command code + case @list[@index].code + when 101; return command_101 # Show Text + when 102; return command_102 # Show Choices + when 402; return command_402 # When [**] + when 403; return command_403 # When Cancel + when 103; return command_103 # Input Number + when 104; return command_104 # Change Text Options [not in VX] + when 105; return command_105 # Button Input Processing [not in VX] + when 106; return command_106 # Wait [in VX: 230] + when 111; return command_111 # Conditional Branch + when 411; return command_411 # Else + when 112; return command_112 # Loop + when 413; return command_413 # Repeat Above + when 113; return command_113 # Break Loop + when 115; return command_115 # Exit Event Processing + when 116; return command_116 # Erase Event [in VX: 214] + when 117; return command_117 # Call Common Event + when 118; return command_118 # Label + when 119; return command_119 # Jump to Label + when 121; return command_121 # Control Switches + when 122; return command_122 # Control Variables + when 123; return command_123 # Control Self Switch + when 124; return command_124 # Control Timer + when 125; return command_125 # Change Gold + when 126; return command_126 # Change Items + when 127; return command_127 # Change Weapons + when 128; return command_128 # Change Armor + when 129; return command_129 # Change Party Member + when 131; return command_131 # Change Windowskin [not in VX] + when 132; return command_132 # Change Battle BGM + when 133; return command_133 # Change Battle End ME + when 134; return command_134 # Change Save Access + when 135; return command_135 # Change Menu Access + when 136; return command_136 # Change Encounter + when 201; return command_201 # Transfer Player + when 202; return command_202 # Set Event Location + when 203; return command_203 # Scroll Map + when 204; return command_204 # Change Map Settings + when 205; return command_205 # Change Fog Color Tone [in VX: Set Move Route] + when 206; return command_206 # Change Fog Opacity [in VX: Get on/off Vehicle] + when 207; return command_207 # Show Animation [in VX: 212] + when 208; return command_208 # Change Transparent Flag [in VX: 211] + when 209; return command_209 # Set Move Route [in VX: 205] + when 210; return command_210 # Wait for Move's Completion + when 221; return command_221 # Prepare for Transition [Not in VX, now called Fadeout Screen] + when 222; return command_222 # Execute Transition [Not in VX, now called Fadein Screen] + when 223; return command_223 # Change Screen Color Tone + when 224; return command_224 # Screen Flash + when 225; return command_225 # Screen Shake + when 231; return command_231 # Show Picture + when 232; return command_232 # Move Picture + when 233; return command_233 # Rotate Picture + when 234; return command_234 # Change Picture Color Tone + when 235; return command_235 # Erase Picture + when 236; return command_236 # Set Weather Effects + when 241; return command_241 # Play BGM + when 242; return command_242 # Fade Out BGM + when 245; return command_245 # Play BGS + when 246; return command_246 # Fade Out BGS + when 247; return command_247 # Memorize BGM/BGS [not in VX] + when 248; return command_248 # Restore BGM/BGS [not in VX] + when 249; return command_249 # Play ME + when 250; return command_250 # Play SE + when 251; return command_251 # Stop SE + when 301; return command_301 # Battle Processing + when 601; return command_601 # If Win + when 602; return command_602 # If Escape + when 603; return command_603 # If Lose + when 302; return command_302 # Shop Processing + when 303; return command_303 # Name Input Processing + when 311; return command_311 # Change HP + when 312; return command_312 # Change SP + when 313; return command_313 # Change State + when 314; return command_314 # Recover All + when 315; return command_315 # Change EXP + when 316; return command_316 # Change Level + when 317; return command_317 # Change Parameters + when 318; return command_318 # Change Skills + when 319; return command_319 # Change Equipment + when 320; return command_320 # Change Actor Name + when 321; return command_321 # Change Actor Class + when 322; return command_322 # Change Actor Graphic + when 331; return command_331 # Change Enemy HP + when 332; return command_332 # Change Enemy SP + when 333; return command_333 # Change Enemy State + when 334; return command_334 # Enemy Recover All + when 335; return command_335 # Enemy Appearance + when 336; return command_336 # Enemy Transform + when 337; return command_337 # Show Battle Animation + when 338; return command_338 # Deal Damage + when 339; return command_339 # Force Action + when 340; return command_340 # Abort Battle + when 351; return command_351 # Call Menu Screen + when 352; return command_352 # Call Save Screen + when 353; return command_353 # Game Over + when 354; return command_354 # Return to Title Screen + when 355; return command_355 # Script + else; return true # Other + end + end + + def command_dummy + return true + end + #----------------------------------------------------------------------------- + # * End Event + #----------------------------------------------------------------------------- + def command_end + # Clear list of event commands + @list = nil + # If main map event and event ID are valid + if @main and @event_id > 0 && $game_map.events[@event_id] + # Unlock event + $game_map.events[@event_id].unlock + end + end + #----------------------------------------------------------------------------- + # * Command Skip + #----------------------------------------------------------------------------- + def command_skip + # Get indent + indent = @list[@index].indent + # Loop + loop do + # If next event command is at the same level as indent + if @list[@index+1].indent == indent + # Continue + return true + end + # Advance index + @index += 1 + end + end + #----------------------------------------------------------------------------- + # * Get Character + # parameter : parameter + #----------------------------------------------------------------------------- + def get_character(parameter) + # Branch by parameter + case parameter + when -1 # player + return $game_player + when 0 # this event + events = $game_map.events + return events == nil ? nil : events[@event_id] + else # specific event + events = $game_map.events + return events == nil ? nil : events[parameter] + end + end + #----------------------------------------------------------------------------- + # * Calculate Operated Value + # operation : operation + # operand_type : operand type (0: invariable 1: variable) + # operand : operand (number or variable ID) + #----------------------------------------------------------------------------- + def operate_value(operation, operand_type, operand) + # Get operand + if operand_type == 0 + value = operand + else + value = $game_variables[operand] + end + # Reverse sign of integer if operation is [decrease] + if operation == 1 + value = -value + end + # Return value + return value + end + #----------------------------------------------------------------------------- + # * Show Text + #----------------------------------------------------------------------------- + def command_101 + # If other text has been set to message_text + if $game_temp.message_text != nil + # End + return false + end + # Set message end waiting flag and callback + @message_waiting = true + $game_temp.message_proc = Proc.new { @message_waiting = false } + # Set message text on first line + $game_temp.message_text = @list[@index].parameters[0] + "\n" + line_count = 1 + # Loop + loop do + # If next event command text is on the second line or after + if @list[@index+1].code == 401 + # Add the second line or after to message_text + $game_temp.message_text += @list[@index+1].parameters[0] + "\n" + line_count += 1 + # If event command is not on the second line or after + else + # If next event command is show choices + if @list[@index+1].code == 102 + # If choices fit on screen + if @list[@index+1].parameters[0].size <= 4 - line_count + # Advance index + @index += 1 + # Choices setup + $game_temp.choice_start = line_count + setup_choices(@list[@index].parameters) + end + # If next event command is input number + elsif @list[@index+1].code == 103 + # If number input window fits on screen + if line_count < 4 + # Advance index + @index += 1 + # Number input setup + $game_temp.num_input_start = line_count + $game_temp.num_input_variable_id = @list[@index].parameters[0] + $game_temp.num_input_digits_max = @list[@index].parameters[1] + end + end + # Continue + return true + end + # Advance index + @index += 1 + end + end + #----------------------------------------------------------------------------- + # * Show Choices + #----------------------------------------------------------------------------- + def command_102 + # If text has been set to message_text + if $game_temp.message_text != nil + # End + return false + end + # Set message end waiting flag and callback + @message_waiting = true + $game_temp.message_proc = Proc.new { @message_waiting = false } + # Choices setup + $game_temp.message_text = "" + $game_temp.choice_start = 0 + setup_choices(@parameters) + # Continue + return true + end + #----------------------------------------------------------------------------- + # * When [**] + #----------------------------------------------------------------------------- + def command_402 + # If fitting choices are selected + if @branch[@list[@index].indent] == @parameters[0] + # Delete branch data + @branch.delete(@list[@index].indent) + # Continue + return true + end + # If it doesn't meet the condition: command skip + return command_skip + end + #----------------------------------------------------------------------------- + # * When Cancel + #----------------------------------------------------------------------------- + def command_403 + # If choices are cancelled + if @branch[@list[@index].indent] == 4 + # Delete branch data + @branch.delete(@list[@index].indent) + # Continue + return true + end + # If it doen't meet the condition: command skip + return command_skip + end + #----------------------------------------------------------------------------- + # * Input Number + #----------------------------------------------------------------------------- + def command_103 + # If text has been set to message_text + if $game_temp.message_text != nil + # End + return false + end + # Set message end waiting flag and callback + @message_waiting = true + $game_temp.message_proc = Proc.new { @message_waiting = false } + # Number input setup + $game_temp.message_text = "" + $game_temp.num_input_start = 0 + $game_temp.num_input_variable_id = @parameters[0] + $game_temp.num_input_digits_max = @parameters[1] + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Change Text Options + #----------------------------------------------------------------------------- + def command_104 + # If message is showing + if $game_temp.message_window_showing + # End + return false + end + # Change each option + $game_system.message_position = @parameters[0] + $game_system.message_frame = @parameters[1] + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Button Input Processing + #----------------------------------------------------------------------------- + def command_105 + # Set variable ID for button input + @button_input_variable_id = @parameters[0] + # Advance index + @index += 1 + # End + return false + end + #----------------------------------------------------------------------------- + # * Wait + #----------------------------------------------------------------------------- + def command_106 + # Set wait count + @wait_count = @parameters[0] * Graphics.frame_rate/20 + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Conditional Branch + #----------------------------------------------------------------------------- + def command_111 + # Initialize local variable: result + result = false + case @parameters[0] + when 0 # switch + result = false + switchname=$data_system.switches[@parameters[1]] + if switchname && switchname[/^s\:/] + result = (eval($~.post_match) == (@parameters[2] == 0)) + else + result = ($game_switches[@parameters[1]] == (@parameters[2] == 0)) + end + when 1 # variable + value1 = $game_variables[@parameters[1]] + if @parameters[2] == 0 + value2 = @parameters[3] + else + value2 = $game_variables[@parameters[3]] + end + case @parameters[4] + when 0 # value1 is equal to value2 + result = (value1 == value2) + when 1 # value1 is greater than or equal to value2 + result = (value1 >= value2) + when 2 # value1 is less than or equal to value2 + result = (value1 <= value2) + when 3 # value1 is greater than value2 + result = (value1 > value2) + when 4 # value1 is less than value2 + result = (value1 < value2) + when 5 # value1 is not equal to value2 + result = (value1 != value2) + end + when 2 # self switch + if @event_id > 0 + key = [$game_map.map_id, @event_id, @parameters[1]] + if @parameters[2] == 0 + result = ($game_self_switches[key] == true) + else + result = ($game_self_switches[key] != true) + end + end + when 3 # timer + if $game_system.timer_working + sec = $game_system.timer / Graphics.frame_rate + if @parameters[2] == 0 + result = (sec >= @parameters[1]) + else + result = (sec <= @parameters[1]) + end + end + when 4, 5 # actor, enemy + when 6 # character + character = get_character(@parameters[1]) + if character != nil + result = (character.direction == @parameters[2]) + end + when 7 + if @parameters[2] == 0 + result = ($Trainer.money >= @parameters[1]) + else + result = ($Trainer.money <= @parameters[1]) + end + when 8, 9, 10 # item, weapon, armor + when 11 # button + result = (Input.press?(@parameters[1])) + when 12 # script + result = pbExecuteScript(@parameters[1]) + end + # Store determinant results in hash + @branch[@list[@index].indent] = result + # If determinant results are true + if @branch[@list[@index].indent] == true + # Delete branch data + @branch.delete(@list[@index].indent) + # Continue + return true + end + # If it doesn't meet the conditions: command skip + return command_skip + end + #----------------------------------------------------------------------------- + # * Else + #----------------------------------------------------------------------------- + def command_411 + # If determinant results are false + if @branch[@list[@index].indent] == false + # Delete branch data + @branch.delete(@list[@index].indent) + # Continue + return true + end + # If it doesn't meet the conditions: command skip + return command_skip + end + #----------------------------------------------------------------------------- + # * Loop + #----------------------------------------------------------------------------- + def command_112 + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Repeat Above + #----------------------------------------------------------------------------- + def command_413 + # Get indent + indent = @list[@index].indent + # Loop + loop do + # Return index + @index -= 1 + # If this event command is the same level as indent + if @list[@index].indent == indent + # Continue + return true + end + end + end + #----------------------------------------------------------------------------- + # * Break Loop + #----------------------------------------------------------------------------- + def command_113 + # Get indent + indent = @list[@index].indent + # Copy index to temporary variables + temp_index = @index + # Loop + loop do + # Advance index + temp_index += 1 + # If a fitting loop was not found + if temp_index >= @list.size-1 + # Continue + return true + end + # If this event command is [repeat above] and indent is shallow + if @list[temp_index].code == 413 and @list[temp_index].indent < indent + # Update index + @index = temp_index + # Continue + return true + end + end + end + #----------------------------------------------------------------------------- + # * Exit Event Processing + #----------------------------------------------------------------------------- + def command_115 + # End event + command_end + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Erase Event + #----------------------------------------------------------------------------- + def command_116 + # If event ID is valid + if @event_id > 0 + # Erase event + $game_map.events[@event_id].erase if $game_map.events[@event_id] + $PokemonMap.addErasedEvent(@event_id) if $PokemonMap + end + # Advance index + @index += 1 + # End + return false + end + #----------------------------------------------------------------------------- + # * Call Common Event + #----------------------------------------------------------------------------- + def command_117 + # Get common event + common_event = $data_common_events[@parameters[0]] + # If common event is valid + if common_event != nil + # Make child interpreter + @child_interpreter = Interpreter.new(@depth + 1) + @child_interpreter.setup(common_event.list, @event_id) + end + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Label + #----------------------------------------------------------------------------- + def command_118 + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Jump to Label + #----------------------------------------------------------------------------- + def command_119 + # Get label name + label_name = @parameters[0] + # Initialize temporary variables + temp_index = 0 + # Loop + loop do + # If a fitting label was not found + if temp_index >= @list.size-1 + # Continue + return true + end + # If this event command is a designated label name + if @list[temp_index].code == 118 and + @list[temp_index].parameters[0] == label_name + # Update index + @index = temp_index + # Continue + return true + end + # Advance index + temp_index += 1 + end + end + #----------------------------------------------------------------------------- + # * Control Switches + #----------------------------------------------------------------------------- + def command_121 + shouldRefresh = false + # Loop for group control + for i in @parameters[0] .. @parameters[1] + next if $game_switches[i] == (@parameters[2] == 0) + # Change switch + $game_switches[i] = (@parameters[2] == 0) + shouldRefresh = true + end + # Refresh map + $game_map.need_refresh = true if shouldRefresh + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Control Variables + #----------------------------------------------------------------------------- + def command_122 + # Initialize value + value = 0 + # Branch with operand + case @parameters[3] + when 0 # invariable (fixed value) + value = @parameters[4] + when 1 # variable + value = $game_variables[@parameters[4]] + when 2 # random number + value = @parameters[4] + rand(@parameters[5] - @parameters[4] + 1) + when 3, 4, 5 # item, actor, enemy + when 6 # character + character = get_character(@parameters[4]) + if character != nil + case @parameters[5] + when 0; value = character.x # x-coordinate + when 1; value = character.y # y-coordinate + when 2; value = character.direction # direction + when 3; value = character.screen_x # screen x-coordinate + when 4; value = character.screen_y # screen y-coordinate + when 5; value = character.terrain_tag # terrain tag + end + end + when 7 # other + case @parameters[4] + when 0; value = $game_map.map_id # map ID + when 1, 3 # number of party members, steps + when 2; value = $Trainer.money # gold + when 4; value = Graphics.frame_count / Graphics.frame_rate # play time + when 5; value = $game_system.timer / Graphics.frame_rate # timer + when 6; value = $game_system.save_count # save count + end + end + shouldRefresh = false + # Loop for group control + for i in @parameters[0] .. @parameters[1] + # Branch with control + case @parameters[2] + when 0 # substitute + next if $game_variables[i] == value + $game_variables[i] = value + when 1 # add + next if $game_variables[i] >= 99999999 + $game_variables[i] += value + when 2 # subtract + next if $game_variables[i] <= -99999999 + $game_variables[i] -= value + when 3 # multiply + next if value == 1 + $game_variables[i] *= value + when 4 # divide + next if value == 1 || value == 0 + $game_variables[i] /= value + when 5 # remainder + next if value == 1 || value == 0 + $game_variables[i] %= value + end + # Maximum limit check + $game_variables[i] = 99999999 if $game_variables[i] > 99999999 + # Minimum limit check + $game_variables[i] = -99999999 if $game_variables[i] < -99999999 + # Refresh map + $game_map.need_refresh = true + end + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Control Self Switch + #----------------------------------------------------------------------------- + def command_123 + # If event ID is valid + if @event_id > 0 + # Make a self switch key + key = [$game_map.map_id, @event_id, @parameters[0]] + newValue = (@parameters[1] == 0) + if $game_self_switches[key] != newValue + # Change self switches + $game_self_switches[key] = newValue + # Refresh map + $game_map.need_refresh = true + end + end + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Control Timer + #----------------------------------------------------------------------------- + def command_124 + # If started + if @parameters[0] == 0 + $game_system.timer = @parameters[1] * Graphics.frame_rate + $game_system.timer_working = true + end + # If stopped + $game_system.timer_working = false if @parameters[0] == 1 + # Continue + return true + end + + def command_125; command_dummy; end # Change Gold + def command_126; command_dummy; end # Change Items + def command_127; command_dummy; end # Change Weapons + def command_128; command_dummy; end # Change Armor + def command_129; command_dummy; end # Change Party Member + #----------------------------------------------------------------------------- + # * Change Windowskin + #----------------------------------------------------------------------------- + def command_131 + # Change windowskin file name + for i in 0...$SpeechFrames.length + if $SpeechFrames[i]==@parameters[0] + $PokemonSystem.textskin=i + MessageConfig.pbSetSpeechFrame("Graphics/Windowskins/"+$SpeechFrames[i]) + return true + end + end + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Change Battle BGM + #----------------------------------------------------------------------------- + def command_132 + # Change battle BGM + $game_system.battle_bgm = @parameters[0] + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Change Battle End ME + #----------------------------------------------------------------------------- + def command_133 + # Change battle end ME + $game_system.battle_end_me = @parameters[0] + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Change Save Access + #----------------------------------------------------------------------------- + def command_134 + # Change save access flag + $game_system.save_disabled = (@parameters[0] == 0) + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Change Menu Access + #----------------------------------------------------------------------------- + def command_135 + # Change menu access flag + $game_system.menu_disabled = (@parameters[0] == 0) + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Change Encounter + #----------------------------------------------------------------------------- + def command_136 + # Change encounter flag + $game_system.encounter_disabled = (@parameters[0] == 0) + # Make encounter count + $game_player.make_encounter_count + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Transfer Player + #----------------------------------------------------------------------------- + def command_201 + # If in battle + if $game_temp.in_battle + # Continue + return true + end + # If transferring player, showing message, or processing transition + if $game_temp.player_transferring or + $game_temp.message_window_showing or + $game_temp.transition_processing + # End + return false + end + # Set transferring player flag + $game_temp.player_transferring = true + # If appointment method is [direct appointment] + if @parameters[0] == 0 + # Set player move destination + $game_temp.player_new_map_id = @parameters[1] + $game_temp.player_new_x = @parameters[2] + $game_temp.player_new_y = @parameters[3] + $game_temp.player_new_direction = @parameters[4] + # If appointment method is [appoint with variables] + else + # Set player move destination + $game_temp.player_new_map_id = $game_variables[@parameters[1]] + $game_temp.player_new_x = $game_variables[@parameters[2]] + $game_temp.player_new_y = $game_variables[@parameters[3]] + $game_temp.player_new_direction = @parameters[4] + end + # Advance index + @index += 1 + # If fade is set + if @parameters[5] == 0 + # Prepare for transition + Graphics.freeze + # Set transition processing flag + $game_temp.transition_processing = true + $game_temp.transition_name = "" + end + # End + return false + end + #----------------------------------------------------------------------------- + # * Set Event Location + #----------------------------------------------------------------------------- + def command_202 + # If in battle + if $game_temp.in_battle + # Continue + return true + end + # Get character + character = get_character(@parameters[0]) + # If no character exists + if character == nil + # Continue + return true + end + # If appointment method is [direct appointment] + if @parameters[1] == 0 + # Set character position + character.moveto(@parameters[2], @parameters[3]) + # If appointment method is [appoint with variables] + elsif @parameters[1] == 1 + # Set character position + character.moveto($game_variables[@parameters[2]], + $game_variables[@parameters[3]]) + # If appointment method is [exchange with another event] + else + old_x = character.x + old_y = character.y + character2 = get_character(@parameters[2]) + if character2 != nil + character.moveto(character2.x, character2.y) + character2.moveto(old_x, old_y) + end + end + # Set character direction + case @parameters[4] + when 8 # up + character.turn_up + when 6 # right + character.turn_right + when 2 # down + character.turn_down + when 4 # left + character.turn_left + end + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Scroll Map + #----------------------------------------------------------------------------- + def command_203 + # If in battle + if $game_temp.in_battle + # Continue + return true + end + # If already scrolling + if $game_map.scrolling? + # End + return false + end + # Start scroll + $game_map.start_scroll(@parameters[0], @parameters[1], @parameters[2]) + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Change Map Settings + #----------------------------------------------------------------------------- + def command_204 + case @parameters[0] + when 0 # panorama + $game_map.panorama_name = @parameters[1] + $game_map.panorama_hue = @parameters[2] + when 1 # fog + $game_map.fog_name = @parameters[1] + $game_map.fog_hue = @parameters[2] + $game_map.fog_opacity = @parameters[3] + $game_map.fog_blend_type = @parameters[4] + $game_map.fog_zoom = @parameters[5] + $game_map.fog_sx = @parameters[6] + $game_map.fog_sy = @parameters[7] + when 2 # battleback + $game_map.battleback_name = @parameters[1] + $game_temp.battleback_name = @parameters[1] + end + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Change Fog Color Tone + #----------------------------------------------------------------------------- + def command_205 + # Start color tone change + $game_map.start_fog_tone_change(@parameters[0], @parameters[1] * Graphics.frame_rate / 20) + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Change Fog Opacity + #----------------------------------------------------------------------------- + def command_206 + # Start opacity level change + $game_map.start_fog_opacity_change(@parameters[0], @parameters[1] * Graphics.frame_rate / 20) + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Show Animation + #----------------------------------------------------------------------------- + def command_207 + # Get character + character = get_character(@parameters[0]) + # If no character exists + if character == nil + # Continue + return true + end + # Set animation ID + character.animation_id = @parameters[1] + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Change Transparent Flag + #----------------------------------------------------------------------------- + def command_208 + # Change player transparent flag + $game_player.transparent = (@parameters[0] == 0) + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Set Move Route + #----------------------------------------------------------------------------- + def command_209 + # Get character + character = get_character(@parameters[0]) + # If no character exists + if character == nil + # Continue + return true + end + # Force move route + character.force_move_route(@parameters[1]) + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Wait for Move's Completion + #----------------------------------------------------------------------------- + def command_210 + # If not in battle + unless $game_temp.in_battle + # Set move route completion waiting flag + @move_route_waiting = true + end + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Prepare for Transition + #----------------------------------------------------------------------------- + def command_221 + # If showing message window + if $game_temp.message_window_showing + # End + return false + end + # Prepare for transition + Graphics.freeze + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Execute Transition + #----------------------------------------------------------------------------- + def command_222 + # If transition processing flag is already set + if $game_temp.transition_processing + # End + return false + end + # Set transition processing flag + $game_temp.transition_processing = true + $game_temp.transition_name = @parameters[0] + # Advance index + @index += 1 + # End + return false + end + #----------------------------------------------------------------------------- + # * Change Screen Color Tone + #----------------------------------------------------------------------------- + def command_223 + # Start changing color tone + $game_screen.start_tone_change(@parameters[0], @parameters[1] * Graphics.frame_rate / 20) + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Screen Flash + #----------------------------------------------------------------------------- + def command_224 + # Start flash + $game_screen.start_flash(@parameters[0], @parameters[1] * Graphics.frame_rate / 20) + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Screen Shake + #----------------------------------------------------------------------------- + def command_225 + # Start shake + $game_screen.start_shake(@parameters[0], @parameters[1], + @parameters[2] * Graphics.frame_rate / 20) + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Show Picture + #----------------------------------------------------------------------------- + def command_231 + # Get picture number + number = @parameters[0] + ($game_temp.in_battle ? 50 : 0) + # If appointment method is [direct appointment] + if @parameters[3] == 0 + x = @parameters[4] + y = @parameters[5] + # If appointment method is [appoint with variables] + else + x = $game_variables[@parameters[4]] + y = $game_variables[@parameters[5]] + end + # Show picture + $game_screen.pictures[number].show(@parameters[1], @parameters[2], + x, y, @parameters[6], @parameters[7], @parameters[8], @parameters[9]) + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Move Picture + #----------------------------------------------------------------------------- + def command_232 + # Get picture number + number = @parameters[0] + ($game_temp.in_battle ? 50 : 0) + # If appointment method is [direct appointment] + if @parameters[3] == 0 + x = @parameters[4] + y = @parameters[5] + # If appointment method is [appoint with variables] + else + x = $game_variables[@parameters[4]] + y = $game_variables[@parameters[5]] + end + # Move picture + $game_screen.pictures[number].move(@parameters[1] * Graphics.frame_rate / 20, + @parameters[2], x, y, + @parameters[6], @parameters[7], @parameters[8], @parameters[9]) + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Rotate Picture + #----------------------------------------------------------------------------- + def command_233 + # Get picture number + number = @parameters[0] + ($game_temp.in_battle ? 50 : 0) + # Set rotation speed + $game_screen.pictures[number].rotate(@parameters[1]) + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Change Picture Color Tone + #----------------------------------------------------------------------------- + def command_234 + # Get picture number + number = @parameters[0] + ($game_temp.in_battle ? 50 : 0) + # Start changing color tone + $game_screen.pictures[number].start_tone_change(@parameters[1], + @parameters[2] * Graphics.frame_rate / 20) + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Erase Picture + #----------------------------------------------------------------------------- + def command_235 + # Get picture number + number = @parameters[0] + ($game_temp.in_battle ? 50 : 0) + # Erase picture + $game_screen.pictures[number].erase + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Set Weather Effects + #----------------------------------------------------------------------------- + def command_236 + # Set Weather Effects + $game_screen.weather(@parameters[0], @parameters[1], @parameters[2]) + # Continue + return true + end + + def command_247 + # Memorize BGM/BGS + $game_system.bgm_memorize + $game_system.bgs_memorize + # Continue + return true + end + #----------------------------------------------------------------------------- + # * Restore BGM/BGS + #----------------------------------------------------------------------------- + def command_248 + # Restore BGM/BGS + $game_system.bgm_restore + $game_system.bgs_restore + # Continue + return true + end + + def command_if(value) + if @branch[@list[@index].indent] == value + @branch.delete(@list[@index].indent) + return true + end + return command_skip + end + + def command_301; command_dummy; end # Battle Processing + def command_601; command_if(0); end # If Win + def command_602; command_if(1); end # If Escape + def command_603; command_if(2); end # If Lose + def command_302; command_dummy; end # Shop Processing + def command_303; command_dummy; end # Name Processing + def command_311; command_dummy; end # Change HP + def command_312; command_dummy; end # Change SP + def command_313; command_dummy; end # Change State + def command_314; command_dummy; end # Recover All + def command_315; command_dummy; end # Change EXP + def command_316; command_dummy; end # Change Level + def command_317; command_dummy; end # Change Parameters + def command_318; command_dummy; end # Change Skills + def command_319; command_dummy; end # Change Equipment + def command_320; command_dummy; end # Change Actor Name + def command_321; command_dummy; end # Change Actor Class + def command_322; command_dummy; end # Change Actor Graphic + def command_331; command_dummy; end # Change Enemy HP + def command_332; command_dummy; end # Change Enemy SP + def command_333; command_dummy; end # Change Enemy State + def command_334; command_dummy; end # Enemy Recover All + def command_335; command_dummy; end # Enemy Appearance + def command_336; command_dummy; end # Enemy Transform + def command_337; command_dummy; end # Show Battle Animation + def command_338; command_dummy; end # Deal Damage + def command_339; command_dummy; end # Force Action + def command_340; command_dummy; end # Abort Battle + #----------------------------------------------------------------------------- + # * Call Menu Screen + #----------------------------------------------------------------------------- + def command_351 + # Set menu calling flag + $game_temp.menu_calling = true + # Advance index + @index += 1 + # End + return false + end + #----------------------------------------------------------------------------- + # * Call Save Screen + #----------------------------------------------------------------------------- + def command_352 + # Set save calling flag + $game_temp.save_calling = true + # Advance index + @index += 1 + # End + return false + end + #----------------------------------------------------------------------------- + # * Game Over + #----------------------------------------------------------------------------- + def command_353 + # Set game over flag + $game_temp.gameover = true + # End + return false + end + #----------------------------------------------------------------------------- + # * Return to Title Screen + #----------------------------------------------------------------------------- + def command_354 + # Set return to title screen flag + $game_temp.to_title = true + # End + return false + end + #----------------------------------------------------------------------------- + # * Script + #----------------------------------------------------------------------------- + def command_355 + script = @list[@index].parameters[0] + "\n" + loop do + if @list[@index+1].code == 655 || @list[@index+1].code == 355 + script += @list[@index+1].parameters[0] + "\n" + else + break + end + @index += 1 + end + result = pbExecuteScript(script) + return true + end +end \ No newline at end of file diff --git a/Data/Scripts/006_Events and files/002_EventHandlers.rb b/Data/Scripts/006_Events and files/002_EventHandlers.rb new file mode 100644 index 000000000..101fc4c12 --- /dev/null +++ b/Data/Scripts/006_Events and files/002_EventHandlers.rb @@ -0,0 +1,172 @@ +# Defines an event that procedures can subscribe to. +class Event + def initialize + @callbacks = [] + end + + # Sets an event handler for this event and removes all other event handlers. + def set(method) + @callbacks.clear + @callbacks.push(method) + end + + # Removes an event handler procedure from the event. + def -(method) + for i in 0...@callbacks.length + next if @callbacks[i]!=method + @callbacks.delete_at(i) + break + end + return self + end + + # Adds an event handler procedure from the event. + def +(method) + for i in 0...@callbacks.length + return self if @callbacks[i]==method + end + @callbacks.push(method) + return self + end + + # Clears the event of event handlers. + def clear + @callbacks.clear + end + + # Triggers the event and calls all its event handlers. Normally called only + # by the code where the event occurred. + # The first argument is the sender of the event, the second argument contains + # the event's parameters. If three or more arguments are given, this method + # supports the following callbacks: + # proc{ |sender,params| } where params is an array of the other parameters, and + # proc{ |sender,arg0,arg1,...| } + def trigger(*arg) + arglist = arg[1,arg.length] + for callback in @callbacks + if callback.arity>2 && arg.length==callback.arity + # Retrofitted for callbacks that take three or more arguments + callback.call(*arg) + else + callback.call(arg[0],arglist) + end + end + end + + # Triggers the event and calls all its event handlers. Normally called only + # by the code where the event occurred. The first argument is the sender of + # the event, the other arguments are the event's parameters. + def trigger2(*arg) + for callback in @callbacks + callback.call(*arg) + end + end +end + + + +class HandlerHash + def initialize(mod) + @mod = mod + @hash = {} + @addIfs = [] + @symbolCache = {} + end + + def fromSymbol(sym) + return sym unless sym.is_a?(Symbol) || sym.is_a?(String) + mod = Object.const_get(@mod) rescue nil + return nil if !mod + return mod.const_get(sym.to_sym) rescue nil + end + + def toSymbol(sym) + return sym.to_sym if sym.is_a?(Symbol) || sym.is_a?(String) + ret = @symbolCache[sym] + return ret if ret + mod = Object.const_get(@mod) rescue nil + return nil if !mod + for key in mod.constants + next if mod.const_get(key)!=sym + ret = key.to_sym + @symbolCache[sym] = ret + break + end + return ret + end + + def addIf(condProc,handler) + @addIfs.push([condProc,handler]) + end + + def add(sym,handler) # 'sym' can be an ID or symbol + id = fromSymbol(sym) + @hash[id] = handler if id + symbol = toSymbol(sym) + @hash[symbol] = handler if symbol + end + + def copy(src,*dests) + handler = self[src] + if handler + for dest in dests + self.add(dest,handler) + end + end + end + + def [](sym) # 'sym' can be an ID or symbol + id = fromSymbol(sym) + ret = nil + ret = @hash[id] if id && @hash[id] # Real ID from the item + symbol = toSymbol(sym) + ret = @hash[symbol] if symbol && @hash[symbol] # Symbol or string + unless ret + for addif in @addIfs + return addif[1] if addif[0].call(id) + end + end + return ret + end + + def trigger(sym,*args) + handler = self[sym] + return (handler) ? handler.call(fromSymbol(sym),*args) : nil + end + + def clear + @hash.clear + end +end + + + +class SpeciesHandlerHash < HandlerHash + def initialize + super(:PBSpecies) + end +end + + + +class AbilityHandlerHash < HandlerHash + def initialize + super(:PBAbilities) + end +end + + + +class ItemHandlerHash < HandlerHash + def initialize + super(:PBItems) + end +end + + + +class MoveHandlerHash < HandlerHash + def initialize + super(:PBMoves) + end +end \ No newline at end of file diff --git a/Data/Scripts/006_Events and files/003_File_Mixins.rb b/Data/Scripts/006_Events and files/003_File_Mixins.rb new file mode 100644 index 000000000..05c4cf5c2 --- /dev/null +++ b/Data/Scripts/006_Events and files/003_File_Mixins.rb @@ -0,0 +1,163 @@ +module FileInputMixin + def fgetb + x=0 + ret=0 + each_byte do |i| + ret=i || 0 + break + end + return ret + end + + def fgetw + x=0 + ret=0 + each_byte do |i| + break if !i + ret|=(i<>3 + return 0 if index>=offset + self.pos=index*8 + return fgetdw + end + + def getLength(index) + self.binmode + self.pos=0 + offset=fgetdw>>3 + return 0 if index>=offset + self.pos=index*8+4 + return fgetdw + end + + def readName(index) + self.binmode + self.pos=0 + offset=fgetdw>>3 + return "" if index>=offset + self.pos=index<<3 + offset=fgetdw + length=fgetdw + return "" if length==0 + self.pos=offset + return read(length) + end +end + + + +module FileOutputMixin + def fputb(b) + b=b&0xFF + write(b.chr) + end + + def fputw(w) + 2.times do + b=w&0xFF + write(b.chr) + w>>=8 + end + end + + def fputdw(w) + 4.times do + b=w&0xFF + write(b.chr) + w>>=8 + end + end +end + + + +class File < IO +=begin + unless defined?(debugopen) + class << self + alias debugopen open + end + end + + def open(f,m="r") + debugopen("debug.txt","ab") { |file| file.write([f,m,Time.now.to_f].inspect+"\r\n") } + if block_given? + debugopen(f,m) { |file| yield file } + else + return debugopen(f,m) + end + end +=end + include FileInputMixin + include FileOutputMixin +end + + + +class StringInput + include FileInputMixin + + def pos=(value) + seek(value) + end + + def each_byte + while !eof? + yield getc + end + end + + def binmode + end +end + + + +class StringOutput + include FileOutputMixin +end \ No newline at end of file diff --git a/Data/Scripts/006_Events and files/004_Intl_Messages.rb b/Data/Scripts/006_Events and files/004_Intl_Messages.rb new file mode 100644 index 000000000..ee1ddcab2 --- /dev/null +++ b/Data/Scripts/006_Events and files/004_Intl_Messages.rb @@ -0,0 +1,776 @@ +def pbAddScriptTexts(items,script) + script.scan(/(?:_I)\s*\(\s*\"((?:[^\\\"]*\\\"?)*[^\"]*)\"/) { |s| + string=s[0] + string.gsub!(/\\\"/,"\"") + string.gsub!(/\\\\/,"\\") + items.push(string) + } +end + +def pbAddRgssScriptTexts(items,script) + script.scan(/(?:_INTL|_ISPRINTF)\s*\(\s*\"((?:[^\\\"]*\\\"?)*[^\"]*)\"/) { |s| + string=s[0] + string.gsub!(/\\r/,"\r") + string.gsub!(/\\n/,"\n") + string.gsub!(/\\1/,"\1") + string.gsub!(/\\\"/,"\"") + string.gsub!(/\\\\/,"\\") + items.push(string) + } +end + +def pbSetTextMessages + Graphics.update + begin + t = Time.now.to_i + texts=[] + for script in $RGSS_SCRIPTS + if Time.now.to_i - t >= 5 + t = Time.now.to_i + Graphics.update + end + scr=Zlib::Inflate.inflate(script[2]) + pbAddRgssScriptTexts(texts,scr) + end + # Must add messages because this code is used by both game system and Editor + MessageTypes.addMessagesAsHash(MessageTypes::ScriptTexts,texts) + commonevents=pbLoadRxData("Data/CommonEvents") + items=[] + choices=[] + for event in commonevents.compact + if Time.now.to_i - t >= 5 + t = Time.now.to_i + Graphics.update + end + begin + neednewline=false + lastitem="" + for j in 0...event.list.size + list = event.list[j] + if neednewline && list.code!=401 + if lastitem!="" + lastitem.gsub!(/([^\.\!\?])\s\s+/) { |m| $1+" " } + items.push(lastitem) + lastitem="" + end + neednewline=false + end + if list.code == 101 + lastitem+="#{list.parameters[0]}" if !$RPGVX + neednewline=true + elsif list.code == 102 + for k in 0...list.parameters[0].length + choices.push(list.parameters[0][k]) + end + neednewline=false + elsif list.code == 401 + lastitem+=" " if lastitem!="" + lastitem+="#{list.parameters[0]}" + neednewline=true + elsif list.code == 355 || list.code == 655 + pbAddScriptTexts(items,list.parameters[0]) + elsif list.code == 111 && list.parameters[0]==12 + pbAddScriptTexts(items,list.parameters[1]) + elsif list.code == 209 + route=list.parameters[1] + for k in 0...route.list.size + if route.list[k].code == 45 + pbAddScriptTexts(items,route.list[k].parameters[0]) + end + end + end + end + if neednewline + if lastitem!="" + items.push(lastitem) + lastitem="" + end + end + end + end + if Time.now.to_i - t >= 5 + t = Time.now.to_i + Graphics.update + end + items|=[] + choices|=[] + items.concat(choices) + MessageTypes.setMapMessagesAsHash(0,items) + mapinfos = pbLoadRxData("Data/MapInfos") + mapnames=[] + for id in mapinfos.keys + mapnames[id]=mapinfos[id].name + end + MessageTypes.setMessages(MessageTypes::MapNames,mapnames) + for id in mapinfos.keys + if Time.now.to_i - t >= 5 + t = Time.now.to_i + Graphics.update + end + filename=sprintf("Data/Map%03d.%s",id,$RPGVX ? "rvdata" : "rxdata") + next if !pbRgssExists?(filename) + map = load_data(filename) + items=[] + choices=[] + for event in map.events.values + if Time.now.to_i - t >= 5 + t = Time.now.to_i + Graphics.update + end + begin + for i in 0...event.pages.size + neednewline=false + lastitem="" + for j in 0...event.pages[i].list.size + list = event.pages[i].list[j] + if neednewline && list.code!=401 + if lastitem!="" + lastitem.gsub!(/([^\.\!\?])\s\s+/) { |m| $1+" " } + items.push(lastitem) + lastitem="" + end + neednewline=false + end + if list.code == 101 + lastitem+="#{list.parameters[0]}" if !$RPGVX + neednewline=true + elsif list.code == 102 + for k in 0...list.parameters[0].length + choices.push(list.parameters[0][k]) + end + neednewline=false + elsif list.code == 401 + lastitem+=" " if lastitem!="" + lastitem+="#{list.parameters[0]}" + neednewline=true + elsif list.code == 355 || list.code==655 + pbAddScriptTexts(items,list.parameters[0]) + elsif list.code == 111 && list.parameters[0]==12 + pbAddScriptTexts(items,list.parameters[1]) + elsif list.code==209 + route=list.parameters[1] + for k in 0...route.list.size + if route.list[k].code==45 + pbAddScriptTexts(items,route.list[k].parameters[0]) + end + end + end + end + if neednewline + if lastitem!="" + items.push(lastitem) + lastitem="" + end + end + end + end + end + if Time.now.to_i - t >= 5 + t = Time.now.to_i + Graphics.update + end + items|=[] + choices|=[] + items.concat(choices) + MessageTypes.setMapMessagesAsHash(id,items) + if Time.now.to_i - t >= 5 + t = Time.now.to_i + Graphics.update + end + end + rescue Hangup + end + Graphics.update +end + +def pbEachIntlSection(file) + lineno=1 + re=/^\s*\[\s*([^\]]+)\s*\]\s*$/ + havesection=false + sectionname=nil + lastsection=[] + file.each_line { |line| + if lineno==1 && line[0]==0xEF && line[1]==0xBB && line[2]==0xBF + line=line[3,line.length-3] + end + if !line[/^\#/] && !line[/^\s*$/] + if line[re] + if havesection + yield lastsection,sectionname + end + lastsection.clear + sectionname=$~[1] + havesection=true + else + if sectionname==nil + raise _INTL("Expected a section at the beginning of the file (line {1})",lineno) + end + lastsection.push(line.gsub(/\s+$/,"")) + end + end + lineno+=1 + if lineno%500==0 + Graphics.update + end + } + if havesection + yield lastsection,sectionname + end +end + +def pbGetText(infile) + begin + file=File.open(infile,"rb") + rescue + raise _INTL("Can't find {1}",infile) + end + intldat=[] + begin + pbEachIntlSection(file) { |section,name| + next if section.length==0 + index=name + if !name[/^([Mm][Aa][Pp])?(\d+)$/] + raise _INTL("Invalid section name {1}",name) + end + ismap=$~[1] && $~[1]!="" + id=$~[2].to_i + itemlength=0 + if section[0][/^\d+$/] + intlhash=[] + itemlength=3 + if ismap + raise _INTL("Section {1} can't be an ordered list (section was recognized as an ordered list because its first line is a number)",name) + end + if section.length%3!=0 + raise _INTL("Section {1}'s line count is not divisible by 3 (section was recognized as an ordered list because its first line is a number)",name) + end + else + intlhash=OrderedHash.new + itemlength=2 + if section.length%2!=0 + raise _INTL("Section {1} has an odd number of entries (section was recognized as a hash because its first line is not a number)",name) + end + end + i=0;loop do break unless i0 + str+=@keys[i].inspect+"=>"+self[@keys[i]].inspect + end + str+="}" + return str + end + + alias :to_s :inspect + + def []=(key,value) + oldvalue=self[key] + if !oldvalue && value + @keys.push(key) + elsif !value + @keys|=[] + @keys-=[key] + end + return super(key,value) + end + + def self._load(string) + ret=self.new + keysvalues=Marshal.load(string) + keys=keysvalues[0] + values=keysvalues[1] + for i in 0...keys.length + ret[keys[i]]=values[i] + end + return ret + end + + def _dump(depth=100) + values=[] + for key in @keys + values.push(self[key]) + end + return Marshal.dump([@keys,values]) + end +end + + + +class Messages + def initialize(filename=nil,delayLoad=false) + @messages=nil + @filename=filename + if @filename && !delayLoad + loadMessageFile(@filename) + end + end + + def delayedLoad + if @filename && !@messages + loadMessageFile(@filename) + @filename=nil + end + end + + def self.stringToKey(str) + if str && str[/[\r\n\t\1]|^\s+|\s+$|\s{2,}/] + key=str.clone + key.gsub!(/^\s+/,"") + key.gsub!(/\s+$/,"") + key.gsub!(/\s{2,}/," ") + return key + end + return str + end + + def self.normalizeValue(value) + if value[/[\r\n\t\x01]|^[\[\]]/] + ret=value.clone + ret.gsub!(/\r/,"<>") + ret.gsub!(/\n/,"<>") + ret.gsub!(/\t/,"<>") + ret.gsub!(/\[/,"<<[>>") + ret.gsub!(/\]/,"<<]>>") + ret.gsub!(/\x01/,"<<1>>") + return ret + end + return value + end + + def self.denormalizeValue(value) + if value[/<<[rnt1\[\]]>>/] + ret=value.clone + ret.gsub!(/<<1>>/,"\1") + ret.gsub!(/<>/,"\r") + ret.gsub!(/<>/,"\n") + ret.gsub!(/<<\[>>/,"[") + ret.gsub!(/<<\]>>/,"]") + ret.gsub!(/<>/,"\t") + return ret + end + return value + end + + def self.writeObject(f,msgs,secname,origMessages=nil) + return if !msgs + if msgs.is_a?(Array) + f.write("[#{secname}]\r\n") + for j in 0...msgs.length + next if msgs[j]==nil || msgs[j]=="" + value=Messages.normalizeValue(msgs[j]) + origValue="" + if origMessages + origValue=Messages.normalizeValue(origMessages.get(secname,j)) + else + origValue=Messages.normalizeValue(MessageTypes.get(secname,j)) + end + f.write("#{j}\r\n") + f.write(origValue+"\r\n") + f.write(value+"\r\n") + end + elsif msgs.is_a?(OrderedHash) + f.write("[#{secname}]\r\n") + keys=msgs.keys + for key in keys + next if msgs[key]==nil || msgs[key]=="" + value=Messages.normalizeValue(msgs[key]) + valkey=Messages.normalizeValue(key) + # key is already serialized + f.write(valkey+"\r\n") + f.write(value+"\r\n") + end + end + end + + def messages + return @messages || [] + end + + def extract(outfile) +# return if !@messages + origMessages=Messages.new("Data/messages.dat") + File.open(outfile,"wb") { |f| + f.write(0xef.chr) + f.write(0xbb.chr) + f.write(0xbf.chr) + f.write("# To localize this text for a particular language, please\r\n") + f.write("# translate every second line of this file.\r\n") + if origMessages.messages[0] + for i in 0...origMessages.messages[0].length + msgs=origMessages.messages[0][i] + Messages.writeObject(f,msgs,"Map#{i}",origMessages) + end + end + for i in 1...origMessages.messages.length + msgs=origMessages.messages[i] + Messages.writeObject(f,msgs,i,origMessages) + end + } + end + + def setMessages(type,array) + @messages=[] if !@messages + arr=[] + for i in 0...array.length + arr[i]=(array[i]) ? array[i] : "" + end + @messages[type]=arr + end + + def addMessages(type,array) + @messages=[] if !@messages + arr=(@messages[type]) ? @messages[type] : [] + for i in 0...array.length + arr[i]=(array[i]) ? array[i] : (arr[i]) ? arr[i] : "" + end + @messages[type]=arr + end + + def self.createHash(type,array) + arr=OrderedHash.new + for i in 0...array.length + if array[i] + key=Messages.stringToKey(array[i]) + arr[key]=array[i] + end + end + return arr + end + + def self.addToHash(type,array,hash) + if !hash + hash=OrderedHash.new + end + for i in 0...array.length + if array[i] + key=Messages.stringToKey(array[i]) + hash[key]=array[i] + end + end + return hash + end + + def setMapMessagesAsHash(type,array) + @messages=[] if !@messages + @messages[0]=[] if !@messages[0] + @messages[0][type]=Messages.createHash(type,array) + end + + def addMapMessagesAsHash(type,array) + @messages=[] if !@messages + @messages[0]=[] if !@messages[0] + @messages[0][type]=Messages.addToHash(type,array,@messages[0][type]) + end + + def setMessagesAsHash(type,array) + @messages=[] if !@messages + @messages[type]=Messages.createHash(type,array) + end + + def addMessagesAsHash(type,array) + @messages=[] if !@messages + @messages[type]=Messages.addToHash(type,array,@messages[type]) + end + + def saveMessages(filename=nil) + filename="Data/messages.dat" if !filename + File.open(filename,"wb") { |f| Marshal.dump(@messages,f) } + end + + def loadMessageFile(filename) + begin + pbRgssOpen(filename,"rb") { |f| @messages=Marshal.load(f) } + if !@messages.is_a?(Array) + @messages=nil + raise "Corrupted data" + end + return @messages + rescue + @messages=nil + return nil + end + end + + def set(type,id,value) + delayedLoad + return if !@messages + return if !@messages[type] + @messages[type][id]=value + end + + def getCount(type) + delayedLoad + return 0 if !@messages + return 0 if !@messages[type] + return @messages[type].length + end + + def get(type,id) + delayedLoad + return "" if !@messages + return "" if !@messages[type] + return "" if !@messages[type][id] + return @messages[type][id] + end + + def getFromHash(type,key) + delayedLoad + return key if !@messages || !@messages[type] || !key + id=Messages.stringToKey(key) + return key if !@messages[type][id] + return @messages[type][id] + end + + def getFromMapHash(type,key) + delayedLoad + return key if !@messages + return key if !@messages[0] + return key if !@messages[0][type] && !@messages[0][0] + id=Messages.stringToKey(key) + if @messages[0][type] && @messages[0][type][id] + return @messages[0][type][id] + elsif @messages[0][0] && @messages[0][0][id] + return @messages[0][0][id] + end + return key + end +end + + + +module MessageTypes + # Value 0 is used for common event and map event text + Species = 1 + Kinds = 2 + Entries = 3 + FormNames = 4 + Moves = 5 + MoveDescriptions = 6 + Items = 7 + ItemPlurals = 8 + ItemDescriptions = 9 + Abilities = 10 + AbilityDescs = 11 + Types = 12 + TrainerTypes = 13 + TrainerNames = 14 + BeginSpeech = 15 + EndSpeechWin = 16 + EndSpeechLose = 17 + RegionNames = 18 + PlaceNames = 19 + PlaceDescriptions = 20 + MapNames = 21 + PhoneMessages = 22 + TrainerLoseText = 23 + ScriptTexts = 24 + @@messages = Messages.new + @@messagesFallback = Messages.new("Data/messages.dat",true) + + def self.stringToKey(str) + return Messages.stringToKey(str) + end + + def self.normalizeValue(value) + return Messages.normalizeValue(value) + end + + def self.denormalizeValue(value) + Messages.denormalizeValue(value) + end + + def self.writeObject(f,msgs,secname) + Messages.denormalizeValue(str) + end + + def self.extract(outfile) + @@messages.extract(outfile) + end + + def self.setMessages(type,array) + @@messages.setMessages(type,array) + end + + def self.addMessages(type,array) + @@messages.addMessages(type,array) + end + + def self.createHash(type,array) + Messages.createHash(type,array) + end + + def self.addMapMessagesAsHash(type,array) + @@messages.addMapMessagesAsHash(type,array) + end + + def self.setMapMessagesAsHash(type,array) + @@messages.setMapMessagesAsHash(type,array) + end + + def self.addMessagesAsHash(type,array) + @@messages.addMessagesAsHash(type,array) + end + + def self.setMessagesAsHash(type,array) + @@messages.setMessagesAsHash(type,array) + end + + def self.saveMessages(filename=nil) + @@messages.saveMessages(filename) + end + + def self.loadMessageFile(filename) + @@messages.loadMessageFile(filename) + end + + def self.get(type,id) + ret=@@messages.get(type,id) + if ret=="" + ret=@@messagesFallback.get(type,id) + end + return ret + end + + def self.getCount(type) + c1=@@messages.getCount(type) + c2=@@messagesFallback.getCount(type) + return c1>c2 ? c1 : c2 + end + + def self.getOriginal(type,id) + return @@messagesFallback.get(type,id) + end + + def self.getFromHash(type,key) + @@messages.getFromHash(type,key) + end + + def self.getFromMapHash(type,key) + @@messages.getFromMapHash(type,key) + end +end + + + +def pbLoadMessages(file) + return MessageTypes.loadMessageFile(file) +end + +def pbGetMessageCount(type) + return MessageTypes.getCount(type) +end + +def pbGetMessage(type,id) + return MessageTypes.get(type,id) +end + +def pbGetMessageFromHash(type,id) + return MessageTypes.getFromHash(type,id) +end + +# Replaces first argument with a localized version and formats the other +# parameters by replacing {1}, {2}, etc. with those placeholders. +def _INTL(*arg) + begin + string=MessageTypes.getFromHash(MessageTypes::ScriptTexts,arg[0]) + rescue + string=arg[0] + end + string=string.clone + for i in 1...arg.length + string.gsub!(/\{#{i}\}/,"#{arg[i]}") + end + return string +end + +# Replaces first argument with a localized version and formats the other +# parameters by replacing {1}, {2}, etc. with those placeholders. +# This version acts more like sprintf, supports e.g. {1:d} or {2:s} +def _ISPRINTF(*arg) + begin + string=MessageTypes.getFromHash(MessageTypes::ScriptTexts,arg[0]) + rescue + string=arg[0] + end + string=string.clone + for i in 1...arg.length + string.gsub!(/\{#{i}\:([^\}]+?)\}/) { |m| + next sprintf("%"+$1,arg[i]) + } + end + return string +end + +def _I(str) + return _MAPINTL($game_map.map_id,str) +end + +def _MAPINTL(mapid,*arg) + string=MessageTypes.getFromMapHash(mapid,arg[0]) + string=string.clone + for i in 1...arg.length + string.gsub!(/\{#{i}\}/,"#{arg[i]}") + end + return string +end + +def _MAPISPRINTF(mapid,*arg) + string=MessageTypes.getFromMapHash(mapid,arg[0]) + string=string.clone + for i in 1...arg.length + string.gsub!(/\{#{i}\:([^\}]+?)\}/) { |m| + next sprintf("%"+$1,arg[i]) + } + end + return string +end \ No newline at end of file diff --git a/Data/Scripts/006_Events and files/005_PBDebug.rb b/Data/Scripts/006_Events and files/005_PBDebug.rb new file mode 100644 index 000000000..988c1ec07 --- /dev/null +++ b/Data/Scripts/006_Events and files/005_PBDebug.rb @@ -0,0 +1,40 @@ +module PBDebug + @@log = [] + + def self.logonerr + begin + yield + rescue + PBDebug.log("") + PBDebug.log("**Exception: #{$!.message}") + PBDebug.log("#{$!.backtrace.inspect}") + PBDebug.log("") +# if $INTERNAL + pbPrintException($!) +# end + PBDebug.flush + end + end + + def self.flush + if $DEBUG && $INTERNAL && @@log.length>0 + File.open("Data/debuglog.txt", "a+b") { |f| f.write("#{@@log}") } + end + @@log.clear + end + + def self.log(msg) + if $DEBUG && $INTERNAL + @@log.push("#{msg}\r\n") +# if @@log.length>1024 + PBDebug.flush +# end + end + end + + def self.dump(msg) + if $DEBUG && $INTERNAL + File.open("Data/dumplog.txt", "a+b") { |f| f.write("#{msg}\r\n") } + end + end +end \ No newline at end of file diff --git a/Data/Scripts/007_Audio/001_Audio.rb b/Data/Scripts/007_Audio/001_Audio.rb new file mode 100644 index 000000000..165da35f2 --- /dev/null +++ b/Data/Scripts/007_Audio/001_Audio.rb @@ -0,0 +1,376 @@ +class Thread + def Thread.exclusive + _old = Thread.critical + begin + Thread.critical = true + return yield + ensure + Thread.critical = _old + end + end +end + + + +def getPlayMusic + return MiniRegistry.get(MiniRegistry::HKEY_CURRENT_USER, + "SOFTWARE\\Enterbrain\\RGSS","PlayMusic",true) +end + +def getPlaySound + return MiniRegistry.get(MiniRegistry::HKEY_CURRENT_USER, + "SOFTWARE\\Enterbrain\\RGSS","PlaySound",true) +end + + + +class AudioContext + attr_reader :context + + def initialize + init = Win32API.new("audio.dll", "AudioContextInitialize", '', 'l') + @context=init.call() + end + + def dispose + if @context!=0 + init = Win32API.new("audio.dll", "AudioContextFree", 'l', '') + init.call(context) + @context=0 + end + end +end + + + +##################################### +# Needed because RGSS doesn't call at_exit procs on exit +# Exit is not called when game is reset (using F12) +$AtExitProcs=[] if !$AtExitProcs + +def exit(code=0) + for p in $AtExitProcs + p.call + end + raise SystemExit.new(code) +end + +def at_exit(&block) + $AtExitProcs.push(Proc.new(&block)) +end + +##################################### +# Works around a problem with FileTest.exist +# if directory contains accent marks +def safeExists?(f) + ret=false + File.open(f,"rb") { ret=true } rescue nil + return ret +end + + + +module AudioState + w32_LL = Win32API.new("kernel32.dll", "LoadLibrary", 'p', 'l') # :nodoc: + w32_FL = Win32API.new("kernel32.dll", "FreeLibrary", 'p', 'l')# :nodoc: + + if safeExists?("audio.dll") + @handle = w32_LL.call("audio.dll") + at_exit { w32_FL.call(@handle) } + AudioContextIsActive = Win32API.new("audio.dll","AudioContextIsActive","l","l")# :nodoc: + AudioContextPlay = Win32API.new("audio.dll","AudioContextPlay","lpllll","")# :nodoc: + AudioContextStop = Win32API.new("audio.dll","AudioContextStop","l","")# :nodoc: + AudioContextFadeOut = Win32API.new("audio.dll","AudioContextFadeOut","ll","")# :nodoc: + AudioContextGetPosition = Win32API.new("audio.dll","AudioContextGetPosition","l","l")# :nodoc: + AudioContextFadeIn = Win32API.new("audio.dll","AudioContextFadeIn","ll","")# :nodoc: + AudioContextSetVolume = Win32API.new("audio.dll","AudioContextSetVolume","ll","")# :nodoc: + AudioContextSEPlay = Win32API.new("audio.dll","AudioContextSEPlay","lplll","")# :nodoc: + if !@MEContext + @MEContext=AudioContext.new + at_exit { @MEContext.dispose } + end + if !@BGMContext + @BGMContext=AudioContext.new + at_exit { @BGMContext.dispose } + end + if !@BGSContext + @BGSContext=AudioContext.new + at_exit { @BGSContext.dispose } + end + if !@SEContext + @SEContext=AudioContext.new + at_exit { @SEContext.dispose } + end + else + AudioContextIsActive = nil # :nodoc: + AudioContextPlay = nil # :nodoc: + AudioContextStop = nil # :nodoc: + AudioContextFadeOut = nil # :nodoc: + AudioContextGetPosition = nil # :nodoc: + AudioContextFadeIn = nil # :nodoc: + AudioContextSetVolume = nil # :nodoc: + AudioContextSEPlay = nil # :nodoc: + end + + @channel = nil + @bgm = nil + @name = "" + @pitch = 100 + @bgmVolume = 100.0 + @meVolume = 100.0 + @bgsVolume = 100.0 + @seVolume = 100.0 + + def self.setWaitingBGM(bgm,volume,pitch,position) + @waitingBGM=[bgm,volume,pitch,position] + end + + def self.bgmActive? + return !@BGMContext ? false : (AudioContextIsActive.call(@BGMContext.context)!=0) + end + + def self.meActive? + return !@MEContext ? false : (AudioContextIsActive.call(@MEContext.context)!=0) + end + + def self.waitingBGM; @waitingBGM; end + def self.context; @BGMContext ? @BGMContext.context : nil; end + def self.meContext; @MEContext ? @MEContext.context : nil; end + def self.bgsContext; @BGSContext ? @BGSContext.context : nil; end + def self.seContext; @SEContext ? @SEContext.context : nil; end + def self.system; @system; end + def self.bgm; @bgm; end + def self.name; @name; end + def self.pitch; @pitch; end + def self.volume; @volume; end + + def self.waitingBGM=(value); + Thread.exclusive { @waitingBGM=value; } + end + + def self.volume=(value); @volume=value; end + def self.bgm=(value); @bgm=value; end + def self.name=(value); @name=value; end + def self.pitch=(value); @pitch=value; end +end + + + +def Audio_bgm_playing? + AudioState.channel!=nil +end + +def Audio_bgm_name + AudioState.name +end + +def Audio_bgm_pitch + AudioState.pitch +end + +def Audio_bgm_play(name, volume, pitch, position = 0) + volume=0 if !getPlayMusic() + begin + filename = canonicalize(RTP.getAudioPath(name)) + if AudioState.meActive? + AudioState.setWaitingBGM(filename,volume,pitch,position) + return + end + AudioState::AudioContextPlay.call(AudioState.context,filename,volume,pitch,position,1) + AudioState.name=filename + AudioState.volume=volume + AudioState.pitch=pitch + rescue Hangup + rescue + p $!.message,$!.backtrace + end +end + +def Audio_bgm_fadein(ms) + AudioState::AudioContextFadeIn.call(AudioState.context,ms.to_i) +end + +def Audio_bgm_fade(ms) + AudioState::AudioContextFadeOut.call(AudioState.context,ms.to_i) +end + +def Audio_bgm_stop() + begin + AudioState::AudioContextStop.call(AudioState.context) + AudioState.waitingBGM=nil + AudioState.name = "" + rescue + p $!.message,$!.backtrace + end +end + +def Audio_bgm_get_position + return AudioState::AudioContextGetPosition.call(AudioState.context) +end + +def Audio_bgm_get_volume + return 0 if !AudioState.bgmActive? + return AudioState.volume +end + +def Audio_bgm_set_volume(volume) + return if !AudioState.bgmActive? + AudioState.volume = volume * 1.0 + AudioState::AudioContextSetVolume.call(AudioState.context,volume.to_i) +end + +def Audio_me_play(name, volume, pitch, position = 0) + volume=0 if !getPlayMusic() + begin + filename = canonicalize(RTP.getAudioPath(name)) + if AudioState.bgmActive? + bgmPosition=Audio_bgm_get_position + AudioState.setWaitingBGM( + AudioState.name, + AudioState.volume, + AudioState.pitch, + bgmPosition + ) + AudioState::AudioContextStop.call(AudioState.context) + end + AudioState::AudioContextPlay.call(AudioState.meContext,filename, + volume,pitch,position,0) + rescue + p $!.message,$!.backtrace + end +end + +def Audio_me_fade(ms) + AudioState::AudioContextFadeOut.call(AudioState.meContext,ms) +end + +def Audio_me_stop() + AudioState::AudioContextStop.call(AudioState.meContext) +end + +def Audio_bgs_play(name, volume, pitch, position = 0) + volume=0 if !getPlaySound() + begin + filename = canonicalize(RTP.getAudioPath(name)) + AudioState::AudioContextPlay.call(AudioState.bgsContext,filename, + volume,pitch,position,0) + rescue + p $!.message,$!.backtrace + end +end + +def Audio_bgs_fade(ms) + AudioState::AudioContextFadeOut.call(AudioState.bgsContext,ms) +end + +def Audio_bgs_stop() + AudioState::AudioContextStop.call(AudioState.bgsContext) +end + +def Audio_se_play(name, volume, pitch, position = 0) + volume=0 if !getPlaySound() + begin + filename = canonicalize(RTP.getAudioPath(name)) + AudioState::AudioContextSEPlay.call(AudioState.seContext,filename, + volume,pitch,position) + rescue + p $!.message,$!.backtrace + end +end + +def Audio_se_stop() + AudioState::AudioContextStop.call(AudioState.seContext) +end + + + +#################################################### +if safeExists?("audio.dll") + module Graphics + if !defined?(audiomodule_update) + class << self + alias audiomodule_update update + end + end + + def self.update + Audio.update + audiomodule_update + end + end + + + + module Audio + @@musicstate = nil + @@soundstate = nil + + def self.update + return if Graphics.frame_count%10!=0 + if AudioState.waitingBGM && !AudioState.meActive? + waitbgm=AudioState.waitingBGM + AudioState.waitingBGM=nil + bgm_play(waitbgm[0],waitbgm[1],waitbgm[2],waitbgm[3]) + end + end + + def self.bgm_play(name,volume=80,pitch=100,position=nil) + begin + if position==nil || position==0 + Audio_bgm_play(name,volume,pitch,0) + else + Audio_bgm_play(name,volume,pitch,position) + Audio_bgm_fadein(500) + end + rescue Hangup + bgm_play(name,volume,pitch,position) + end + end + + def self.bgm_fade(ms) + Audio_bgm_fade(ms) + end + + def self.bgm_stop + Audio_bgm_stop() + end + + def self.bgm_position + return Audio_bgm_get_position + end + + def self.me_play(name,volume=80,pitch=100) + Audio_me_play(name,volume,pitch,0) + end + + def self.me_fade(ms) + Audio_me_fade(ms) + end + + def self.me_stop + Audio_me_stop() + end + + def self.bgs_play(name,volume=80,pitch=100) + Audio_bgs_play(name,volume,pitch,0) + end + + def self.bgs_fade(ms) + Audio_bgs_fade(ms) + end + + def self.bgs_stop + Audio_bgs_stop() + end + +=begin + def self.se_play(name,volume=80,pitch=100) + Audio_se_play(name,volume,pitch,0) + end + + def self.se_stop + Audio_se_stop() + end +=end + end +end # safeExists?("audio.dll") diff --git a/Data/Scripts/007_Audio/002_AudioPlay.rb b/Data/Scripts/007_Audio/002_AudioPlay.rb new file mode 100644 index 000000000..b7b3e28b9 --- /dev/null +++ b/Data/Scripts/007_Audio/002_AudioPlay.rb @@ -0,0 +1,292 @@ +def pbStringToAudioFile(str) + if str[/^(.*)\:\s*(\d+)\s*\:\s*(\d+)\s*$/] # Of the format "XXX: ###: ###" + file = $1 + volume = $2.to_i + pitch = $3.to_i + return RPG::AudioFile.new(file,volume,pitch) + elsif str[/^(.*)\:\s*(\d+)\s*$/] # Of the format "XXX: ###" + file = $1 + volume = $2.to_i + return RPG::AudioFile.new(file,volume,100) + else + return RPG::AudioFile.new(str,100,100) + end +end + +# Converts an object to an audio file. +# str -- Either a string showing the filename or an RPG::AudioFile object. +# Possible formats for _str_: +# filename volume and pitch 100 +# filename:volume pitch 100 +# filename:volume:pitch +# volume -- Volume of the file, up to 100 +# pitch -- Pitch of the file, normally 100 +def pbResolveAudioFile(str,volume=nil,pitch=nil) + if str.is_a?(String) + str = pbStringToAudioFile(str) + str.volume = volume || 100 + str.pitch = pitch || 100 + end + if str.is_a?(RPG::AudioFile) + if volume || pitch + return RPG::AudioFile.new(str.name,volume || str.volume || 100 , + pitch || str.pitch || 100) + else + return str + end + end + return str +end + +################################################################################ + +# Plays a BGM file. +# param -- Either a string showing the filename +# (relative to Audio/BGM/) or an RPG::AudioFile object. +# Possible formats for _param_: +# filename volume and pitch 100 +# filename:volume pitch 100 +# filename:volume:pitch +# volume -- Volume of the file, up to 100 +# pitch -- Pitch of the file, normally 100 +def pbBGMPlay(param,volume=nil,pitch=nil) + return if !param + param=pbResolveAudioFile(param,volume,pitch) + if param.name && param.name!="" + if $game_system && $game_system.respond_to?("bgm_play") + $game_system.bgm_play(param) + return + elsif (RPG.const_defined?(:BGM) rescue false) + b=RPG::BGM.new(param.name,param.volume,param.pitch) + if b && b.respond_to?("play") + b.play + return + end + end + Audio.bgm_play(canonicalize("Audio/BGM/"+param.name),param.volume,param.pitch) + end +end + +# Fades out or stops BGM playback. 'x' is the time in seconds to fade out. +def pbBGMFade(x=0.0); pbBGMStop(x);end + +# Fades out or stops BGM playback. 'x' is the time in seconds to fade out. +def pbBGMStop(timeInSeconds=0.0) + if $game_system && timeInSeconds>0.0 && $game_system.respond_to?("bgm_fade") + $game_system.bgm_fade(timeInSeconds) + return + elsif $game_system && $game_system.respond_to?("bgm_stop") + $game_system.bgm_stop + return + elsif (RPG.const_defined?(:BGM) rescue false) + begin + (timeInSeconds>0.0) ? RPG::BGM.fade((timeInSeconds*1000).floor) : RPG::BGM.stop + return + rescue + end + end + (timeInSeconds>0.0) ? Audio.bgm_fade((timeInSeconds*1000).floor) : Audio.bgm_stop +end + +################################################################################ + +# Plays an ME file. +# param -- Either a string showing the filename +# (relative to Audio/ME/) or an RPG::AudioFile object. +# Possible formats for _param_: +# filename volume and pitch 100 +# filename:volume pitch 100 +# filename:volume:pitch +# volume -- Volume of the file, up to 100 +# pitch -- Pitch of the file, normally 100 +def pbMEPlay(param,volume=nil,pitch=nil) + return if !param + param=pbResolveAudioFile(param,volume,pitch) + if param.name && param.name!="" + if $game_system && $game_system.respond_to?("me_play") + $game_system.me_play(param) + return + elsif (RPG.const_defined?(:ME) rescue false) + b=RPG::ME.new(param.name,param.volume,param.pitch) + if b && b.respond_to?("play") + b.play; return + end + end + Audio.me_play(canonicalize("Audio/ME/"+param.name),param.volume,param.pitch) + end +end + +# Fades out or stops ME playback. 'x' is the time in seconds to fade out. +def pbMEFade(x=0.0); pbMEStop(x);end + +# Fades out or stops ME playback. 'x' is the time in seconds to fade out. +def pbMEStop(timeInSeconds=0.0) + if $game_system && timeInSeconds>0.0 && $game_system.respond_to?("me_fade") + $game_system.me_fade(timeInSeconds) + return + elsif $game_system && $game_system.respond_to?("me_stop") + $game_system.me_stop(nil) + return + elsif (RPG.const_defined?(:ME) rescue false) + begin + (timeInSeconds>0.0) ? RPG::ME.fade((timeInSeconds*1000).floor) : RPG::ME.stop + return + rescue + end + end + (timeInSeconds>0.0) ? Audio.me_fade((timeInSeconds*1000).floor) : Audio.me_stop +end + +################################################################################ + +# Plays a BGS file. +# param -- Either a string showing the filename +# (relative to Audio/BGS/) or an RPG::AudioFile object. +# Possible formats for _param_: +# filename volume and pitch 100 +# filename:volume pitch 100 +# filename:volume:pitch +# volume -- Volume of the file, up to 100 +# pitch -- Pitch of the file, normally 100 +def pbBGSPlay(param,volume=nil,pitch=nil) + return if !param + param=pbResolveAudioFile(param,volume,pitch) + if param.name && param.name!="" + if $game_system && $game_system.respond_to?("bgs_play") + $game_system.bgs_play(param) + return + elsif (RPG.const_defined?(:BGS) rescue false) + b=RPG::BGS.new(param.name,param.volume,param.pitch) + if b && b.respond_to?("play") + b.play; return + end + end + Audio.bgs_play(canonicalize("Audio/BGS/"+param.name),param.volume,param.pitch) + end +end + +# Fades out or stops BGS playback. 'x' is the time in seconds to fade out. +def pbBGSFade(x=0.0); pbBGSStop(x);end + +# Fades out or stops BGS playback. 'x' is the time in seconds to fade out. +def pbBGSStop(timeInSeconds=0.0) + if $game_system && timeInSeconds>0.0 && $game_system.respond_to?("bgs_fade") + $game_system.bgs_fade(timeInSeconds) + return + elsif $game_system && $game_system.respond_to?("bgs_play") + $game_system.bgs_play(nil) + return + elsif (RPG.const_defined?(:BGS) rescue false) + begin + (timeInSeconds>0.0) ? RPG::BGS.fade((timeInSeconds*1000).floor) : RPG::BGS.stop + return + rescue + end + end + (timeInSeconds>0.0) ? Audio.bgs_fade((timeInSeconds*1000).floor) : Audio.bgs_stop +end + +################################################################################ + +# Plays an SE file. +# param -- Either a string showing the filename +# (relative to Audio/SE/) or an RPG::AudioFile object. +# Possible formats for _param_: +# filename volume and pitch 100 +# filename:volume pitch 100 +# filename:volume:pitch +# volume -- Volume of the file, up to 100 +# pitch -- Pitch of the file, normally 100 +def pbSEPlay(param,volume=nil,pitch=nil) + return if !param + param = pbResolveAudioFile(param,volume,pitch) + if param.name && param.name!="" + if $game_system && $game_system.respond_to?("se_play") + $game_system.se_play(param) + return + end + if (RPG.const_defined?(:SE) rescue false) + b = RPG::SE.new(param.name,param.volume,param.pitch) + if b && b.respond_to?("play") + b.play + return + end + end + Audio.se_play(canonicalize("Audio/SE/"+param.name),param.volume,param.pitch) + end +end + +# Stops SE playback. +def pbSEFade(x=0.0); pbSEStop(x);end + +# Stops SE playback. +def pbSEStop(timeInSeconds=0.0) + if $game_system + $game_system.se_stop + elsif (RPG.const_defined?(:SE) rescue false) + RPG::SE.stop rescue nil + else + Audio.se_stop + end +end + +################################################################################ + +# Plays a sound effect that plays when the player moves the cursor. +def pbPlayCursorSE + if $data_system && $data_system.respond_to?("cursor_se") && + $data_system.cursor_se && $data_system.cursor_se.name!="" + pbSEPlay($data_system.cursor_se) + elsif $data_system && $data_system.respond_to?("sounds") && + $data_system.sounds && $data_system.sounds[0] && $data_system.sounds[0].name!="" + pbSEPlay($data_system.sounds[0]) + elsif FileTest.audio_exist?("Audio/SE/GUI sel cursor") + pbSEPlay("GUI sel cursor",80) + end +end + +# Plays a sound effect that plays when a decision is confirmed or a choice is made. +def pbPlayDecisionSE + if $data_system && $data_system.respond_to?("decision_se") && + $data_system.decision_se && $data_system.decision_se.name!="" + pbSEPlay($data_system.decision_se) + elsif $data_system && $data_system.respond_to?("sounds") && + $data_system.sounds && $data_system.sounds[1] && $data_system.sounds[1].name!="" + pbSEPlay($data_system.sounds[1]) + elsif FileTest.audio_exist?("Audio/SE/GUI sel decision") + pbSEPlay("GUI sel decision",80) + end +end + +# Plays a sound effect that plays when a choice is canceled. +def pbPlayCancelSE + if $data_system && $data_system.respond_to?("cancel_se") && + $data_system.cancel_se && $data_system.cancel_se.name!="" + pbSEPlay($data_system.cancel_se) + elsif $data_system && $data_system.respond_to?("sounds") && + $data_system.sounds && $data_system.sounds[2] && $data_system.sounds[2].name!="" + pbSEPlay($data_system.sounds[2]) + elsif FileTest.audio_exist?("Audio/SE/GUI sel cancel") + pbSEPlay("GUI sel cancel",80) + end +end + +# Plays a buzzer sound effect. +def pbPlayBuzzerSE + if $data_system && $data_system.respond_to?("buzzer_se") && + $data_system.buzzer_se && $data_system.buzzer_se.name!="" + pbSEPlay($data_system.buzzer_se) + elsif $data_system && $data_system.respond_to?("sounds") && + $data_system.sounds && $data_system.sounds[3] && $data_system.sounds[3].name!="" + pbSEPlay($data_system.sounds[3]) + elsif FileTest.audio_exist?("Audio/SE/GUI sel buzzer") + pbSEPlay("GUI sel buzzer",80) + end +end + +# Plays a sound effect that plays when the player moves the cursor. +def pbPlayCloseMenuSE + if FileTest.audio_exist?("Audio/SE/GUI menu close") + pbSEPlay("GUI menu close",80) + end +end \ No newline at end of file diff --git a/Data/Scripts/007_Audio/003_AudioUtilities.rb b/Data/Scripts/007_Audio/003_AudioUtilities.rb new file mode 100644 index 000000000..8b1eea711 --- /dev/null +++ b/Data/Scripts/007_Audio/003_AudioUtilities.rb @@ -0,0 +1,1350 @@ +=begin +This script contains various utility functions and classes for dealing +with audio. This is a stand-alone script. + +Audio.square(durationInMs,freq,volume,timbre,async) - Generates a square wave. +Audio.beep(durationInMs,freq,volume,timbre,async) - Alias for Audio.square +Audio.sine(durationInMs,freq,volume,timbre,async) - Generates a sine wave. +Audio.triangle(durationInMs,freq,volume,timbre,async) - Generates a triangle wave. +Audio.saw(durationInMs,freq,volume,async) - Generates a saw wave. +Audio.noise(durationInMs,volume,async) - Generates white noise. +Audio.playTone(toneFile,async) - Plays a tone in the Apple iPod alarm tone format. +Parameters: + durationInMs - duration of the sound in milliseconds. + The module Audio::NoteLength contains useful durations for tones. + If 0 or nil, the frequency is determined using the maximum duration + of the given sound envelopes. + freq - the frequency of the sound in Hz. The higher the frequency, + the higher the pitch. If 0, no sound will be generated. + The module Audio::Note contains useful frequencies for tones. + freq can also be a SoundEnvelope or an array of two element arrays, + as follows: + freq[0] - time in ms to apply the specified frequency + freq[1] - frequency to apply. In between, values will be interpolated + volume - volume of the sound, from 0 through 100 + volume can also be a SoundEnvelope. + async - specifies whether the function will return immediately + without waiting for the sound to finish (stands for asynchronous) + timbre - specifies the timbre of the tone; from 0.0 through 1.0 + timbre can also be a SoundEnvelope or an array of two element arrays, + as follows: + volume[0] - time in ms to apply the specified timbre + volume[1] - timbre to apply. In between, values will be interpolated + +WaveData - A class for holding audio data in memory. This class +is easy to serialize into the save file. + intensity() - Calculates the intensity, or loudness of the data + Returns a value from 0 through 127. + time() - Length of the data in seconds. + play() - Plays the wave data + +getPlayTime(filename) - Gets the length of an audio file in seconds. + Supports WAV, MP3, and OGG files. +getWaveData(filename) - Creates wave data from the given WAV file path. + Returns a WaveData object or an integer: 1=not found; 2=invalid format; + 3=format not supported; 4=no sound in the data (the last error is helpful + for diagnosing whether anything was recorded, since a recording device + can record even if no microphone is attached.) + +beginRecord() - Starts recording. Returns 0 if successful. +getRecorderSample() - Gets a single sample from the microphone. + The beginRecord function must have been called beforehand. +stopRecord() - Stops recording without saving the recording to a file. +endRecord(file) - Stops recording and saves the recording to a file. +=end + +if !defined?(safeExists?) + def safeExists?(f) + ret=false + File.open(f,"rb") { ret=true } rescue nil + return ret + end +end + +def pbSaveSoundData(samples, freq, filename) + samples="" if !samples + data=[ + 0x46464952,samples.length+0x2C, + 0x45564157,0x20746d66,0x10, + 0x01,0x01, # PCM,mono + freq,freq, + 1,8, # 8-bit + 0x61746164,samples.length + ].pack("VVVVVvvVVvvVV") + f=File.open(filename,"wb") + if f + f.write(data) + f.write(samples) + f.close + end +end + +# plays 8 bit mono sound data (default: 11025 Hz) +def pbPlaySoundData(samples,volume,async=false,sampleFreq=11025) + return if !samples || samples.length==0 || sampleFreq==0 + waveOutOpen = Win32API.new("winmm.dll","waveOutOpen","plplll","l") + waveOutPrepareHeader = Win32API.new("winmm.dll","waveOutPrepareHeader","lpl","l") + waveOutWrite = Win32API.new("winmm.dll","waveOutWrite","lpl","l") + waveOutSetVolume = Win32API.new("winmm.dll","waveOutSetVolume","ll","l") + waveOutClose = Win32API.new("winmm.dll","waveOutClose","l","l") + waveOutGetNumDevs = Win32API.new("winmm.dll","waveOutGetNumDevs","","l") + getStringAddress = proc { |obj| + next 0 if !obj + buffer=" "*4 + rtlMoveMemory_pi = Win32API.new('kernel32', 'RtlMoveMemory', 'pii', 'i') + stringPointer=(obj.__id__*2)+12 + rtlMoveMemory_pi.call(buffer,stringPointer,4) + next buffer.unpack("L")[0] + } + saveToTemp = proc { |samples,freq| + chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + ret = nil + for i in 1...1000 + name="" + 8.times { name += chars[rand(chars.length),1] } + name = ENV["TEMP"]+"\\"+name+"_tmp.wav" + next if safeExists?(name) + pbSaveSoundData(samples,freq,name) + ret = name + break + end + return ret + } + playThenDelete = proc { |path,volume,length,async| + next if !path || !safeExists?(path) + thread=Thread.new{ + Thread.stop + _path=Thread.current[:path] + _length=Thread.current[:length] + sleep(_length) + File.delete(_path) rescue nil + } + thread[:path]=path + thread[:length]=length + Audio.se_play(path,volume) + thread.run + sleep(length) + } + waveHdr=[getStringAddress.call(samples),samples.length,0,0,0,0,0,0].pack("V*") + # 8 bit mono sound data + waveFormat=[0x01,0x01,sampleFreq,sampleFreq,1,8,0].pack("vvVVvvv") + duration=samples.length + waveOutHandle=" "*4 + code=waveOutOpen.call(waveOutHandle,-1,waveFormat,0,0,0) + if code!=0 + timeLength=samples.length.to_f/sampleFreq + path=saveToTemp.call(samples,sampleFreq) + playThenDelete.call(path,volume,timeLength,async) + return + end + waveOutHandle=waveOutHandle.unpack("L")[0] + volume=(volume*65535/100) + volume=(volume<<16)|volume + waveOutSetVolume.call(waveOutHandle,volume) + code=waveOutPrepareHeader.call(waveOutHandle,waveHdr,waveHdr.length) + if code!=0 + waveOutClose.call(waveOutHandle) + return + end + thread=Thread.new{ + Thread.stop + waveOut=Thread.current[:waveOut] + waveHdr=Thread.current[:waveHeader] + waveData=Thread.current[:waveData] + waveOutUnprepareHeader=Win32API.new("winmm.dll","waveOutUnprepareHeader","lpl","l") + waveOutClose=Win32API.new("winmm.dll","waveOutClose","l","l") + loop do + sleep(1) + hdr=waveHdr.unpack("V*") + flags=hdr[4] + if (flags&1)==1 + # All done + waveOutUnprepareHeader.call(waveOut,waveHdr,waveHdr.length) + waveOutClose.call(waveOut) + break + end + end + } + thread[:waveOut]=waveOutHandle + thread[:waveHeader]=waveHdr + thread[:waveData]=@samples + if waveOutWrite.call(waveOutHandle,waveHdr,waveHdr.length)!=0 + waveOutClose.call(waveOutHandle) + return + end + thread.run + sleep(@duration/1000.0) if !async + return +end + + + +class NoteEnvelope + attr_accessor :fall + attr_accessor :max + attr_reader :envelope + + def initialize(fall=1200,maxPoint=30) + @fall=fall # time until fall to zero + @maxPoint=maxPoint # maximum point + @envelope=SoundEnvelope.new + end + + def falling(duration) + return self if duration<=0 + @envelope.changeDiscrete(0,@maxPoint) + if duration>=@fall + @envelope.change(fall,0) + @envelope.change(duration-fall,0) + else + @envelope.change(duration,@maxPoint*duration/@fall) + end + @envelope.changeDiscrete(0,0) + return self + end + + def sweeping(duration,sweepDuration) + return self if duration<=0 + return steady(duration) if sweepDuration<=0 + @envelope.changeDiscrete(0,@maxPoint) + falling=true + while duration>0 + dur=duration>sweepDuration ? sweepDuration : duration + if falling + self.falling(dur) + else + sd=sweepDuration + if sd>@fall + d=[(sweepDuration-@fall),dur].min + @envelope.change(d,0) + dur-=d + sd-=d + end + if d==sd + @envelope.change(dur,@maxPoint) + else + @envelope.change(dur,@maxPoint*(@fall-(sd-dur))/@fall) + end + end + falling=!falling + duration-=sweepDuration + end + @envelope.changeDiscrete(0,0) + return self + end + + def rest(duration) + if duration>0 + @envelope.changeDiscrete(0,0) + @envelope.changeDiscrete(duration,0) + end + return self + end + + def steady(duration) + if duration>0 + @envelope.changeDiscrete(0,@maxPoint) + @envelope.changeDiscrete(duration,@maxPoint) + @envelope.changeDiscrete(0,0) + end + return self + end +end + + + +# A class for holding audio data in memory. This class +# is easy to serialize into the save file. +class WaveData + def initialize(samplesPerSec,samples) + @freq=samplesPerSec + @samples=samples.is_a?(String) ? samples.clone : samples.pack("C*") + end + + def setSamples(samples) + @samples=samples.is_a?(String) ? samples.clone : samples + end + + def self._load(string) + data=Marshal.load(string) + ret=self.new(data[0],[]) + ret.setSamples(Zlib::Inflate.inflate(data[1])) + return ret + end + + def _dump(depth=100) + return Marshal.dump([@freq,Zlib::Deflate.deflate(@samples)]) + end + + def intensity + distance=@samples.length/2000 + i=distance/2 + count=0 + volume=0 + while i<@samples.length + vol=(@samples[i]-128).abs + vol=127 if vol>127 + if vol>=16 + volume+=vol + count+=1 + end + i+=distance + end + return 0 if count==0 + return volume/count # from 0 through 127 + end + + def time + return @freq==0 ? 0.0 : (@samples.length)*1.0/@freq + end + + def play + # Play sound data asynchronously + pbPlaySoundData(@samples,100,true,@freq) + end + + def save(filename) + pbSaveSoundData(@samples,@freq,filename) + end +end + + + +# A class for specifying volume, frequency, and timbre envelopes. +class SoundEnvelope; include Enumerable + def initialize(env=nil) + @e=[]; + set(env) if env + end + + def self.fromToneFile(file) + envelope=self.new + File.open(file,"rb") { |f| + f.gets if !f.eof? + while !f.eof? + ln=f.gets + if ln[ /^(\d+)\s+(\d+)/ ] + envelope.addValueChange($1.to_i,$2.to_i) + end + end + } + return envelope + end + + def length; @e.length; end + def [](x); @e[x]; end + def each; @e.each { |x| yield x }; end + def clear; @e.clear; end + + def changeAbsolute(pos,volume) + return self if pos<0 + velength=@e.length + if velength>0 + @e.push([pos,volume]) + stableSort() + else + @e.push([pos,volume]) + end + return self + end + + def self.initial(value) + return self.new.change(0,value) + end + + def self.smoothVolume(duration,volume) + env=self.new + return env if duration<8 + env.change(0,0) + env.change(5,volume) + env.changeAbsolute(duration-10,volume) + env.changeAbsolute(duration-5,0) + env.changeAbsolute(duration,0) + return env + end + + # Creates a volume envelope using the given attack, decay, and + # release times and the given sustain level. + # duration - duration of the sound + # attack - attack time (in ms), or time between the start of the sound + # and the time where the sound reaches its maximum volume. + # If this value is less than 0, the sound will decay from silence to + # the sustain volume instead (see below). + # decay - decay time (in ms), or time after the attack phase until + # the time where the sound reaches its sustain volume + # sustain - sustain volume, or normal volume of sound (0-100) + # release - release time (in ms), or amount of time to fade out the + # sound when it reaches its end. The sound's duration includes its + # release time. + def self.attackDecayRelease(duration,attack,decay,sustain,release) + env=self.new + if attack<=0 + env.change(0,attack==0 ? 100 : 0) + else + env.change(attack,100) + end + env.change(decay,sustain) + env.changeAbsolute(duration-release,sustain) + if release>20 + env.changeAbsolute(duration-release-4,0) + end + env.changeAbsolute(duration,0) + return env + end + + def self.blink(value,onDuration,offDuration,totalDuration) + return self.new.addValueChanges( + value,onDuration,0,offDuration).repeat(totalDuration) + end + + def change(delta,volume) + return self if delta<0 + velength=@e.length + if velength>0 + @e.push([@e[velength-1][0]+delta,volume]) + else + @e.push([delta,volume]) + end + return self + end + + def duration + return @e.length==0 ? 0 : @e[@e.length-1][0] + end + + def value + return @e.length==0 ? 0 : @e[@e.length-1][1] + end + + def addValueChange(value,duration) + changeDiscrete(0,value) + changeDiscrete(duration,value) + return self + end + + def sweep(value1,value2,duration,sweepDuration=1000/16) + val=true + while duration>0 + dur=durationdesiredDuration + deltaNew=(newDuration-self.duration) + deltaDesired=(desiredDuration-self.duration) + newValue=deltaNew==0 ? self.value : self.value+(item[1]-self.value)*deltaDesired/deltaNew + @e.push([desiredDuration,newValue]) + break + else + @e.push([newDuration,item[1]]) + end + i+=1 + if i>=oldLength + i=0; currentDuration+=oldDuration + end + end + return self + end + + # Changes the volume, frequency, etc. abruptly without blending. + def changeDiscrete(delta,volume) + return self if delta<0 + velength=@e.length + if velength>0 + oldValue=@e[velength-1][1] + oldDelta=@e[velength-1][0] + newDelta=oldDelta+delta + newValue=oldValue + if newDelta!=oldDelta || newValue!=oldValue + @e.push([newDelta,newValue]) + oldDelta=newDelta + oldValue=newValue + end + newValue=volume + if newDelta!=oldDelta || newValue!=oldValue + @e.push([newDelta,newValue]) + end + else + @e.push([delta,volume]) + end + return self + end + + def set(value) + if value.is_a?(SoundEnvelope) || value.is_a?(Array) + @e.clear + for v in value; @e.push(v); end + end + return self + end + + private + + def stableSort + pm=1;while pm<@e.length + pl=pm; while pl>0 && @e[pl-1][0]>@e[pl][0] + tmp=@e[pl]; @e[pl]=@e[pl-1]; @e[pl-1]=tmp + pl-=1; end + pm+=1;end + end +end + + + +# internal class +class SoundEnvelopeIterator# :nodoc: + def initialize(env) + @env=env + @envIndex=0 + end + + def getValue(frame) + value=0 + if @envIndex==@env.length + value=@env[@envIndex-1][1] + elsif @envIndex==0 + value=@env[@envIndex][1] + else + lastPos=@env[@envIndex-1][0] + thisPos=@env[@envIndex][0] + if thisPos!=lastPos + lastVolume=@env[@envIndex-1][1] + thisVolume=@env[@envIndex][1] + value=(thisVolume-lastVolume)*(frame-lastPos)/(thisPos-lastPos)+lastVolume + else + value=@env[@envIndex][1] + end + end + while @envIndex+1<=@env.length && @env[@envIndex][0]==frame.to_i + @envIndex+=1 + end + return value + end +end + + + +# internal class +class WaveForm# :nodoc: + SAMPLEFREQ=11025 + + def initialize(proc,freq,duration,timbre=0.5) + @duration=duration # in ms + @volumeEnvelope=SoundEnvelope.new + @freqEnvelope=SoundEnvelope.new + @timbreEnvelope=SoundEnvelope.new + @proc=proc + @freq=freq + @timbre=timbre + if @freq.is_a?(Array) || @freq.is_a?(SoundEnvelope) + @freqEnvelope.set(@freq) + @freq=@freq.length>0 ? @freq[0][1] : 800 + end + if @timbre.is_a?(Array) || @timbre.is_a?(SoundEnvelope) + @timbreEnvelope.set(@timbre) + @timbre=@timbre.length>0 ? @timbre[0][1] : 0.5 + end + end + + def setFrequencyEnvelope(env) + if env.is_a?(Numeric) + @freqEnvelope.clear + @freqEnvelope.addValueChange(env,@duration) + else + @freqEnvelope.set(env) + end + end + + def setVolumeEnvelope(env) + if env.is_a?(Numeric) + @volumeEnvelope.clear + @volumeEnvelope.addValueChange(env,@duration) + else + @volumeEnvelope.set(env) + end + end + + def lcm(x,y) + return y if x==0; return x if y==0 + return x if x==y + if x>y; incr=x + while x%y!=0; x+=incr; end + return x + else; incr=y + while y%x!=0; y+=incr; end + return y + end + end + + def start + @i=0 + @volumeIterator=SoundEnvelopeIterator.new(@volumeEnvelope) + @freqIterator=SoundEnvelopeIterator.new(@freqEnvelope) + @timbreIterator=SoundEnvelopeIterator.new(@timbreEnvelope) + @sampleCount=@duration*SAMPLEFREQ/1000 + @samples=" "*@sampleCount + @exactSamplesPerPass=@freq==0 ? 1.0 : [SAMPLEFREQ.to_f/@freq,1].max + @step=1.0/@exactSamplesPerPass + @exactCounter=0 + end + + def self.frac(x) + return x-x.floor + end + + def nextSample + vol=100 + fframe=@i*1000.0/SAMPLEFREQ + if @volumeEnvelope.length>0 # Update volume + vol=@volumeIterator.getValue(fframe) + end + if @proc + updateBuffer=false + if @freqEnvelope.length>0 # Update frequency + freq=@freqIterator.getValue(fframe) + if freq!=@freq # update sample buffer + @freq=freq + @exactSamplesPerPass=@freq==0 ? 1.0 : [SAMPLEFREQ.to_f/@freq,1].max + @step=1.0/@exactSamplesPerPass + end + end + if @timbreEnvelope.length>0 # Update timbre + @timbre=@timbreIterator.getValue(fframe) + end + if @freq==0 || vol==0 + @samples[@i]=0x80 + else + sample=@proc.call(@exactCounter,@timbre) + @samples[@i]=0x80+(vol*sample).round + end + else # noise + v=vol.abs.to_i*2 + @samples[@i]=0x80+(rand(v).to_i-(v/2)) + end + @i+=1 + @exactCounter+=@step + while @exactCounter>1.0; @exactCounter-=1.0; end + end + + def mixSamples(other) + for i in 0...other.sampleCount + newSamp=((@samples[i]-0x80)+(other.samples[i]-0x80))/2+0x80 + @samples[i]=newSamp + end + end + + def play(volume=100, async=false) + pbPlaySoundData(@samples,volume,async,SAMPLEFREQ) + end + + def generateSound + start + while @i<@sampleCount + nextSample + end + end + + def toWaveData + return WaveData.new(SAMPLEFREQ,@samples) + end + + def save(filename) + pbSaveSoundData(@samples,SAMPLEFREQ,filename) + end + + attr_accessor :samples,:sampleCount +end + + + +module Audio + module Note + REST = 0 + GbelowC = 196 + A = 220 + Asharp = 233 + B = 247 + C = 262 + Csharp = 277 + D = 294 + Dsharp = 311 + E = 330 + F = 349 + Fsharp = 370 + G = 392 + Gsharp = 415 + end + + module NoteLength + WHOLE = 1600 + HALF = WHOLE/2 + QUARTER = HALF/2 + EIGHTH = QUARTER/2 + SIXTEENTH = EIGHTH/2 + end + + def self.noise(durationInMs=200, volume=100, async=false) + return if durationInMs<=0 + waveForm=WaveForm.new(nil,0,durationInMs) + if volume.is_a?(Array) || volume.is_a?(SoundEnvelope) + waveForm.setVolumeEnvelope(volume) + volume=100 + end + waveForm.generateSound + waveForm.play(volume,async) + end + + # internal method + def self.envelopeDuration(env) + return 0 if !env + if env.is_a?(SoundEnvelope) || env.is_a?(Array) + return SoundEnvelope.new(env).duration + end + return 0 + end + + def self.oscillateDouble(durationInMs, freq1, freq2, volume1, volume2, + timbre1, timbre2, proc1, proc2, async=false) + return if durationInMs<0 + freq1Zero=(freq1.is_a?(Numeric) && freq1<=0) || + (freq1.is_a?(Array) && freq1.length==0) + freq2Zero=(freq2.is_a?(Numeric) && freq2<=0) || + (freq2.is_a?(Array) && freq2.length==0) + if freq1Zero && freq2Zero + Thread.sleep(durationInMs/1000.0) if !async + return + end + if durationInMs==0 || durationInMs==nil + durationInMs=[ + envelopeDuration(freq1), + envelopeDuration(freq2), + envelopeDuration(volume1), + envelopeDuration(volume2), + envelopeDuration(timbre1), + envelopeDuration(timbre2) + ].max + return if durationInMs<=0 + end + waveForm1=WaveForm.new(proc1,freq1,durationInMs,timbre1) + waveForm2=WaveForm.new(proc2,freq2,durationInMs,timbre2) + waveForm1.setVolumeEnvelope(volume1) + waveForm2.setVolumeEnvelope(volume2) + waveForm1.generateSound + waveForm2.generateSound + waveForm1.mixSamples(waveForm2) + waveForm1.play(100,async) + end + + def self.oscillate(durationInMs=200, freq=800, volume=100, timbre=0.5, async=false, &block) + return if durationInMs<0 + if (freq.is_a?(Numeric) && freq<=0) || (freq.is_a?(Array) && freq.length==0) + Thread.sleep(durationInMs/1000.0) if !async + return + end + if durationInMs==0 || durationInMs==nil + durationInMs=[ + envelopeDuration(freq), + envelopeDuration(volume), + envelopeDuration(timbre)].max + return if durationInMs<=0 + end + waveForm=WaveForm.new(block,freq,durationInMs,timbre) + if volume.is_a?(Array) || volume.is_a?(SoundEnvelope) + waveForm.setVolumeEnvelope(volume) + volume=100 + end + waveForm.generateSound + waveForm.play(volume,async) + end + + def self.frac(x) + return x-x.floor + end + + TWOPI = Math::PI*2 + @@sineProc2 = proc { |z,timbre| + x = (z1 + for i in 0...tone.length + dtmf(tone[i,1],durationInMs,volume,false) + end + end + t1=0 + t1=1209 if "14ghi7pqrs*".include?(tone) + t1=1336 if "2abc5jkl8tuv0".include?(tone) + t1=1477 if "3def6mno9wxyz#".include?(tone) + t1=1633 if "ABCD".include?(tone) + t2=0 + t2=697 if "12abc3defA".include?(tone) + t2=770 if "4ghi5jkl6mnoB".include?(tone) + t2=852 if "7pqrs8tuv9wxyzC".include?(tone) + t2=941 if "*0#D".include?(tone) + return if t1==0 || t2==0 + f1=Math::PI*2.0*t1/WaveForm::SAMPLEFREQ + f2=Math::PI*2.0*t2/WaveForm::SAMPLEFREQ + doubleSine(durationInMs,t1,t2,volume,volume,0.5,0.5,async) + end + + def self.beep(durationInMs=200, freq=800, volume=100, timbre=0.5, async=false) + square(durationInMs,freq,volume,timbre,async) + end + + @@triangleProc2 = proc { |z,timbre| + next (z>4 + next if t==0 || t==15 + freqs=[44100,22050,11025,48000] + bitrates=[32,40,48,56,64,80,96,112,128,160,192,224,256,320] + bitrate=bitrates[t] + t=(rstr[1]>>2)&3 + freq=freqs[t] + t=(rstr[1]>>1)&1 + filesize=FileTest.size(filename) + frameLength=((144000*bitrate)/freq)+t + numFrames=filesize/(frameLength+4) + time=(numFrames*1152.0/freq) + break + end + end + } + return time +end + +# Creates wave data from the given WAV file path +def getWaveData(filename) + time=-1 + fgetdw=proc { |file| + (file.eof? ? 0 : (file.read(4).unpack("V")[0] || 0)) + } + fgetw=proc { |file| + (file.eof? ? 0 : (file.read(2).unpack("v")[0] || 0)) + } + return 1 if !safeExists?(filename) # Not found + File.open(filename,"rb") { |file| + file.pos=0 + fdw=fgetdw.call(file) + if fdw==0x46464952 # "RIFF" + filesize=fgetdw.call(file) + wave=fgetdw.call(file) + if wave!=0x45564157 # "WAVE" + return 2 + end + fmt=fgetdw.call(file) + if fmt!=0x20746d66 # "fmt " + return 2 + end + fmtsize=fgetdw.call(file) + format=fgetw.call(file) + if format!=1 + return 3 # unsupported + end + channels=fgetw.call(file) # channels (1 or 2) + if channels!=1 + return 3 # unsupported + end + rate=fgetdw.call(file) # samples per second + bytessec=fgetdw.call(file) # avg bytes per second + if bytessec==0 + return 2 + end + bytessample=fgetw.call(file) # bytes per sample + bitssample=fgetw.call(file) # bits per sample (8, 16, etc.) + if bitssample!=8 && bitssample!=16 + return 3 # unsupported + end + data=fgetdw.call(file) + if data!=0x61746164 # "data" + return 2 + end + datasize=fgetdw.call(file) + data=file.read(datasize) + samples=nil + if bitssample==8 + samples=data.unpack("C*") + start=0 + for i in 0...samples.length + s=samples[i] + if s<0x70 || s>=0x90 + start=i + break + end + end + finish=start + i=samples.length-1 + while i>=start + s=samples[i] + if s<0x70 || s>=0x90 + finish=i+1 + break + end + i-=1 + end + if finish==start + return 4 # Nothing was recorded + end + start=0 + finish=samples.length + wave=WaveData.new(rate,samples[start,finish-start]) + return wave + elsif bitssample==16 + samples=data.unpack("v*") + start=0 + for i in 0...samples.length + s=samples[i] + if s>0x1000 && s<0xF000 + start=i + break + end + end + finish=start + i=samples.length-1 + while i>=start + s=samples[i] + if s<0x1000 && s<0xF000 + finish=i+1 + break + end + i-=1 + end + if finish==start + return 4 # Nothing was recorded + end + start=0 + # Convert to 8-bit samples + for i in start...finish + samples[i]=((samples[i]-0x8000)>>8)&0xFF + end + finish=samples.length + return WaveData.new(rate,samples[start,finish-start]) + end + end + } + return 2 +end + +############################### + +begin + MciSendString = Win32API.new('winmm','mciSendString','%w(p,p,l,l)','l') + MciErrorString = Win32API.new('winmm','mciGetErrorString','%w(l,p,l)','l') +rescue + MciSendString = nil + MciErrorString = nil +end + +# Starts recording. Returns 0 if successful. +def beginRecord + return 256+72 if !MciSendString + MciSendString.call("open new type waveaudio alias RECORDER buffer 4",0,0,0) + MciSendString.call("set RECORDER channels 1",0,0,0) + retval=MciSendString.call("record RECORDER",0,0,0) + if retval!=0 + MciSendString.call("close RECORDER",0,0,0) + end + return retval +end + + +# Gets a single sample from the microphone. +# The beginRecord or beginRecordUI function must have been called beforehand. +def getRecorderSample + return 0x8000 if !MciSendString + buffer="\0"*256 + ret=0 + MciSendString.call("stop RECORDER",0,0,0) + MciSendString.call("status RECORDER bitspersample",buffer,256,0) + bitspersample=buffer.to_i + MciSendString.call("status RECORDER level",buffer,256,0) + MciSendString.call("record RECORDER",0,0,0) + if bitspersample==8 + ret=buffer.to_i<<8 # max 128 + else + ret=buffer.to_i # max 0x8000 + end + return ret +end + +def stopRecord() + return if !MciSendString + MciSendString.call("stop RECORDER",0,0,0) + MciSendString.call("close RECORDER",0,0,0) +end + +def endRecord(file) + return if !MciSendString + MciSendString.call("stop RECORDER",0,0,0) + if file && file!="" + MciSendString.call("save RECORDER #{file}",0,0,0) + end + MciSendString.call("close RECORDER",0,0,0) +end + +#Audio.sine(140,SoundEnvelope.initial(6400).change(140,11400),50) \ No newline at end of file diff --git a/Data/Scripts/008_Objects and windows/001_FileTests.rb b/Data/Scripts/008_Objects and windows/001_FileTests.rb new file mode 100644 index 000000000..864e2d34c --- /dev/null +++ b/Data/Scripts/008_Objects and windows/001_FileTests.rb @@ -0,0 +1,657 @@ +#=============================================================================== +# Checking for files and directories +#=============================================================================== +# Works around a problem with FileTest.directory if directory contains accent marks +def safeIsDirectory?(f) + ret = false + Dir.chdir(f) { ret = true } rescue nil + return ret +end + +# Works around a problem with FileTest.exist if path contains accent marks +def safeExists?(f) + return FileTest.exist?(f) if f[/\A[\x20-\x7E]*\z/] + ret = false + begin + File.open(f,"rb") { ret = true } + rescue Errno::ENOENT, Errno::EINVAL, Errno::EACCES + ret = false + end + return ret +end + +# Similar to "Dir.glob", but designed to work around a problem with accessing +# files if a path contains accent marks. +# "dir" is the directory path, "wildcard" is the filename pattern to match. +def safeGlob(dir,wildcard) + ret = [] + afterChdir = false + begin + Dir.chdir(dir) { + afterChdir = true + Dir.glob(wildcard) { |f| ret.push(dir+"/"+f) } + } + rescue Errno::ENOENT + raise if afterChdir + end + if block_given? + ret.each { |f| yield(f) } + end + return (block_given?) ? nil : ret +end + +# Finds the real path for an image file. This includes paths in encrypted +# archives. Returns nil if the path can't be found. +def pbResolveBitmap(x) + return nil if !x + noext = x.gsub(/\.(bmp|png|gif|jpg|jpeg)$/,"") + filename = nil +# RTP.eachPathFor(x) { |path| +# filename = pbTryString(path) if !filename +# filename = pbTryString(path+".gif") if !filename +# } + RTP.eachPathFor(noext) { |path| + filename = pbTryString(path+".png") if !filename + filename = pbTryString(path+".gif") if !filename +# filename = pbTryString(path+".jpg") if !filename +# filename = pbTryString(path+".jpeg") if !filename +# filename = pbTryString(path+".bmp") if !filename + } + return filename +end + +# Finds the real path for an image file. This includes paths in encrypted +# archives. Returns _x_ if the path can't be found. +def pbBitmapName(x) + ret = pbResolveBitmap(x) + return (ret) ? ret : x +end + +def getUnicodeString(addr) + return "" if addr==0 + rtlMoveMemory_pi = Win32API.new('kernel32', 'RtlMoveMemory', 'pii', 'i') + ret = "" + data = "xx" + index = (addr.is_a?(String)) ? 0 : addr + loop do + if addr.is_a?(String) + data = addr[index,2] + else + rtlMoveMemory_pi.call(data, index, 2) + end + codepoint = data.unpack("v")[0] + break if codepoint==0 + index += 2 + if codepoint<=0x7F + ret += codepoint.chr + elsif codepoint<=0x7FF + ret += (0xC0|((codepoint>>6)&0x1F)).chr + ret += (0x80|(codepoint &0x3F)).chr + elsif codepoint<=0xFFFF + ret += (0xE0|((codepoint>>12)&0x0F)).chr + ret += (0x80|((codepoint>>6)&0x3F)).chr + ret += (0x80|(codepoint &0x3F)).chr + elsif codepoint<=0x10FFFF + ret += (0xF0|((codepoint>>18)&0x07)).chr + ret += (0x80|((codepoint>>12)&0x3F)).chr + ret += (0x80|((codepoint>>6)&0x3F)).chr + ret += (0x80|(codepoint &0x3F)).chr + end + end + return ret +end + +def getUnicodeStringFromAnsi(addr) + return "" if addr==0 + rtlMoveMemory_pi = Win32API.new('kernel32', 'RtlMoveMemory', 'pii', 'i') + ret = "" + data = "x" + index = (addr.is_a?(String)) ? 0 : addr + loop do + if addr.is_a?(String) + data = addr[index,1] + else + rtlMoveMemory_pi.call(data, index, 1) + end + index += 1 + codepoint = data.unpack("C")[0] + break if codepoint==0 || !codepoint + break if codepoint==0 + if codepoint<=0x7F + ret += codepoint.chr + else + ret += (0xC0|((codepoint>>6)&0x1F)).chr + ret += (0x80|(codepoint &0x3F)).chr + end + end + return ret +end + +def getKnownFolder(guid) + packedGuid = guid.pack("VvvC*") + shGetKnownFolderPath = Win32API.new("shell32.dll","SHGetKnownFolderPath","pllp","i") rescue nil + coTaskMemFree = Win32API.new("ole32.dll","CoTaskMemFree","i","") rescue nil + return "" if !(shGetKnownFolderPath && coTaskMemFree) + path = "\0"*4 + ret = shGetKnownFolderPath.call(packedGuid,0,0,path) + path = path.unpack("V")[0] + ret = getUnicodeString(path) + coTaskMemFree.call(path) + return ret +end + + + +module RTP + @rtpPaths = nil + + def self.exists?(filename,extensions=[]) + return false if !filename || filename=="" + eachPathFor(filename) { |path| + return true if safeExists?(path) + for ext in extensions + return true if safeExists?(path+ext) + end + } + return false + end + + def self.getImagePath(filename) + return self.getPath(filename,["",".png",".gif"]) # ".jpg",".bmp",".jpeg" + end + + def self.getAudioPath(filename) + return self.getPath(filename,["",".mp3",".wav",".wma",".mid",".ogg",".midi"]) + end + + def self.getPath(filename,extensions=[]) + return filename if !filename || filename=="" + eachPathFor(filename) { |path| + return path if safeExists?(path) + for ext in extensions + file = path+ext + return file if safeExists?(file) + end + } + return filename + end + + # Gets the absolute RGSS paths for the given file name + def self.eachPathFor(filename) + return if !filename + if filename[/^[A-Za-z]\:[\/\\]/] || filename[/^[\/\\]/] + # filename is already absolute + yield filename + else + # relative path + RTP.eachPath { |path| + if path=="./" + yield filename + else + yield path+filename + end + } + end + end + + # Gets all RGSS search paths + def self.eachPath + # XXX: Use "." instead of Dir.pwd because of problems retrieving files if + # the current directory contains an accent mark + yield ".".gsub(/[\/\\]/,"/").gsub(/[\/\\]$/,"")+"/" + if !@rtpPaths + tmp = Sprite.new + isRgss2 = tmp.respond_to?("wave_amp") + tmp.dispose + @rtpPaths = [] + if isRgss2 + rtp = getGameIniValue("Game","RTP") + if rtp!="" + rtp = MiniRegistry.get(MiniRegistry::HKEY_LOCAL_MACHINE, + "SOFTWARE\\Enterbrain\\RGSS2\\RTP",rtp,nil) + if rtp && safeIsDirectory?(rtp) + @rtpPaths.push(rtp.sub(/[\/\\]$/,"")+"/") + end + end + else + %w( RTP1 RTP2 RTP3 ).each { |v| + rtp = getGameIniValue("Game",v) + if rtp!="" + rtp = MiniRegistry.get(MiniRegistry::HKEY_LOCAL_MACHINE, + "SOFTWARE\\Enterbrain\\RGSS\\RTP",rtp,nil) + if rtp && safeIsDirectory?(rtp) + @rtpPaths.push(rtp.sub(/[\/\\]$/,"")+"/") + end + end + } + end + end + @rtpPaths.each { |x| yield x } + end + + private + + @@folder = nil + + def self.getGameIniValue(section,key) + val = "\0"*256 + gps = Win32API.new('kernel32', 'GetPrivateProfileString',%w(p p p p l p), 'l') + gps.call(section, key, "", val, 256, ".\\Game.ini") + val.delete!("\0") + return val + end + + def self.isDirWritable(dir) + return false if !dir || dir=="" + loop do + name = dir.gsub(/[\/\\]$/,"")+"/writetest" + for i in 0...12 + name += sprintf("%02X",rand(256)) + end + name += ".tmp" + if !safeExists?(name) + retval = false + begin + File.open(name,"wb") { retval = true } + rescue Errno::EINVAL, Errno::EACCES, Errno::ENOENT + ensure + File.delete(name) rescue nil + end + return retval + end + end + end + + def self.ensureGameDir(dir) + title = RTP.getGameIniValue("Game","Title") + title = "RGSS Game" if title=="" + title = title.gsub(/[^\w ]/,"_") + newdir = dir.gsub(/[\/\\]$/,"")+"/" + # Convert to UTF-8 because of ANSI function + newdir += getUnicodeStringFromAnsi(title) + Dir.mkdir(newdir) rescue nil + ret = safeIsDirectory?(newdir) ? newdir : dir + return ret + end + + def self.getSaveFileName(fileName) + return getSaveFolder().gsub(/[\/\\]$/,"")+"/"+fileName + end + + def self.getSaveFolder + if !@@folder + # XXX: Use "." instead of Dir.pwd because of problems retrieving files if + # the current directory contains an accent mark + pwd = "." + # Get the known folder path for saved games + savedGames = getKnownFolder([ + 0x4c5c32ff,0xbb9d,0x43b0,0xb5,0xb4,0x2d,0x72,0xe5,0x4e,0xaa,0xa4]) + if savedGames && savedGames!="" && isDirWritable(savedGames) + pwd = ensureGameDir(savedGames) + end + if isDirWritable(pwd) + @@folder = pwd + else + appdata = ENV["LOCALAPPDATA"] + if isDirWritable(appdata) + appdata = ensureGameDir(appdata) + else + appdata = ENV["APPDATA"] + if isDirWritable(appdata) + appdata = ensureGameDir(appdata) + elsif isDirWritable(pwd) + appdata = pwd + else + appdata = "." + end + end + @@folder = appdata + end + end + return @@folder + end +end + + + +module FileTest + Image_ext = ['.bmp', '.png', '.jpg', '.jpeg', '.gif'] + Audio_ext = ['.mp3', '.mid', '.midi', '.ogg', '.wav', '.wma'] + + def self.audio_exist?(filename) + return RTP.exists?(filename,Audio_ext) + end + + def self.image_exist?(filename) + return RTP.exists?(filename,Image_ext) + end +end + + + +# Used to determine whether a data file exists (rather than a graphics or +# audio file). Doesn't check RTP, but does check encrypted archives. +def pbRgssExists?(filename) + filename = canonicalize(filename) + if safeExists?("./Game.rgssad") || safeExists?("./Game.rgss2a") + return pbGetFileChar(filename)!=nil + else + return safeExists?(filename) + end +end + +# Opens an IO, even if the file is in an encrypted archive. +# Doesn't check RTP for the file. +def pbRgssOpen(file,mode=nil) + #File.open("debug.txt","ab") { |fw| fw.write([file,mode,Time.now.to_f].inspect+"\r\n") } + if !safeExists?("./Game.rgssad") && !safeExists?("./Game.rgss2a") + if block_given? + File.open(file,mode) { |f| yield f } + return nil + else + return File.open(file,mode) + end + end + file = canonicalize(file) + Marshal.neverload = true + begin + str = load_data(file) + ensure + Marshal.neverload = false + end + if block_given? + StringInput.open(str) { |f| yield f } + return nil + else + return StringInput.open(str) + end +end + +# Gets at least the first byte of a file. Doesn't check RTP, but does check +# encrypted archives. +def pbGetFileChar(file) + file = canonicalize(file) + if !safeExists?("./Game.rgssad") && !safeExists?("./Game.rgss2a") + return nil if !safeExists?(file) + begin + File.open(file,"rb") { |f| return f.read(1) } # read one byte + rescue Errno::ENOENT, Errno::EINVAL, Errno::EACCES + return nil + end + end + Marshal.neverload = true + str = nil + begin + str = load_data(file) + rescue Errno::ENOENT, Errno::EINVAL, Errno::EACCES, RGSSError + str = nil + ensure + Marshal.neverload = false + end + return str +end + +def pbTryString(x) + ret = pbGetFileChar(x) + return (ret!=nil && ret!="") ? x : nil +end + +# Gets the contents of a file. Doesn't check RTP, but does check +# encrypted archives. +def pbGetFileString(file) + file = canonicalize(file) + if !(safeExists?("./Game.rgssad") || safeExists?("./Game.rgss2a")) + return nil if !safeExists?(file) + begin + File.open(file,"rb") { |f| return f.read } # read all data + rescue Errno::ENOENT, Errno::EINVAL, Errno::EACCES + return nil + end + end + Marshal.neverload = true + str = nil + begin + str = load_data(file) + rescue Errno::ENOENT, Errno::EINVAL, Errno::EACCES, RGSSError + str = nil + ensure + Marshal.neverload = false + end + return str +end + + + +#=============================================================================== +# +#=============================================================================== +module MiniRegistry + HKEY_CLASSES_ROOT = 0x80000000 + HKEY_CURRENT_USER = 0x80000001 + HKEY_LOCAL_MACHINE = 0x80000002 + HKEY_USERS = 0x80000003 + FormatMessageA = Win32API.new("kernel32","FormatMessageA","LPLLPLP","L") + RegOpenKeyExA = Win32API.new("advapi32","RegOpenKeyExA","LPLLP","L") + RegCloseKey = Win32API.new("advapi32","RegCloseKey","L","L") + RegQueryValueExA = Win32API.new("advapi32","RegQueryValueExA","LPLPPP","L") + + def self.open(hkey,subkey,bit64=false) + key = 0.chr*4 + flag = bit64 ? 0x20119 : 0x20019 + rg = RegOpenKeyExA.call(hkey, subkey, 0, flag, key) + return nil if rg!=0 + key = key.unpack("V")[0] + if block_given? + begin + yield(key) + ensure + check(RegCloseKey.call(key)) + end + else + return key + end + end + + def self.close(hkey); check(RegCloseKey.call(hkey)) if hkey; end + + def self.get(hkey,subkey,name,defaultValue=nil,bit64=false) + self.open(hkey,subkey,bit64) { |key| + return self.read(key,name) rescue defaultValue + } + return defaultValue + end + + def self.read(hkey,name) + hkey = 0 if !hkey + type = 0.chr*4 + size = 0.chr*4 + check(RegQueryValueExA.call(hkey,name,0,type,0,size)) + data = " "*size.unpack("V")[0] + check(RegQueryValueExA.call(hkey,name,0,type,data,size)) + type = type.unpack("V")[0] + data = data[0,size.unpack("V")[0]] + case type + when 1; return data.chop # REG_SZ + when 2; return data.gsub(/%([^%]+)%/) { ENV[$1] || $& } # REG_EXPAND_SZ + when 3; return data # REG_BINARY + when 4; return data.unpack("V")[0] # REG_DWORD + when 5; return data.unpack("V")[0] # REG_DWORD_BIG_ENDIAN + when 11; qw = data.unpack("VV"); return (data[1]<<32|data[0]) # REG_QWORD + else; raise "Type #{type} not supported." + end + end + + private + + def self.check(code) + if code!=0 + msg = "\0"*1024 + len = FormatMessageA.call(0x1200, 0, code, 0, msg, 1024, 0) + raise msg[0, len].tr("\r", '').chomp + end + end +end + + + +class StringInput + include Enumerable + + class << self + def new( str ) + if block_given? + begin + f = super + yield f + ensure + f.close if f + end + else + super + end + end + alias open new + end + + def initialize( str ) + @string = str + @pos = 0 + @closed = false + @lineno = 0 + end + + attr_reader :lineno,:string + + def inspect + return "#<#{self.class}:#{@closed ? 'closed' : 'open'},src=#{@string[0,30].inspect}>" + end + + def close + raise IOError, 'closed stream' if @closed + @pos = nil + @closed = true + end + + def closed?; @closed; end + + def pos + raise IOError, 'closed stream' if @closed + [@pos, @string.size].min + end + + alias tell pos + + def rewind; seek(0); end + + def pos=(value); seek(value); end + + def seek(offset, whence=IO::SEEK_SET) + raise IOError, 'closed stream' if @closed + case whence + when IO::SEEK_SET; @pos = offset + when IO::SEEK_CUR; @pos += offset + when IO::SEEK_END; @pos = @string.size - offset + else + raise ArgumentError, "unknown seek flag: #{whence}" + end + @pos = 0 if @pos < 0 + @pos = [@pos, @string.size + 1].min + offset + end + + def eof? + raise IOError, 'closed stream' if @closed + @pos > @string.size + end + + def each( &block ) + raise IOError, 'closed stream' if @closed + begin + @string.each(&block) + ensure + @pos = 0 + end + end + + def gets + raise IOError, 'closed stream' if @closed + if idx = @string.index(?\n, @pos) + idx += 1 # "\n".size + line = @string[ @pos ... idx ] + @pos = idx + @pos += 1 if @pos == @string.size + else + line = @string[ @pos .. -1 ] + @pos = @string.size + 1 + end + @lineno += 1 + line + end + + def getc + raise IOError, 'closed stream' if @closed + ch = @string[@pos] + @pos += 1 + @pos += 1 if @pos == @string.size + ch + end + + def read( len = nil ) + raise IOError, 'closed stream' if @closed + if !len + return nil if eof? + rest = @string[@pos ... @string.size] + @pos = @string.size + 1 + return rest + end + str = @string[@pos, len] + @pos += len + @pos += 1 if @pos == @string.size + str + end + + def read_all; read(); end + + alias sysread read +end + + + +module ::Marshal + class << self + if !@oldloadAliased + alias oldload load + @oldloadAliased = true + end + + @@neverload = false + + def neverload + return @@neverload + end + + def neverload=(value) + @@neverload = value + end + + def load(port,*arg) + if @@neverload + if port.is_a?(IO) + return port.read + end + return port + end + oldpos = port.pos if port.is_a?(IO) + begin + oldload(port,*arg) + rescue + p [$!.class,$!.message,$!.backtrace] + if port.is_a?(IO) + port.pos = oldpos + return port.read + end + return port + end + end + end +end \ No newline at end of file diff --git a/Data/Scripts/008_Objects and windows/002_BitmapCache.rb b/Data/Scripts/008_Objects and windows/002_BitmapCache.rb new file mode 100644 index 000000000..e44135d17 --- /dev/null +++ b/Data/Scripts/008_Objects and windows/002_BitmapCache.rb @@ -0,0 +1,497 @@ +class Hangup < Exception; end + + + +def strsplit(str,re) + ret=[] + tstr=str + while re=~tstr + ret[ret.length]=$~.pre_match + tstr=$~.post_match + end + ret[ret.length]=tstr if ret.length + return ret +end + +def canonicalize(c) + csplit = strsplit(c,/[\/\\]/) + pos = -1 + ret = [] + retstr = "" + for x in csplit + if x=="." + elsif x==".." + if pos>=0 + ret.delete_at(pos) + pos -= 1 + end + else + ret.push(x) + pos += 1 + end + end + for i in 0...ret.length + retstr += "/" if i>0 + retstr += ret[i] + end + return retstr +end + + + +##################################################################### +class WeakRef + @@id_map = {} + @@id_rev_map = {} + @@final = lambda { |id| + __old_status = Thread.critical + Thread.critical = true + begin + rids = @@id_map[id] + if rids + for rid in rids + @@id_rev_map.delete(rid) + end + @@id_map.delete(id) + end + rid = @@id_rev_map[id] + if rid + @@id_rev_map.delete(id) + @@id_map[rid].delete(id) + @@id_map.delete(rid) if @@id_map[rid].empty? + end + ensure + Thread.critical = __old_status + end + } + + # Create a new WeakRef from +orig+. + def initialize(orig) + __setobj__(orig) + end + + def __getobj__ + unless @@id_rev_map[self.__id__] == @__id + return nil + end + begin + ObjectSpace._id2ref(@__id) + rescue RangeError + return nil + end + end + + def __setobj__(obj) + @__id = obj.__id__ + __old_status = Thread.critical + begin + Thread.critical = true + unless @@id_rev_map.key?(self) + ObjectSpace.define_finalizer obj, @@final + ObjectSpace.define_finalizer self, @@final + end + @@id_map[@__id] = [] unless @@id_map[@__id] + ensure + Thread.critical = __old_status + end + @@id_map[@__id].push self.__id__ + @@id_rev_map[self.__id__] = @__id + end + + # Returns true if the referenced object still exists, and false if it has + # been garbage collected. + def weakref_alive? + @@id_rev_map[self.__id__] == @__id + end +end + + + +class WeakHashtable + include Enumerable + + def initialize + @hash={} + end + + def clear + @hash.clear + end + + def delete(value) + @hash.delete(value) + end + + def include?(value) + @hash.include?(value) + end + + def each + @hash.each { |i| yield i } + end + + def keys + @hash.keys + end + + def values + @hash.values + end + + def [](key) + o=@hash[key] + return o if !o + if o.weakref_alive? + o=o.__getobj__ + else + @hash.delete(key) + o=nil + end + return o + end + + def []=(key,o) + if o!=nil + o=WeakRef.new(o) + end + @hash[key]=o + end +end + + + +# Cache from RPG Maker VX library +module Cache + def self.system(x,hue=0) + BitmapCache.load_bitmap("Graphics/System/"+x,hue, true) + end + + def self.character(x,hue=0) + BitmapCache.load_bitmap("Graphics/Characters/"+x,hue, true) + end + + def self.picture(x,hue=0) + BitmapCache.load_bitmap("Graphics/Pictures/"+x,hue, true) + end + + def self.animation(x,hue=0) + BitmapCache.load_bitmap("Graphics/Animations/"+x,hue, true) + end + + def self.battler(x,hue=0) + BitmapCache.load_bitmap("Graphics/Battlers/"+x,hue, true) + end + + def self.face(x,hue=0) + BitmapCache.load_bitmap("Graphics/Faces/"+x,hue, true) + end + + def self.parallax(x,hue=0) + BitmapCache.load_bitmap("Graphics/Parallaxes/"+x,hue, true) + end + + def self.clear + BitmapCache.clear() + end + + def self.load_bitmap(dir,name,hue=0) + BitmapCache.load_bitmap(dir+name,hue, true) + end +end + + + +# RPG::Cache from RPG Maker XP library +module RPG + module Cache + def self.load_bitmap(folder_name, filename, hue = 0) + BitmapCache.load_bitmap(folder_name+filename.to_s,hue, true) + end + + def self.animation(filename, hue) + self.load_bitmap("Graphics/Animations/", filename, hue) + end + + def self.autotile(filename) + self.load_bitmap("Graphics/Autotiles/", filename) + end + + def self.battleback(filename) + self.load_bitmap("Graphics/Battlebacks/", filename) + end + + def self.battler(filename, hue) + self.load_bitmap("Graphics/Battlers/", filename, hue) + end + + def self.character(filename, hue) + self.load_bitmap("Graphics/Characters/", filename, hue) + end + + def self.fog(filename, hue) + self.load_bitmap("Graphics/Fogs/", filename, hue) + end + + def self.gameover(filename) + self.load_bitmap("Graphics/Gameovers/", filename) + end + + def self.icon(filename) + self.load_bitmap("Graphics/Icons/", filename) + end + + def self.panorama(filename, hue) + self.load_bitmap("Graphics/Panoramas/", filename, hue) + end + + def self.picture(filename) + self.load_bitmap("Graphics/Pictures/", filename) + end + + def self.tileset(filename) + self.load_bitmap("Graphics/Tilesets/", filename) + end + + def self.title(filename) + self.load_bitmap("Graphics/Titles/", filename) + end + + def self.windowskin(filename) + self.load_bitmap("Graphics/Windowskins/", filename) + end + + def self.tile(filename, tile_id, hue) + BitmapCache.tile(filename,tile_id,hue) + end + + def self.clear + BitmapCache.clear() + end + end +end + + + +# A safer version of RPG::Cache, this module loads bitmaps that keep an internal +# reference count. Each call to dispose decrements the reference count and the +# bitmap is freed when the reference count reaches 0. +class Thread + def Thread.exclusive + _old = Thread.critical + begin + Thread.critical = true + return yield + ensure + Thread.critical = _old + end + end +end + + + +class BitmapWrapper < Bitmap + @@disposedBitmaps={} + @@keys={} +=begin + @@final = lambda { |id| + Thread.exclusive { + if @@disposedBitmaps[id]!=true + File.open("debug.txt","ab") { |f| + f.write("Bitmap finalized without being disposed: #{@@keys[id]}\r\n") + } + end + @@disposedBitmaps[id]=nil + } + } +=end + attr_reader :refcount + + def dispose + return if self.disposed? + @refcount-=1 + if @refcount==0 + super + #Thread.exclusive { @@disposedBitmaps[__id__]=true } + end + end + + def initialize(*arg) + super + @refcount=1 + #Thread.exclusive { @@keys[__id__]=arg.inspect+caller(1).inspect } + #ObjectSpace.define_finalizer(self,@@final) + end + + def resetRef # internal + @refcount=1 + end + + def copy + bm=self.clone + bm.resetRef + return bm + end + + def addRef + @refcount+=1 + end +end + + + +module BitmapCache + @cache = WeakHashtable.new + + def self.fromCache(i) + return nil if !@cache.include?(i) + obj=@cache[i] + return nil if obj && obj.disposed? + return obj + end + + def self.setKey(key,obj) + @cache[key]=obj + end + + def self.debug + File.open("bitmapcache2.txt","wb") { |f| + for i in @cache.keys + k = fromCache(i) + if !k + f.write("#{i} (nil)\r\n") + elsif k.disposed? + f.write("#{i} (disposed)\r\n") + else + f.write("#{i} (#{k.refcount}, #{k.width}x#{k.height})\r\n") + end + end + } + end + + def self.load_bitmap(path, hue = 0, failsafe = false) + cached = true + path = canonicalize(path) + objPath = fromCache(path) + if !objPath + @cleancounter = ((@cleancounter || 0) + 1)%10 + if @cleancounter == 0 + for i in @cache.keys + @cache.delete(i) if !fromCache(i) + end + end + begin + bm = BitmapWrapper.new(path) + rescue Hangup + begin + bm = BitmapWrapper.new(path) + rescue + raise _INTL("Failed to load the bitmap located at: {1}",path) if !failsafe + bm = BitmapWrapper.new(32,32) + end + rescue + raise _INTL("Failed to load the bitmap located at: {1}",path) if !failsafe + bm = BitmapWrapper.new(32,32) + end + objPath = bm + @cache[path] = objPath + cached=false + end + if hue == 0 + objPath.addRef if cached + return objPath + else + key = [path, hue] + objKey = fromCache(key) + if !objKey + bitmap = objPath.copy + bitmap.hue_change(hue) if hue!=0 + objKey = bitmap + @cache[key] = objKey + else + objKey.addRef + end + return objKey + end + end + + def self.animation(filename, hue) + self.load_bitmap("Graphics/Animations/"+filename, hue) + end + + def self.autotile(filename) + self.load_bitmap("Graphics/Autotiles/"+ filename) + end + + def self.battleback(filename) + self.load_bitmap("Graphics/Battlebacks/"+ filename) + end + + def self.battler(filename, hue) + self.load_bitmap("Graphics/Battlers/"+ filename, hue) + end + + def self.character(filename, hue) + self.load_bitmap("Graphics/Characters/"+ filename, hue) + end + + def self.fog(filename, hue) + self.load_bitmap("Graphics/Fogs/"+ filename, hue) + end + + def self.gameover(filename) + self.load_bitmap("Graphics/Gameovers/"+ filename) + end + + def self.icon(filename) + self.load_bitmap("Graphics/Icons/"+ filename) + end + + def self.panorama(filename, hue) + self.load_bitmap("Graphics/Panoramas/"+ filename, hue) + end + + def self.picture(filename) + self.load_bitmap("Graphics/Pictures/"+ filename) + end + + def self.tileset(filename) + self.load_bitmap("Graphics/Tilesets/"+ filename) + end + + def self.title(filename) + self.load_bitmap("Graphics/Titles/"+ filename) + end + + def self.windowskin(filename) + self.load_bitmap("Graphics/Windowskins/"+ filename) + end + + def self.tileEx(filename, tile_id, hue) + key = [filename, tile_id, hue] + objKey=fromCache(key) + if !objKey + bitmap=BitmapWrapper.new(32, 32) + x = (tile_id - 384) % 8 * 32 + y = (tile_id - 384) / 8 * 32 + rect = Rect.new(x, y, 32, 32) + tileset = yield(filename) + bitmap.blt(0, 0, tileset, rect) + tileset.dispose + bitmap.hue_change(hue) if hue!=0 + objKey=bitmap + @cache[key]=objKey + else + objKey.addRef + end + objKey + end + + def self.tile(filename, tile_id, hue) + return self.tileEx(filename, tile_id,hue) { |f| self.tileset(f) } + end + + def self.clear + @cache = {} + GC.start + end +end \ No newline at end of file diff --git a/Data/Scripts/008_Objects and windows/003_Window.rb b/Data/Scripts/008_Objects and windows/003_Window.rb new file mode 100644 index 000000000..60cc33535 --- /dev/null +++ b/Data/Scripts/008_Objects and windows/003_Window.rb @@ -0,0 +1,608 @@ +class WindowCursorRect < Rect + def initialize(window) + @window=window + @x=0 + @y=0 + @width=0 + @height=0 + end + + attr_reader :x,:y,:width,:height + + def empty + needupdate=@x!=0 || @y!=0 || @width!=0 || @height!=0 + if needupdate + @x=0 + @y=0 + @width=0 + @height=0 + @window.width=@window.width + end + end + + def isEmpty? + return @x==0 && @y==0 && @width==0 && @height==0 + end + + def set(x,y,width,height) + needupdate=@x!=x || @y!=y || @width!=width || @height!=height + if needupdate + @x=x + @y=y + @width=width + @height=height + @window.width=@window.width + end + end + + def height=(value) + @height=value; @window.width=@window.width + end + + def width=(value) + @width=value; @window.width=@window.width + end + + def x=(value) + @x=value; @window.width=@window.width + end + + def y=(value) + @y=value; @window.width=@window.width + end +end + + + +class Window + attr_reader :tone + attr_reader :color + attr_reader :blend_type + attr_reader :contents_blend_type + attr_reader :viewport + attr_reader :contents + attr_reader :ox + attr_reader :oy + attr_reader :x + attr_reader :y + attr_reader :z + attr_reader :width + attr_reader :active + attr_reader :pause + attr_reader :height + attr_reader :opacity + attr_reader :back_opacity + attr_reader :contents_opacity + attr_reader :visible + attr_reader :cursor_rect + attr_reader :openness + attr_reader :stretch + + def windowskin + @_windowskin + end + + def initialize(viewport=nil) + @sprites={} + @spritekeys=[ + "back", + "corner0","side0","scroll0", + "corner1","side1","scroll1", + "corner2","side2","scroll2", + "corner3","side3","scroll3", + "cursor","contents","pause" + ] + @sidebitmaps=[nil,nil,nil,nil] + @cursorbitmap=nil + @bgbitmap=nil + @viewport=viewport + for i in @spritekeys + @sprites[i]=Sprite.new(@viewport) + end + @disposed=false + @tone=Tone.new(0,0,0) + @color=Color.new(0,0,0,0) + @blankcontents=Bitmap.new(1,1) # RGSS2 requires this + @contents=@blankcontents + @_windowskin=nil + @rpgvx=false # Set to true to emulate RPGVX windows + @x=0 + @y=0 + @width=0 + @openness=255 + @height=0 + @ox=0 + @oy=0 + @z=0 + @stretch=true + @visible=true + @active=true + @blend_type=0 + @contents_blend_type=0 + @opacity=255 + @back_opacity=255 + @contents_opacity=255 + @cursor_rect=WindowCursorRect.new(self) + @cursorblink=0 + @cursoropacity=255 + @pause=false + @pauseopacity=255 + @pauseframe=0 + privRefresh(true) + end + + def dispose + if !self.disposed? + for i in @sprites + i[1].dispose if i[1] + @sprites[i[0]]=nil + end + for i in 0...@sidebitmaps.length + @sidebitmaps[i].dispose if @sidebitmaps[i] + @sidebitmaps[i]=nil + end + @blankcontents.dispose + @cursorbitmap.dispose if @cursorbitmap + @backbitmap.dispose if @backbitmap + @sprites.clear + @sidebitmaps.clear + @_windowskin=nil + @_contents=nil + @disposed=true + end + end + + def openness=(value) + @openness=value + @openness=0 if @openness<0 + @openness=255 if @openness>255 + privRefresh + end + + def stretch=(value) + @stretch=value + privRefresh(true) + end + + def visible=(value) + @visible=value + privRefresh + end + + def viewport=(value) + @viewport=value + for i in @spritekeys + @sprites[i].dispose + if @sprites[i].is_a?(Sprite) + @sprites[i]=Sprite.new(@viewport) + else + @sprites[i]=nil + end + end + privRefresh(true) + end + + def z=(value) + @z=value + privRefresh + end + + def disposed? + return @disposed + end + + def contents=(value) + @contents=value + privRefresh + end + + def windowskin=(value) + @_windowskin=value + if value && value.is_a?(Bitmap) && !value.disposed? && value.width==128 + @rpgvx=true + else + @rpgvx=false + end + privRefresh(true) + end + + def ox=(value) + @ox=value + privRefresh + end + + def active=(value) + @active=value + privRefresh(true) + end + + def cursor_rect=(value) + if !value + @cursor_rect.empty + else + @cursor_rect.set(value.x,value.y,value.width,value.height) + end + end + + def oy=(value) + @oy=value + privRefresh + end + + def width=(value) + @width=value + privRefresh(true) + end + + def height=(value) + @height=value + privRefresh(true) + end + + def pause=(value) + @pause=value + @pauseopacity=0 if !value + privRefresh + end + + def x=(value) + @x=value + privRefresh + end + + def y=(value) + @y=value + privRefresh + end + + def opacity=(value) + @opacity=value + @opacity=0 if @opacity<0 + @opacity=255 if @opacity>255 + privRefresh + end + + def back_opacity=(value) + @back_opacity=value + @back_opacity=0 if @back_opacity<0 + @back_opacity=255 if @back_opacity>255 + privRefresh + end + + def contents_opacity=(value) + @contents_opacity=value + @contents_opacity=0 if @contents_opacity<0 + @contents_opacity=255 if @contents_opacity>255 + privRefresh + end + + def tone=(value) + @tone=value + privRefresh + end + + def color=(value) + @color=value + privRefresh + end + + def blend_type=(value) + @blend_type=value + privRefresh + end + + def flash(color,duration) + return if disposed? + for i in @sprites + i[1].flash(color,duration) + end + end + + def update + return if disposed? + mustchange=false + if @active + if @cursorblink==0 + @cursoropacity-=8 + @cursorblink=1 if @cursoropacity<=128 + else + @cursoropacity+=8 + @cursorblink=0 if @cursoropacity>=255 + end + mustchange=true if !@cursor_rect.isEmpty? + else + mustchange=true if @cursoropacity!=128 + @cursoropacity=128 + end + if @pause + @pauseframe=(Graphics.frame_count / 8) % 4 + @pauseopacity=[@pauseopacity+64,255].min + mustchange=true + end + privRefresh if mustchange + for i in @sprites + i[1].update + end + end + + private + + def ensureBitmap(bitmap,dwidth,dheight) + if !bitmap||bitmap.disposed?||bitmap.width 0 + @sprites["scroll1"].visible = @visible && hascontents && @ox > 0 + @sprites["scroll2"].visible = @visible && hascontents && + (@contents.width - @ox) > @width-32 + @sprites["scroll3"].visible = @visible && hascontents && + (@contents.height - @oy) > @height-32 + else + for i in 0...4 + @sprites["corner#{i}"].visible=false + @sprites["side#{i}"].visible=false + @sprites["scroll#{i}"].visible=false + end + @sprites["contents"].visible=@visible && @openness==255 + @sprites["contents"].color=@color + @sprites["contents"].tone=@tone + @sprites["contents"].blend_type=@contents_blend_type + @sprites["contents"].opacity=contopac + @sprites["back"].visible=false + @sprites["pause"].visible=false + @sprites["cursor"].visible=false + end + for i in @sprites + i[1].z=@z + end + if @rpgvx + @sprites["cursor"].z=@z # For Compatibility + @sprites["contents"].z=@z # For Compatibility + @sprites["pause"].z=@z # For Compatibility + else + @sprites["cursor"].z=@z+1 # For Compatibility + @sprites["contents"].z=@z+2 # For Compatibility + @sprites["pause"].z=@z+2 # For Compatibility + end + if @rpgvx + trimX=64 + trimY=0 + backRect=Rect.new(0,0,64,64) + blindsRect=Rect.new(0,64,64,64) + else + trimX=128 + trimY=0 + backRect=Rect.new(0,0,128,128) + blindsRect=nil + end + @sprites["corner0"].src_rect.set(trimX,trimY+0,16,16); + @sprites["corner1"].src_rect.set(trimX+48,trimY+0,16,16); + @sprites["corner2"].src_rect.set(trimX,trimY+48,16,16); + @sprites["corner3"].src_rect.set(trimX+48,trimY+48,16,16); + @sprites["scroll0"].src_rect.set(trimX+24, trimY+16, 16, 8) # up + @sprites["scroll3"].src_rect.set(trimX+24, trimY+40, 16, 8) # down + @sprites["scroll1"].src_rect.set(trimX+16, trimY+24, 8, 16) # left + @sprites["scroll2"].src_rect.set(trimX+40, trimY+24, 8, 16) # right + cursorX=trimX + cursorY=trimY+64 + sideRects=[ + Rect.new(trimX+16,trimY+0,32,16), + Rect.new(trimX,trimY+16,16,32), + Rect.new(trimX+48,trimY+16,16,32), + Rect.new(trimX+16,trimY+48,32,16) + ] + if @width>32 && @height>32 + @sprites["contents"].src_rect.set(@ox,@oy,@width-32,@height-32) + else + @sprites["contents"].src_rect.set(0,0,0,0) + end + pauseRects=[ + trimX+32,trimY+64, + trimX+48,trimY+64, + trimX+32,trimY+80, + trimX+48,trimY+80, + ] + pauseWidth=16 + pauseHeight=16 + @sprites["pause"].src_rect.set( + pauseRects[@pauseframe*2], + pauseRects[@pauseframe*2+1], + pauseWidth,pauseHeight + ) + @sprites["pause"].x=@x+(@width/2)-(pauseWidth/2) + @sprites["pause"].y=@y+@height-16 # 16 refers to skin margin + @sprites["contents"].x=@x+16 + @sprites["contents"].y=@y+16 + @sprites["corner0"].x=@x + @sprites["corner0"].y=@y + @sprites["corner1"].x=@x+@width-16 + @sprites["corner1"].y=@y + @sprites["corner2"].x=@x + @sprites["corner2"].y=@y+@height-16 + @sprites["corner3"].x=@x+@width-16 + @sprites["corner3"].y=@y+@height-16 + @sprites["side0"].x=@x+16 + @sprites["side0"].y=@y + @sprites["side1"].x=@x + @sprites["side1"].y=@y+16 + @sprites["side2"].x=@x+@width-16 + @sprites["side2"].y=@y+16 + @sprites["side3"].x=@x+16 + @sprites["side3"].y=@y+@height-16 + @sprites["scroll0"].x = @x+@width / 2 - 8 + @sprites["scroll0"].y = @y+8 + @sprites["scroll1"].x = @x+8 + @sprites["scroll1"].y = @y+@height / 2 - 8 + @sprites["scroll2"].x = @x+@width - 16 + @sprites["scroll2"].y = @y+@height / 2 - 8 + @sprites["scroll3"].x = @x+@width / 2 - 8 + @sprites["scroll3"].y = @y+@height - 16 + @sprites["back"].x=@x+2 + @sprites["back"].y=@y+2 + @sprites["cursor"].x=@x+16+@cursor_rect.x + @sprites["cursor"].y=@y+16+@cursor_rect.y + if changeBitmap && @_windowskin && !@_windowskin.disposed? + width=@cursor_rect.width + height=@cursor_rect.height + if width > 0 && height > 0 + cursorrects=[ + # sides + Rect.new(cursorX+2, cursorY+0, 28, 2), + Rect.new(cursorX+0, cursorY+2, 2, 28), + Rect.new(cursorX+30, cursorY+2, 2, 28), + Rect.new(cursorX+2, cursorY+30, 28, 2), + # corners + Rect.new(cursorX+0, cursorY+0, 2, 2), + Rect.new(cursorX+30, cursorY+0, 2, 2), + Rect.new(cursorX+0, cursorY+30, 2, 2), + Rect.new(cursorX+30, cursorY+30, 2, 2), + # back + Rect.new(cursorX+2, cursorY+2, 28, 28) + ] + margin=2 + fullmargin=4 + @cursorbitmap = ensureBitmap(@cursorbitmap, width, height) + @cursorbitmap.clear + @sprites["cursor"].bitmap=@cursorbitmap + @sprites["cursor"].src_rect.set(0,0,width,height) + rect = Rect.new(margin,margin, + width - fullmargin, height - fullmargin) + @cursorbitmap.stretch_blt(rect, @_windowskin, cursorrects[8]) + @cursorbitmap.blt(0, 0, @_windowskin, cursorrects[4])# top left + @cursorbitmap.blt(width-margin, 0, @_windowskin, cursorrects[5]) # top right + @cursorbitmap.blt(0, height-margin, @_windowskin, cursorrects[6]) # bottom right + @cursorbitmap.blt(width-margin, height-margin, @_windowskin, cursorrects[7]) # bottom left + rect = Rect.new(margin, 0, + width - fullmargin, margin) + @cursorbitmap.stretch_blt(rect, @_windowskin, cursorrects[0]) + rect = Rect.new(0, margin, + margin, height - fullmargin) + @cursorbitmap.stretch_blt(rect, @_windowskin, cursorrects[1]) + rect = Rect.new(width - margin, margin, + margin, height - fullmargin) + @cursorbitmap.stretch_blt(rect, @_windowskin, cursorrects[2]) + rect = Rect.new(margin, height-margin, + width - fullmargin, margin) + @cursorbitmap.stretch_blt(rect, @_windowskin, cursorrects[3]) + else + @sprites["cursor"].visible=false + @sprites["cursor"].src_rect.set(0,0,0,0) + end + for i in 0...4 + dwidth = (i==0 || i==3) ? @width-32 : 16 + dheight = (i==0 || i==3) ? 16 : @height-32 + @sidebitmaps[i]=ensureBitmap(@sidebitmaps[i],dwidth,dheight) + @sprites["side#{i}"].bitmap=@sidebitmaps[i] + @sprites["side#{i}"].src_rect.set(0,0,dwidth,dheight) + @sidebitmaps[i].clear + if sideRects[i].width>0 && sideRects[i].height>0 + @sidebitmaps[i].stretch_blt(@sprites["side#{i}"].src_rect, + @_windowskin,sideRects[i]) + end + end + backwidth=@width-4 + backheight=@height-4 + if backwidth>0 && backheight>0 + @backbitmap=ensureBitmap(@backbitmap,backwidth,backheight) + @sprites["back"].bitmap=@backbitmap + @sprites["back"].src_rect.set(0,0,backwidth,backheight) + @backbitmap.clear + if @stretch + @backbitmap.stretch_blt(@sprites["back"].src_rect,@_windowskin,backRect) + else + tileBitmap(@backbitmap,@sprites["back"].src_rect,@_windowskin,backRect) + end + if blindsRect + tileBitmap(@backbitmap,@sprites["back"].src_rect,@_windowskin,blindsRect) + end + else + @sprites["back"].visible=false + @sprites["back"].src_rect.set(0,0,0,0) + end + end + if @openness!=255 + opn=@openness/255.0 + for k in @spritekeys + sprite=@sprites[k] + ratio=(@height<=0) ? 0 : (sprite.y-@y)*1.0/@height + sprite.zoom_y=opn + sprite.oy=0 + sprite.y=(@y+(@height/2.0)+(@height*ratio*opn)-(@height/2*opn)).floor + end + else + for k in @spritekeys + sprite=@sprites[k] + sprite.zoom_y=1.0 + end + end + i=0 + # Ensure Z order + for k in @spritekeys + sprite=@sprites[k] + y=sprite.y + sprite.y=i + sprite.oy=(sprite.zoom_y<=0) ? 0 : (i-y)/sprite.zoom_y + end + end +end \ No newline at end of file diff --git a/Data/Scripts/008_Objects and windows/004_SpriteWindow.rb b/Data/Scripts/008_Objects and windows/004_SpriteWindow.rb new file mode 100644 index 000000000..9e5f8f214 --- /dev/null +++ b/Data/Scripts/008_Objects and windows/004_SpriteWindow.rb @@ -0,0 +1,781 @@ +module MessageConfig + FontName = "Power Green" + # in Graphics/Windowskins/ (specify empty string to use the default windowskin) + TextSkinName = "speech hgss 1" + ChoiceSkinName = "choice 1" + WindowOpacity = 255 + TextSpeed = nil # can be positive to wait frames or negative to + # show multiple characters in a single frame + LIGHTTEXTBASE = Color.new(248,248,248) + LIGHTTEXTSHADOW = Color.new(72,80,88) + DARKTEXTBASE = Color.new(80,80,88) + DARKTEXTSHADOW = Color.new(160,160,168) + # 0 = Pause cursor is displayed at end of text + # 1 = Pause cursor is displayed at bottom right + # 2 = Pause cursor is displayed at lower middle side + CURSORMODE = 1 + FontSubstitutes = { + "Power Red and Blue" => "Pokemon RS", + "Power Red and Green" => "Pokemon FireLeaf", + "Power Green" => "Pokemon Emerald", + "Power Green Narrow" => "Pokemon Emerald Narrow", + "Power Green Small" => "Pokemon Emerald Small", + "Power Clear" => "Pokemon DP" + } + @@systemFrame = nil + @@defaultTextSkin = nil + @@systemFont = nil + @@textSpeed = nil + + def self.pbTryFonts(*args) + for a in args + if a && a.is_a?(String) + return a if Font.exist?(a) + a=MessageConfig::FontSubstitutes[a] || a + return a if Font.exist?(a) + elsif a && a.is_a?(Array) + for aa in a + ret=MessageConfig.pbTryFonts(aa) + return ret if ret!="" + end + end + end + return "" + end + + def self.pbDefaultSystemFrame + return "" if !MessageConfig::ChoiceSkinName + return pbResolveBitmap("Graphics/Windowskins/"+MessageConfig::ChoiceSkinName) || "" + end + + def self.pbDefaultSpeechFrame + return "" if !MessageConfig::TextSkinName + return pbResolveBitmap("Graphics/Windowskins/"+MessageConfig::TextSkinName) || "" + end + + def self.pbDefaultSystemFontName + return MessageConfig.pbTryFonts(MessageConfig::FontName,"Arial Narrow","Arial") + end + + def self.pbDefaultTextSpeed + return (TextSpeed) ? TextSpeed : (Graphics.width>400) ? -2 : 1 + end + + def self.pbDefaultWindowskin + skin=load_data("Data/System.rxdata").windowskin_name rescue nil + if skin && skin!="" + skin=pbResolveBitmap("Graphics/Windowskins/"+skin) || "" + end + skin=pbResolveBitmap("Graphics/System/Window") if !skin || skin=="" + skin=pbResolveBitmap("Graphics/Windowskins/001-Blue01") if !skin || skin=="" + return skin || "" + end + + def self.pbGetSystemFrame + if !@@systemFrame + skin=MessageConfig.pbDefaultSystemFrame + skin=MessageConfig.pbDefaultWindowskin if !skin || skin=="" + @@systemFrame=skin || "" + end + return @@systemFrame + end + + def self.pbGetSpeechFrame + if !@@defaultTextSkin + skin=MessageConfig.pbDefaultSpeechFrame + skin=MessageConfig.pbDefaultWindowskin if !skin || skin=="" + @@defaultTextSkin=skin || "" + end + return @@defaultTextSkin + end + + def self.pbGetSystemFontName + @@systemFont=pbDefaultSystemFontName if !@@systemFont + return @@systemFont + end + + def self.pbGetTextSpeed + @@textSpeed=pbDefaultTextSpeed if !@@textSpeed + return @@textSpeed + end + + def self.pbSetSystemFrame(value) + @@systemFrame=pbResolveBitmap(value) || "" + end + + def self.pbSetSpeechFrame(value) + @@defaultTextSkin=pbResolveBitmap(value) || "" + end + + def self.pbSetSystemFontName(value) + @@systemFont=MessageConfig.pbTryFonts(value,"Arial Narrow","Arial") + end + + def self.pbSetTextSpeed(value) + @@textSpeed=value + end +end + + + +#=============================================================================== +# Position a window +#=============================================================================== +def pbBottomRight(window) + window.x=Graphics.width-window.width + window.y=Graphics.height-window.height +end + +def pbBottomLeft(window) + window.x=0 + window.y=Graphics.height-window.height +end + +def pbBottomLeftLines(window,lines,width=nil) + window.x=0 + window.width=width ? width : Graphics.width + window.height=(window.borderY rescue 32)+lines*32 + window.y=Graphics.height-window.height +end + +def pbPositionFaceWindow(facewindow,msgwindow) + return if !facewindow + if msgwindow + if facewindow.height<=msgwindow.height + facewindow.y=msgwindow.y + else + facewindow.y=msgwindow.y+msgwindow.height-facewindow.height + end + facewindow.x=Graphics.width-facewindow.width + msgwindow.x=0 + msgwindow.width=Graphics.width-facewindow.width + else + facewindow.height=Graphics.height if facewindow.height>Graphics.height + facewindow.x=0 + facewindow.y=0 + end +end + +def pbPositionNearMsgWindow(cmdwindow,msgwindow,side) + return if !cmdwindow + if msgwindow + height=[cmdwindow.height,Graphics.height-msgwindow.height].min + if cmdwindow.height!=height + cmdwindow.height=height + end + cmdwindow.y=msgwindow.y-cmdwindow.height + if cmdwindow.y<0 + cmdwindow.y=msgwindow.y+msgwindow.height + if cmdwindow.y+cmdwindow.height>Graphics.height + cmdwindow.y=msgwindow.y-cmdwindow.height + end + end + case side + when :left + cmdwindow.x=msgwindow.x + when :right + cmdwindow.x=msgwindow.x+msgwindow.width-cmdwindow.width + else + cmdwindow.x=msgwindow.x+msgwindow.width-cmdwindow.width + end + else + cmdwindow.height=Graphics.height if cmdwindow.height>Graphics.height + cmdwindow.x=0 + cmdwindow.y=0 + end +end + +# internal function +def pbRepositionMessageWindow(msgwindow, linecount=2) + msgwindow.height=32*linecount+msgwindow.borderY + msgwindow.y=(Graphics.height)-(msgwindow.height) + if $game_system && $game_system.respond_to?("message_position") + case $game_system.message_position + when 0 # up + msgwindow.y=0 + when 1 # middle + msgwindow.y=(Graphics.height/2)-(msgwindow.height/2) + when 2 + msgwindow.y=(Graphics.height)-(msgwindow.height) + end + end + if $game_system && $game_system.respond_to?("message_frame") + if $game_system.message_frame != 0 + msgwindow.opacity = 0 + end + end + if $game_message + case $game_message.background + when 1; msgwindow.opacity=0 # dim + when 2; msgwindow.opacity=0 # transparent + end + end +end + +# internal function +def pbUpdateMsgWindowPos(msgwindow,event,eventChanged=false) + if event + if eventChanged + msgwindow.resizeToFit2(msgwindow.text,Graphics.width*2/3,msgwindow.height) + end + msgwindow.y=event.screen_y-48-msgwindow.height + if msgwindow.y<0 + msgwindow.y=event.screen_y+24 + end + msgwindow.x=event.screen_x-(msgwindow.width/2) + msgwindow.x=0 if msgwindow.x<0 + if msgwindow.x>Graphics.width-msgwindow.width + msgwindow.x=Graphics.width-msgwindow.width + end + else + curwidth=msgwindow.width + if curwidth!=Graphics.width + msgwindow.width=Graphics.width + msgwindow.width=Graphics.width + end + end +end + +#=============================================================================== +# Determine the colour of a background +#=============================================================================== +def isDarkBackground(background,rect=nil) + return true if !background || background.disposed? + rect = background.rect if !rect + return true if rect.width<=0 || rect.height<=0 + xSeg = (rect.width/16) + xLoop = (xSeg==0) ? 1 : 16 + xStart = (xSeg==0) ? rect.x+(rect.width/2) : rect.x+xSeg/2 + ySeg = (rect.height/16) + yLoop = (ySeg==0) ? 1 : 16 + yStart = (ySeg==0) ? rect.y+(rect.height/2) : rect.y+ySeg/2 + count = 0 + y = yStart + r = 0; g = 0; b = 0 + yLoop.times do + x = xStart + xLoop.times do + clr = background.get_pixel(x,y) + if clr.alpha!=0 + r += clr.red + g += clr.green + b += clr.blue + count += 1 + end + x += xSeg + end + y += ySeg + end + return true if count==0 + r /= count + g /= count + b /= count + return (r*0.299+g*0.587+b*0.114)<160 +end + +def isDarkWindowskin(windowskin) + return true if !windowskin || windowskin.disposed? + if windowskin.width==192 && windowskin.height==128 + return isDarkBackground(windowskin,Rect.new(0,0,128,128)) + elsif windowskin.width==128 && windowskin.height==128 + return isDarkBackground(windowskin,Rect.new(0,0,64,64)) + elsif windowskin.width==96 && windowskin.height==48 + return isDarkBackground(windowskin,Rect.new(32,16,16,16)) + else + clr = windowskin.get_pixel(windowskin.width/2, windowskin.height/2) + return (clr.red*0.299+clr.green*0.587+clr.blue*0.114)<160 + end +end + +#=============================================================================== +# Determine which text colours to use based on the darkness of the background +#=============================================================================== +def getSkinColor(windowskin,color,isDarkSkin) + if !windowskin || windowskin.disposed? || + windowskin.width!=128 || windowskin.height!=128 + # Base color, shadow color (these are reversed on dark windowskins) + textcolors = [ + "0070F8","78B8E8", # 1 Blue + "E82010","F8A8B8", # 2 Red + "60B048","B0D090", # 3 Green + "48D8D8","A8E0E0", # 4 Cyan + "D038B8","E8A0E0", # 5 Magenta + "E8D020","F8E888", # 6 Yellow + "A0A0A8","D0D0D8", # 7 Grey + "F0F0F8","C8C8D0", # 8 White + "9040E8","B8A8E0", # 9 Purple + "F89818","F8C898", # 10 Orange + colorToRgb32(MessageConfig::DARKTEXTBASE), + colorToRgb32(MessageConfig::DARKTEXTSHADOW), # 11 Dark default + colorToRgb32(MessageConfig::LIGHTTEXTBASE), + colorToRgb32(MessageConfig::LIGHTTEXTSHADOW) # 12 Light default + ] + if color==0 || color>textcolors.length/2 # No special colour, use default + if isDarkSkin # Dark background, light text + return shadowc3tag(MessageConfig::LIGHTTEXTBASE,MessageConfig::LIGHTTEXTSHADOW) + end + # Light background, dark text + return shadowc3tag(MessageConfig::DARKTEXTBASE,MessageConfig::DARKTEXTSHADOW) + end + # Special colour as listed above + if isDarkSkin && color!=12 # Dark background, light text + return sprintf("",textcolors[2*(color-1)+1],textcolors[2*(color-1)]) + end + # Light background, dark text + return sprintf("",textcolors[2*(color-1)],textcolors[2*(color-1)+1]) + else # VX windowskin + color = 0 if color>=32 + x = 64 + (color % 8) * 8 + y = 96 + (color / 8) * 8 + pixel = windowskin.get_pixel(x, y) + return shadowctagFromColor(pixel) + end +end + +def getDefaultTextColors(windowskin) + if !windowskin || windowskin.disposed? || + windowskin.width!=128 || windowskin.height!=128 + if isDarkWindowskin(windowskin) + return [MessageConfig::LIGHTTEXTBASE,MessageConfig::LIGHTTEXTSHADOW] # White + else + return [MessageConfig::DARKTEXTBASE,MessageConfig::DARKTEXTSHADOW] # Dark gray + end + else # VX windowskin + color = windowskin.get_pixel(64, 96) + shadow = nil + isDark = (color.red+color.green+color.blue)/3 < 128 + if isDark + shadow = Color.new(color.red+64,color.green+64,color.blue+64) + else + shadow = Color.new(color.red-64,color.green-64,color.blue-64) + end + return [color,shadow] + end +end + +#=============================================================================== +# Makes sure a bitmap exists +#=============================================================================== +def pbDoEnsureBitmap(bitmap,dwidth,dheight) + if !bitmap || bitmap.disposed? || bitmap.width0 : false +end + +# pbFadeOutIn(z) { block } +# Fades out the screen before a block is run and fades it back in after the +# block exits. z indicates the z-coordinate of the viewport used for this effect +def pbFadeOutIn(z=99999,nofadeout=false) + col=Color.new(0,0,0,0) + viewport=Viewport.new(0,0,Graphics.width,Graphics.height) + viewport.z=z + numFrames = (Graphics.frame_rate*0.4).floor + alphaDiff = (255.0/numFrames).ceil + for j in 0..numFrames + col.set(0,0,0,j*alphaDiff) + viewport.color=col + Graphics.update + Input.update + end + pbPushFade + begin + yield if block_given? + ensure + pbPopFade + if !nofadeout + for j in 0..numFrames + col.set(0,0,0,(numFrames-j)*alphaDiff) + viewport.color=col + Graphics.update + Input.update + end + end + viewport.dispose + end +end + +def pbFadeOutInWithUpdate(z,sprites,nofadeout=false) + col=Color.new(0,0,0,0) + viewport=Viewport.new(0,0,Graphics.width,Graphics.height) + viewport.z=z + numFrames = (Graphics.frame_rate*0.4).floor + alphaDiff = (255.0/numFrames).ceil + for j in 0..numFrames + col.set(0,0,0,j*alphaDiff) + viewport.color=col + pbUpdateSpriteHash(sprites) + Graphics.update + Input.update + end + pbPushFade + begin + yield if block_given? + ensure + pbPopFade + if !nofadeout + for j in 0..numFrames + col.set(0,0,0,(numFrames-j)*alphaDiff) + viewport.color=col + pbUpdateSpriteHash(sprites) + Graphics.update + Input.update + end + end + viewport.dispose + end +end + +def pbFadeOutAndHide(sprites) + visiblesprites = {} + numFrames = (Graphics.frame_rate*0.4).floor + alphaDiff = (255.0/numFrames).ceil + pbDeactivateWindows(sprites) { + for j in 0..numFrames + pbSetSpritesToColor(sprites,Color.new(0,0,0,j*alphaDiff)) + (block_given?) ? yield : pbUpdateSpriteHash(sprites) + end + } + for i in sprites + next if !i[1] + next if pbDisposed?(i[1]) + visiblesprites[i[0]] = true if i[1].visible + i[1].visible = false + end + return visiblesprites +end + +def pbFadeInAndShow(sprites,visiblesprites=nil) + if visiblesprites + for i in visiblesprites + if i[1] && sprites[i[0]] && !pbDisposed?(sprites[i[0]]) + sprites[i[0]].visible = true + end + end + end + numFrames = (Graphics.frame_rate*0.4).floor + alphaDiff = (255.0/numFrames).ceil + pbDeactivateWindows(sprites) { + for j in 0..numFrames + pbSetSpritesToColor(sprites,Color.new(0,0,0,((numFrames-j)*alphaDiff))) + (block_given?) ? yield : pbUpdateSpriteHash(sprites) + end + } +end + +# Restores which windows are active for the given sprite hash. +# _activeStatuses_ is the result of a previous call to pbActivateWindows +def pbRestoreActivations(sprites,activeStatuses) + return if !sprites || !activeStatuses + for k in activeStatuses.keys + if sprites[k] && sprites[k].is_a?(Window) && !pbDisposed?(sprites[k]) + sprites[k].active=activeStatuses[k] ? true : false + end + end +end + +# Deactivates all windows. If a code block is given, deactivates all windows, +# runs the code in the block, and reactivates them. +def pbDeactivateWindows(sprites) + if block_given? + pbActivateWindow(sprites,nil) { yield } + else + pbActivateWindow(sprites,nil) + end +end + +# Activates a specific window of a sprite hash. _key_ is the key of the window +# in the sprite hash. If a code block is given, deactivates all windows except +# the specified window, runs the code in the block, and reactivates them. +def pbActivateWindow(sprites,key) + return if !sprites + activeStatuses={} + for i in sprites + if i[1] && i[1].is_a?(Window) && !pbDisposed?(i[1]) + activeStatuses[i[0]]=i[1].active + i[1].active=(i[0]==key) + end + end + if block_given? + begin + yield + ensure + pbRestoreActivations(sprites,activeStatuses) + end + return {} + else + return activeStatuses + end +end + +#=============================================================================== +# Create background planes for a sprite hash +#=============================================================================== +# Adds a background to the sprite hash. +# _planename_ is the hash key of the background. +# _background_ is a filename within the Graphics/Pictures/ folder and can be +# an animated image. +# _viewport_ is a viewport to place the background in. +def addBackgroundPlane(sprites,planename,background,viewport=nil) + sprites[planename]=AnimatedPlane.new(viewport) + bitmapName=pbResolveBitmap("Graphics/Pictures/#{background}") + if bitmapName==nil + # Plane should exist in any case + sprites[planename].bitmap=nil + sprites[planename].visible=false + else + sprites[planename].setBitmap(bitmapName) + for spr in sprites.values + if spr.is_a?(Window) + spr.windowskin=nil + end + end + end +end + +# Adds a background to the sprite hash. +# _planename_ is the hash key of the background. +# _background_ is a filename within the Graphics/Pictures/ folder and can be +# an animated image. +# _color_ is the color to use if the background can't be found. +# _viewport_ is a viewport to place the background in. +def addBackgroundOrColoredPlane(sprites,planename,background,color,viewport=nil) + bitmapName=pbResolveBitmap("Graphics/Pictures/#{background}") + if bitmapName==nil + # Plane should exist in any case + sprites[planename]=ColoredPlane.new(color,@viewport) + else + sprites[planename]=AnimatedPlane.new(viewport) + sprites[planename].setBitmap(bitmapName) + for spr in sprites.values + if spr.is_a?(Window) + spr.windowskin=nil + end + end + end +end + + + +#=============================================================================== +# Ensure required method definitions +#=============================================================================== +module Graphics + if !self.respond_to?("width") + def self.width; return 640; end + end + if !self.respond_to?("height") + def self.height; return 480; end + end +end + + + +if !defined?(_INTL) + def _INTL(*args) + string=args[0].clone + for i in 1...args.length + string.gsub!(/\{#{i}\}/,"#{args[i]}") + end + return string + end +end + +if !defined?(_ISPRINTF) + def _ISPRINTF(*args) + string=args[0].clone + for i in 1...args.length + string.gsub!(/\{#{i}\:([^\}]+?)\}/) { |m| + next sprintf("%"+$1,args[i]) + } + end + return string + end +end + +if !defined?(_MAPINTL) + def _MAPINTL(*args) + string=args[1].clone + for i in 2...args.length + string.gsub!(/\{#{i}\}/,"#{args[i+1]}") + end + return string + end +end \ No newline at end of file diff --git a/Data/Scripts/008_Objects and windows/005_SpriteWindow_text.rb b/Data/Scripts/008_Objects and windows/005_SpriteWindow_text.rb new file mode 100644 index 000000000..67181a107 --- /dev/null +++ b/Data/Scripts/008_Objects and windows/005_SpriteWindow_text.rb @@ -0,0 +1,2369 @@ +#=============================================================================== +# +#=============================================================================== +class SpriteWindowCursorRect < Rect + def initialize(window) + @window=window + @x=0 + @y=0 + @width=0 + @height=0 + end + + attr_reader :x,:y,:width,:height + + def empty + needupdate=@x!=0 || @y!=0 || @width!=0 || @height!=0 + if needupdate + @x=0 + @y=0 + @width=0 + @height=0 + @window.width=@window.width + end + end + + def isEmpty? + return @x==0 && @y==0 && @width==0 && @height==0 + end + + def set(x,y,width,height) + needupdate=@x!=x || @y!=y || @width!=width || @height!=height + if needupdate + @x=x + @y=y + @width=width + @height=height + @window.width=@window.width + end + end + + def height=(value) + @height=value; @window.width=@window.width + end + + def width=(value) + @width=value; @window.width=@window.width + end + + def x=(value) + @x=value; @window.width=@window.width + end + + def y=(value) + @y=value; @window.width=@window.width + end +end + + + +#=============================================================================== +# SpriteWindow is a class based on Window which emulates Window's functionality. +# This class is necessary in order to change the viewport of windows (with +# viewport=) and to make windows fade in and out (with tone=). +#=============================================================================== +class SpriteWindow < Window + attr_reader :tone + attr_reader :color + attr_reader :viewport + attr_reader :contents + attr_reader :ox + attr_reader :oy + attr_reader :x + attr_reader :y + attr_reader :z + attr_reader :zoom_x + attr_reader :zoom_y + attr_reader :offset_x + attr_reader :offset_y + attr_reader :width + attr_reader :active + attr_reader :pause + attr_reader :height + attr_reader :opacity + attr_reader :back_opacity + attr_reader :contents_opacity + attr_reader :visible + attr_reader :cursor_rect + attr_reader :contents_blend_type + attr_reader :blend_type + attr_reader :openness + + def windowskin + @_windowskin + end + + # Flags used to preserve compatibility + # with RGSS/RGSS2's version of Window + module CompatBits + CorrectZ = 1 + ExpandBack = 2 + ShowScrollArrows = 4 + StretchSides = 8 + ShowPause = 16 + ShowCursor = 32 + end + + attr_reader :compat + + def compat=(value) + @compat=value + privRefresh(true) + end + + def initialize(viewport=nil) + @sprites={} + @spritekeys=[ + "back", + "corner0","side0","scroll0", + "corner1","side1","scroll1", + "corner2","side2","scroll2", + "corner3","side3","scroll3", + "cursor","contents","pause" + ] + @viewport=viewport + @sidebitmaps=[nil,nil,nil,nil] + @cursorbitmap=nil + @bgbitmap=nil + for i in @spritekeys + @sprites[i]=Sprite.new(@viewport) + end + @disposed=false + @tone=Tone.new(0,0,0) + @color=Color.new(0,0,0,0) + @blankcontents=Bitmap.new(1,1) # RGSS2 requires this + @contents=@blankcontents + @_windowskin=nil + @rpgvx=false + @compat=CompatBits::ExpandBack|CompatBits::StretchSides + @x=0 + @y=0 + @width=0 + @height=0 + @offset_x=0 + @offset_y=0 + @zoom_x=1.0 + @zoom_y=1.0 + @ox=0 + @oy=0 + @z=0 + @stretch=true + @visible=true + @active=true + @openness=255 + @opacity=255 + @back_opacity=255 + @blend_type=0 + @contents_blend_type=0 + @contents_opacity=255 + @cursor_rect=SpriteWindowCursorRect.new(self) + @cursorblink=0 + @cursoropacity=255 + @pause=false + @pauseframe=0 + @flash=0 + @pauseopacity=0 + @skinformat=0 + @skinrect=Rect.new(0,0,0,0) + @trim=[16,16,16,16] + privRefresh(true) + end + + def dispose + if !self.disposed? + for i in @sprites + i[1].dispose if i[1] + @sprites[i[0]]=nil + end + for i in 0...@sidebitmaps.length + @sidebitmaps[i].dispose if @sidebitmaps[i] + @sidebitmaps[i]=nil + end + @blankcontents.dispose + @cursorbitmap.dispose if @cursorbitmap + @backbitmap.dispose if @backbitmap + @sprites.clear + @sidebitmaps.clear + @_windowskin=nil + @disposed=true + end + end + + def stretch=(value) + @stretch=value + privRefresh(true) + end + + def visible=(value) + @visible=value + privRefresh + end + + def viewport=(value) + @viewport=value + for i in @spritekeys + @sprites[i].dispose if @sprites[i] + end + for i in @spritekeys + if @sprites[i].is_a?(Sprite) + @sprites[i]=Sprite.new(@viewport) + else + @sprites[i]=nil + end + end + privRefresh(true) + end + + def z=(value) + @z=value + privRefresh + end + + def disposed? + return @disposed + end + + def contents=(value) + if @contents!=value + @contents=value + privRefresh if @visible + end + end + + def ox=(value) + if @ox!=value + @ox=value + privRefresh if @visible + end + end + + def oy=(value) + if @oy!=value + @oy=value + privRefresh if @visible + end + end + + def active=(value) + @active=value + privRefresh(true) + end + + def cursor_rect=(value) + if !value + @cursor_rect.empty + else + @cursor_rect.set(value.x,value.y,value.width,value.height) + end + end + + def openness=(value) + @openness=value + @openness=0 if @openness<0 + @openness=255 if @openness>255 + privRefresh + end + + def width=(value) + @width=value + privRefresh(true) + end + + def height=(value) + @height=value + privRefresh(true) + end + + def pause=(value) + @pause=value + @pauseopacity=0 if !value + privRefresh if @visible + end + + def x=(value) + @x=value + privRefresh if @visible + end + + def y=(value) + @y=value + privRefresh if @visible + end + + def zoom_x=(value) + @zoom_x=value + privRefresh if @visible + end + + def zoom_y=(value) + @zoom_y=value + privRefresh if @visible + end + + def offset_x=(value) + @x=value + privRefresh if @visible + end + + def offset_y=(value) + @y=value + privRefresh if @visible + end + + def opacity=(value) + @opacity=value + @opacity=0 if @opacity<0 + @opacity=255 if @opacity>255 + privRefresh if @visible + end + + def back_opacity=(value) + @back_opacity=value + @back_opacity=0 if @back_opacity<0 + @back_opacity=255 if @back_opacity>255 + privRefresh if @visible + end + + def contents_opacity=(value) + @contents_opacity=value + @contents_opacity=0 if @contents_opacity<0 + @contents_opacity=255 if @contents_opacity>255 + privRefresh if @visible + end + + def tone=(value) + @tone=value + privRefresh if @visible + end + + def color=(value) + @color=value + privRefresh if @visible + end + + def blend_type=(value) + @blend_type=value + privRefresh if @visible + end + + def flash(color,duration) + return if disposed? + @flash=duration+1 + for i in @sprites + i[1].flash(color,duration) + end + end + + def update + return if disposed? + mustchange=false + if @active + if @cursorblink==0 + @cursoropacity-=8 + @cursorblink=1 if @cursoropacity<=128 + else + @cursoropacity+=8 + @cursorblink=0 if @cursoropacity>=255 + end + privRefreshCursor + else + @cursoropacity=128 + privRefreshCursor + end + if @pause + oldpauseframe=@pauseframe + oldpauseopacity=@pauseopacity + @pauseframe=(Graphics.frame_count / 8) % 4 + @pauseopacity=[@pauseopacity+64,255].min + mustchange=@pauseframe!=oldpauseframe || @pauseopacity!=oldpauseopacity + end + privRefresh if mustchange + if @flash>0 + for i in @sprites.values + i.update + end + @flash-=1 + end + end + + ############# + attr_reader :skinformat + attr_reader :skinrect + + def loadSkinFile(file) + if (self.windowskin.width==80 || self.windowskin.width==96) && + self.windowskin.height==48 + # Body = X, Y, width, height of body rectangle within windowskin + @skinrect.set(32,16,16,16) + # Trim = X, Y, width, height of trim rectangle within windowskin + @trim=[32,16,16,16] + elsif self.windowskin.width==80 && self.windowskin.height==80 + @skinrect.set(32,32,16,16) + @trim=[32,16,16,48] + end + end + + def windowskin=(value) + oldSkinWidth=(@_windowskin && !@_windowskin.disposed?) ? @_windowskin.width : -1 + oldSkinHeight=(@_windowskin && !@_windowskin.disposed?) ? @_windowskin.height : -1 + @_windowskin=value + if @skinformat==1 + @rpgvx=false + if @_windowskin && !@_windowskin.disposed? + if @_windowskin.width!=oldSkinWidth || @_windowskin.height!=oldSkinHeight + # Update skinrect and trim if windowskin's dimensions have changed + @skinrect.set((@_windowskin.width-16)/2,(@_windowskin.height-16)/2,16,16) + @trim=[@skinrect.x,@skinrect.y,@skinrect.x,@skinrect.y] + end + else + @skinrect.set(16,16,16,16) + @trim=[16,16,16,16] + end + else + if value && value.is_a?(Bitmap) && !value.disposed? && value.width==128 + @rpgvx=true + else + @rpgvx=false + end + @trim=[16,16,16,16] + end + privRefresh(true) + end + + def skinrect=(value) + @skinrect=value + privRefresh + end + + def skinformat=(value) + if @skinformat!=value + @skinformat=value + privRefresh(true) + end + end + + def borderX + return 32 if !@trim || skinformat==0 + if @_windowskin && !@_windowskin.disposed? + return @trim[0]+(@_windowskin.width-@trim[2]-@trim[0]) + end + return 32 + end + + def borderY + return 32 if !@trim || skinformat==0 + if @_windowskin && !@_windowskin.disposed? + return @trim[1]+(@_windowskin.height-@trim[3]-@trim[1]) + end + return 32 + end + + def leftEdge; self.startX; end + def topEdge; self.startY; end + def rightEdge; self.borderX-self.leftEdge; end + def bottomEdge; self.borderY-self.topEdge; end + + def startX + return !@trim || skinformat==0 ? 16 : @trim[0] + end + + def startY + return !@trim || skinformat==0 ? 16 : @trim[1] + end + + def endX + return !@trim || skinformat==0 ? 16 : @trim[2] + end + + def endY + return !@trim || skinformat==0 ? 16 : @trim[3] + end + + def startX=(value) + @trim[0]=value + privRefresh + end + + def startY=(value) + @trim[1]=value + privRefresh + end + + def endX=(value) + @trim[2]=value + privRefresh + end + + def endY=(value) + @trim[3]=value + privRefresh + end + + ############# + private + + def ensureBitmap(bitmap,dwidth,dheight) + if !bitmap||bitmap.disposed?||bitmap.width0 && @skinformat==0 && !@rpgvx + # Compatibility Mode: Cursor, pause, and contents have higher Z + @sprites["cursor"].z=@z+1 + @sprites["contents"].z=@z+2 + @sprites["pause"].z=@z+2 + end + if @skinformat==0 + startX=16 + startY=16 + endX=16 + endY=16 + trimStartX=16 + trimStartY=16 + trimWidth=32 + trimHeight=32 + if @rpgvx + trimX=64 + trimY=0 + backRect=Rect.new(0,0,64,64) + blindsRect=Rect.new(0,64,64,64) + else + trimX=128 + trimY=0 + backRect=Rect.new(0,0,128,128) + blindsRect=nil + end + if @_windowskin && !@_windowskin.disposed? + @sprites["corner0"].src_rect.set(trimX,trimY+0,16,16); + @sprites["corner1"].src_rect.set(trimX+48,trimY+0,16,16); + @sprites["corner2"].src_rect.set(trimX,trimY+48,16,16); + @sprites["corner3"].src_rect.set(trimX+48,trimY+48,16,16); + @sprites["scroll0"].src_rect.set(trimX+24, trimY+16, 16, 8) # up + @sprites["scroll3"].src_rect.set(trimX+24, trimY+40, 16, 8) # down + @sprites["scroll1"].src_rect.set(trimX+16, trimY+24, 8, 16) # left + @sprites["scroll2"].src_rect.set(trimX+40, trimY+24, 8, 16) # right + cursorX=trimX + cursorY=trimY+64 + sideRects=[ + Rect.new(trimX+16,trimY+0,32,16), + Rect.new(trimX,trimY+16,16,32), + Rect.new(trimX+48,trimY+16,16,32), + Rect.new(trimX+16,trimY+48,32,16) + ] + pauseRects=[ + trimX+32,trimY+64, + trimX+48,trimY+64, + trimX+32,trimY+80, + trimX+48,trimY+80, + ] + pauseWidth=16 + pauseHeight=16 + @sprites["pause"].src_rect.set( + pauseRects[@pauseframe*2], + pauseRects[@pauseframe*2+1], + pauseWidth,pauseHeight + ) + end + else + trimStartX=@trim[0] + trimStartY=@trim[1] + trimWidth=@trim[0]+(@skinrect.width-@trim[2]+@trim[0]) + trimHeight=@trim[1]+(@skinrect.height-@trim[3]+@trim[1]) + if @_windowskin && !@_windowskin.disposed? + # width of left end of window + startX=@skinrect.x + # width of top end of window + startY=@skinrect.y + backWidth=@skinrect.width + backHeight=@skinrect.height + cx=@skinrect.x+@skinrect.width # right side of BODY rect + cy=@skinrect.y+@skinrect.height # bottom side of BODY rect + # width of right end of window + endX=(!@_windowskin || @_windowskin.disposed?) ? @skinrect.x : @_windowskin.width-cx + # height of bottom end of window + endY=(!@_windowskin || @_windowskin.disposed?) ? @skinrect.y : @_windowskin.height-cy + @sprites["corner0"].src_rect.set(0,0,startX,startY); + @sprites["corner1"].src_rect.set(cx,0,endX,startY); + @sprites["corner2"].src_rect.set(0,cy,startX,endY); + @sprites["corner3"].src_rect.set(cx,cy,endX,endY); + backRect=Rect.new(@skinrect.x,@skinrect.y, + @skinrect.width,@skinrect.height); + blindsRect=nil + sideRects=[ + Rect.new(startX,0,@skinrect.width,startY), # side0 (top) + Rect.new(0,startY,startX,@skinrect.height), # side1 (left) + Rect.new(cx,startY,endX,@skinrect.height), # side2 (right) + Rect.new(startX,cy,@skinrect.width,endY) # side3 (bottom) + ] + end + end + if @width>trimWidth && @height>trimHeight + @sprites["contents"].src_rect.set(@ox,@oy,@width-trimWidth,@height-trimHeight) + else + @sprites["contents"].src_rect.set(0,0,0,0) + end + @sprites["contents"].x=@x+trimStartX + @sprites["contents"].y=@y+trimStartY + if (@compat & CompatBits::ShowScrollArrows)>0 && @skinformat==0 + # Compatibility mode: Make scroll arrows visible + if @skinformat==0 && @_windowskin && !@_windowskin.disposed? && + @contents && !@contents.disposed? + @sprites["scroll0"].visible = @visible && hascontents && @oy > 0 + @sprites["scroll1"].visible = @visible && hascontents && @ox > 0 + @sprites["scroll2"].visible = @visible && (@contents.width - @ox) > @width-trimWidth + @sprites["scroll3"].visible = @visible && (@contents.height - @oy) > @height-trimHeight + end + end + if @_windowskin && !@_windowskin.disposed? + backTrimX=startX+endX + backTrimY=startX+endX + borderX=startX+endX + borderY=startY+endY + @sprites["corner0"].x=@x + @sprites["corner0"].y=@y + @sprites["corner1"].x=@x+@width-endX + @sprites["corner1"].y=@y + @sprites["corner2"].x=@x + @sprites["corner2"].y=@y+@height-endY + @sprites["corner3"].x=@x+@width-endX + @sprites["corner3"].y=@y+@height-endY + @sprites["side0"].x=@x+startX + @sprites["side0"].y=@y + @sprites["side1"].x=@x + @sprites["side1"].y=@y+startY + @sprites["side2"].x=@x+@width-endX + @sprites["side2"].y=@y+startY + @sprites["side3"].x=@x+startX + @sprites["side3"].y=@y+@height-endY + @sprites["scroll0"].x = @x+@width / 2 - 8 + @sprites["scroll0"].y = @y+8 + @sprites["scroll1"].x = @x+8 + @sprites["scroll1"].y = @y+@height / 2 - 8 + @sprites["scroll2"].x = @x+@width - 16 + @sprites["scroll2"].y = @y+@height / 2 - 8 + @sprites["scroll3"].x = @x+@width / 2 - 8 + @sprites["scroll3"].y = @y+@height - 16 + @sprites["cursor"].x=@x+startX+@cursor_rect.x + @sprites["cursor"].y=@y+startY+@cursor_rect.y + if (@compat & CompatBits::ExpandBack)>0 && @skinformat==0 + # Compatibility mode: Expand background + @sprites["back"].x=@x+2 + @sprites["back"].y=@y+2 + else + @sprites["back"].x=@x+startX + @sprites["back"].y=@y+startY + end + end + if changeBitmap && @_windowskin && !@_windowskin.disposed? + if @skinformat==0 + @sprites["cursor"].x=@x+startX+@cursor_rect.x + @sprites["cursor"].y=@y+startY+@cursor_rect.y + width=@cursor_rect.width + height=@cursor_rect.height + if width > 0 && height > 0 + cursorrects=[ + # sides + Rect.new(cursorX+2, cursorY+0, 28, 2), + Rect.new(cursorX+0, cursorY+2, 2, 28), + Rect.new(cursorX+30, cursorY+2, 2, 28), + Rect.new(cursorX+2, cursorY+30, 28, 2), + # corners + Rect.new(cursorX+0, cursorY+0, 2, 2), + Rect.new(cursorX+30, cursorY+0, 2, 2), + Rect.new(cursorX+0, cursorY+30, 2, 2), + Rect.new(cursorX+30, cursorY+30, 2, 2), + # back + Rect.new(cursorX+2, cursorY+2, 28, 28) + ] + margin=2 + fullmargin=4 + @cursorbitmap = ensureBitmap(@cursorbitmap, width, height) + @cursorbitmap.clear + @sprites["cursor"].bitmap=@cursorbitmap + @sprites["cursor"].src_rect.set(0,0,width,height) + rect = Rect.new(margin,margin,width - fullmargin, height - fullmargin) + @cursorbitmap.stretch_blt(rect, @_windowskin, cursorrects[8]) + @cursorbitmap.blt(0, 0, @_windowskin, cursorrects[4])# top left + @cursorbitmap.blt(width-margin, 0, @_windowskin, cursorrects[5]) # top right + @cursorbitmap.blt(0, height-margin, @_windowskin, cursorrects[6]) # bottom right + @cursorbitmap.blt(width-margin, height-margin, @_windowskin, cursorrects[7]) # bottom left + rect = Rect.new(margin, 0,width - fullmargin, margin) + @cursorbitmap.stretch_blt(rect, @_windowskin, cursorrects[0]) + rect = Rect.new(0, margin,margin, height - fullmargin) + @cursorbitmap.stretch_blt(rect, @_windowskin, cursorrects[1]) + rect = Rect.new(width - margin, margin, margin, height - fullmargin) + @cursorbitmap.stretch_blt(rect, @_windowskin, cursorrects[2]) + rect = Rect.new(margin, height-margin, width - fullmargin, margin) + @cursorbitmap.stretch_blt(rect, @_windowskin, cursorrects[3]) + else + @sprites["cursor"].visible=false + @sprites["cursor"].src_rect.set(0,0,0,0) + end + end + for i in 0..3 + case i + when 0 + dwidth = @width-startX-endX + dheight = startY + when 1 + dwidth = startX + dheight = @height-startY-endY + when 2 + dwidth = endX + dheight = @height-startY-endY + when 3 + dwidth = @width-startX-endX + dheight = endY + end + @sidebitmaps[i]=ensureBitmap(@sidebitmaps[i],dwidth,dheight) + @sprites["side#{i}"].bitmap=@sidebitmaps[i] + @sprites["side#{i}"].src_rect.set(0,0,dwidth,dheight) + @sidebitmaps[i].clear + if sideRects[i].width>0 && sideRects[i].height>0 + if (@compat & CompatBits::StretchSides)>0 && @skinformat==0 + # Compatibility mode: Stretch sides + @sidebitmaps[i].stretch_blt(@sprites["side#{i}"].src_rect, + @_windowskin,sideRects[i]) + else + tileBitmap(@sidebitmaps[i],@sprites["side#{i}"].src_rect, + @_windowskin,sideRects[i]) + end + end + end + if (@compat & CompatBits::ExpandBack)>0 && @skinformat==0 + # Compatibility mode: Expand background + backwidth=@width-4 + backheight=@height-4 + else + backwidth=@width-borderX + backheight=@height-borderY + end + if backwidth>0 && backheight>0 + @backbitmap=ensureBitmap(@backbitmap,backwidth,backheight) + @sprites["back"].bitmap=@backbitmap + @sprites["back"].src_rect.set(0,0,backwidth,backheight) + @backbitmap.clear + if @stretch + @backbitmap.stretch_blt(@sprites["back"].src_rect,@_windowskin,backRect) + else + tileBitmap(@backbitmap,@sprites["back"].src_rect,@_windowskin,backRect) + end + if blindsRect + tileBitmap(@backbitmap,@sprites["back"].src_rect,@_windowskin,blindsRect) + end + else + @sprites["back"].visible=false + @sprites["back"].src_rect.set(0,0,0,0) + end + end + if @openness!=255 + opn=@openness/255.0 + for k in @spritekeys + sprite=@sprites[k] + ratio=(@height<=0) ? 0 : (sprite.y-@y)*1.0/@height + sprite.zoom_y=opn + sprite.zoom_x=1.0 + sprite.oy=0 + sprite.y=(@y+(@height/2.0)+(@height*ratio*opn)-(@height/2*opn)).floor + oldbitmap=sprite.bitmap + oldsrcrect=sprite.src_rect.clone + end + else + for k in @spritekeys + sprite=@sprites[k] + sprite.zoom_x=1.0 + sprite.zoom_y=1.0 + end + end + i=0 + # Ensure Z order + for k in @spritekeys + sprite=@sprites[k] + y=sprite.y + sprite.y=i + sprite.oy=(sprite.zoom_y<=0) ? 0 : (i-y)/sprite.zoom_y + sprite.zoom_x*=@zoom_x + sprite.zoom_y*=@zoom_y + sprite.x*=@zoom_x + sprite.y*=@zoom_y + sprite.x+=(@offset_x/sprite.zoom_x) + sprite.y+=(@offset_y/sprite.zoom_y) + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +class SpriteWindow_Base < SpriteWindow + TEXTPADDING=4 # In pixels + + def initialize(x, y, width, height) + super() + self.x = x + self.y = y + self.width = width + self.height = height + self.z = 100 + @curframe=MessageConfig.pbGetSystemFrame() + @curfont=MessageConfig.pbGetSystemFontName() + @sysframe=AnimatedBitmap.new(@curframe) + @customskin=nil + __setWindowskin(@sysframe.bitmap) + __resolveSystemFrame() + pbSetSystemFont(self.contents) if self.contents + end + + def __setWindowskin(skin) + if skin && (skin.width==192 && skin.height==128) || # RPGXP Windowskin + (skin.width==128 && skin.height==128) # RPGVX Windowskin + self.skinformat=0 + else + self.skinformat=1 + end + self.windowskin=skin + end + + def __resolveSystemFrame + if self.skinformat==1 + if !@resolvedFrame + @resolvedFrame=MessageConfig.pbGetSystemFrame() + @resolvedFrame.sub!(/\.[^\.\/\\]+$/,"") + end + self.loadSkinFile("#{@resolvedFrame}.txt") if @resolvedFrame!="" + end + end + + def setSkin(skin) # Filename of windowskin to apply. Supports XP, VX, and animated skins. + @customskin.dispose if @customskin + @customskin=nil + resolvedName=pbResolveBitmap(skin) + return if !resolvedName || resolvedName=="" + @customskin=AnimatedBitmap.new(resolvedName) + __setWindowskin(@customskin.bitmap) + if self.skinformat==1 + skinbase=resolvedName.sub(/\.[^\.\/\\]+$/,"") + self.loadSkinFile("#{skinbase}.txt") + end + end + + def setSystemFrame + @customskin.dispose if @customskin + @customskin=nil + __setWindowskin(@sysframe.bitmap) + __resolveSystemFrame() + end + + def update + super + if self.windowskin + if @customskin + if @customskin.totalFrames>1 + @customskin.update + __setWindowskin(@customskin.bitmap) + end + elsif @sysframe + if @sysframe.totalFrames>1 + @sysframe.update + __setWindowskin(@sysframe.bitmap) + end + end + end + if @curframe!=MessageConfig.pbGetSystemFrame() + @curframe=MessageConfig.pbGetSystemFrame() + if @sysframe && !@customskin + @sysframe.dispose if @sysframe + @sysframe=AnimatedBitmap.new(@curframe) + @resolvedFrame=nil + __setWindowskin(@sysframe.bitmap) + __resolveSystemFrame() + end + begin + refresh + rescue NoMethodError + end + end + if @curfont!=MessageConfig.pbGetSystemFontName() + @curfont=MessageConfig.pbGetSystemFontName() + if self.contents && !self.contents.disposed? + pbSetSystemFont(self.contents) + end + begin + refresh + rescue NoMethodError + end + end + end + + def dispose + self.contents.dispose if self.contents + @sysframe.dispose + @customskin.dispose if @customskin + super + end +end + + + +#=============================================================================== +# +#=============================================================================== +# Represents a window with no formatting capabilities. Its text color can be set, +# though, and line breaks are supported, but the text is generally unformatted. +class Window_UnformattedTextPokemon < SpriteWindow_Base + attr_reader :text + attr_reader :baseColor + attr_reader :shadowColor + # Letter-by-letter mode. This mode is not supported in this class. + attr_accessor :letterbyletter + + def text=(value) + @text=value + refresh + end + + def baseColor=(value) + @baseColor=value + refresh + end + + def shadowColor=(value) + @shadowColor=value + refresh + end + + def initialize(text="") + super(0,0,33,33) + self.contents=Bitmap.new(1,1) + pbSetSystemFont(self.contents) + @text=text + @letterbyletter=false # Not supported in this class + colors=getDefaultTextColors(self.windowskin) + @baseColor=colors[0] + @shadowColor=colors[1] + resizeToFit(text) + end + + def self.newWithSize(text,x,y,width,height,viewport=nil) + ret=self.new(text) + ret.x=x + ret.y=y + ret.width=width + ret.height=height + ret.viewport=viewport + ret.refresh + return ret + end + + def resizeToFitInternal(text,maxwidth) # maxwidth is maximum acceptable window width + dims=[0,0] + cwidth=maxwidth<0 ? Graphics.width : maxwidth + getLineBrokenChunks(self.contents,text, + cwidth-self.borderX-SpriteWindow_Base::TEXTPADDING,dims,true) + return dims + end + + def setTextToFit(text,maxwidth=-1) + resizeToFit(text,maxwidth) + self.text=text + end + + def resizeToFit(text,maxwidth=-1) # maxwidth is maximum acceptable window width + dims=resizeToFitInternal(text,maxwidth) + self.width=dims[0]+self.borderX+SpriteWindow_Base::TEXTPADDING + self.height=dims[1]+self.borderY + refresh + end + + def resizeHeightToFit(text,width=-1) # width is current window width + dims=resizeToFitInternal(text,width) + self.width=width<0 ? Graphics.width : width + self.height=dims[1]+self.borderY + refresh + end + + def setSkin(skin) + super(skin) + privRefresh(true) + oldbaser = @baseColor.red + oldbaseg = @baseColor.green + oldbaseb = @baseColor.blue + oldbasea = @baseColor.alpha + oldshadowr = @shadowColor.red + oldshadowg = @shadowColor.green + oldshadowb = @shadowColor.blue + oldshadowa = @shadowColor.alpha + colors = getDefaultTextColors(self.windowskin) + @baseColor = colors[0] + @shadowColor = colors[1] + if oldbaser!=@baseColor.red || oldbaseg!=@baseColor.green || + oldbaseb!=@baseColor.blue || oldbasea!=@baseColor.alpha || + oldshadowr!=@shadowColor.red || oldshadowg!=@shadowColor.green || + oldshadowb!=@shadowColor.blue || oldshadowa!=@shadowColor.alpha + self.text = self.text + end + end + + def refresh + self.contents=pbDoEnsureBitmap(self.contents,self.width-self.borderX, + self.height-self.borderY) + self.contents.clear + drawTextEx(self.contents,0,0,self.contents.width,0, + @text.gsub(/\r/,""),@baseColor,@shadowColor) + end +end + + + +#=============================================================================== +# +#=============================================================================== +class Window_AdvancedTextPokemon < SpriteWindow_Base + attr_reader :text + attr_reader :baseColor + attr_reader :shadowColor + attr_accessor :letterbyletter + attr_reader :lineHeight + attr_reader :waitcount + + def initialize(text="") + @cursorMode = MessageConfig::CURSORMODE + @endOfText = nil + @scrollstate = 0 + @realframes = 0 + @scrollY = 0 + @nodraw = false + @lineHeight = 32 + @linesdrawn = 0 + @bufferbitmap = nil + @letterbyletter = false + @starting = true + @displaying = false + @lastDrawnChar = -1 + @fmtchars = [] + @frameskipChanged = false + @frameskip = MessageConfig.pbGetTextSpeed() + super(0,0,33,33) + @pausesprite = nil + @text = "" + self.contents = Bitmap.new(1,1) + pbSetSystemFont(self.contents) + self.resizeToFit(text,Graphics.width) + colors = getDefaultTextColors(self.windowskin) + @baseColor = colors[0] + @shadowColor = colors[1] + self.text = text + @starting = false + end + + def self.newWithSize(text,x,y,width,height,viewport=nil) + ret = self.new(text) + ret.x = x + ret.y = y + ret.width = width + ret.height = height + ret.viewport = viewport + return ret + end + + def dispose + return if disposed? + @pausesprite.dispose if @pausesprite + @pausesprite = nil + super + end + + def waitcount=(value) + @waitcount = (value<=0) ? 0 : value + end + + attr_reader :cursorMode + + def cursorMode=(value) + @cursorMode = value + moveCursor + end + + def lineHeight(value) + @lineHeight = value + self.text = self.text + end + + def baseColor=(value) + @baseColor = value + refresh + end + + def shadowColor=(value) + @shadowColor = value + refresh + end + + def textspeed + @frameskip + end + + def textspeed=(value) + @frameskipChanged = true if @frameskip!=value + @frameskip = value + end + + def width=(value) + super + self.text = self.text if !@starting + end + + def height=(value) + super + self.text = self.text if !@starting + end + + def resizeToFit(text,maxwidth=-1) + dims = resizeToFitInternal(text,maxwidth) + oldstarting = @starting + @starting = true + self.width = dims[0]+self.borderX+SpriteWindow_Base::TEXTPADDING + self.height = dims[1]+self.borderY + @starting = oldstarting + redrawText + end + + def resizeToFit2(text,maxwidth,maxheight) + dims = resizeToFitInternal(text,maxwidth) + oldstarting = @starting + @starting = true + self.width = [dims[0]+self.borderX+SpriteWindow_Base::TEXTPADDING,maxwidth].min + self.height = [dims[1]+self.borderY,maxheight].min + @starting = oldstarting + redrawText + end + + def resizeToFitInternal(text,maxwidth) + dims = [0,0] + cwidth = (maxwidth<0) ? Graphics.width : maxwidth + chars = getFormattedTextForDims(self.contents,0,0, + cwidth-self.borderX-2-6,-1,text,@lineHeight,true) + for ch in chars + dims[0] = [dims[0],ch[1]+ch[3]].max + dims[1] = [dims[1],ch[2]+ch[4]].max + end + return dims + end + + def resizeHeightToFit(text,width=-1) + dims = resizeToFitInternal(text,width) + oldstarting = @starting + @starting = true + self.width = (width<0) ? Graphics.width : width + self.height = dims[1]+self.borderY + @starting = oldstarting + redrawText + end + + def setSkin(skin,redrawText=true) + super(skin) + privRefresh(true) + oldbaser = @baseColor.red + oldbaseg = @baseColor.green + oldbaseb = @baseColor.blue + oldbasea = @baseColor.alpha + oldshadowr = @shadowColor.red + oldshadowg = @shadowColor.green + oldshadowb = @shadowColor.blue + oldshadowa = @shadowColor.alpha + colors = getDefaultTextColors(self.windowskin) + @baseColor = colors[0] + @shadowColor = colors[1] + if redrawText && + (oldbaser!=@baseColor.red || oldbaseg!=@baseColor.green || + oldbaseb!=@baseColor.blue || oldbasea!=@baseColor.alpha || + oldshadowr!=@shadowColor.red || oldshadowg!=@shadowColor.green || + oldshadowb!=@shadowColor.blue || oldshadowa!=@shadowColor.alpha) + setText(self.text) + end + end + + def setTextToFit(text,maxwidth=-1) + resizeToFit(text,maxwidth) + self.text = text + end + + def text=(value) + setText(value) + end + + def setText(value) + @waitcount = 0 + @curchar = 0 + @drawncurchar = -1 + @lastDrawnChar = -1 + oldtext = @text + @text = value + @textlength = unformattedTextLength(value) + @scrollstate = 0 + @scrollY = 0 + @linesdrawn = 0 + @realframes = 0 + @textchars = [] + width = 1 + height = 1 + numlines = 0 + visiblelines = (self.height-self.borderY)/32 + if value.length==0 + @fmtchars = [] + @bitmapwidth = width + @bitmapheight = height + @numtextchars = 0 + else + if @letterbyletter + @fmtchars = [] + fmt = getFormattedText(self.contents,0,0, + self.width-self.borderX-SpriteWindow_Base::TEXTPADDING,-1, + shadowctag(@baseColor,@shadowColor)+value,32,true) + @oldfont = self.contents.font.clone + for ch in fmt + chx = ch[1]+ch[3] + chy = ch[2]+ch[4] + width = chx if width=visiblelines + fclone = ch.clone + fclone[0] = "\1" + @fmtchars.push(fclone) + @textchars.push("\1") + end + end + # Don't add newline characters, since they + # can slow down letter-by-letter display + if ch[5] || (ch[0]!="\r") + @fmtchars.push(ch) + @textchars.push(ch[5] ? "" : ch[0]) + end + end + fmt.clear + else + @fmtchars = getFormattedText(self.contents,0,0, + self.width-self.borderX-SpriteWindow_Base::TEXTPADDING,-1, + shadowctag(@baseColor,@shadowColor)+value,32,true) + @oldfont = self.contents.font.clone + for ch in @fmtchars + chx = ch[1]+ch[3] + chy = ch[2]+ch[4] + width = chx if width=@fmtchars.length + # index after the last character's index + return @fmtchars[@lastDrawnChar][14]+1 + end + + def maxPosition + pos = 0 + for ch in @fmtchars + # index after the last character's index + pos = ch[14]+1 if pos=@fmtchars.length # End of message + if @textchars[@curchar]=="\1" # Pause message + @pausing = true if @curchar<@numtextchars-1 + self.startPause + refresh + break + end + break if @textchars[@curchar]!="\n" # Skip past newlines only + break if @linesdrawn>=visiblelines-1 # No more empty lines to continue to + @linesdrawn += 1 + end + end + + def allocPause + return if @pausesprite + @pausesprite = AnimatedSprite.create("Graphics/Pictures/pause",4,3) + @pausesprite.z = 100000 + @pausesprite.visible = false + end + + def startPause + allocPause + @pausesprite.visible = true + @pausesprite.frame = 0 + @pausesprite.start + moveCursor + end + + def stopPause + return if !@pausesprite + @pausesprite.stop + @pausesprite.visible = false + end + + def moveCursor + return if !@pausesprite + cursor = @cursorMode + cursor = 2 if cursor==0 && !@endOfText + case cursor + when 0 # End of text + @pausesprite.x = self.x+self.startX+@endOfText.x+@endOfText.width-2 + @pausesprite.y = self.y+self.startY+@endOfText.y-@scrollY + when 1 # Lower right + pauseWidth = @pausesprite.bitmap ? @pausesprite.framewidth : 16 + pauseHeight = @pausesprite.bitmap ? @pausesprite.frameheight : 16 + @pausesprite.x = self.x+self.width-(20*2)+(pauseWidth/2) + @pausesprite.y = self.y+self.height-(30*2)+(pauseHeight/2) + when 2 # Lower middle + pauseWidth = @pausesprite.bitmap ? @pausesprite.framewidth : 16 + pauseHeight = @pausesprite.bitmap ? @pausesprite.frameheight : 16 + @pausesprite.x = self.x+(self.width/2)-(pauseWidth/2) + @pausesprite.y = self.y+self.height-(18*2)+(pauseHeight/2) + end + end + + def refresh + oldcontents = self.contents + self.contents = pbDoEnsureBitmap(oldcontents,@bitmapwidth,@bitmapheight) + self.oy = @scrollY + numchars = @numtextchars + numchars = [@curchar,@numtextchars].min if self.letterbyletter + startchar = 0 + return if busy? && @drawncurchar==@curchar && @scrollstate==0 + if !self.letterbyletter || !oldcontents.equal?(self.contents) + @drawncurchar = -1 + @needclear = true + end + if @needclear + self.contents.font = @oldfont if @oldfont + self.contents.clear + @needclear = false + end + if @nodraw + @nodraw = false + return + end + maxX = self.width-self.borderX + maxY = self.height-self.borderY + for i in @drawncurchar+1..numchars + next if i>=@fmtchars.length + if !self.letterbyletter + next if @fmtchars[i][1]>=maxX + next if @fmtchars[i][2]>=maxY + end + drawSingleFormattedChar(self.contents,@fmtchars[i]) + @lastDrawnChar = i + end + if !self.letterbyletter + # all characters were drawn, reset old font + self.contents.font = @oldfont if @oldfont + end + if numchars>0 && numchars!=@numtextchars + fch = @fmtchars[numchars-1] + if fch + rcdst = Rect.new(fch[1],fch[2],fch[3],fch[4]) + if @textchars[numchars]=="\1" + @endOfText = rcdst + allocPause + moveCursor + else + @endOfText = Rect.new(rcdst.x+rcdst.width,rcdst.y,8,1) + end + end + end + @drawncurchar = @curchar + end + + def redrawText + if @letterbyletter + oldPosition = self.position + self.text = self.text + oldPosition = @numtextchars if oldPosition>@numtextchars + while self.position!=oldPosition + refresh + updateInternal + end + else + self.text = self.text + end + end + + def updateInternal + curcharskip = @frameskip<0 ? @frameskip.abs : 1 + visiblelines = (self.height-self.borderY)/@lineHeight + if @textchars[@curchar]=="\1" + if !@pausing + @realframes += 1 + if @realframes>=@frameskip || @frameskip<0 + curcharSkip(curcharskip) + @realframes = 0 + end + end + elsif @textchars[@curchar]=="\n" + if @linesdrawn>=visiblelines-1 + if @scrollstate<@lineHeight + @scrollstate += [(@lineHeight/4),1].max + @scrollY += [(@lineHeight/4),1].max + end + if @scrollstate>=@lineHeight + @realframes += 1 + if @realframes>=@frameskip || @frameskip<0 + curcharSkip(curcharskip) + @linesdrawn += 1 + @realframes = 0 + @scrollstate = 0 + end + end + else + @realframes += 1 + if @realframes>=@frameskip || @frameskip<0 + curcharSkip(curcharskip) + @linesdrawn += 1 + @realframes = 0 + end + end + elsif @curchar<=@numtextchars + @realframes += 1 + if @realframes>=@frameskip || @frameskip<0 + curcharSkip(curcharskip) + @realframes = 0 + end + if @textchars[@curchar]=="\1" + @pausing = true if @curchar<@numtextchars-1 + self.startPause + refresh + end + else + @displaying = false + @scrollstate = 0 + @scrollY = 0 + @linesdrawn = 0 + end + end + + def update + super + @pausesprite.update if @pausesprite && @pausesprite.visible + if @waitcount>0 + @waitcount -= 1 + return + end + if busy? + refresh if !@frameskipChanged + updateInternal + # following line needed to allow "textspeed=-999" to work seamlessly + refresh if @frameskipChanged + end + @frameskipChanged = false + end + + private + + def curcharSkip(skip) + skip.times do + @curchar += 1 + break if @textchars[@curchar]=="\n" || # newline + @textchars[@curchar]=="\1" || # pause + @textchars[@curchar]=="\2" || # letter-by-letter break + @textchars[@curchar]==nil + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +class Window_InputNumberPokemon < SpriteWindow_Base + attr_reader :number + attr_reader :sign + + def initialize(digits_max) + @digits_max=digits_max + @number=0 + @frame=0 + @sign=false + @negative=false + super(0,0,32,32) + self.width=digits_max*24+8+self.borderX + self.height=32+self.borderY + colors=getDefaultTextColors(self.windowskin) + @baseColor=colors[0] + @shadowColor=colors[1] + @index=digits_max-1 + self.active=true + refresh + end + + def active=(value) + super + refresh + end + + def number + @number*(@sign && @negative ? -1 : 1) + end + + def sign=(value) + @sign=value + self.width=@digits_max*24+8+self.borderX+(@sign ? 24 : 0) + @index=(@digits_max-1)+(@sign ? 1 : 0) + refresh + end + + def number=(value) + value=0 if !value.is_a?(Numeric) + if @sign + @negative=(value<0) + @number = [value.abs, 10 ** @digits_max - 1].min + else + @number = [[value, 0].max, 10 ** @digits_max - 1].min + end + refresh + end + + def refresh + self.contents=pbDoEnsureBitmap(self.contents, + self.width-self.borderX,self.height-self.borderY) + pbSetSystemFont(self.contents) + self.contents.clear + s=sprintf("%0*d",@digits_max,@number.abs) + x=0 + if @sign + textHelper(0,0,@negative ? "-" : "+",0) + end + for i in 0...@digits_max + index=i+(@sign ? 1 : 0) + textHelper(index*24,0,s[i,1],index) + end + end + + def update + super + digits=@digits_max+(@sign ? 1 : 0) + refresh if @frame%15==0 + if self.active + if Input.repeat?(Input::UP) or Input.repeat?(Input::DOWN) + pbPlayCursorSE() + if @index==0 && @sign + @negative=!@negative + else + place = 10 ** (digits - 1 - @index) + n = @number / place % 10 + @number -= n*place + if Input.repeat?(Input::UP) + n = (n + 1) % 10 + elsif Input.repeat?(Input::DOWN) + n = (n + 9) % 10 + end + @number += n*place + end + refresh + elsif Input.repeat?(Input::RIGHT) + if digits >= 2 + pbPlayCursorSE() + @index = (@index + 1) % digits + @frame=0 + refresh + end + elsif Input.repeat?(Input::LEFT) + if digits >= 2 + pbPlayCursorSE() + @index = (@index + digits - 1) % digits + @frame=0 + refresh + end + end + end + @frame=(@frame+1)%30 + end + + private + + def textHelper(x,y,text,i) + textwidth=self.contents.text_size(text).width + self.contents.font.color=@shadowColor + pbDrawShadow(self.contents,x+(12-textwidth/2),y, textwidth+4, 32, text) + self.contents.font.color=@baseColor + self.contents.draw_text(x+(12-textwidth/2),y, textwidth+4, 32, text) + if @index==i && @active && @frame/15==0 + colors=getDefaultTextColors(self.windowskin) + self.contents.fill_rect(x+(12-textwidth/2),y+30,textwidth,2,colors[0]) + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +class SpriteWindow_Selectable < SpriteWindow_Base + attr_reader :index + + def initialize(x, y, width, height) + super(x, y, width, height) + @item_max = 1 + @column_max = 1 + @virtualOy=0 + @index = -1 + @row_height = 32 + @column_spacing = 32 + @ignore_input = false + end + + def itemCount + return @item_max || 0 + end + + def index=(index) + if @index!=index + @index = index + priv_update_cursor_rect(true) + end + end + + def rowHeight + return @row_height || 32 + end + + def rowHeight=(value) + if @row_height!=value + oldTopRow=self.top_row + @row_height=[1,value].max + self.top_row=oldTopRow + update_cursor_rect + end + end + + def columns + return @column_max || 1 + end + + def columns=(value) + if @column_max!=value + @column_max=[1,value].max + update_cursor_rect + end + end + + def columnSpacing + return @column_spacing || 32 + end + + def columnSpacing=(value) + if @column_spacing!=value + @column_spacing=[0,value].max + update_cursor_rect + end + end + + def ignore_input=(value) + @ignore_input=value + end + + def count + return @item_max + end + + def row_max + return ((@item_max + @column_max - 1) / @column_max).to_i + end + + def top_row + return (@virtualOy / (@row_height || 32)).to_i + end + + def top_row=(row) + row = row_max-1 if row>row_max-1 + row = 0 if row<0 + @virtualOy = row*@row_height + end + + def top_item + return top_row * @column_max + end + + def page_row_max + return priv_page_row_max.to_i + end + + def page_item_max + return priv_page_item_max.to_i + end + + def itemRect(item) + if item<0 || item>=@item_max || itemself.top_item+self.page_item_max + return Rect.new(0,0,0,0) + else + cursor_width = (self.width-self.borderX-(@column_max-1)*@column_spacing) / @column_max + x = item % @column_max * (cursor_width + @column_spacing) + y = item / @column_max * @row_height - @virtualOy + return Rect.new(x, y, cursor_width, @row_height) + end + end + + def refresh; end + + def update_cursor_rect + priv_update_cursor_rect + end + + def update + super + if self.active and @item_max > 0 and @index >= 0 and !@ignore_input + if Input.repeat?(Input::UP) + if @index >= @column_max or + (Input.trigger?(Input::UP) && (@item_max%@column_max)==0) + oldindex = @index + @index = (@index - @column_max + @item_max) % @item_max + if @index!=oldindex + pbPlayCursorSE() + update_cursor_rect + end + end + elsif Input.repeat?(Input::DOWN) + if @index < @item_max - @column_max or + (Input.trigger?(Input::DOWN) && (@item_max%@column_max)==0) + oldindex = @index + @index = (@index + @column_max) % @item_max + if @index!=oldindex + pbPlayCursorSE() + update_cursor_rect + end + end + elsif Input.repeat?(Input::LEFT) + if @column_max >= 2 and @index > 0 + oldindex = @index + @index -= 1 + if @index!=oldindex + pbPlayCursorSE() + update_cursor_rect + end + end + elsif Input.repeat?(Input::RIGHT) + if @column_max >= 2 and @index < @item_max - 1 + oldindex = @index + @index += 1 + if @index!=oldindex + pbPlayCursorSE() + update_cursor_rect + end + end + elsif Input.repeat?(Input::L) + if @index > 0 + oldindex = @index + @index = [self.index-self.page_item_max, 0].max + if @index!=oldindex + pbPlayCursorSE() + self.top_row -= self.page_row_max + update_cursor_rect + end + end + elsif Input.repeat?(Input::R) + if @index < @item_max-1 + oldindex = @index + @index = [self.index+self.page_item_max, @item_max-1].min + if @index!=oldindex + pbPlayCursorSE() + self.top_row += self.page_row_max + update_cursor_rect + end + end + end + end + end + + private + + def priv_page_row_max + return (self.height - self.borderY) / @row_height + end + + def priv_page_item_max + return (self.height - self.borderY) / @row_height * @column_max + end + + def priv_update_cursor_rect(force=false) + if @index < 0 + self.cursor_rect.empty + self.refresh + return + end + dorefresh = false + row = @index / @column_max + # This code makes lists scroll only when the cursor hits the top and bottom + # of the visible list. +# if row < self.top_row +# self.top_row = row +# dorefresh=true +# end +# if row > self.top_row + (self.page_row_max - 1) +# self.top_row = row - (self.page_row_max - 1) +# dorefresh=true +# end +# if oldindex-self.top_item>=((self.page_item_max - 1)/2) +# self.top_row+=1 +# end +# self.top_row = [self.top_row, self.row_max - self.page_row_max].min + # This code makes the cursor stay in the middle of the visible list as much + # as possible. + new_top_row = row - ((self.page_row_max - 1)/2).floor + new_top_row = [[new_top_row, self.row_max - self.page_row_max].min, 0].max + if self.top_row != new_top_row + self.top_row = new_top_row +# dorefresh = true + end + # End of code + cursor_width = (self.width-self.borderX) / @column_max + x = self.index % @column_max * (cursor_width + @column_spacing) + y = self.index / @column_max * @row_height - @virtualOy + self.cursor_rect.set(x, y, cursor_width, @row_height) + self.refresh if dorefresh || force + end +end + + + +#=============================================================================== +# +#=============================================================================== +module UpDownArrowMixin + def initUpDownArrow + @uparrow = AnimatedSprite.create("Graphics/Pictures/uparrow",8,2,self.viewport) + @downarrow = AnimatedSprite.create("Graphics/Pictures/downarrow",8,2,self.viewport) + @uparrow.z = 99998 + @downarrow.z = 99998 + @uparrow.visible = false + @downarrow.visible = false + @uparrow.play + @downarrow.play + end + + def dispose + @uparrow.dispose + @downarrow.dispose + super + end + + def viewport=(value) + super + @uparrow.viewport = self.viewport + @downarrow.viewport = self.viewport + end + + def color=(value) + super + @uparrow.color = value + @downarrow.color = value + end + + def adjustForZoom(sprite) + sprite.zoom_x = self.zoom_x + sprite.zoom_y = self.zoom_y + sprite.x = sprite.x*self.zoom_x + self.offset_x/self.zoom_x + sprite.y = sprite.y*self.zoom_y + self.offset_y/self.zoom_y + end + + def update + super + @uparrow.x = self.x+(self.width/2)-(@uparrow.framewidth/2) + @downarrow.x = self.x+(self.width/2)-(@downarrow.framewidth/2) + @uparrow.y = self.y + @downarrow.y = self.y+self.height-@downarrow.frameheight + @uparrow.visible = self.visible && self.active && (self.top_item!=0 && + @item_max > self.page_item_max) + @downarrow.visible = self.visible && self.active && + (self.top_item+self.page_item_max<@item_max && @item_max > self.page_item_max) + @uparrow.z = self.z+1 + @downarrow.z = self.z+1 + adjustForZoom(@uparrow) + adjustForZoom(@downarrow) + @uparrow.viewport = self.viewport + @downarrow.viewport = self.viewport + @uparrow.update + @downarrow.update + end +end + + + +#=============================================================================== +# +#=============================================================================== +class SpriteWindow_SelectableEx < SpriteWindow_Selectable + include UpDownArrowMixin + + def initialize(*arg) + super(*arg) + initUpDownArrow + end +end + + + +#=============================================================================== +# +#=============================================================================== +class Window_DrawableCommand < SpriteWindow_SelectableEx + attr_reader :baseColor + attr_reader :shadowColor + + def initialize(x,y,width,height,viewport=nil) + super(x,y,width,height) + self.viewport = viewport if viewport + if isDarkWindowskin(self.windowskin) + @selarrow = AnimatedBitmap.new("Graphics/Pictures/selarrow_white") + else + @selarrow = AnimatedBitmap.new("Graphics/Pictures/selarrow") + end + @index = 0 + colors = getDefaultTextColors(self.windowskin) + @baseColor = colors[0] + @shadowColor = colors[1] + refresh + end + + def dispose + @selarrow.dispose + super + end + + def baseColor=(value) + @baseColor = value + refresh + end + + def shadowColor=(value) + @shadowColor = value + refresh + end + + def textWidth(bitmap,text) + return tmpbitmap.text_size(i).width + end + + def getAutoDims(commands,dims,width=nil) + rowMax = ((commands.length + self.columns - 1) / self.columns).to_i + windowheight = (rowMax*self.rowHeight) + windowheight += self.borderY + if !width || width<0 + width=0 + tmpbitmap = BitmapWrapper.new(1,1) + pbSetSystemFont(tmpbitmap) + for i in commands + width = [width,tmpbitmap.text_size(i).width].max + end + # one 16 to allow cursor + width += 16+16+SpriteWindow_Base::TEXTPADDING + tmpbitmap.dispose + end + # Store suggested width and height of window + dims[0] = [self.borderX+1,(width*self.columns)+self.borderX+ + (self.columns-1)*self.columnSpacing].max + dims[1] = [self.borderY+1,windowheight].max + dims[1] = [dims[1],Graphics.height].min + end + + def setSkin(skin) + super(skin) + privRefresh(true) + colors = getDefaultTextColors(self.windowskin) + @baseColor = colors[0] + @shadowColor = colors[1] + end + + def drawCursor(index,rect) + if self.index==index + pbCopyBitmap(self.contents,@selarrow.bitmap,rect.x,rect.y) + end + return Rect.new(rect.x+16,rect.y,rect.width-16,rect.height) + end + + def itemCount # to be implemented by derived classes + return 0 + end + + def drawItem(index,count,rect) # to be implemented by derived classes + end + + def refresh + @item_max = itemCount() + dwidth = self.width-self.borderX + dheight = self.height-self.borderY + self.contents = pbDoEnsureBitmap(self.contents,dwidth,dheight) + self.contents.clear + for i in 0...@item_max + next if iself.top_item+self.page_item_max + drawItem(i,@item_max,itemRect(i)) + end + end + + def update + oldindex = self.index + super + refresh if self.index!=oldindex + end +end + + + +#=============================================================================== +# +#=============================================================================== +class Window_CommandPokemon < Window_DrawableCommand + attr_reader :commands + + def initialize(commands,width=nil) + @starting=true + @commands=[] + dims=[] + super(0,0,32,32) + getAutoDims(commands,dims,width) + self.width=dims[0] + self.height=dims[1] + @commands=commands + self.active=true + colors=getDefaultTextColors(self.windowskin) + self.baseColor=colors[0] + self.shadowColor=colors[1] + refresh + @starting=false + end + + def self.newWithSize(commands,x,y,width,height,viewport=nil) + ret=self.new(commands,width) + ret.x=x + ret.y=y + ret.width=width + ret.height=height + ret.viewport=viewport + return ret + end + + def self.newEmpty(x,y,width,height,viewport=nil) + ret=self.new([],width) + ret.x=x + ret.y=y + ret.width=width + ret.height=height + ret.viewport=viewport + return ret + end + + def index=(value) + super + refresh if !@starting + end + + def commands=(value) + @commands=value + @item_max=commands.length + self.update_cursor_rect + self.refresh + end + + def width=(value) + super + if !@starting + self.index=self.index + self.update_cursor_rect + end + end + + def height=(value) + super + if !@starting + self.index=self.index + self.update_cursor_rect + end + end + + def resizeToFit(commands,width=nil) + dims=[] + getAutoDims(commands,dims,width) + self.width=dims[0] + self.height=dims[1] + end + + def itemCount + return @commands ? @commands.length : 0 + end + + def drawItem(index,count,rect) + pbSetSystemFont(self.contents) if @starting + rect=drawCursor(index,rect) + pbDrawShadowText(self.contents,rect.x,rect.y,rect.width,rect.height, + @commands[index],self.baseColor,self.shadowColor) + end +end + + + +#=============================================================================== +# +#=============================================================================== +class Window_CommandPokemonEx < Window_CommandPokemon +end + + +#=============================================================================== +# +#=============================================================================== +class Window_AdvancedCommandPokemon < Window_DrawableCommand + attr_reader :commands + + def textWidth(bitmap,text) + dims=[nil,0] + chars=getFormattedText(bitmap,0,0, + Graphics.width-self.borderX-SpriteWindow_Base::TEXTPADDING-16, + -1,text,self.rowHeight,true,true) + for ch in chars + dims[0]=dims[0] ? [dims[0],ch[1]].min : ch[1] + dims[1]=[dims[1],ch[1]+ch[3]].max + end + dims[0]=0 if !dims[0] + return dims[1]-dims[0] + end + + def initialize(commands,width=nil) + @starting=true + @commands=[] + dims=[] + super(0,0,32,32) + getAutoDims(commands,dims,width) + self.width=dims[0] + self.height=dims[1] + @commands=commands + self.active=true + colors=getDefaultTextColors(self.windowskin) + self.baseColor=colors[0] + self.shadowColor=colors[1] + refresh + @starting=false + end + + def self.newWithSize(commands,x,y,width,height,viewport=nil) + ret=self.new(commands,width) + ret.x=x + ret.y=y + ret.width=width + ret.height=height + ret.viewport=viewport + return ret + end + + def self.newEmpty(x,y,width,height,viewport=nil) + ret=self.new([],width) + ret.x=x + ret.y=y + ret.width=width + ret.height=height + ret.viewport=viewport + return ret + end + + def index=(value) + super + refresh if !@starting + end + + def commands=(value) + @commands=value + @item_max=commands.length + self.update_cursor_rect + self.refresh + end + + def width=(value) + oldvalue=self.width + super + if !@starting && oldvalue!=value + self.index=self.index + self.update_cursor_rect + end + end + + def height=(value) + oldvalue=self.height + super + if !@starting && oldvalue!=value + self.index=self.index + self.update_cursor_rect + end + end + + def resizeToFit(commands,width=nil) + dims=[] + getAutoDims(commands,dims,width) + self.width=dims[0] + self.height=dims[1] + end + + def itemCount + return @commands ? @commands.length : 0 + end + + def drawItem(index,count,rect) + pbSetSystemFont(self.contents) + rect=drawCursor(index,rect) + if toUnformattedText(@commands[index]).gsub(/\n/,"")==@commands[index] + # Use faster alternative for unformatted text without line breaks + pbDrawShadowText(self.contents,rect.x,rect.y,rect.width,rect.height, + @commands[index],self.baseColor,self.shadowColor) + else + chars=getFormattedText( + self.contents,rect.x,rect.y,rect.width,rect.height, + @commands[index],rect.height,true,true) + drawFormattedChars(self.contents,chars) + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +class Window_AdvancedCommandPokemonEx < Window_AdvancedCommandPokemon +end \ No newline at end of file diff --git a/Data/Scripts/008_Objects and windows/006_SpriteWindow_sprites.rb b/Data/Scripts/008_Objects and windows/006_SpriteWindow_sprites.rb new file mode 100644 index 000000000..56451f7ef --- /dev/null +++ b/Data/Scripts/008_Objects and windows/006_SpriteWindow_sprites.rb @@ -0,0 +1,1085 @@ +module GifLibrary + @@loadlib = Win32API.new("Kernel32.dll","LoadLibrary",'p','') + if safeExists?("gif.dll") + PngDll = @@loadlib.call("gif.dll") + GifToPngFiles = Win32API.new("gif.dll","GifToPngFiles",'pp','l') + GifToPngFilesInMemory = Win32API.new("gif.dll","GifToPngFilesInMemory",'plp','l') + CopyDataString = Win32API.new("gif.dll","CopyDataString",'lpl','l') + FreeDataString = Win32API.new("gif.dll","FreeDataString",'l','') + else + PngDll=nil + end + + def self.getDataFromResult(result) + datasize=CopyDataString.call(result,"",0) + ret=nil + if datasize!=0 + data="0"*datasize + CopyDataString.call(result,data,datasize) + ret=data.unpack("V*") + end + FreeDataString.call(result) + return ret + end +end + + + +class AnimatedBitmap + def initialize(file,hue=0) + if file==nil + raise "Filename is nil (missing graphic)\r\n\r\n"+ + "If you see this error in the Continue/New Game screen, you may be loading another game's save file. "+ + "Check your project's title (\"Game > Change Title...\" in RMXP).\r\n" + end + if file.split(/[\\\/]/)[-1][/^\[\d+(?:,\d+)?]/] # Starts with 1 or more digits in square brackets + @bitmap = PngAnimatedBitmap.new(file,hue) + else + @bitmap = GifBitmap.new(file,hue) + end + end + + def [](index); @bitmap[index]; end + def width; @bitmap.bitmap.width; end + def height; @bitmap.bitmap.height; end + def length; @bitmap.length; end + def each; @bitmap.each { |item| yield item }; end + def bitmap; @bitmap.bitmap; end + def currentIndex; @bitmap.currentIndex; end + def frameDelay; @bitmap.frameDelay; end + def totalFrames; @bitmap.totalFrames; end + def disposed?; @bitmap.disposed?; end + def update; @bitmap.update; end + def dispose; @bitmap.dispose; end + def deanimate; @bitmap.deanimate; end + def copy; @bitmap.copy; end +end + + + +class PngAnimatedBitmap + # Creates an animated bitmap from a PNG file. + def initialize(file,hue=0) + @frames=[] + @currentFrame=0 + @framecount=0 + panorama=BitmapCache.load_bitmap(file,hue) + if file.split(/[\\\/]/)[-1][/^\[(\d+)(?:,(\d+))?]/] # Starts with 1 or more digits in brackets + # File has a frame count + numFrames = $1.to_i + delay = $2.to_i || 10 + raise "Invalid frame count in #{file}" if numFrames<=0 + raise "Invalid frame delay in #{file}" if delay<=0 + if panorama.width % numFrames != 0 + raise "Bitmap's width (#{panorama.width}) is not divisible by frame count: #{file}" + end + @frameDelay = delay + subWidth=panorama.width/numFrames + for i in 0...numFrames + subBitmap=BitmapWrapper.new(subWidth,panorama.height) + subBitmap.blt(0,0,panorama,Rect.new(subWidth*i,0,subWidth,panorama.height)) + @frames.push(subBitmap) + end + panorama.dispose + else + @frames=[panorama] + end + end + + def [](index) + return @frames[index] + end + + def width; self.bitmap.width; end + + def height; self.bitmap.height; end + + def deanimate + for i in 1...@frames.length + @frames[i].dispose + end + @frames=[@frames[0]] + @currentFrame=0 + return @frames[0] + end + + def bitmap + @frames[@currentFrame] + end + + def currentIndex + @currentFrame + end + + def frameDelay(index) + return @frameDelay + end + + def length + @frames.length + end + + def each + @frames.each { |item| yield item} + end + + def totalFrames + @frameDelay*@frames.length + end + + def disposed? + @disposed + end + + def update + return if disposed? + if @frames.length>1 + @framecount+=1 + if @framecount>=@frameDelay + @framecount=0 + @currentFrame+=1 + @currentFrame%=@frames.length + end + end + end + + def dispose + if !@disposed + for i in @frames + i.dispose + end + end + @disposed=true + end + + attr_accessor :frames # internal + + def copy + x=self.clone + x.frames=x.frames.clone + for i in 0...x.frames.length + x.frames[i]=x.frames[i].copy + end + return x + end +end + + + +#internal class +class GifBitmap + # Creates a bitmap from a GIF file with the specified + # optional viewport. Can also load non-animated bitmaps. + def initialize(file,hue=0) + @gifbitmaps=[] + @gifdelays=[] + @totalframes=0 + @framecount=0 + @currentIndex=0 + @disposed=false + bitmap=nil + filestring=nil + filestrName=nil + file="" if !file + file=canonicalize(file) + begin + bitmap=BitmapCache.load_bitmap(file,hue) + rescue + bitmap=nil + end + if !bitmap || (bitmap.width==32 && bitmap.height==32) + if !file || file.length<1 || file[file.length-1]!=0x2F + if (filestring=pbGetFileChar(file)) + filestrName=file + elsif (filestring=pbGetFileChar(file+".gif")) + filestrName=file+".gif" + elsif (filestring=pbGetFileChar(file+".png")) + filestrName=file+".png" + elsif (filestring=pbGetFileChar(file+".jpg")) + filestrName=file+".jpg" + elsif (filestring=pbGetFileChar(file+".bmp")) + filestrName=file+".bmp" + end + end + end + if bitmap && filestring && filestring[0]==0x47 && + bitmap.width==32 && bitmap.height==32 + #File.open("debug.txt","ab") { |f| f.puts("rejecting bitmap") } + bitmap.dispose + bitmap=nil + end + if bitmap + #File.open("debug.txt","ab") { |f| f.puts("reusing bitmap") } + # Have a regular non-animated bitmap + @totalframes=1 + @framecount=0 + @gifbitmaps=[bitmap] + @gifdelays=[1] + else + tmpBase=File.basename(file)+"_tmp_" + filestring=pbGetFileString(filestrName) if filestring + Dir.chdir(ENV["TEMP"]) { # navigate to temp folder since game might be on a CD-ROM + if filestring && filestring[0]==0x47 && GifLibrary::PngDll + result=GifLibrary::GifToPngFilesInMemory.call(filestring, + filestring.length,tmpBase) + else + result=0 + end + if result>0 + @gifdelays=GifLibrary.getDataFromResult(result) + @totalframes=@gifdelays.pop + for i in 0...@gifdelays.length + @gifdelays[i]=[@gifdelays[i],1].max + bmfile=sprintf("%s%d.png",tmpBase,i) + if safeExists?(bmfile) + gifbitmap=BitmapWrapper.new(bmfile) + @gifbitmaps.push(gifbitmap) + bmfile.hue_change(hue) if hue!=0 + if hue==0 && @gifdelays.length==1 + BitmapCache.setKey(file,gifbitmap) + end + File.delete(bmfile) + else + @gifbitmaps.push(BitmapWrapper.new(32,32)) + end + end + end + } + if @gifbitmaps.length==0 + @gifbitmaps=[BitmapWrapper.new(32,32)] + @gifdelays=[1] + end + if @gifbitmaps.length==1 + BitmapCache.setKey(file,@gifbitmaps[0]) + end + end + end + + def [](index) + return @gifbitmaps[index] + end + + def width; self.bitmap.width; end + + def height; self.bitmap.height; end + + def deanimate + for i in 1...@gifbitmaps.length + @gifbitmaps[i].dispose + end + @gifbitmaps=[@gifbitmaps[0]] + @currentIndex=0 + return @gifbitmaps[0] + end + + def bitmap + @gifbitmaps[@currentIndex] + end + + def currentIndex + @currentIndex + end + + def frameDelay(index) + return @gifdelay[index]/2 # Due to frame count being incremented by 2 + end + + def length + @gifbitmaps.length + end + + def each + @gifbitmaps.each { |item| yield item } + end + + def totalFrames + @totalframes/2 # Due to frame count being incremented by 2 + end + + def disposed? + @disposed + end + + def width + @gifbitmaps.length==0 ? 0 : @gifbitmaps[0].width + end + + def height + @gifbitmaps.length==0 ? 0 : @gifbitmaps[0].height + end + + # This function must be called in order to animate the GIF image. + def update + return if disposed? + if @gifbitmaps.length>0 + @framecount+=2 + @framecount=@totalframes<=0 ? 0 : @framecount%@totalframes + frametoshow=0 + for i in 0...@gifdelays.length + frametoshow=i if @gifdelays[i]<=@framecount + end + @currentIndex=frametoshow + end + end + + def dispose + if !@disposed + for i in @gifbitmaps + i.dispose + end + end + @disposed=true + end + + attr_accessor :gifbitmaps # internal + attr_accessor :gifdelays # internal + + def copy + x=self.clone + x.gifbitmaps=x.gifbitmaps.clone + x.gifdelays=x.gifdelays.clone + for i in 0...x.gifbitmaps.length + x.gifbitmaps[i]=x.gifbitmaps[i].copy + end + return x + end +end + + + +def pbGetTileBitmap(filename, tile_id, hue) + return BitmapCache.tileEx(filename, tile_id, hue) { |f| + AnimatedBitmap.new("Graphics/Tilesets/"+filename).deanimate; + } +end + +def pbGetTileset(name,hue=0) + return AnimatedBitmap.new("Graphics/Tilesets/"+name,hue).deanimate +end + +def pbGetAutotile(name,hue=0) + return AnimatedBitmap.new("Graphics/Autotiles/"+name,hue).deanimate +end + +def pbGetAnimation(name,hue=0) + return AnimatedBitmap.new("Graphics/Animations/"+name,hue).deanimate +end + + + +#=============================================================================== +# SpriteWrapper is a class based on Sprite which wraps Sprite's properties. +#=============================================================================== +class SpriteWrapper < Sprite + def initialize(viewport=nil) + @sprite = Sprite.new(viewport) + end + + def dispose; @sprite.dispose; end + def disposed?; return @sprite.disposed?; end + def viewport; return @sprite.viewport; end + def flash(color,duration); return @sprite.flash(color,duration); end + def update; return @sprite.update; end + def x; @sprite.x; end + def x=(value); @sprite.x = value; end + def y; @sprite.y; end + def y=(value); @sprite.y = value; end + def bitmap; @sprite.bitmap; end + def bitmap=(value); @sprite.bitmap = value; end + def src_rect; @sprite.src_rect; end + def src_rect=(value); @sprite.src_rect = value; end + def visible; @sprite.visible; end + def visible=(value); @sprite.visible = value; end + def z; @sprite.z; end + def z=(value); @sprite.z = value; end + def ox; @sprite.ox; end + def ox=(value); @sprite.ox = value; end + def oy; @sprite.oy; end + def oy=(value); @sprite.oy = value; end + def zoom_x; @sprite.zoom_x; end + def zoom_x=(value); @sprite.zoom_x = value; end + def zoom_y; @sprite.zoom_y; end + def zoom_y=(value); @sprite.zoom_y = value; end + def angle; @sprite.angle; end + def angle=(value); @sprite.angle = value; end + def mirror; @sprite.mirror; end + def mirror=(value); @sprite.mirror = value; end + def bush_depth; @sprite.bush_depth; end + def bush_depth=(value); @sprite.bush_depth = value; end + def opacity; @sprite.opacity; end + def opacity=(value); @sprite.opacity = value; end + def blend_type; @sprite.blend_type; end + def blend_type=(value); @sprite.blend_type = value; end + def color; @sprite.color; end + def color=(value); @sprite.color = value; end + def tone; @sprite.tone; end + def tone=(value); @sprite.tone = value; end + + def viewport=(value) + return if self.viewport==value + bitmap = @sprite.bitmap + src_rect = @sprite.src_rect + visible = @sprite.visible + x = @sprite.x + y = @sprite.y + z = @sprite.z + ox = @sprite.ox + oy = @sprite.oy + zoom_x = @sprite.zoom_x + zoom_y = @sprite.zoom_y + angle = @sprite.angle + mirror = @sprite.mirror + bush_depth = @sprite.bush_depth + opacity = @sprite.opacity + blend_type = @sprite.blend_type + color = @sprite.color + tone = @sprite.tone + @sprite.dispose + @sprite = Sprite.new(value) + @sprite.bitmap = bitmap + @sprite.src_rect = src_rect + @sprite.visible = visible + @sprite.x = x + @sprite.y = y + @sprite.z = z + @sprite.ox = ox + @sprite.oy = oy + @sprite.zoom_x = zoom_x + @sprite.zoom_y = zoom_y + @sprite.angle = angle + @sprite.mirror = mirror + @sprite.bush_depth = bush_depth + @sprite.opacity = opacity + @sprite.blend_type = blend_type + @sprite.color = color + @sprite.tone = tone + end +end + + + +#=============================================================================== +# Sprite class that maintains a bitmap of its own. +# This bitmap can't be changed to a different one. +#=============================================================================== +class BitmapSprite < SpriteWrapper + def initialize(width,height,viewport=nil) + super(viewport) + self.bitmap=Bitmap.new(width,height) + @initialized=true + end + + def bitmap=(value) + super(value) if !@initialized + end + + def dispose + self.bitmap.dispose if !self.disposed? + super + end +end + + + +#=============================================================================== +# +#=============================================================================== +class AnimatedSprite < SpriteWrapper + attr_reader :frame + attr_reader :framewidth + attr_reader :frameheight + attr_reader :framecount + attr_reader :animname + + def initializeLong(animname,framecount,framewidth,frameheight,frameskip) + @animname=pbBitmapName(animname) + @realframes=0 + @frameskip=[1,frameskip].max + @frameskip *= Graphics.frame_rate/20 + raise _INTL("Frame width is 0") if framewidth==0 + raise _INTL("Frame height is 0") if frameheight==0 + begin + @animbitmap=AnimatedBitmap.new(animname).deanimate + rescue + @animbitmap=Bitmap.new(framewidth,frameheight) + end + if @animbitmap.width%framewidth!=0 + raise _INTL("Bitmap's width ({1}) is not a multiple of frame width ({2}) [Bitmap={3}]", + @animbitmap.width,framewidth,animname) + end + if @animbitmap.height%frameheight!=0 + raise _INTL("Bitmap's height ({1}) is not a multiple of frame height ({2}) [Bitmap={3}]", + @animbitmap.height,frameheight,animname) + end + @framecount=framecount + @framewidth=framewidth + @frameheight=frameheight + @framesperrow=@animbitmap.width/@framewidth + @playing=false + self.bitmap=@animbitmap + self.src_rect.width=@framewidth + self.src_rect.height=@frameheight + self.frame=0 + end + + # Shorter version of AnimationSprite. All frames are placed on a single row + # of the bitmap, so that the width and height need not be defined beforehand + def initializeShort(animname,framecount,frameskip) + @animname=pbBitmapName(animname) + @realframes=0 + @frameskip=[1,frameskip].max + @frameskip *= Graphics.frame_rate/20 + begin + @animbitmap=AnimatedBitmap.new(animname).deanimate + rescue + @animbitmap=Bitmap.new(framecount*4,32) + end + if @animbitmap.width%framecount!=0 + raise _INTL("Bitmap's width ({1}) is not a multiple of frame count ({2}) [Bitmap={3}]", + @animbitmap.width,framewidth,animname) + end + @framecount=framecount + @framewidth=@animbitmap.width/@framecount + @frameheight=@animbitmap.height + @framesperrow=framecount + @playing=false + self.bitmap=@animbitmap + self.src_rect.width=@framewidth + self.src_rect.height=@frameheight + self.frame=0 + end + + def initialize(*args) + if args.length==1 + super(args[0][3]) + initializeShort(args[0][0],args[0][1],args[0][2]) + else + super(args[5]) + initializeLong(args[0],args[1],args[2],args[3],args[4]) + end + end + + def self.create(animname,framecount,frameskip,viewport=nil) + return self.new([animname,framecount,frameskip,viewport]) + end + + def dispose + return if disposed? + @animbitmap.dispose + @animbitmap=nil + super + end + + def playing? + return @playing + end + + def frame=(value) + @frame=value + @realframes=0 + self.src_rect.x=@frame%@framesperrow*@framewidth + self.src_rect.y=@frame/@framesperrow*@frameheight + end + + def start + @playing=true + @realframes=0 + end + + alias play start + + def stop + @playing=false + end + + def update + super + if @playing + @realframes+=1 + if @realframes==@frameskip + @realframes=0 + self.frame+=1 + self.frame%=self.framecount + end + end + end +end + + + +#=============================================================================== +# Displays an icon bitmap in a sprite. Supports animated images. +#=============================================================================== +class IconSprite < SpriteWrapper + attr_reader :name + + def initialize(*args) + if args.length==0 + super(nil) + self.bitmap=nil + elsif args.length==1 + super(args[0]) + self.bitmap=nil + elsif args.length==2 + super(nil) + self.x=args[0] + self.y=args[1] + else + super(args[2]) + self.x=args[0] + self.y=args[1] + end + @name="" + @_iconbitmap=nil + end + + def dispose + clearBitmaps() + super + end + + # Sets the icon's filename. Alias for setBitmap. + def name=(value) + setBitmap(value) + end + + # Sets the icon's filename. + def setBitmap(file,hue=0) + oldrc=self.src_rect + clearBitmaps() + @name=file + return if file==nil + if file!="" + @_iconbitmap=AnimatedBitmap.new(file,hue) + # for compatibility + self.bitmap=@_iconbitmap ? @_iconbitmap.bitmap : nil + self.src_rect=oldrc + else + @_iconbitmap=nil + end + end + + def clearBitmaps + @_iconbitmap.dispose if @_iconbitmap + @_iconbitmap=nil + self.bitmap=nil if !self.disposed? + end + + def update + super + return if !@_iconbitmap + @_iconbitmap.update + if self.bitmap!=@_iconbitmap.bitmap + oldrc=self.src_rect + self.bitmap=@_iconbitmap.bitmap + self.src_rect=oldrc + end + end +end + + + +#=============================================================================== +# Old GifSprite class, retained for compatibility +#=============================================================================== +class GifSprite < IconSprite + def initialize(path) + super(0,0) + setBitmap(path) + end +end + + + +#=============================================================================== +# SpriteWrapper that stores multiple bitmaps, and displays only one at once. +#=============================================================================== +class ChangelingSprite < SpriteWrapper + def initialize(x=0,y=0,viewport=nil) + super(viewport) + self.x = x + self.y = y + @bitmaps = {} + @currentBitmap = nil + end + + def addBitmap(key,path) + @bitmaps[key].dispose if @bitmaps[key] + @bitmaps[key] = AnimatedBitmap.new(path) + end + + def changeBitmap(key) + @currentBitmap = @bitmaps[key] + self.bitmap = (@currentBitmap) ? @currentBitmap.bitmap : nil + end + + def dispose + return if disposed? + for bm in @bitmaps.values; bm.dispose; end + @bitmaps.clear + super + end + + def update + return if disposed? + for bm in @bitmaps.values; bm.update; end + self.bitmap = (@currentBitmap) ? @currentBitmap.bitmap : nil + end +end + + + +#=============================================================================== +# Displays an icon bitmap in a window. Supports animated images. +#=============================================================================== +class IconWindow < SpriteWindow_Base + attr_reader :name + + def initialize(x,y,width,height,viewport=nil) + super(x,y,width,height) + self.viewport=viewport + self.contents=nil + @name="" + @_iconbitmap=nil + end + + def dispose + clearBitmaps() + super + end + + def update + super + if @_iconbitmap + @_iconbitmap.update + self.contents=@_iconbitmap.bitmap + end + end + + def clearBitmaps + @_iconbitmap.dispose if @_iconbitmap + @_iconbitmap=nil + self.contents=nil if !self.disposed? + end + + # Sets the icon's filename. Alias for setBitmap. + def name=(value) + setBitmap(value) + end + + # Sets the icon's filename. + def setBitmap(file,hue=0) + clearBitmaps() + @name=file + return if file==nil + if file!="" + @_iconbitmap=AnimatedBitmap.new(file,hue) + # for compatibility + self.contents=@_iconbitmap ? @_iconbitmap.bitmap : nil + else + @_iconbitmap=nil + end + end +end + + + +#=============================================================================== +# Displays an icon bitmap in a window. Supports animated images. +# Accepts bitmaps and paths to bitmap files in its constructor. +#=============================================================================== +class PictureWindow < SpriteWindow_Base + def initialize(pathOrBitmap) + super(0,0,32,32) + self.viewport=viewport + self.contents=nil + @_iconbitmap=nil + setBitmap(pathOrBitmap) + end + + def dispose + clearBitmaps() + super + end + + def update + super + if @_iconbitmap + if @_iconbitmap.is_a?(Bitmap) + self.contents=@_iconbitmap + else + @_iconbitmap.update + self.contents=@_iconbitmap.bitmap + end + end + end + + def clearBitmaps + @_iconbitmap.dispose if @_iconbitmap + @_iconbitmap=nil + self.contents=nil if !self.disposed? + end + + # Sets the icon's bitmap or filename. (hue parameter + # is ignored unless pathOrBitmap is a filename) + def setBitmap(pathOrBitmap,hue=0) + clearBitmaps() + if pathOrBitmap!=nil && pathOrBitmap!="" + if pathOrBitmap.is_a?(Bitmap) + @_iconbitmap=pathOrBitmap + self.contents=@_iconbitmap + self.width=@_iconbitmap.width+self.borderX + self.height=@_iconbitmap.height+self.borderY + elsif pathOrBitmap.is_a?(AnimatedBitmap) + @_iconbitmap=pathOrBitmap + self.contents=@_iconbitmap.bitmap + self.width=@_iconbitmap.bitmap.width+self.borderX + self.height=@_iconbitmap.bitmap.height+self.borderY + else + @_iconbitmap=AnimatedBitmap.new(pathOrBitmap,hue) + self.contents=@_iconbitmap ? @_iconbitmap.bitmap : nil + self.width=@_iconbitmap ? @_iconbitmap.bitmap.width+self.borderX : + 32+self.borderX + self.height=@_iconbitmap ? @_iconbitmap.bitmap.height+self.borderY : + 32+self.borderY + end + else + @_iconbitmap=nil + self.width=32+self.borderX + self.height=32+self.borderY + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +class Plane + def update; end + def refresh; end +end + + + +#=============================================================================== +# This class works around a limitation that planes are always +# 640 by 480 pixels in size regardless of the window's size. +#=============================================================================== +class LargePlane < Plane + attr_accessor :borderX + attr_accessor :borderY + + def initialize(viewport=nil) + @__sprite=Sprite.new(viewport) + @__disposed=false + @__ox=0 + @__oy=0 + @__bitmap=nil + @__visible=true + @__sprite.visible=false + @borderX=0 + @borderY=0 + end + + def disposed? + return @__disposed + end + + def dispose + if !@__disposed + @__sprite.bitmap.dispose if @__sprite.bitmap + @__sprite.dispose + @__sprite=nil + @__bitmap=nil + @__disposed=true + end + super + end + + def ox; @__ox; end + def oy; @__oy; end + + def ox=(value); + return if @__ox==value + @__ox = value + refresh + end + + def oy=(value); + return if @__oy==value + @__oy = value + refresh + end + + def bitmap + return @__bitmap + end + + def bitmap=(value) + if value==nil + if @__bitmap!=nil + @__bitmap=nil + @__sprite.visible=(@__visible && !@__bitmap.nil?) + end + elsif @__bitmap!=value && !value.disposed? + @__bitmap=value + refresh + elsif value.disposed? + if @__bitmap!=nil + @__bitmap=nil + @__sprite.visible=(@__visible && !@__bitmap.nil?) + end + end + end + + def viewport; @__sprite.viewport; end + def zoom_x; @__sprite.zoom_x; end + def zoom_y; @__sprite.zoom_y; end + def opacity; @__sprite.opacity; end + def blend_type; @__sprite.blend_type; end + def visible; @__visible; end + def z; @__sprite.z; end + def color; @__sprite.color; end + def tone; @__sprite.tone; end + + def zoom_x=(v); + return if @__sprite.zoom_x==v + @__sprite.zoom_x = v + refresh + end + + def zoom_y=(v); + return if @__sprite.zoom_y==v + @__sprite.zoom_y = v + refresh + end + + def opacity=(v); @__sprite.opacity=(v); end + def blend_type=(v); @__sprite.blend_type=(v); end + def visible=(v); @__visible=v; @__sprite.visible=(@__visible && !@__bitmap.nil?); end + def z=(v); @__sprite.z=(v); end + def color=(v); @__sprite.color=(v); end + def tone=(v); @__sprite.tone=(v); end + def update; ;end + + def refresh + @__sprite.visible = (@__visible && !@__bitmap.nil?) + if @__bitmap + if !@__bitmap.disposed? + @__ox += @__bitmap.width*@__sprite.zoom_x if @__ox<0 + @__oy += @__bitmap.height*@__sprite.zoom_y if @__oy<0 + @__ox -= @__bitmap.width*@__sprite.zoom_x if @__ox>@__bitmap.width + @__oy -= @__bitmap.height*@__sprite.zoom_y if @__oy>@__bitmap.height + dwidth = (Graphics.width/@__sprite.zoom_x+@borderX).to_i # +2 + dheight = (Graphics.height/@__sprite.zoom_y+@borderY).to_i # +2 + @__sprite.bitmap = ensureBitmap(@__sprite.bitmap,dwidth,dheight) + @__sprite.bitmap.clear + tileBitmap(@__sprite.bitmap,@__bitmap,@__bitmap.rect) + else + @__sprite.visible = false + end + end + end + + private + + def ensureBitmap(bitmap,dwidth,dheight) + if !bitmap || bitmap.disposed? || bitmap.width0; left -= srcbitmap.width; end + while top>0; top -= srcbitmap.height; end + y = top; while y",ret) +end + +def shadowctag(base,shadow) + return sprintf("",colorToRgb16(base),colorToRgb16(shadow)) +end + +def shadowc3tag(base,shadow) + return sprintf("",colorToRgb32(base),colorToRgb32(shadow)) +end + +def shadowctagFromColor(color) + return shadowc3tag(color,getContrastColor(color)) +end + +def shadowctagFromRgb(param) + return shadowctagFromColor(rgbToColor(param)) +end + +def colorToRgb32(color) + return "" if !color + if color.alpha.to_i==255 + return sprintf("%02X%02X%02X",color.red.to_i,color.green.to_i,color.blue.to_i) + else + return sprintf("%02X%02X%02X%02X", + color.red.to_i,color.green.to_i,color.blue.to_i,color.alpha.to_i) + end +end + +def colorToRgb16(color) + ret=(color.red.to_i>>3) + ret|=((color.green.to_i>>3)<<5) + ret|=((color.blue.to_i>>3)<<10) + return sprintf("%04X",ret) +end + +def rgbToColor(param) + return Font.default_color if !param + baseint=param.to_i(16) + if param.length==8 # 32-bit hex + return Color.new( + (baseint>>24)&0xFF, + (baseint>>16)&0xFF, + (baseint>>8)&0xFF, + (baseint)&0xFF + ) + elsif param.length==6 # 24-bit hex + return Color.new( + (baseint>>16)&0xFF, + (baseint>>8)&0xFF, + (baseint)&0xFF + ) + elsif param.length==4 # 16-bit hex + return Color.new( + ((baseint)&0x1F)<<3, + ((baseint>>5)&0x1F)<<3, + ((baseint>>10)&0x1F)<<3 + ) + elsif param.length==1 # Color number + i=param.to_i + return Font.default_color if i>=8 + return [ + Color.new(255, 255, 255, 255), + Color.new(128, 128, 255, 255), + Color.new(255, 128, 128, 255), + Color.new(128, 255, 128, 255), + Color.new(128, 255, 255, 255), + Color.new(255, 128, 255, 255), + Color.new(255, 255, 128, 255), + Color.new(192, 192, 192, 255) + ][i] + else + return Font.default_color + end +end + +def Rgb16ToColor(param) + baseint=param.to_i(16) + return Color.new( + ((baseint)&0x1F)<<3, + ((baseint>>5)&0x1F)<<3, + ((baseint>>10)&0x1F)<<3 + ) +end + +def getContrastColor(color) + raise "No color given" if !color + r=color.red; g=color.green; b=color.blue + yuv=[ + r * 0.299 + g * 0.587 + b * 0.114, + r * -0.1687 + g * -0.3313 + b * 0.500 + 0.5, + r * 0.500 + g * -0.4187 + b * -0.0813 + 0.5 + ] + if yuv[0]<127.5 + yuv[0]+=(255-yuv[0])/2 + else + yuv[0]=yuv[0]/2 + end + return Color.new( + yuv[0] + 1.4075 * (yuv[2] - 0.5), + yuv[0] - 0.3455 * (yuv[1] - 0.5) - 0.7169 * (yuv[2] - 0.5), + yuv[0] + 1.7790 * (yuv[1] - 0.5), + color.alpha + ) +end + + + +#=============================================================================== +# Format text +#=============================================================================== +FORMATREGEXP = /<(\/?)(c|c2|c3|o|fn|br|fs|i|b|r|pg|pog|u|s|icon|img|ac|ar|al|outln|outln2)(\s*\=\s*([^>]*))?>/i + +def fmtescape(text) + if text[/[&<>]/] + text2=text.gsub(/&/,"&") + text2.gsub!(//,">") + return text2 + end + return text +end + +def toUnformattedText(text) + text2=text.gsub(FORMATREGEXP,"") + text2.gsub!(/</,"<") + text2.gsub!(/>/,">") + text2.gsub!(/'/,"'") + text2.gsub!(/"/,"\"") + text2.gsub!(/&/,"&") + return text2 +end + +def unformattedTextLength(text) + return toUnformattedText(text).scan(/./m).length +end + +def itemIconTag(item) + return "" if !item + if item.respond_to?("icon_name") + return sprintf("",item.icon_name) + else + ix=item.icon_index % 16 * 24 + iy=item.icon_index / 16 * 24 + return sprintf("",ix,iy) + end +end + +def getFormattedTextForDims(bitmap,xDst,yDst,widthDst,heightDst,text,lineheight, + newlineBreaks=true,explicitBreaksOnly=false) + text2=text.gsub(/<(\/?)(c|c2|c3|o|u|s)(\s*\=\s*([^>]*))?>/i,"") + if newlineBreaks + text2.gsub!(/<(\/?)(br)(\s*\=\s*([^>]*))?>/i,"\n") + end + return getFormattedText( + bitmap,xDst,yDst,widthDst,heightDst, + text2,lineheight,newlineBreaks, + explicitBreaksOnly,true) +end + +def getFormattedTextFast(bitmap,xDst,yDst,widthDst,heightDst,text,lineheight, + newlineBreaks=true,explicitBreaksOnly=false) + numchars=0 + x=y=0 + characters=[] + charactersInternal=[] + textchunks=[] + controls=[] + charsonline=0 + textchunks.push(text) + text=textchunks.join("") + textchars=text.scan(/./m) + lastword=[0,0] # position of last word + hadspace=false + hadnonspace=false + bold=bitmap.font.bold + italic=bitmap.font.italic + colorclone=bitmap.font.color + defaultfontname=bitmap.font.name + if defaultfontname.is_a?(Array) + defaultfontname=defaultfontname.find { |i| Font.exist?(i) } || "Arial" + elsif !Font.exist?(defaultfontname) + defaultfontname="Arial" + end + defaultfontname=defaultfontname.clone + havenl=false + position=0;while positionwidthDst && lastword[1]!=0 && + (!hadnonspace || !hadspace) + havenl=true + characters.insert(lastword[0],["\n",x,y*lineheight+yDst,0,lineheight, + false,false,false,colorclone,nil,false,false,"",8,position]) + lastword[0]+=1 + y+=1 + x=0 + for i in lastword[0]...characters.length + characters[i][2]+=lineheight + charwidth=characters[i][3]-2 + characters[i][1]=x + x+=charwidth + end + lastword[1]=0 + end + position+=1 + end + # This code looks at whether the text occupies exactly two lines when + # displayed. If it does, it balances the length of each line. +=begin + # Count total number of lines + numlines = (x==0 && y>0) ? y-1 : y + realtext = (newlineBreaks) ? text : text.gsub(/\n/," ") + if numlines==2 && !explicitBreaksOnly && !realtext[/\n/] && realtext.length>=50 + # Set half to middle of text (known to contain no formatting) + half = realtext.length/2 + leftSearch = 0 + rightSearch = 0 + # Search left for a space + i = half; while i>=0 + break if realtext[i,1][/\s/]||isWaitChar(realtext[i]) # found a space + leftSearch += 1 + i -= 1 + end + # Search right for a space + i = half; while i=0 + for j in firstspace...i + characters[j]=nil + end + firstspace=-1 + elsif characters[i][0][/[ \r\t]/] + if firstspace<0 + firstspace=i + end + else + firstspace=-1 + end + end + if firstspace>0 + for j in firstspace...characters.length + characters[j]=nil + end + end + characters.compact! + end + for i in 0...characters.length + characters[i][1]=xDst+characters[i][1] + end + # Remove all characters with Y greater or equal to _yDst_+_heightDst_ + if heightDst>=0 + for i in 0...characters.length + if characters[i][2]>=yDst+heightDst + characters[i]=nil + end + end + characters.compact! + end + return characters +end + +def isWaitChar(x) + return (x=="\001" || x=="\002") +end + +def getLastParam(array,default) + i=array.length-1 + while i>=0 + return array[i] if array[i] + i-=1 + end + return default +end + +def getLastColors(colorstack,opacitystack,defaultcolors) + colors=getLastParam(colorstack,defaultcolors) + opacity=getLastParam(opacitystack,255) + if opacity!=255 + colors=[Color.new(colors[0].red,colors[0].green,colors[0].blue, + colors[0].alpha*opacity/255), + colors[1] ? Color.new(colors[1].red,colors[1].green,colors[1].blue, + colors[1].alpha*opacity/255) : nil] + end + return colors +end + + + +#=============================================================================== +# Formats a string of text and returns an array containing a list of formatted +# characters. +#=============================================================================== +=begin +Parameters: +bitmap: Source bitmap. Will be used to determine the default font of + the text. +xDst: X coordinate of the text's top left corner. +yDst: Y coordinate of the text's top left corner. +widthDst: Width of the text. Used to determine line breaks. +heightDst: Height of the text. If -1, there is no height restriction. If + 1 or greater, any characters exceeding the height are removed + from the returned list. +newLineBreaks: If true, newline characters will be treated as line breaks. The + default is true. + +Return Values: +A list of formatted characters. Returns an empty array if _bitmap_ is nil +or disposed, or if _widthDst_ is 0 or less or _heightDst_ is 0. + +Formatting Specification: +This function uses the following syntax when formatting the text. + ... - Formats the text in bold. + ... - Formats the text in italics. + ... - Underlines the text. + ... - Draws a strikeout line over the text. + ... - Left-aligns the text. Causes line breaks before and after + the text. + - Right-aligns the text until the next line break. + ... - Right-aligns the text. Causes line breaks before and after + the text. + ... - Centers the text. Causes line breaks before and after the + text. +
- Causes a line break. + ... - Color specification. A total of four formats are supported: + RRGGBBAA, RRGGBB, 16-bit RGB, and Window_Base color numbers. + ... - Color specification where the first half is the base color + and the second half is the shadow color. 16-bit RGB is + supported. +Added 2009-10-20 + ... - Color specification where B is the base color and S is the + shadow color. B and/or S can be omitted. A total of four + formats are supported: + RRGGBBAA, RRGGBB, 16-bit RGB, and Window_Base color numbers. +Added 2009-9-12 + - Displays the text in the given opacity (0-255) +Added 2009-10-19 + - Displays the text in outline format. +Added 2010-05-12 + - Displays the text in outline format (outlines more + exaggerated. + ... - Formats the text in the specified font, or Arial if the + font doesn't exist. + ... - Changes the font size to X. + - Displays the icon X (in Graphics/Icons/). + +In addition, the syntax supports the following: +' - Converted to "'". +< - Converted to "<". +> - Converted to ">". +& - Converted to "&". +" - Converted to double quotation mark. + +To draw the characters, pass the returned array to the +_drawFormattedChars_ function. +=end + +def getFormattedText(bitmap,xDst,yDst,widthDst,heightDst,text,lineheight=32, + newlineBreaks=true,explicitBreaksOnly=false, + collapseAlignments=false) + dummybitmap=nil + if !bitmap || bitmap.disposed? # allows function to be called with nil bitmap + dummybitmap=Bitmap.new(1,1) + bitmap=dummybitmap + return + end + if !bitmap || bitmap.disposed? || widthDst<=0 || heightDst==0 || text.length==0 + return [] + end + textchunks=[] + controls=[] + oldtext=text + while text[FORMATREGEXP] + textchunks.push($~.pre_match) + if $~[3] + controls.push([$~[2].downcase,$~[4],-1,$~[1]=="/" ? true : false]) + else + controls.push([$~[2].downcase,"",-1,$~[1]=="/" ? true : false]) + end + text=$~.post_match + end + if controls.length==0 + ret=getFormattedTextFast(bitmap,xDst,yDst,widthDst,heightDst,text,lineheight, + newlineBreaks,explicitBreaksOnly) + dummybitmap.dispose if dummybitmap + return ret + end + numchars=0 + x=y=0 + characters=[] + charactersInternal=[] + charsonline=0 + realtext=nil + realtextStart="" + if !explicitBreaksOnly && textchunks.join("").length==0 + # All commands occurred at the beginning of the text string + realtext=(newlineBreaks) ? text : text.gsub(/\n/," ") + realtextStart=oldtext[0,oldtext.length-realtext.length] + realtextHalf=text.length/2 + end + textchunks.push(text) + for chunk in textchunks + chunk.gsub!(/</,"<") + chunk.gsub!(/>/,">") + chunk.gsub!(/'/,"'") + chunk.gsub!(/"/,"\"") + chunk.gsub!(/&/,"&") + end + textlen=0 + for i in 0...controls.length + textlen+=textchunks[i].scan(/./m).length + controls[i][2]=textlen + end + text=textchunks.join("") + textchars=text.scan(/./m) + colorstack=[] + boldcount=0 + italiccount=0 + outlinecount=0 + underlinecount=0 + strikecount=0 + rightalign=0 + outline2count=0 + opacitystack=[] + oldfont=bitmap.font.clone + defaultfontname=bitmap.font.name + defaultfontsize=bitmap.font.size + fontsize=defaultfontsize + fontnamestack=[] + fontsizestack=[] + defaultcolors=[oldfont.color.clone,nil] + if defaultfontname.is_a?(Array) + defaultfontname=defaultfontname.find { |i| Font.exist?(i) } || "Arial" + elsif !Font.exist?(defaultfontname) + defaultfontname="Arial" + end + defaultfontname=defaultfontname.clone + fontname=defaultfontname + alignstack=[] + lastword=[0,0] # position of last word + hadspace=false + hadnonspace=false + havenl=false + position=0; while position0 && nextline==0 + else + alignstack.pop + nextline=1 if x>0 && nextline==0 + end + elsif control=="al" # Left align + if !endtag + alignstack.push(0) + nextline=1 if x>0 && nextline==0 + else + alignstack.pop; + nextline=1 if x>0 && nextline==0 + end + elsif control=="ac" # Center align + if !endtag + alignstack.push(2) + nextline=1 if x>0 && nextline==0 + else + alignstack.pop; + nextline=1 if x>0 && nextline==0 + end + elsif control=="icon" # Icon + if !endtag + param=param.sub(/\s+$/,"") + graphic="Graphics/Icons/#{param}" + controls[i]=nil + break + end + elsif control=="img" # Icon + if !endtag + param=param.sub(/\s+$/,"") + param=param.split("|") + graphic=param[0] + if param.length>1 + graphicX=param[1].to_i + graphicY=param[2].to_i + graphicWidth=param[3].to_i + graphicHeight=param[4].to_i + end + controls[i]=nil + break + end + elsif control=="br" # Line break + if !endtag + nextline+=1 + end + elsif control=="r" # Right align this line + if !endtag + x=0 + rightalign=1; lastword=[characters.length,x] + end + end + controls[i]=nil + end + end + bitmap.font.bold=(boldcount>0) + bitmap.font.italic=(italiccount>0) + if graphic + if !graphicWidth + tempgraphic=Bitmap.new(graphic) + graphicWidth=tempgraphic.width + graphicHeight=tempgraphic.height + tempgraphic.dispose + end + width=graphicWidth # +8 # No padding + xStart=0 # 4 + yStart=[(lineheight/2)-(graphicHeight/2),0].max + graphicRect=Rect.new(graphicX,graphicY,graphicWidth,graphicHeight) + else + yStart=0 + xStart=0 + width=isWaitChar(textchars[position]) ? 0 : bitmap.text_size(textchars[position]).width + width+=2 if width>0 && outline2count>0 + end + if rightalign==1 && nextline==0 + alignment=1 + else + alignment=getLastParam(alignstack,0) + end + nextline.times do + havenl=true + characters.push(["\n",x,y*lineheight+yDst,0,lineheight,false,false,false, + defaultcolors[0],defaultcolors[1],false,false,"",8,position,nil,0]) + charactersInternal.push([alignment,y,0]) + y+=1; + x=0; + rightalign=0; + lastword=[characters.length,x] + hadspace=false + hadnonspace=false + end + if textchars[position]=="\n" + if newlineBreaks + if nextline==0 + havenl=true + characters.push(["\n",x,y*lineheight+yDst,0,lineheight,false,false,false, + defaultcolors[0],defaultcolors[1],false,false,"",8,position,nil,0]) + charactersInternal.push([alignment,y,0]) + y+=1 + x=0 + end + rightalign=0 + hadspace=true + hadnonspace=false + position+=1 + next + else + textchars[position]=" " + if !graphic + width=bitmap.text_size(textchars[position]).width + width+=2 if width>0 && outline2count>0 + end + end + end + isspace=(textchars[position][/\s/] || isWaitChar(textchars[position])) ? true : false + if hadspace && !isspace + # set last word to here + lastword[0]=characters.length + lastword[1]=x + hadspace=false + hadnonspace=true + elsif isspace + hadspace=true + end + textx=x+xStart # independent of xDst + texty=(lineheight*y)+yDst+yStart + colors=getLastColors(colorstack,opacitystack,defaultcolors) + oldx=x + # Push character, textx will be calculated later + if heightDst<0 || texty0) ? 2+(width/2) : 2 + characters.push([ + graphic ? graphic : textchars[position], + x+xStart,texty,width+extraspace,lineheight, + graphic ? true : false, + (boldcount>0),(italiccount>0),colors[0],colors[1], + (underlinecount>0),(strikecount>0),fontname,fontsize, + position,graphicRect, + ((outlinecount>0) ? 1 : 0)+((outline2count>0) ? 2 : 0) + ]) + charactersInternal.push([alignment,y,xStart,textchars[position],extraspace]) + end + x+=width + if !explicitBreaksOnly && x+2>widthDst && lastword[1]!=0 && + (!hadnonspace || !hadspace) + havenl=true + characters.insert(lastword[0],["\n",x,y*lineheight+yDst,0,lineheight,false, + false,false,defaultcolors[0],defaultcolors[1],false,false,"",8,position, + nil]) + charactersInternal.insert(lastword[0],[alignment,y,0]) + lastword[0]+=1 + y+=1 + x=0 + for i in lastword[0]...characters.length + characters[i][2]+=lineheight + charactersInternal[i][1]+=1 + extraspace=(charactersInternal[i][4]) ? charactersInternal[i][4] : 0 + charwidth=characters[i][3]-extraspace + characters[i][1]=x+charactersInternal[i][2] + x+=charwidth + end + lastword[1]=0 + end + position+=1 if !graphic + end + # This code looks at whether the text occupies exactly two lines when + # displayed. If it does, it balances the length of each line. +=begin + # Count total number of lines + numlines = (x==0 && y>0) ? y : y+1 + if numlines==2 && realtext && !realtext[/\n/] && realtext.length>=50 + # Set half to middle of text (known to contain no formatting) + half = realtext.length/2 + leftSearch = 0 + rightSearch = 0 + # Search left for a space + i = half; while i>=0 + break if realtext[i,1][/\s/]||isWaitChar(realtext[i,1]) # found a space + leftSearch += 1 + i -= 1 + end + # Search right for a space + i = half; while i=0 + for j in firstspace...i + characters[j]=nil + charactersInternal[j]=nil + end + firstspace=-1 + elsif characters[i][0][/[ \r\t]/] + if firstspace<0 + firstspace=i + end + else + firstspace=-1 + end + end + if firstspace>0 + for j in firstspace...characters.length + characters[j]=nil + charactersInternal[j]=nil + end + end + characters.compact! + charactersInternal.compact! + end + # Calculate Xs based on alignment + # First, find all text runs with the same alignment on the same line + totalwidth=0 + widthblocks=[] + lastalign=0 + lasty=0 + runstart=0 + for i in 0...characters.length + c=characters[i] + if i>0 && (charactersInternal[i][0]!=lastalign || + charactersInternal[i][1]!=lasty) + # Found end of run + widthblocks.push([runstart,i,lastalign,totalwidth,lasty]) + runstart=i + totalwidth=0 + end + lastalign=charactersInternal[i][0] + lasty=charactersInternal[i][1] + extraspace=(charactersInternal[i][4]) ? charactersInternal[i][4] : 0 + totalwidth+=c[3]-extraspace + end + widthblocks.push([runstart,characters.length,lastalign,totalwidth,lasty]) + if collapseAlignments + # Calculate the total width of each line + totalLineWidths=[] + for block in widthblocks + y=block[4] + if !totalLineWidths[y] + totalLineWidths[y]=0 + end + if totalLineWidths[y]!=0 + # padding in case more than one line has different alignments + totalLineWidths[y]+=16 + end + totalLineWidths[y]+=block[3] + end + # Calculate a new width for the next step + widthDst=[widthDst,(totalLineWidths.compact.max || 0)].min + end + # Now, based on the text runs found, recalculate Xs + for block in widthblocks + next if block[0]>=block[1] + for i in block[0]...block[1] + case block[2] + when 1; characters[i][1]=xDst+(widthDst-block[3]-4)+characters[i][1] + when 2; characters[i][1]=xDst+((widthDst/2)-(block[3]/2))+characters[i][1] + else; characters[i][1]=xDst+characters[i][1] + end + end + end + # Remove all characters with Y greater or equal to _yDst_+_heightDst_ + if heightDst>=0 + for i in 0...characters.length + if characters[i][2]>=yDst+heightDst + characters[i]=nil + end + end + characters.compact! + end + bitmap.font=oldfont + dummybitmap.dispose if dummybitmap + return characters +end + + + +#=============================================================================== +# Draw text and images on a bitmap +#=============================================================================== +def getLineBrokenChunks(bitmap,value,width,dims,plain=false) + x=0 + y=0 + textheight=0 + ret=[] + if dims + dims[0]=0 + dims[1]=0 + end + re=/]+)>/ + reNoMatch=/]+>/ + return ret if !bitmap || bitmap.disposed? || width<=0 + textmsg=value.clone + lines=0 + color=Font.default_color + while (c = textmsg.slice!(/\n|[^ \r\t\f\n\-]*\-+|(\S*([ \r\t\f]?))/)) != nil + break if c=="" + ccheck=c + if ccheck=="\n" + x=0 +# y+=(textheight==0) ? bitmap.text_size("X").height : textheight + y+=(textheight==0) ? bitmap.text_size("X").height+1 : textheight + textheight=0 + next + end + if ccheck[/0 && x+textwidth>width + minTextSize=bitmap.text_size(word.gsub(/\s*/,"")) + if x>0 && x+minTextSize.width>width + x=0 +# y+=32 # (textheight==0) ? bitmap.text_size("X").height : textheight + y+=(textheight==0) ? bitmap.text_size("X").height+1 : textheight + textheight=0 + end + end +# textheight=32 # [textheight,textSize.height].max + textheight=[textheight,textSize.height+1].max + ret.push([word,x,y,textwidth,textheight,color]) + x+=textwidth + dims[0]=x if dims && dims[0]"+text + chars=getFormattedText(bitmap,x,y,width,-1,text,lineheight) + drawFormattedChars(bitmap,chars) +end + +# Deprecated -- not to be used in new code +def coloredToFormattedText(text,baseColor=nil,shadowColor=nil) + base=!baseColor ? Color.new(12*8,12*8,12*8) : baseColor.clone + shadow=!shadowColor ? Color.new(26*8,26*8,25*8) : shadowColor.clone + text2=text.gsub(/&/,"&") + text2.gsub!(//,">") + text2.gsub!(/\\\[([A-Fa-f0-9]{8,8})\]/) { "" } + text2=""+text2 + text2.gsub!(/\\\\/,"\\") + return text2 +end + +# Deprecated -- not to be used in new code +def drawColoredTextEx(bitmap,x,y,width,text,baseColor=nil,shadowColor=nil) + chars=getFormattedText(bitmap,x,y,width,-1,coloredToFormattedText(text),32) + drawFormattedChars(bitmap,chars) +end + +def pbDrawShadow(bitmap,x,y,width,height,string) + return if !bitmap || !string + pbDrawShadowText(bitmap,x,y,width,height,string,nil,bitmap.font.color) +end + +def pbDrawShadowText(bitmap,x,y,width,height,string,baseColor,shadowColor=nil,align=0) + return if !bitmap || !string + width=(width<0) ? bitmap.text_size(string).width+4 : width + height=(height<0) ? bitmap.text_size(string).height+4 : height + if shadowColor && shadowColor.alpha>0 + bitmap.font.color=shadowColor + bitmap.draw_text(x+2,y,width,height,string,align) + bitmap.draw_text(x,y+2,width,height,string,align) + bitmap.draw_text(x+2,y+2,width,height,string,align) + end + if baseColor && baseColor.alpha>0 + bitmap.font.color=baseColor + bitmap.draw_text(x,y,width,height,string,align) + end +end + +def pbDrawOutlineText(bitmap,x,y,width,height,string,baseColor,shadowColor=nil,align=0) + return if !bitmap || !string + width=(width<0) ? bitmap.text_size(string).width+4 : width + height=(height<0) ? bitmap.text_size(string).height+4 : height + if shadowColor && shadowColor.alpha>0 + bitmap.font.color=shadowColor + bitmap.draw_text(x-2,y-2,width,height,string,align) + bitmap.draw_text(x,y-2,width,height,string,align) + bitmap.draw_text(x+2,y-2,width,height,string,align) + bitmap.draw_text(x-2,y,width,height,string,align) + bitmap.draw_text(x+2,y,width,height,string,align) + bitmap.draw_text(x-2,y+2,width,height,string,align) + bitmap.draw_text(x,y+2,width,height,string,align) + bitmap.draw_text(x+2,y+2,width,height,string,align) + end + if baseColor && baseColor.alpha>0 + bitmap.font.color=baseColor + bitmap.draw_text(x,y,width,height,string,align) + end +end + +# Draws text on a bitmap. _textpos_ is an array of text commands. Each text +# command is an array that contains the following: +# 0 - Text to draw +# 1 - X coordinate +# 2 - Y coordinate +# 3 - If true or 1, the text is right aligned. If 2, the text is centered. +# Otherwise, the text is left aligned. +# 4 - Base color +# 5 - Shadow color +# 6 - If true or 1, the text has an outline. Otherwise, the text has a shadow. +def pbDrawTextPositions(bitmap,textpos) + for i in textpos + textsize = bitmap.text_size(i[0]) + x = i[1] + y = i[2] + if i[3]==true || i[3]==1 # right align + x -= textsize.width + elsif i[3]==2 # centered + x -= (textsize.width/2) + end + if i[6]==true || i[6]==1 # outline text + pbDrawOutlineText(bitmap,x,y,textsize.width,textsize.height,i[0],i[4],i[5]) + else + pbDrawShadowText(bitmap,x,y,textsize.width,textsize.height,i[0],i[4],i[5]) + end + end +end + + + +#=============================================================================== +# Draw images on a bitmap +#=============================================================================== +def pbCopyBitmap(dstbm,srcbm,x,y,opacity=255) + rc = Rect.new(0,0,srcbm.width,srcbm.height) + dstbm.blt(x,y,srcbm,rc,opacity) +end + +def pbDrawImagePositions(bitmap,textpos) + for i in textpos + srcbitmap=AnimatedBitmap.new(pbBitmapName(i[0])) + x=i[1] + y=i[2] + srcx=i[3] || 0 + srcy=i[4] || 0 + width=(i[5] && i[5]>=0) ? i[5] : srcbitmap.width + height=(i[6] && i[6]>=0) ? i[6] : srcbitmap.height + srcrect=Rect.new(srcx,srcy,width,height) + bitmap.blt(x,y,srcbitmap.bitmap,srcrect) + srcbitmap.dispose + end +end \ No newline at end of file diff --git a/Data/Scripts/008_Objects and windows/008_Messages.rb b/Data/Scripts/008_Objects and windows/008_Messages.rb new file mode 100644 index 000000000..2b4a19379 --- /dev/null +++ b/Data/Scripts/008_Objects and windows/008_Messages.rb @@ -0,0 +1,1431 @@ +#=============================================================================== +# Message variables +#=============================================================================== +class Game_Temp + attr_accessor :background + attr_writer :message_window_showing + attr_writer :player_transferring + attr_writer :transition_processing + + def message_window_showing + return @message_window_showing || false + end + + def player_transferring + return @player_transferring || false + end + + def transition_processing + return @transition_processing || false + end +end + + + +class Game_Message + attr_writer :background + attr_writer :visible + + def visible + return @visible || false + end + + def background + return @background || 0 + end +end + + + +class Game_System + attr_writer :message_position + + def message_position + return @message_position || 2 + end +end + + + +#=============================================================================== +# +#=============================================================================== +class Scene_Map + def updatemini + oldmws=$game_temp.message_window_showing + oldvis=$game_message ? $game_message.visible : false + $game_temp.message_window_showing=true + $game_message.visible=true if $game_message + loop do + $game_map.update + $game_player.update + $game_system.update + if $game_screen + $game_screen.update + else + $game_map.screen.update + end + break unless $game_temp.player_transferring + transfer_player + break if $game_temp.transition_processing + end + $game_temp.message_window_showing=oldmws + $game_message.visible=oldvis if $game_message + @spriteset.update if @spriteset + @message_window.update if @message_window + end +end + + + +class Scene_Battle + def updatemini + if self.respond_to?("update_basic") + update_basic(true) + update_info_viewport # Update information viewport + if $game_message && $game_message.visible + @info_viewport.visible = false + @message_window.visible = true + end + else + oldmws=$game_temp.message_window_showing + $game_temp.message_window_showing=true + # Update system (timer) and screen + $game_system.update + if $game_screen + $game_screen.update + else + $game_map.screen.update + end + # If timer has reached 0 + if $game_system.timer_working and $game_system.timer == 0 + # Abort battle + $game_temp.battle_abort = true + end + # Update windows + @help_window.update if @help_window + @party_command_window.update if @party_command_window + @actor_command_window.update if @actor_command_window + @status_window.update if @status_window + $game_temp.message_window_showing=oldmws + @message_window.update if @message_window + # Update sprite set + @spriteset.update if @spriteset + end + end +end + + + +def pbMapInterpreter + if $game_map.respond_to?("interpreter") + return $game_map.interpreter + elsif $game_system + return $game_system.map_interpreter + end + return nil +end + +def pbMapInterpreterRunning? + interp = pbMapInterpreter + return interp && interp.running? +end + +def pbRefreshSceneMap + if $scene && $scene.is_a?(Scene_Map) + if $scene.respond_to?("miniupdate") + $scene.miniupdate + else + $scene.updatemini + end + elsif $scene && $scene.is_a?(Scene_Battle) + $scene.updatemini + end +end + +def pbUpdateSceneMap + if $scene && $scene.is_a?(Scene_Map) && !pbIsFaded? + if $scene.respond_to?("miniupdate") + $scene.miniupdate + else + $scene.updatemini + end + elsif $scene && $scene.is_a?(Scene_Battle) + $scene.updatemini + end +end + + + +#=============================================================================== +# +#=============================================================================== +def pbEventCommentInput(*args) + parameters = [] + list = *args[0].list # Event or event page + elements = *args[1] # Number of elements + trigger = *args[2] # Trigger + return nil if list == nil + return nil unless list.is_a?(Array) + for item in list + next unless item.code == 108 || item.code == 408 + if item.parameters[0] == trigger + start = list.index(item) + 1 + finish = start + elements + for id in start...finish + next if !list[id] + parameters.push(list[id].parameters[0]) + end + return parameters + end + end + return nil +end + +def pbCurrentEventCommentInput(elements,trigger) + return nil if !pbMapInterpreterRunning? + event = pbMapInterpreter.get_character(0) + return nil if !event + return pbEventCommentInput(event,elements,trigger) +end + +def pbButtonInputProcessing(variableNumber=0,timeoutFrames=0) + ret=0 + timeoutFrames = timeoutFrames*Graphics.frame_rate/20 + loop do + Graphics.update + Input.update + pbUpdateSceneMap + for i in 1..18 + ret=i if Input.trigger?(i) + end + break if ret!=0 + if timeoutFrames>0 + i+=1 + break if i>=timeoutFrames + end + end + Input.update + if variableNumber && variableNumber>0 + $game_variables[variableNumber]=ret + $game_map.need_refresh = true if $game_map + end + return ret +end + + + +#=============================================================================== +# Interpreter functions for displaying messages +#=============================================================================== +module InterpreterMixin + # Freezes all events on the map (for use at the beginning of common events) + def pbGlobalLock + for event in $game_map.events.values + event.minilock + end + end + + # Unfreezes all events on the map (for use at the end of common events) + def pbGlobalUnlock + for event in $game_map.events.values + event.unlock + end + end + + def pbRepeatAbove(index) + index=@list[index].indent + loop do + index-=1 + return index+1 if @list[index].indent==indent + end + end + + def pbBreakLoop(index) + indent = @list[index].indent + temp_index=index + # Copy index to temporary variables + loop do + # Advance index + temp_index += 1 + # If a fitting loop was not found + return index+1 if temp_index >= @list.size-1 + return temp_index+1 if @list[temp_index].code == 413 and + @list[temp_index].indent < indent + end + end + + def pbJumpToLabel(index,label_name) + temp_index = 0 + loop do + return index+1 if temp_index >= @list.size-1 + return temp_index+1 if @list[temp_index].code == 118 and + @list[temp_index].parameters[0] == label_name + temp_index += 1 + end + end + + # Gets the next index in the interpreter, ignoring + # certain events between messages + def pbNextIndex(index) + return -1 if !@list || @list.length==0 + i=index+1 + loop do + return i if i>=@list.length-1 + case @list[i].code + when 118, 108, 408 # Label, Comment + i+=1 + when 413 # Repeat Above + i=pbRepeatAbove(i) + when 113 # Break Loop + i=pbBreakLoop(i) + when 119 # Jump to Label + newI=pbJumpToLabel(i,@list[i].parameters[0]) + i = (newI>i) ? newI : i+1 + else + return i + end + end + end + + # Helper function that shows a picture in a script. To be used in + # a script event command. + def pbShowPicture(number,name,origin,x,y,zoomX=100,zoomY=100,opacity=255,blendType=0) + number = number + ($game_temp.in_battle ? 50 : 0) + $game_screen.pictures[number].show(name,origin, + x, y, zoomX,zoomY,opacity,blendType) + end + + # Erases an event and adds it to the list of erased events so that + # it can stay erased when the game is saved then loaded again. To be used in + # a script event command. + def pbEraseThisEvent + if $game_map.events[@event_id] + $game_map.events[@event_id].erase + $PokemonMap.addErasedEvent(@event_id) if $PokemonMap + end + @index+=1 + return true + end + + # Runs a common event. To be used in a script event command. + def pbCommonEvent(id) + common_event = $data_common_events[id] + if $game_temp.in_battle + if common_event != nil + interp = Interpreter.new + interp.setup(common_event.list,0) + begin + Graphics.update + Input.update + interp.update + pbUpdateSceneMap + end while interp.running? + end + else + $game_system.battle_interpreter.setup(common_event.list, 0) + end + end + + # Sets another event's self switch (eg. pbSetSelfSwitch(20,"A",true) ). + # To be used in a script event command. + def pbSetSelfSwitch(event,swtch,value,mapid=-1) + mapid = @map_id if mapid<0 + oldValue = $game_self_switches[[mapid,event,swtch]] + $game_self_switches[[mapid,event,swtch]] = value + if value!=oldValue && $MapFactory.hasMap?(mapid) + $MapFactory.getMap(mapid,false).need_refresh = true + end + end + + # Must use this approach to share the methods because the methods already + # defined in a class override those defined in an included module + CustomEventCommands=<<_END_ + + def command_242 + pbBGMFade(pbParams[0]) + return true + end + + def command_246 + pbBGSFade(pbParams[0]) + return true + end + + def command_251 + pbSEStop + return true + end + + def command_241 + pbBGMPlay(pbParams[0]) + return true + end + + def command_245 + pbBGSPlay(pbParams[0]) + return true + end + + def command_249 + pbMEPlay(pbParams[0]) + return true + end + + def command_250 + pbSEPlay(pbParams[0]) + return true + end +_END_ +end + + + +class Game_Interpreter # Used by RMVX + include InterpreterMixin + eval(InterpreterMixin::CustomEventCommands) + @@immediateDisplayAfterWait=false + @buttonInput=false + + def pbParams + return @params + end + + def command_105 + return false if @buttonInput + @buttonInput=true + pbButtonInputProcessing(@list[@index].parameters[0]) + @buttonInput=false + @index+=1 + return true + end + + def command_101 + if $game_temp.message_window_showing + return false + end + $game_message=Game_Message.new if !$game_message + message="" + commands=nil + numInputVar=nil + numInputDigitsMax=nil + text="" + facename=@list[@index].parameters[0] + faceindex=@list[@index].parameters[1] + if facename && facename!="" + text+="\\ff[#{facename},#{faceindex}]" + end + if $game_message + $game_message.background=@list[@index].parameters[2] + end + $game_system.message_position=@list[@index].parameters[3] + message+=text + messageend="" + loop do + nextIndex=pbNextIndex(@index) + code=@list[nextIndex].code + if code == 401 + text=@list[nextIndex].parameters[0] + text+=" " if text!="" && text[text.length-1,1]!=" " + message+=text + @index=nextIndex + else + if code == 102 + commands=@list[nextIndex].parameters + @index=nextIndex + elsif code == 106 && @@immediateDisplayAfterWait + params=@list[nextIndex].parameters + if params[0]<=10 + nextcode=@list[nextIndex+1].code + if nextcode==101||nextcode==102||nextcode==103 + @index=nextIndex + else + break + end + else + break + end + elsif code == 103 + numInputVar=@list[nextIndex].parameters[0] + numInputDigitsMax=@list[nextIndex].parameters[1] + @index=nextIndex + elsif code == 101 + messageend="\1" + end + break + end + end + message=_MAPINTL($game_map.map_id,message) + @message_waiting=true + if commands + cmdlist=[] + for cmd in commands[0] + cmdlist.push(_MAPINTL($game_map.map_id,cmd)) + end + command=pbMessage(message+messageend,cmdlist,commands[1]) + @branch[@list[@index].indent] = command + elsif numInputVar + params=ChooseNumberParams.new + params.setMaxDigits(numInputDigitsMax) + params.setDefaultValue($game_variables[numInputVar]) + $game_variables[numInputVar]=pbMessageChooseNumber(message+messageend,params) + $game_map.need_refresh = true if $game_map + else + pbMessage(message+messageend) + end + @message_waiting=false + return true + end + + def command_102 + @message_waiting=true + command=pbShowCommands(nil,@list[@index].parameters[0],@list[@index].parameters[1]) + @message_waiting=false + @branch[@list[@index].indent] = command + Input.update # Must call Input.update again to avoid extra triggers + return true + end + + def command_103 + varnumber=@list[@index].parameters[0] + @message_waiting=true + params=ChooseNumberParams.new + params.setMaxDigits(@list[@index].parameters[1]) + params.setDefaultValue($game_variables[varnumber]) + $game_variables[varnumber]=pbChooseNumber(nil,params) + $game_map.need_refresh = true if $game_map + @message_waiting=false + return true + end +end + + + +class Interpreter # Used by RMXP + include InterpreterMixin + eval(InterpreterMixin::CustomEventCommands) + @@immediateDisplayAfterWait=false + @buttonInput=false + + def pbParams + return @parameters + end + + def command_105 + return false if @buttonInput + @buttonInput=true + pbButtonInputProcessing(@list[@index].parameters[0]) + @buttonInput=false + @index+=1 + return true + end + + def command_101 + if $game_temp.message_window_showing + return false + end + message="" + commands=nil + numInputVar=nil + numInputDigitsMax=nil + text="" + firstText=nil + if @list[@index].parameters.length==1 + text+=@list[@index].parameters[0] + firstText=@list[@index].parameters[0] + text+=" " if text[text.length-1,1]!=" " + message+=text + else + facename=@list[@index].parameters[0] + faceindex=@list[@index].parameters[1] + if facename && facename!="" + text+="\\ff[#{facename},#{faceindex}]" + message+=text + end + end + messageend="" + loop do + nextIndex=pbNextIndex(@index) + code=@list[nextIndex].code + if code == 401 + text=@list[nextIndex].parameters[0] + text+=" " if text[text.length-1,1]!=" " + message+=text + @index=nextIndex + else + if code == 102 + commands=@list[nextIndex].parameters + @index=nextIndex + elsif code == 106 && @@immediateDisplayAfterWait + params=@list[nextIndex].parameters + if params[0]<=10 + nextcode=@list[nextIndex+1].code + if nextcode==101 || nextcode==102 || nextcode==103 + @index=nextIndex + else + break + end + else + break + end + elsif code == 103 + numInputVar=@list[nextIndex].parameters[0] + numInputDigitsMax=@list[nextIndex].parameters[1] + @index=nextIndex + elsif code == 101 + if @list[@index].parameters.length==1 + text=@list[@index].parameters[0] + if text[/\A\\ignr/] && text==firstText + text+=" " if text[text.length-1,1]!=" " + message+=text + @index=nextIndex + continue + end + end + messageend="\1" + end + break + end + end + @message_waiting=true # needed to allow parallel process events to work while + # a message is displayed + message=_MAPINTL($game_map.map_id,message) + if commands + cmdlist=[] + for cmd in commands[0] + cmdlist.push(_MAPINTL($game_map.map_id,cmd)) + end + command=pbMessage(message+messageend,cmdlist,commands[1]) + @branch[@list[@index].indent] = command + elsif numInputVar + params=ChooseNumberParams.new + params.setMaxDigits(numInputDigitsMax) + params.setDefaultValue($game_variables[numInputVar]) + $game_variables[numInputVar]=pbMessageChooseNumber(message+messageend,params) + $game_map.need_refresh = true if $game_map + else + pbMessage(message+messageend,nil) + end + @message_waiting=false + return true + end + + def command_102 + @message_waiting=true + command=pbShowCommands(nil,@list[@index].parameters[0],@list[@index].parameters[1]) + @message_waiting=false + @branch[@list[@index].indent] = command + Input.update # Must call Input.update again to avoid extra triggers + return true + end + + def command_103 + varnumber=@list[@index].parameters[0] + @message_waiting=true + params=ChooseNumberParams.new + params.setMaxDigits(@list[@index].parameters[1]) + params.setDefaultValue($game_variables[varnumber]) + $game_variables[varnumber]=pbChooseNumber(nil,params) + $game_map.need_refresh = true if $game_map + @message_waiting=false + return true + end +end + + + +#=============================================================================== +# +#=============================================================================== +class ChooseNumberParams + def initialize + @maxDigits=0 + @minNumber=0 + @maxNumber=0 + @skin=nil + @messageSkin=nil + @negativesAllowed=false + @initialNumber=0 + @cancelNumber=nil + end + + def setMessageSkin(value) + @messageSkin=value + end + + def messageSkin # Set the full path for the message's window skin + @messageSkin + end + + def setSkin(value) + @skin=value + end + + def skin + @skin + end + + def setNegativesAllowed(value) + @negativeAllowed=value + end + + def negativesAllowed + @negativeAllowed ? true : false + end + + def setRange(minNumber,maxNumber) + maxNumber=minNumber if minNumber>maxNumber + @maxDigits=0 + @minNumber=minNumber + @maxNumber=maxNumber + end + + def setDefaultValue(number) + @initialNumber=number + @cancelNumber=nil + end + + def setInitialValue(number) + @initialNumber=number + end + + def setCancelValue(number) + @cancelNumber=number + end + + def initialNumber + return clamp(@initialNumber,self.minNumber,self.maxNumber) + end + + def cancelNumber + return @cancelNumber || self.initialNumber + end + + def minNumber + ret=0 + if @maxDigits>0 + ret=-((10**@maxDigits)-1) + elsif + ret=@minNumber + end + ret=0 if !@negativeAllowed && ret<0 + return ret + end + + def maxNumber + ret=0 + if @maxDigits>0 + ret=((10**@maxDigits)-1) + elsif + ret=@maxNumber + end + ret=0 if !@negativeAllowed && ret<0 + return ret + end + + def setMaxDigits(value) + @maxDigits=[1,value].max + end + + def maxDigits + if @maxDigits>0 + return @maxDigits + else + return [numDigits(self.minNumber),numDigits(self.maxNumber)].max + end + end + + private + + def clamp(v,mn,mx) + return vmx ? mx : v) + end + + def numDigits(number) + ans = 1 + number=number.abs + while number >= 10 + ans+=1 + number/=10 + end + return ans + end +end + + + +def pbChooseNumber(msgwindow,params) + return 0 if !params + ret=0 + maximum=params.maxNumber + minimum=params.minNumber + defaultNumber=params.initialNumber + cancelNumber=params.cancelNumber + cmdwindow=Window_InputNumberPokemon.new(params.maxDigits) + cmdwindow.z=99999 + cmdwindow.visible=true + cmdwindow.setSkin(params.skin) if params.skin + cmdwindow.sign=params.negativesAllowed # must be set before number + cmdwindow.number=defaultNumber + curnumber=defaultNumber + pbPositionNearMsgWindow(cmdwindow,msgwindow,:right) + command=0 + loop do + Graphics.update + Input.update + pbUpdateSceneMap + cmdwindow.update + msgwindow.update if msgwindow + yield if block_given? + if Input.trigger?(Input::C) + ret=cmdwindow.number + if ret>maximum + pbPlayBuzzerSE() + elsif ret1 + @facebitmaptmp.update + @facebitmap.blt(0,0,@facebitmaptmp.bitmap,Rect.new( + (@faceIndex % 4) * 96, + (@faceIndex / 4) * 96, 96, 96 + )) + end + end + + def dispose + @facebitmaptmp.dispose + @facebitmap.dispose if @facebitmap + super + end +end + + + +#=============================================================================== +# +#=============================================================================== +def pbGetBasicMapNameFromId(id) + begin + map = pbLoadRxData("Data/MapInfos") + return "" if !map + return map[id].name + rescue + return "" + end +end + +def pbGetMapNameFromId(id) + map=pbGetBasicMapNameFromId(id) + map.gsub!(/\\PN/,$Trainer.name) if $Trainer + return map +end + +def pbCsvField!(str) + ret="" + str.sub!(/\A\s*/,"") + if str[0,1]=="\"" + str[0,1]="" + escaped=false + fieldbytes=0 + str.scan(/./) do |s| + fieldbytes+=s.length + break if s=="\"" && !escaped + if s=="\\" && !escaped + escaped=true + else + ret+=s + escaped=false + end + end + str[0,fieldbytes]="" + if !str[/\A\s*,/] && !str[/\A\s*$/] + raise _INTL("Invalid quoted field (in: {1})",ret) + end + str[0,str.length]=$~.post_match + else + if str[/,/] + str[0,str.length]=$~.post_match + ret=$~.pre_match + else + ret=str.clone + str[0,str.length]="" + end + ret.gsub!(/\s+$/,"") + end + return ret +end + +def pbCsvPosInt!(str) + ret=pbCsvField!(str) + if !ret[/\A\d+$/] + raise _INTL("Field {1} is not a positive integer",ret) + end + return ret.to_i +end + + + +#=============================================================================== +# Money and coins windows +#=============================================================================== +def pbGetGoldString + moneyString="" + begin + moneyString=_INTL("${1}",$Trainer.money.to_s_formatted) + rescue + if $data_system.respond_to?("words") + moneyString=_INTL("{1} {2}",$game_party.gold,$data_system.words.gold) + else + moneyString=_INTL("{1} {2}",$game_party.gold,Vocab.gold) + end + end + return moneyString +end + +def pbDisplayGoldWindow(msgwindow) + moneyString=pbGetGoldString() + goldwindow=Window_AdvancedTextPokemon.new(_INTL("Money:\n{1}",moneyString)) + goldwindow.setSkin("Graphics/Windowskins/goldskin") + goldwindow.resizeToFit(goldwindow.text,Graphics.width) + goldwindow.width=160 if goldwindow.width<=160 + if msgwindow.y==0 + goldwindow.y=Graphics.height-goldwindow.height + else + goldwindow.y=0 + end + goldwindow.viewport=msgwindow.viewport + goldwindow.z=msgwindow.z + return goldwindow +end + +def pbDisplayCoinsWindow(msgwindow,goldwindow) + coinString=($PokemonGlobal) ? $PokemonGlobal.coins.to_s_formatted : "0" + coinwindow=Window_AdvancedTextPokemon.new(_INTL("Coins:\n{1}",coinString)) + coinwindow.setSkin("Graphics/Windowskins/goldskin") + coinwindow.resizeToFit(coinwindow.text,Graphics.width) + coinwindow.width=160 if coinwindow.width<=160 + if msgwindow.y==0 + coinwindow.y=(goldwindow) ? goldwindow.y-coinwindow.height : Graphics.height-coinwindow.height + else + coinwindow.y=(goldwindow) ? goldwindow.height : 0 + end + coinwindow.viewport=msgwindow.viewport + coinwindow.z=msgwindow.z + return coinwindow +end + + + +#=============================================================================== +# +#=============================================================================== +def pbCreateStatusWindow(viewport=nil) + msgwindow=Window_AdvancedTextPokemon.new("") + if !viewport + msgwindow.z=99999 + else + msgwindow.viewport=viewport + end + msgwindow.visible=false + msgwindow.letterbyletter=false + pbBottomLeftLines(msgwindow,2) + skinfile=MessageConfig.pbGetSpeechFrame() + msgwindow.setSkin(skinfile) + return msgwindow +end + +def pbCreateMessageWindow(viewport=nil,skin=nil) + msgwindow=Window_AdvancedTextPokemon.new("") + if !viewport + msgwindow.z=99999 + else + msgwindow.viewport=viewport + end + msgwindow.visible=true + msgwindow.letterbyletter=true + msgwindow.back_opacity=MessageConfig::WindowOpacity + pbBottomLeftLines(msgwindow,2) + $game_temp.message_window_showing=true if $game_temp + $game_message.visible=true if $game_message + skin=MessageConfig.pbGetSpeechFrame() if !skin + msgwindow.setSkin(skin) + return msgwindow +end + +def pbDisposeMessageWindow(msgwindow) + $game_temp.message_window_showing=false if $game_temp + $game_message.visible=false if $game_message + msgwindow.dispose +end + + + +#=============================================================================== +# Main message-displaying function +#=============================================================================== +def pbMessageDisplay(msgwindow,message,letterbyletter=true,commandProc=nil) + return if !msgwindow + oldletterbyletter=msgwindow.letterbyletter + msgwindow.letterbyletter=(letterbyletter) ? true : false + ret=nil + count=0 + commands=nil + facewindow=nil + goldwindow=nil + coinwindow=nil + cmdvariable=0 + cmdIfCancel=0 + msgwindow.waitcount=0 + autoresume=false + text=message.clone + msgback=nil + linecount=(Graphics.height>400) ? 3 : 2 + ### Text replacement + text.gsub!(/\\sign\[([^\]]*)\]/i) { # \sign[something] gets turned into + next "\\op\\cl\\ts[]\\w["+$1+"]" # \op\cl\ts[]\w[something] + } + text.gsub!(/\\\\/,"\5") + text.gsub!(/\\1/,"\1") + if $game_actors + text.gsub!(/\\n\[([1-8])\]/i) { + m = $1.to_i + next $game_actors[m].name + } + end + text.gsub!(/\\pn/i,$Trainer.name) if $Trainer + text.gsub!(/\\pm/i,_INTL("${1}",$Trainer.money.to_s_formatted)) if $Trainer + text.gsub!(/\\n/i,"\n") + text.gsub!(/\\\[([0-9a-f]{8,8})\]/i) { "" } + text.gsub!(/\\pg/i,"\\b") if $Trainer && $Trainer.male? + text.gsub!(/\\pg/i,"\\r") if $Trainer && $Trainer.female? + text.gsub!(/\\pog/i,"\\r") if $Trainer && $Trainer.male? + text.gsub!(/\\pog/i,"\\b") if $Trainer && $Trainer.female? + text.gsub!(/\\pg/i,"") + text.gsub!(/\\pog/i,"") + text.gsub!(/\\b/i,"") + text.gsub!(/\\r/i,"") + isDarkSkin = isDarkWindowskin(msgwindow.windowskin) + text.gsub!(/\\[Cc]\[([0-9]+)\]/) { + m = $1.to_i + next getSkinColor(msgwindow.windowskin,m,isDarkSkin) + } + begin + last_text = text.clone + text.gsub!(/\\v\[([0-9]+)\]/i) { $game_variables[$1.to_i] } + end until text == last_text + begin + last_text = text.clone + text.gsub!(/\\l\[([0-9]+)\]/i) { + linecount = [1,$1.to_i].max + next "" + } + end until text == last_text + colortag = "" + if ($game_message && $game_message.background>0) || + ($game_system && $game_system.respond_to?("message_frame") && + $game_system.message_frame != 0) + colortag = getSkinColor(msgwindow.windowskin,0,true) + else + colortag = getSkinColor(msgwindow.windowskin,0,isDarkSkin) + end + text = colortag+text + ### Controls + textchunks=[] + controls=[] + while text[/(?:\\(w|f|ff|ts|cl|me|se|wt|wtnp|ch)\[([^\]]*)\]|\\(g|cn|wd|wm|op|cl|wu|\.|\||\!|\^))/i] + textchunks.push($~.pre_match) + if $~[1] + controls.push([$~[1].downcase,$~[2],-1]) + else + controls.push([$~[3].downcase,"",-1]) + end + text=$~.post_match + end + textchunks.push(text) + for chunk in textchunks + chunk.gsub!(/\005/,"\\") + end + textlen = 0 + for i in 0...controls.length + control = controls[i][0] + case control + when "wt", "wtnp", ".", "|" + textchunks[i] += "\2" + when "!" + textchunks[i] += "\1" + end + textlen += toUnformattedText(textchunks[i]).scan(/./m).length + controls[i][2] = textlen + end + text = textchunks.join("") + unformattedText = toUnformattedText(text) + signWaitCount = 0 + signWaitTime = Graphics.frame_rate/2 + haveSpecialClose = false + specialCloseSE = "" + for i in 0...controls.length + control = controls[i][0] + param = controls[i][1] + case control + when "op" + signWaitCount = signWaitTime+1 + when "cl" + text = text.sub(/\001\z/,"") # fix: '$' can match end of line as well + haveSpecialClose = true + specialCloseSE = param + when "f" + facewindow.dispose if facewindow + facewindow = PictureWindow.new("Graphics/Pictures/#{param}") + when "ff" + facewindow.dispose if facewindow + facewindow = FaceWindowVX.new(param) + when "ch" + cmds = param.clone + cmdvariable = pbCsvPosInt!(cmds) + cmdIfCancel = pbCsvField!(cmds).to_i + commands = [] + while cmds.length>0 + commands.push(pbCsvField!(cmds)) + end + when "wtnp", "^" + text = text.sub(/\001\z/,"") # fix: '$' can match end of line as well + when "se" + if controls[i][2]==0 + startSE = param + controls[i] = nil + end + end + end + if startSE!=nil + pbSEPlay(pbStringToAudioFile(startSE)) + elsif signWaitCount==0 && letterbyletter + pbPlayDecisionSE() + end + ########## Position message window ############## + pbRepositionMessageWindow(msgwindow,linecount) + if $game_message && $game_message.background==1 + msgback = IconSprite.new(0,msgwindow.y,msgwindow.viewport) + msgback.z = msgwindow.z-1 + msgback.setBitmap("Graphics/System/MessageBack") + end + if facewindow + pbPositionNearMsgWindow(facewindow,msgwindow,:left) + facewindow.viewport = msgwindow.viewport + facewindow.z = msgwindow.z + end + atTop = (msgwindow.y==0) + ########## Show text ############################# + msgwindow.text = text + Graphics.frame_reset if Graphics.frame_rate>40 + begin + if signWaitCount>0 + signWaitCount -= 1 + if atTop + msgwindow.y = -msgwindow.height*signWaitCount/signWaitTime + else + msgwindow.y = Graphics.height-msgwindow.height*(signWaitTime-signWaitCount)/signWaitTime + end + end + for i in 0...controls.length + next if !controls[i] + next if controls[i][2]>msgwindow.position || msgwindow.waitcount!=0 + control = controls[i][0] + param = controls[i][1] + case control + when "f" + facewindow.dispose if facewindow + facewindow = PictureWindow.new("Graphics/Pictures/#{param}") + pbPositionNearMsgWindow(facewindow,msgwindow,:left) + facewindow.viewport = msgwindow.viewport + facewindow.z = msgwindow.z + when "ff" + facewindow.dispose if facewindow + facewindow = FaceWindowVX.new(param) + pbPositionNearMsgWindow(facewindow,msgwindow,:left) + facewindow.viewport = msgwindow.viewport + facewindow.z = msgwindow.z + when "g" # Display gold window + goldwindow.dispose if goldwindow + goldwindow = pbDisplayGoldWindow(msgwindow) + when "cn" # Display coins window + coinwindow.dispose if coinwindow + coinwindow = pbDisplayCoinsWindow(msgwindow,goldwindow) + when "wu" + msgwindow.y = 0 + atTop = true + msgback.y = msgwindow.y if msgback + pbPositionNearMsgWindow(facewindow,msgwindow,:left) + msgwindow.y = -msgwindow.height*signWaitCount/signWaitTime + when "wm" + atTop = false + msgwindow.y = (Graphics.height-msgwindow.height)/2 + msgback.y = msgwindow.y if msgback + pbPositionNearMsgWindow(facewindow,msgwindow,:left) + when "wd" + atTop = false + msgwindow.y = Graphics.height-msgwindow.height + msgback.y = msgwindow.y if msgback + pbPositionNearMsgWindow(facewindow,msgwindow,:left) + msgwindow.y = Graphics.height-msgwindow.height*(signWaitTime-signWaitCount)/signWaitTime + when "w" # Change windowskin + if param=="" + msgwindow.windowskin = nil + else + msgwindow.setSkin("Graphics/Windowskins/#{param}",false) + end + when "ts" # Change text speed + msgwindow.textspeed = (param=="") ? -999 : param.to_i + when "." # Wait 0.25 seconds + msgwindow.waitcount += Graphics.frame_rate/4 + when "|" # Wait 1 second + msgwindow.waitcount += Graphics.frame_rate + when "wt" # Wait X/20 seconds + param = param.sub(/\A\s+/,"").sub(/\s+\z/,"") + msgwindow.waitcount += param.to_i*Graphics.frame_rate/20 + when "wtnp" # Wait X/20 seconds, no pause + param = param.sub(/\A\s+/,"").sub(/\s+\z/,"") + msgwindow.waitcount = param.to_i*Graphics.frame_rate/20 + autoresume = true + when "^" # Wait, no pause + autoresume = true + when "se" # Play SE + pbSEPlay(pbStringToAudioFile(param)) + when "me" # Play ME + pbMEPlay(pbStringToAudioFile(param)) + end + controls[i] = nil + end + break if !letterbyletter + Graphics.update + Input.update + facewindow.update if facewindow + if $DEBUG && Input.trigger?(Input::F6) + pbRecord(unformattedText) + end + if autoresume && msgwindow.waitcount==0 + msgwindow.resume if msgwindow.busy? + break if !msgwindow.busy? + end + if Input.trigger?(Input::C) || Input.trigger?(Input::B) + if msgwindow.busy? + pbPlayDecisionSE if msgwindow.pausing? + msgwindow.resume + else + break if signWaitCount==0 + end + end + pbUpdateSceneMap + msgwindow.update + yield if block_given? + end until (!letterbyletter || commandProc || commands) && !msgwindow.busy? + Input.update # Must call Input.update again to avoid extra triggers + msgwindow.letterbyletter=oldletterbyletter + if commands + $game_variables[cmdvariable]=pbShowCommands(msgwindow,commands,cmdIfCancel) + $game_map.need_refresh = true if $game_map + end + if commandProc + ret=commandProc.call(msgwindow) + end + msgback.dispose if msgback + goldwindow.dispose if goldwindow + coinwindow.dispose if coinwindow + facewindow.dispose if facewindow + if haveSpecialClose + pbSEPlay(pbStringToAudioFile(specialCloseSE)) + atTop = (msgwindow.y==0) + for i in 0..signWaitTime + if atTop + msgwindow.y = -msgwindow.height*i/signWaitTime + else + msgwindow.y = Graphics.height-msgwindow.height*(signWaitTime-i)/signWaitTime + end + Graphics.update + Input.update + pbUpdateSceneMap + msgwindow.update + end + end + return ret +end + + + +#=============================================================================== +# Message-displaying functions +#=============================================================================== +def pbMessage(message,commands=nil,cmdIfCancel=0,skin=nil,defaultCmd=0,&block) + ret = 0 + msgwindow = pbCreateMessageWindow(nil,skin) + if commands + ret = pbMessageDisplay(msgwindow,message,true, + proc { |msgwindow| + next Kernel.pbShowCommands(msgwindow,commands,cmdIfCancel,defaultCmd,&block) + },&block) + else + pbMessageDisplay(msgwindow,message,&block) + end + pbDisposeMessageWindow(msgwindow) + Input.update + return ret +end + +def pbConfirmMessage(message,&block) + return (pbMessage(message,[_INTL("Yes"),_INTL("No")],2,&block)==0) +end + +def pbConfirmMessageSerious(message,&block) + return (pbMessage(message,[_INTL("No"),_INTL("Yes")],1,&block)==1) +end + +def pbMessageChooseNumber(message,params,&block) + msgwindow = pbCreateMessageWindow(nil,params.messageSkin) + ret = pbMessageDisplay(msgwindow,message,true, + proc { |msgwindow| + next pbChooseNumber(msgwindow,params,&block) + },&block) + pbDisposeMessageWindow(msgwindow) + return ret +end + +def pbShowCommands(msgwindow,commands=nil,cmdIfCancel=0,defaultCmd=0) + return 0 if !commands + cmdwindow=Window_CommandPokemonEx.new(commands) + cmdwindow.z=99999 + cmdwindow.visible=true + cmdwindow.resizeToFit(cmdwindow.commands) + pbPositionNearMsgWindow(cmdwindow,msgwindow,:right) + cmdwindow.index=defaultCmd + command=0 + loop do + Graphics.update + Input.update + cmdwindow.update + msgwindow.update if msgwindow + yield if block_given? + if Input.trigger?(Input::B) + if cmdIfCancel>0 + command=cmdIfCancel-1 + break + elsif cmdIfCancel<0 + command=cmdIfCancel + break + end + end + if Input.trigger?(Input::C) + command=cmdwindow.index + break + end + pbUpdateSceneMap + end + ret=command + cmdwindow.dispose + Input.update + return ret +end + +def pbShowCommandsWithHelp(msgwindow,commands,help,cmdIfCancel=0,defaultCmd=0) + msgwin=msgwindow + msgwin=pbCreateMessageWindow(nil) if !msgwindow + oldlbl=msgwin.letterbyletter + msgwin.letterbyletter=false + if commands + cmdwindow=Window_CommandPokemonEx.new(commands) + cmdwindow.z=99999 + cmdwindow.visible=true + cmdwindow.resizeToFit(cmdwindow.commands) + cmdwindow.height=msgwin.y if cmdwindow.height>msgwin.y + cmdwindow.index=defaultCmd + command=0 + msgwin.text=help[cmdwindow.index] + msgwin.width=msgwin.width # Necessary evil to make it use the proper margins + loop do + Graphics.update + Input.update + oldindex=cmdwindow.index + cmdwindow.update + if oldindex!=cmdwindow.index + msgwin.text=help[cmdwindow.index] + end + msgwin.update + yield if block_given? + if Input.trigger?(Input::B) + if cmdIfCancel>0 + command=cmdIfCancel-1 + break + elsif cmdIfCancel<0 + command=cmdIfCancel + break + end + end + if Input.trigger?(Input::C) + command=cmdwindow.index + break + end + pbUpdateSceneMap + end + ret=command + cmdwindow.dispose + Input.update + end + msgwin.letterbyletter=oldlbl + msgwin.dispose if !msgwindow + return ret +end + +# frames is the number of 1/20 seconds to wait for +def pbMessageWaitForInput(msgwindow,frames,showPause=false) + return if !frames || frames<=0 + msgwindow.startPause if msgwindow && showPause + frames = frames*Graphics.frame_rate/20 + frames.times do + Graphics.update + Input.update + msgwindow.update if msgwindow + pbUpdateSceneMap + if Input.trigger?(Input::C) || Input.trigger?(Input::B) + break + end + yield if block_given? + end + msgwindow.stopPause if msgwindow && showPause +end \ No newline at end of file diff --git a/Data/Scripts/008_Objects and windows/009_TextEntry.rb b/Data/Scripts/008_Objects and windows/009_TextEntry.rb new file mode 100644 index 000000000..d9b7bd11c --- /dev/null +++ b/Data/Scripts/008_Objects and windows/009_TextEntry.rb @@ -0,0 +1,1676 @@ +#=============================================================================== +# +#=============================================================================== +class Window_CharacterEntry < Window_DrawableCommand + XSIZE=13 + YSIZE=4 + + def initialize(charset,viewport=nil) + @viewport=viewport + @charset=charset + @othercharset="" + super(0,96,480,192) + colors=getDefaultTextColors(self.windowskin) + self.baseColor=colors[0] + self.shadowColor=colors[1] + self.columns=XSIZE + refresh + end + + def setOtherCharset(value) + @othercharset=value.clone + refresh + end + + def setCharset(value) + @charset=value.clone + refresh + end + + def character + if self.index<0 || self.index>=@charset.length + return ""; + else + return @charset[self.index] + end + end + + def command + return -1 if self.index==@charset.length + return -2 if self.index==@charset.length+1 + return -3 if self.index==@charset.length+2 + return self.index + end + + def itemCount + return @charset.length+3 + end + + def drawItem(index,count,rect) + rect=drawCursor(index,rect) + if index==@charset.length # -1 + pbDrawShadowText(self.contents,rect.x,rect.y,rect.width,rect.height,"[ ]", + self.baseColor,self.shadowColor) + elsif index==@charset.length+1 # -2 + pbDrawShadowText(self.contents,rect.x,rect.y,rect.width,rect.height,@othercharset, + self.baseColor,self.shadowColor) + elsif index==@charset.length+2 # -3 + pbDrawShadowText(self.contents,rect.x,rect.y,rect.width,rect.height,_INTL("OK"), + self.baseColor,self.shadowColor) + else + pbDrawShadowText(self.contents,rect.x,rect.y,rect.width,rect.height,@charset[index], + self.baseColor,self.shadowColor) + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +class CharacterEntryHelper + attr_reader :text + attr_reader :maxlength + attr_reader :passwordChar + attr_accessor :cursor + + def initialize(text) + @maxlength=-1 + @text=text + @passwordChar="" + @cursor=text.scan(/./m).length + ensure + end + + def text=(value) + @text=value + ensure + end + + def textChars + chars=text.scan(/./m) + if @passwordChar!="" + chars.length.times { |i| chars[i] = @passwordChar } + end + return chars + end + + def passwordChar=(value) + @passwordChar=value ? value : "" + end + + def maxlength=(value) + @maxlength=value + ensure + end + + def length + return self.text.scan(/./m).length + end + + def canInsert? + chars=self.text.scan(/./m) + return false if @maxlength>=0 && chars.length>=@maxlength + return true + end + + def insert(ch) + chars=self.text.scan(/./m) + return false if @maxlength>=0 && chars.length>=@maxlength + chars.insert(@cursor,ch) + @text="" + for ch in chars + @text+=ch if ch + end + @cursor+=1 + return true + end + + def canDelete? + chars=self.text.scan(/./m) + return false if chars.length<=0 || @cursor<=0 + return true + end + + def delete + chars=self.text.scan(/./m) + return false if chars.length<=0 || @cursor<=0 + chars.delete_at(@cursor-1) + @text="" + for ch in chars + @text+=ch if ch + end + @cursor-=1 + return true + end + + private + + def ensure + return if @maxlength<0 + chars=self.text.scan(/./m) + if chars.length>@maxlength && @maxlength>=0 + chars=chars[0,@maxlength] + end + @text="" + for ch in chars + @text+=ch if ch + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +class Window_TextEntry < SpriteWindow_Base + def initialize(text,x,y,width,height,heading=nil,usedarkercolor=false) + super(x,y,width,height) + colors=getDefaultTextColors(self.windowskin) + @baseColor=colors[0] + @shadowColor=colors[1] + if usedarkercolor + @baseColor=Color.new(16,24,32) + @shadowColor=Color.new(168,184,184) + end + @helper=CharacterEntryHelper.new(text) + @heading=heading + self.active=true + @frame=0 + refresh + end + + def text + @helper.text + end + + def maxlength + @helper.maxlength + end + + def passwordChar + @helper.passwordChar + end + + def text=(value) + @helper.text=value + self.refresh + end + + def passwordChar=(value) + @helper.passwordChar=value + refresh + end + + def maxlength=(value) + @helper.maxlength=value + self.refresh + end + + def insert(ch) + if @helper.insert(ch) + @frame=0 + self.refresh + return true + end + return false + end + + def delete + if @helper.delete + @frame=0 + self.refresh + return true + end + return false + end + + def update + @frame += 1 + @frame %= 20 + self.refresh if (@frame%10)==0 + return if !self.active + # Moving cursor + if Input.repeat?(Input::LEFT) && Input.press?(Input::A) + if @helper.cursor > 0 + @helper.cursor -= 1 + @frame = 0 + self.refresh + end + elsif Input.repeat?(Input::RIGHT) && Input.press?(Input::A) + if @helper.cursor < self.text.scan(/./m).length + @helper.cursor += 1 + @frame = 0 + self.refresh + end + elsif Input.repeat?(Input::B) # Backspace + self.delete if @helper.cursor > 0 + end + end + + def refresh + self.contents=pbDoEnsureBitmap(self.contents,self.width-self.borderX, + self.height-self.borderY) + bitmap=self.contents + bitmap.clear + x=0 + y=0 + if @heading + textwidth=bitmap.text_size(@heading).width + pbDrawShadowText(bitmap,x,y, textwidth+4, 32, @heading,@baseColor,@shadowColor) + y+=32 + end + x+=4 + width=self.width-self.borderX + height=self.height-self.borderY + cursorcolor=Color.new(16,24,32) + textscan=self.text.scan(/./m) + scanlength=textscan.length + @helper.cursor=scanlength if @helper.cursor>scanlength + @helper.cursor=0 if @helper.cursor<0 + startpos=@helper.cursor + fromcursor=0 + while (startpos>0) + c=(@helper.passwordChar!="") ? @helper.passwordChar : textscan[startpos-1] + fromcursor+=bitmap.text_size(c).width + break if fromcursor>width-4 + startpos-=1 + end + for i in startpos...scanlength + c=(@helper.passwordChar!="") ? @helper.passwordChar : textscan[i] + textwidth=bitmap.text_size(c).width + next if c=="\n" + # Draw text + pbDrawShadowText(bitmap,x,y, textwidth+4, 32, c,@baseColor,@shadowColor) + # Draw cursor if necessary + if ((@frame/10)&1) == 0 && i==@helper.cursor + bitmap.fill_rect(x,y+4,2,24,cursorcolor) + end + # Add x to drawn text width + x += textwidth + end + if ((@frame/10)&1) == 0 && textscan.length==@helper.cursor + bitmap.fill_rect(x,y+4,2,24,cursorcolor) + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +def getLineBrokenText(bitmap,value,width,dims) + x=0 + y=0 + textheight=0 + ret=[] + if dims + dims[0]=0 + dims[1]=0 + end + line=0 + position=0 + column=0 + return ret if !bitmap || bitmap.disposed? || width<=0 + textmsg=value.clone + lines=0 + color=Font.default_color + ret.push(["",0,0,0,bitmap.text_size("X").height,0,0,0,0]) + while ((c = textmsg.slice!(/\n|(\S*([ \r\t\f]?))/)) != nil) + break if c=="" + length=c.scan(/./m).length + ccheck=c + if ccheck=="\n" + ret.push(["\n",x,y,0,textheight,line,position,column,0]) + x=0 + y+=(textheight==0) ? bitmap.text_size("X").height : textheight + line+=1 + textheight=0 + column=0 + position+=length + ret.push(["",x,y,0,textheight,line,position,column,0]) + next + end + textcols=[] + words=[ccheck] + for i in 0...words.length + word=words[i] + if word && word!="" + textSize=bitmap.text_size(word) + textwidth=textSize.width + if x>0 && x+textwidth>=width-2 + # Zero-length word break + ret.push(["",x,y,0,textheight,line,position,column,0]) + x=0 + column=0 + y+=(textheight==0) ? bitmap.text_size("X").height : textheight + line+=1 + textheight=0 + end + textheight=[textheight,textSize.height].max + ret.push([word,x,y,textwidth,textheight,line,position,column,length]) + x+=textwidth + dims[0]=x if dims && dims[0]=totallines + maximumY=0 + for i in 0...textchars.length + thisline=textchars[i][5] + y=textchars[i][2] + return y if thisline==line + maximumY=y if maximumY=totallines + endpos=0 + for i in 0...textchars.length + thisline=textchars[i][5] + thispos=textchars[i][6] + thislength=textchars[i][8] + if thisline==line + endpos+=thislength + end + end + return endpos + end + end + + def getPosFromLineAndColumn(line,column) + textchars=getTextChars + if textchars.length==0 + return 0 + else + totallines=getTotalLines() + line=0 if line<0 + line=totallines-1 if line>=totallines + endpos=0 + for i in 0...textchars.length + thisline=textchars[i][5] + thispos=textchars[i][6] + thiscolumn=textchars[i][7] + thislength=textchars[i][8] + if thisline==line + endpos=thispos+thislength +# echoln [endpos,thispos+(column-thiscolumn),textchars[i]] + if column>=thiscolumn && column<=thiscolumn+thislength && thislength>0 + return thispos+(column-thiscolumn) + end + end + end + if endpos==0 +# echoln [totallines,line,column] +# echoln textchars + end +# echoln "endpos=#{endpos}" + return endpos + end + end + + def getLastVisibleLine + textchars=getTextChars() + textheight=[1,self.contents.text_size("X").height].max + lastVisible=@firstline+((self.height-self.borderY)/textheight)-1 + return lastVisible + end + + def updateCursorPos(doRefresh) + # Calculate new cursor position + @helper.cursor=getPosFromLineAndColumn(@cursorLine,@cursorColumn) + if doRefresh + @frame=0 + self.refresh + end + if @cursorLine<@firstline + @firstline=@cursorLine + end + lastVisible=getLastVisibleLine() + if @cursorLine>lastVisible + @firstline+=(@cursorLine-lastVisible) + end + end + + def moveCursor(lineOffset, columnOffset) + # Move column offset first, then lines (since column offset + # can affect line offset) +# echoln ["beforemoving",@cursorLine,@cursorColumn] + totalColumns=getColumnsInLine(@cursorLine) # check current line + totalLines=getTotalLines() + oldCursorLine=@cursorLine + oldCursorColumn=@cursorColumn + @cursorColumn+=columnOffset + if @cursorColumn<0 && @cursorLine>0 + # Will happen if cursor is moved left from the beginning of a line + @cursorLine-=1 + @cursorColumn=getColumnsInLine(@cursorLine) + elsif @cursorColumn>totalColumns && @cursorLinetotalColumns + @cursorColumn=0 if @cursorColumn<0 # totalColumns can be 0 + # Move line offset + @cursorLine+=lineOffset + @cursorLine=0 if @cursorLine<0 + @cursorLine=totalLines-1 if @cursorLine>=totalLines + # Ensure column bounds again + totalColumns=getColumnsInLine(@cursorLine) + @cursorColumn=totalColumns if @cursorColumn>totalColumns + @cursorColumn=0 if @cursorColumn<0 # totalColumns can be 0 + updateCursorPos( + oldCursorLine!=@cursorLine || + oldCursorColumn!=@cursorColumn + ) +# echoln ["aftermoving",@cursorLine,@cursorColumn] + end + + def update + @frame+=1 + @frame%=20 + self.refresh if ((@frame%10)==0) + return if !self.active + # Moving cursor + if Input.repeat?(Input::UP) + moveCursor(-1,0) + return + elsif Input.repeat?(Input::DOWN) + moveCursor(1,0) + return + elsif Input.repeat?(Input::LEFT) + moveCursor(0,-1) + return + elsif Input.repeat?(Input::RIGHT) + moveCursor(0,1) + return + end + if !@peekMessage + @peekMessage = Win32API.new("user32.dll","PeekMessage","pliii","i") rescue nil + end + if @peekMessage + msg=[0,0,0,0,0,0,0].pack("V*") + retval=@peekMessage.call(msg,0,0x102,0x102,1) + if retval!=0 + p "WM_CHAR #{msg[2]}" + end + end + if Input.press?(Input::CTRL) && Input.trigger?(Input::HOME) + # Move cursor to beginning + @cursorLine=0 + @cursorColumn=0 + updateCursorPos(true) + return + elsif Input.press?(Input::CTRL) && Input.trigger?(Input::ENDKEY) + # Move cursor to end + @cursorLine=getTotalLines()-1 + @cursorColumn=getColumnsInLine(@cursorLine) + updateCursorPos(true) + return + elsif Input.repeat?(Input::ENTER) + self.insert("\n") + return + elsif Input.repeat?(Input::BACKSPACE) # Backspace + self.delete + return + end + # Letter keys + for i in 65..90 + if Input.repeatex?(i) + shift=(Input.press?(Input::SHIFT)) ? 0x41 : 0x61 + insert((shift+i-65).chr) + return + end + end + # Number keys + shifted=")!@\#$%^&*(" + unshifted="0123456789" + for i in 48..57 + if Input.repeatex?(i) + insert((Input.press?(Input::SHIFT)) ? shifted[i-48].chr : unshifted[i-48].chr) + return + end + end + keys=[ + [32," "," "], + [106,"*","*"], + [107,"+","+"], + [109,"-","-"], + [111,"/","/"], + [186,";",":"], + [187,"=","+"], + [188,",","<"], + [189,"-","_"], + [190,".",">"], + [191,"/","?"], + [219,"[","{"], + [220,"\\","|"], + [221,"]","}"], + [222,"'","\""] + ] + for i in keys + if Input.repeatex?(i[0]) + insert((Input.press?(Input::SHIFT)) ? i[2] : i[1]) + return + end + end + end + + def refresh + newContents=pbDoEnsureBitmap(self.contents,self.width-self.borderX, + self.height-self.borderY) + @textchars=nil if self.contents!=newContents + self.contents=newContents + bitmap=self.contents + bitmap.clear + x=0 + y=0 + getTextChars + x+=4 + width=self.width-self.borderX + height=self.height-self.borderY + cursorcolor=Color.new(0,0,0) + textchars=getTextChars() + scanlength=@helper.length + startY=getLineY(@firstline) + lastheight=32 + for i in 0...textchars.length + thisline=textchars[i][5] + thispos=textchars[i][6] + thiscolumn=textchars[i][7] + thislength=textchars[i][8] + textY=textchars[i][2]-startY + # Don't draw lines before the first or zero-length segments + next if thisline<@firstline || thislength==0 + # Don't draw lines beyond the window's height + break if textY >= height + c=textchars[i][0] + # Don't draw spaces + next if c==" " + textwidth=textchars[i][3]+4 # add 4 to prevent draw_text from stretching text + textheight=textchars[i][4] + lastheight=textheight + # Draw text + pbDrawShadowText(bitmap,textchars[i][1],textY, textwidth, textheight, c,@baseColor,@shadowColor) + end + # Draw cursor + if ((@frame/10)&1) == 0 + textheight=bitmap.text_size("X").height + cursorY=(textheight*@cursorLine)-startY + cursorX=0 + for i in 0...textchars.length + thisline=textchars[i][5] + thispos=textchars[i][6] + thiscolumn=textchars[i][7] + thislength=textchars[i][8] + if thisline==@cursorLine && @cursorColumn>=thiscolumn && + @cursorColumn<=thiscolumn+thislength + cursorY=textchars[i][2]-startY + cursorX=textchars[i][1] + textheight=textchars[i][4] + posToCursor=@cursorColumn-thiscolumn + if posToCursor>=0 + partialString=textchars[i][0].scan(/./m)[0,posToCursor].join("") + cursorX+=bitmap.text_size(partialString).width + end + break + end + end + cursorY+=4 + cursorHeight=[4,textheight-4,bitmap.text_size("X").height-4].max + bitmap.fill_rect(cursorX,cursorY,2,cursorHeight,cursorcolor) + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +class Window_TextEntry_Keyboard < Window_TextEntry + def update + @frame+=1 + @frame%=20 + self.refresh if ((@frame%10)==0) + return if !self.active + # Moving cursor + if Input.repeat?(Input::LEFT) + if @helper.cursor > 0 + @helper.cursor-=1 + @frame=0 + self.refresh + end + return + elsif Input.repeat?(Input::RIGHT) + if @helper.cursor < self.text.scan(/./m).length + @helper.cursor+=1 + @frame=0 + self.refresh + end + return + elsif Input.repeat?(Input::BACKSPACE) + self.delete if @helper.cursor>0 + return + elsif Input.trigger?(Input::ENTER) || Input.trigger?(Input::ESC) + return + end + if !@toUnicode + @toUnicode = Win32API.new("user32.dll","ToUnicode","iippii","i") rescue nil + @mapVirtualKey = Win32API.new("user32.dll","MapVirtualKey","ii","i") rescue nil + @getKeyboardState = Win32API.new("user32.dll","GetKeyboardState","p","i") rescue nil + end + if @getKeyboardState + kbs = "\0"*256 + @getKeyboardState.call(kbs) + kbcount = 0 + for i in 3...256 + next if !Input.triggerex?(i) + vsc = @mapVirtualKey.call(i,0) + buf = "\0"*8 + ret = @toUnicode.call(i,vsc,kbs,buf,4,0) + next if ret<=0 + b = buf.unpack("v*") + for j in 0...ret + if buf[j]<=0x7F + insert(buf[j].chr) + elsif buf[j]<=0x7FF + insert((0xC0|((buf[j]>>6)&0x1F)).chr+(0x80|(buf[j]&0x3F)).chr) + else + str = (0xE0|((buf[j]>>12)&0x0F)).chr + str += (0x80|((buf[j]>>6)&0x3F)).chr + str += (0x80|(buf[j]&0x3F)).chr + insert(str) + end + kbcount += 1 + end + end + return if kbcount>0 + end + # Letter keys + for i in 65..90 + if Input.repeatex?(i) + shift=(Input.press?(Input::SHIFT)) ? 0x41 : 0x61 + insert((shift+i-65).chr) + return + end + end + # Number keys + shifted = ")!@\#$%^&*(" + unshifted = "0123456789" + for i in 48..57 + if Input.repeatex?(i) + insert((Input.press?(Input::SHIFT)) ? shifted[i-48].chr : unshifted[i-48].chr) + return + end + end + keys = [ + [32," "," "], + [106,"*","*"], + [107,"+","+"], + [109,"-","-"], + [111,"/","/"], + [186,";",":"], + [187,"=","+"], + [188,",","<"], + [189,"-","_"], + [190,".",">"], + [191,"/","?"], + [219,"[","{"], + [221,"]","}"], + [222,"'","\""] + ] + for i in keys + if Input.repeatex?(i[0]) + insert((Input.press?(Input::SHIFT)) ? i[2] : i[1]) + return + end + end + end +end + + + +#=============================================================================== +# Text entry screen - free typing. +#=============================================================================== +class PokemonEntryScene + @@Characters=[ + [("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz").scan(/./),"[*]"], + [("0123456789 !@\#$%^&*() ~`-_+={}[] :;'\"<>,.?/ ").scan(/./),"[A]"], + ] + USEKEYBOARD=true + + def pbStartScene(helptext,minlength,maxlength,initialText,subject=0,pokemon=nil) + @sprites={} + @viewport=Viewport.new(0,0,Graphics.width,Graphics.height) + @viewport.z=99999 + if USEKEYBOARD + @sprites["entry"]=Window_TextEntry_Keyboard.new(initialText, + 0,0,400-112,96,helptext,true) + $fullInputUpdate = true + else + @sprites["entry"]=Window_TextEntry.new(initialText,0,0,400,96,helptext,true) + end + @sprites["entry"].x=(Graphics.width/2)-(@sprites["entry"].width/2)+32 + @sprites["entry"].viewport=@viewport + @sprites["entry"].visible=true + @minlength=minlength + @maxlength=maxlength + @symtype=0 + @sprites["entry"].maxlength=maxlength + if !USEKEYBOARD + @sprites["entry2"]=Window_CharacterEntry.new(@@Characters[@symtype][0]) + @sprites["entry2"].setOtherCharset(@@Characters[@symtype][1]) + @sprites["entry2"].viewport=@viewport + @sprites["entry2"].visible=true + @sprites["entry2"].x=(Graphics.width/2)-(@sprites["entry2"].width/2) + end + if minlength==0 + @sprites["helpwindow"]=Window_UnformattedTextPokemon.newWithSize( + _INTL("Enter text using the keyboard. Press\nEnter to confirm, or Esc to cancel."), + 32,Graphics.height-96,Graphics.width-64,96,@viewport + ) + else + @sprites["helpwindow"]=Window_UnformattedTextPokemon.newWithSize( + _INTL("Enter text using the keyboard.\nPress Enter to confirm."), + 32,Graphics.height-96,Graphics.width-64,96,@viewport + ) + end + @sprites["helpwindow"].letterbyletter=false + @sprites["helpwindow"].viewport=@viewport + @sprites["helpwindow"].visible=USEKEYBOARD + @sprites["helpwindow"].baseColor=Color.new(16,24,32) + @sprites["helpwindow"].shadowColor=Color.new(168,184,184) + addBackgroundPlane(@sprites,"background","Naming/bg_2",@viewport) + case subject + when 1 # Player + meta=pbGetMetadata(0,MetadataPlayerA+$PokemonGlobal.playerID) + if meta + @sprites["shadow"]=IconSprite.new(0,0,@viewport) + @sprites["shadow"].setBitmap("Graphics/Pictures/Naming/icon_shadow") + @sprites["shadow"].x=33*2 + @sprites["shadow"].y=32*2 + filename=pbGetPlayerCharset(meta,1,nil,true) + @sprites["subject"]=TrainerWalkingCharSprite.new(filename,@viewport) + charwidth=@sprites["subject"].bitmap.width + charheight=@sprites["subject"].bitmap.height + @sprites["subject"].x = 44*2 - charwidth/8 + @sprites["subject"].y = 38*2 - charheight/4 + end + when 2 # Pokémon + if pokemon + @sprites["shadow"]=IconSprite.new(0,0,@viewport) + @sprites["shadow"].setBitmap("Graphics/Pictures/Naming/icon_shadow") + @sprites["shadow"].x=33*2 + @sprites["shadow"].y=32*2 + @sprites["subject"]=PokemonIconSprite.new(pokemon,@viewport) + @sprites["subject"].setOffset(PictureOrigin::Center) + @sprites["subject"].x=88 + @sprites["subject"].y=54 + @sprites["gender"]=BitmapSprite.new(32,32,@viewport) + @sprites["gender"].x=430 + @sprites["gender"].y=54 + @sprites["gender"].bitmap.clear + pbSetSystemFont(@sprites["gender"].bitmap) + textpos=[] + if pokemon.male? + textpos.push([_INTL("♂"),0,0,false,Color.new(0,128,248),Color.new(168,184,184)]) + elsif pokemon.female? + textpos.push([_INTL("♀"),0,0,false,Color.new(248,24,24),Color.new(168,184,184)]) + end + pbDrawTextPositions(@sprites["gender"].bitmap,textpos) + end + when 3 # NPC + @sprites["shadow"]=IconSprite.new(0,0,@viewport) + @sprites["shadow"].setBitmap("Graphics/Pictures/Naming/icon_shadow") + @sprites["shadow"].x=33*2 + @sprites["shadow"].y=32*2 + @sprites["subject"]=TrainerWalkingCharSprite.new(pokemon.to_s,@viewport) + charwidth=@sprites["subject"].bitmap.width + charheight=@sprites["subject"].bitmap.height + @sprites["subject"].x = 44*2 - charwidth/8 + @sprites["subject"].y = 38*2 - charheight/4 + when 4 # Storage box + @sprites["subject"]=TrainerWalkingCharSprite.new(nil,@viewport) + @sprites["subject"].altcharset="Graphics/Pictures/Naming/icon_storage" + @sprites["subject"].animspeed=4 + charwidth=@sprites["subject"].bitmap.width + charheight=@sprites["subject"].bitmap.height + @sprites["subject"].x = 44*2 - charwidth/8 + @sprites["subject"].y = 26*2 - charheight/2 + end + pbFadeInAndShow(@sprites) + end + + def pbEntry1 + ret="" + loop do + Graphics.update + Input.update + if Input.trigger?(Input::ESC) && @minlength==0 + ret="" + break + elsif Input.trigger?(Input::ENTER) && @sprites["entry"].text.length>=@minlength + ret=@sprites["entry"].text + break + end + @sprites["helpwindow"].update + @sprites["entry"].update + @sprites["subject"].update if @sprites["subject"] + end + Input.update + return ret + end + + def pbEntry2 + ret="" + loop do + Graphics.update + Input.update + @sprites["helpwindow"].update + @sprites["entry"].update + @sprites["entry2"].update + @sprites["subject"].update if @sprites["subject"] + if Input.trigger?(Input::C) + index=@sprites["entry2"].command + if index==-3 # Confirm text + ret=@sprites["entry"].text + if ret.length<@minlength || ret.length>@maxlength + pbPlayBuzzerSE() + else + pbPlayDecisionSE() + break + end + elsif index==-1 # Insert a space + if @sprites["entry"].insert(" ") + pbPlayDecisionSE() + else + pbPlayBuzzerSE() + end + elsif index==-2 # Change character set + pbPlayDecisionSE() + @symtype+=1 + @symtype=0 if @symtype>=@@Characters.length + @sprites["entry2"].setCharset(@@Characters[@symtype][0]) + @sprites["entry2"].setOtherCharset(@@Characters[@symtype][1]) + else # Insert given character + if @sprites["entry"].insert(@sprites["entry2"].character) + pbPlayDecisionSE() + else + pbPlayBuzzerSE() + end + end + next + end + end + Input.update + return ret + end + + def pbEntry + return USEKEYBOARD ? pbEntry1 : pbEntry2 + end + + def pbEndScene + $fullInputUpdate = false + pbFadeOutAndHide(@sprites) + pbDisposeSpriteHash(@sprites) + @viewport.dispose + end +end + + + +#=============================================================================== +# Text entry screen - arrows to select letter. +#=============================================================================== +class PokemonEntryScene2 + @@Characters = [ + [("ABCDEFGHIJ ,."+"KLMNOPQRST '-"+"UVWXYZ ♂♀"+" "+"0123456789 ").scan(/./),_INTL("UPPER")], + [("abcdefghij ,."+"klmnopqrst '-"+"uvwxyz ♂♀"+" "+"0123456789 ").scan(/./),_INTL("lower")], + [(",.:;!? ♂♀ "+"\"'()<>[] "+"~@#%*&$ "+"+-=^_/\\| "+" ").scan(/./),_INTL("other")], + ] + ROWS = 13 + COLUMNS = 5 + MODE1 = -5 + MODE2 = -4 + MODE3 = -3 + BACK = -2 + OK = -1 + + class NameEntryCursor + def initialize(viewport) + @sprite = SpriteWrapper.new(viewport) + @cursortype = 0 + @cursor1 = AnimatedBitmap.new("Graphics/Pictures/Naming/cursor_1") + @cursor2 = AnimatedBitmap.new("Graphics/Pictures/Naming/cursor_2") + @cursor3 = AnimatedBitmap.new("Graphics/Pictures/Naming/cursor_3") + @cursorPos = 0 + updateInternal + end + + def setCursorPos(value) + @cursorPos = value + end + + def updateCursorPos + value=@cursorPos + if value==PokemonEntryScene2::MODE1 # Upper case + @sprite.x=48 + @sprite.y=120 + @cursortype=1 + elsif value==PokemonEntryScene2::MODE2 # Lower case + @sprite.x=112 + @sprite.y=120 + @cursortype=1 + elsif value==PokemonEntryScene2::MODE3 # Other symbols + @sprite.x=176 + @sprite.y=120 + @cursortype=1 + elsif value==PokemonEntryScene2::BACK # Back + @sprite.x=312 + @sprite.y=120 + @cursortype=2 + elsif value==PokemonEntryScene2::OK # OK + @sprite.x=392 + @sprite.y=120 + @cursortype=2 + elsif value>=0 + @sprite.x=52+32*(value%PokemonEntryScene2::ROWS) + @sprite.y=180+38*(value/PokemonEntryScene2::ROWS) + @cursortype=0 + end + end + + def visible=(value) + @sprite.visible=value + end + + def visible + @sprite.visible + end + + def color=(value) + @sprite.color=value + end + + def color + @sprite.color + end + + def disposed? + @sprite.disposed? + end + + def updateInternal + @cursor1.update + @cursor2.update + @cursor3.update + updateCursorPos + case @cursortype + when 0; @sprite.bitmap=@cursor1.bitmap + when 1; @sprite.bitmap=@cursor2.bitmap + when 2; @sprite.bitmap=@cursor3.bitmap + end + end + + def update + updateInternal + end + + def dispose + @cursor1.dispose + @cursor2.dispose + @cursor3.dispose + @sprite.dispose + end + end + + + + def pbStartScene(helptext,minlength,maxlength,initialText,subject=0,pokemon=nil) + @sprites={} + @viewport=Viewport.new(0,0,Graphics.width,Graphics.height) + @viewport.z=99999 + @helptext=helptext + @helper=CharacterEntryHelper.new(initialText) + @bitmaps=[ + AnimatedBitmap.new("Graphics/Pictures/Naming/overlay_tab_1"), + AnimatedBitmap.new("Graphics/Pictures/Naming/overlay_tab_2"), + AnimatedBitmap.new("Graphics/Pictures/Naming/overlay_tab_3") + ] + @bitmaps[3]=@bitmaps[0].bitmap.clone + @bitmaps[4]=@bitmaps[1].bitmap.clone + @bitmaps[5]=@bitmaps[2].bitmap.clone + for i in 0...3 + pos=0 + pbSetSystemFont(@bitmaps[i+3]) + textPos=[] + for y in 0...COLUMNS + for x in 0...ROWS + textPos.push([@@Characters[i][0][pos],44+x*32,18+y*38,2, + Color.new(16,24,32), Color.new(160,160,160)]) + pos+=1 + end + end + pbDrawTextPositions(@bitmaps[i+3],textPos) + end + @bitmaps[6]=BitmapWrapper.new(24,6) + @bitmaps[6].fill_rect(2,2,22,4,Color.new(168,184,184)) + @bitmaps[6].fill_rect(0,0,22,4,Color.new(16,24,32)) + @sprites["bg"]=IconSprite.new(0,0,@viewport) + @sprites["bg"].setBitmap("Graphics/Pictures/Naming/bg") + case subject + when 1 # Player + meta=pbGetMetadata(0,MetadataPlayerA+$PokemonGlobal.playerID) + if meta + @sprites["shadow"]=IconSprite.new(0,0,@viewport) + @sprites["shadow"].setBitmap("Graphics/Pictures/Naming/icon_shadow") + @sprites["shadow"].x=33*2 + @sprites["shadow"].y=32*2 + filename=pbGetPlayerCharset(meta,1,nil,true) + @sprites["subject"]=TrainerWalkingCharSprite.new(filename,@viewport) + charwidth=@sprites["subject"].bitmap.width + charheight=@sprites["subject"].bitmap.height + @sprites["subject"].x = 44*2 - charwidth/8 + @sprites["subject"].y = 38*2 - charheight/4 + end + when 2 # Pokémon + if pokemon + @sprites["shadow"]=IconSprite.new(0,0,@viewport) + @sprites["shadow"].setBitmap("Graphics/Pictures/Naming/icon_shadow") + @sprites["shadow"].x=33*2 + @sprites["shadow"].y=32*2 + @sprites["subject"]=PokemonIconSprite.new(pokemon,@viewport) + @sprites["subject"].setOffset(PictureOrigin::Center) + @sprites["subject"].x=88 + @sprites["subject"].y=54 + @sprites["gender"]=BitmapSprite.new(32,32,@viewport) + @sprites["gender"].x=430 + @sprites["gender"].y=54 + @sprites["gender"].bitmap.clear + pbSetSystemFont(@sprites["gender"].bitmap) + textpos=[] + if pokemon.male? + textpos.push([_INTL("♂"),0,0,false,Color.new(0,128,248),Color.new(168,184,184)]) + elsif pokemon.female? + textpos.push([_INTL("♀"),0,0,false,Color.new(248,24,24),Color.new(168,184,184)]) + end + pbDrawTextPositions(@sprites["gender"].bitmap,textpos) + end + when 3 # NPC + @sprites["shadow"]=IconSprite.new(0,0,@viewport) + @sprites["shadow"].setBitmap("Graphics/Pictures/Naming/icon_shadow") + @sprites["shadow"].x=33*2 + @sprites["shadow"].y=32*2 + @sprites["subject"]=TrainerWalkingCharSprite.new(pokemon.to_s,@viewport) + charwidth=@sprites["subject"].bitmap.width + charheight=@sprites["subject"].bitmap.height + @sprites["subject"].x = 44*2 - charwidth/8 + @sprites["subject"].y = 38*2 - charheight/4 + when 4 # Storage box + @sprites["subject"]=TrainerWalkingCharSprite.new(nil,@viewport) + @sprites["subject"].altcharset="Graphics/Pictures/Naming/icon_storage" + @sprites["subject"].animspeed=4 + charwidth=@sprites["subject"].bitmap.width + charheight=@sprites["subject"].bitmap.height + @sprites["subject"].x = 44*2 - charwidth/8 + @sprites["subject"].y = 26*2 - charheight/2 + end + @sprites["bgoverlay"]=BitmapSprite.new(Graphics.width,Graphics.height,@viewport) + pbDoUpdateOverlay + @blanks=[] + @mode=0 + @minlength=minlength + @maxlength=maxlength + @maxlength.times { |i| + @sprites["blank#{i}"]=SpriteWrapper.new(@viewport) + @sprites["blank#{i}"].bitmap=@bitmaps[6] + @sprites["blank#{i}"].x=160+24*i + @blanks[i]=0 + } + @sprites["bottomtab"]=SpriteWrapper.new(@viewport) # Current tab + @sprites["bottomtab"].x=22 + @sprites["bottomtab"].y=162 + @sprites["bottomtab"].bitmap=@bitmaps[0+3] + @sprites["toptab"]=SpriteWrapper.new(@viewport) # Next tab + @sprites["toptab"].x=22-504 + @sprites["toptab"].y=162 + @sprites["toptab"].bitmap=@bitmaps[1+3] + @sprites["controls"]=IconSprite.new(0,0,@viewport) + @sprites["controls"].setBitmap(_INTL("Graphics/Pictures/Naming/overlay_controls")) + @sprites["controls"].x=16 + @sprites["controls"].y=96 + @init=true + @sprites["overlay"]=BitmapSprite.new(Graphics.width,Graphics.height,@viewport) + pbDoUpdateOverlay2 + @sprites["cursor"]=NameEntryCursor.new(@viewport) + @cursorpos=0 + @refreshOverlay=true + @sprites["cursor"].setCursorPos(@cursorpos) + pbFadeInAndShow(@sprites) { pbUpdate } + end + + def pbUpdateOverlay + @refreshOverlay=true + end + + def pbDoUpdateOverlay2 + overlay=@sprites["overlay"].bitmap + overlay.clear + modeIcon=[[_INTL("Graphics/Pictures/Naming/icon_mode"),48+@mode*64,120,@mode*60,0,60,44]] + pbDrawImagePositions(overlay,modeIcon) + end + + def pbDoUpdateOverlay + return if !@refreshOverlay + @refreshOverlay=false + bgoverlay=@sprites["bgoverlay"].bitmap + bgoverlay.clear + pbSetSystemFont(bgoverlay) + textPositions=[ + [@helptext,160,12,false,Color.new(16,24,32),Color.new(168,184,184)] + ] + chars=@helper.textChars + x=166 + for ch in chars + textPositions.push([ch,x,48,false,Color.new(16,24,32),Color.new(168,184,184)]) + x+=24 + end + pbDrawTextPositions(bgoverlay,textPositions) + end + + def pbChangeTab(newtab=@mode+1) + pbSEPlay("GUI naming tab swap start") + @sprites["cursor"].visible = false + @sprites["toptab"].bitmap = @bitmaps[(newtab%3)+3] + # Move bottom (old) tab down off the screen, and move top (new) tab right + # onto the screen + deltaX = 48*20/Graphics.frame_rate + deltaY = 24*20/Graphics.frame_rate + loop do + if @sprites["bottomtab"].y<414 + @sprites["bottomtab"].y += deltaY + @sprites["bottomtab"].y = 414 if @sprites["bottomtab"].y>414 + end + if @sprites["toptab"].x<22 + @sprites["toptab"].x += deltaX + @sprites["toptab"].x = 22 if @sprites["toptab"].x>22 + end + Graphics.update + Input.update + pbUpdate + break if @sprites["toptab"].x>=22 && @sprites["bottomtab"].y>=414 + end + # Swap top and bottom tab around + @sprites["toptab"].x, @sprites["bottomtab"].x = @sprites["bottomtab"].x, @sprites["toptab"].x + @sprites["toptab"].y, @sprites["bottomtab"].y = @sprites["bottomtab"].y, @sprites["toptab"].y + @sprites["toptab"].bitmap, @sprites["bottomtab"].bitmap = @sprites["bottomtab"].bitmap, @sprites["toptab"].bitmap + Graphics.update + Input.update + pbUpdate + # Set the current mode + @mode = (newtab)%3 + # Set the top tab up to be the next tab + newtab = @bitmaps[((@mode+1)%3)+3] + @sprites["cursor"].visible = true + @sprites["toptab"].bitmap = newtab + @sprites["toptab"].x = 22-504 + @sprites["toptab"].y = 162 + pbSEPlay("GUI naming tab swap end") + pbDoUpdateOverlay2 + end + + def pbUpdate + for i in 0...3 + @bitmaps[i].update + end + if @init || Graphics.frame_count%5==0 + @init = false + cursorpos = @helper.cursor + cursorpos = @maxlength-1 if cursorpos>=@maxlength + cursorpos = 0 if cursorpos<0 + @maxlength.times { |i| + @blanks[i] = (i==cursorpos) ? 1 : 0 + @sprites["blank#{i}"].y = [78,82][@blanks[i]] + } + end + pbDoUpdateOverlay + pbUpdateSpriteHash(@sprites) + end + + def pbColumnEmpty?(m) + return false if m>=ROWS-1 + chset=@@Characters[@mode][0] + return ( + chset[m]==" " && + chset[m+((ROWS-1))]==" " && + chset[m+((ROWS-1)*2)]==" " && + chset[m+((ROWS-1)*3)]==" " + ) + end + + def wrapmod(x,y) + result=x%y + result+=y if result<0 + return result + end + + def pbMoveCursor + oldcursor=@cursorpos + cursordiv=@cursorpos/ROWS + cursormod=@cursorpos%ROWS + cursororigin=@cursorpos-cursormod + if Input.repeat?(Input::LEFT) + if @cursorpos<0 # Controls + @cursorpos-=1 + @cursorpos=OK if @cursorposOK + else + begin + cursormod=wrapmod((cursormod+1),ROWS) + @cursorpos=cursororigin+cursormod + end while pbColumnEmpty?(cursormod) + end + elsif Input.repeat?(Input::UP) + if @cursorpos<0 # Controls + case @cursorpos + when MODE1; @cursorpos = ROWS*(COLUMNS-1) + when MODE2; @cursorpos = ROWS*(COLUMNS-1)+2 + when MODE3; @cursorpos = ROWS*(COLUMNS-1)+4 + when BACK; @cursorpos = ROWS*(COLUMNS-1)+8 + when OK; @cursorpos = ROWS*(COLUMNS-1)+11 + end + elsif @cursorpos=ROWS*(COLUMNS-1) # Bottom row of letters + case @cursorpos + when ROWS*(COLUMNS-1),ROWS*(COLUMNS-1)+1 + @cursorpos = MODE1 + when ROWS*(COLUMNS-1)+2,ROWS*(COLUMNS-1)+3 + @cursorpos = MODE2 + when ROWS*(COLUMNS-1)+4,ROWS*(COLUMNS-1)+5,ROWS*(COLUMNS-1)+6 + @cursorpos = MODE3 + when ROWS*(COLUMNS-1)+7,ROWS*(COLUMNS-1)+8,ROWS*(COLUMNS-1)+9,ROWS*(COLUMNS-1)+10 + @cursorpos = BACK + when ROWS*(COLUMNS-1)+11,ROWS*(COLUMNS-1)+12 + @cursorpos = OK + end + else + cursordiv=wrapmod((cursordiv+1),COLUMNS) + @cursorpos=(cursordiv*ROWS)+cursormod + end + end + if @cursorpos!=oldcursor # Cursor position changed + @sprites["cursor"].setCursorPos(@cursorpos) + pbPlayCursorSE() + return true + else + return false + end + end + + def pbEntry + ret="" + loop do + Graphics.update + Input.update + pbUpdate + next if pbMoveCursor + if Input.trigger?(Input::F5) + pbChangeTab + elsif Input.trigger?(Input::A) + @cursorpos = OK + @sprites["cursor"].setCursorPos(@cursorpos) + elsif Input.trigger?(Input::B) + @helper.delete + pbPlayCancelSE() + pbUpdateOverlay + elsif Input.trigger?(Input::C) + case @cursorpos + when BACK # Backspace + @helper.delete + pbPlayCancelSE() + pbUpdateOverlay + when OK # Done + pbSEPlay("GUI naming confirm") + if @helper.length>=@minlength + ret=@helper.text + break + end + when MODE1 + pbChangeTab(0) if @mode!=0 + when MODE2 + pbChangeTab(1) if @mode!=1 + when MODE3 + pbChangeTab(2) if @mode!=2 + else + cursormod=@cursorpos%ROWS + cursordiv=@cursorpos/ROWS + charpos=cursordiv*(ROWS)+cursormod + chset=@@Characters[@mode][0] + if @helper.length>=@maxlength + @helper.delete + end + @helper.insert(chset[charpos]) + pbPlayCursorSE() + if @helper.length>=@maxlength + @cursorpos=OK + @sprites["cursor"].setCursorPos(@cursorpos) + end + pbUpdateOverlay + end + end + end + Input.update + return ret + end + + def pbEndScene + pbFadeOutAndHide(@sprites) { pbUpdate } + for bitmap in @bitmaps + bitmap.dispose if bitmap + end + @bitmaps.clear + pbDisposeSpriteHash(@sprites) + @viewport.dispose + end +end + + + +class PokemonEntry + def initialize(scene) + @scene=scene + end + + def pbStartScreen(helptext,minlength,maxlength,initialText,mode=-1,pokemon=nil) + @scene.pbStartScene(helptext,minlength,maxlength,initialText,mode,pokemon) + ret=@scene.pbEntry + @scene.pbEndScene + return ret + end +end + + + +#=============================================================================== +# Interpreter functions for naming the player +#=============================================================================== +class Interpreter + def command_303 + if $Trainer + $Trainer.name=pbEnterPlayerName(_INTL("Your name?"),1,@parameters[1],$Trainer.name) + return true + end + if $game_actors && $data_actors && $data_actors[@parameters[0]] != nil + # Set battle abort flag + $game_temp.battle_abort = true + ret="" + pbFadeOutIn { + sscene=PokemonEntryScene.new + sscreen=PokemonEntry.new(sscene) + $game_actors[@parameters[0]].name=sscreen.pbStartScreen( + _INTL("Enter {1}'s name.",$game_actors[@parameters[0]].name), + 1,@parameters[1],$game_actors[@parameters[0]].name) + } + end + return true + end +end + + + +class Game_Interpreter + def command_303 + if $Trainer + $Trainer.name=pbEnterPlayerName(_INTL("Your name?"),1,@params[1],$Trainer.name) + return true + end + if $game_actors && $data_actors && $data_actors[@params[0]] != nil + # Set battle abort flag + ret="" + pbFadeOutIn { + sscene=PokemonEntryScene.new + sscreen=PokemonEntry.new(sscene) + $game_actors[@params[0]].name=sscreen.pbStartScreen( + _INTL("Enter {1}'s name.",$game_actors[@params[0]].name), + 1,@params[1],$game_actors[@params[0]].name) + } + end + return true + end +end + + + +#=============================================================================== +# +#=============================================================================== +def pbEnterText(helptext,minlength,maxlength,initialText="",mode=0,pokemon=nil,nofadeout=false) + ret="" + if ($PokemonSystem.textinput==1 rescue false) # Keyboard + pbFadeOutIn(99999,nofadeout) { + sscene=PokemonEntryScene.new + sscreen=PokemonEntry.new(sscene) + ret=sscreen.pbStartScreen(helptext,minlength,maxlength,initialText,mode,pokemon) + } + else # Cursor + pbFadeOutIn(99999,nofadeout) { + sscene=PokemonEntryScene2.new + sscreen=PokemonEntry.new(sscene) + ret=sscreen.pbStartScreen(helptext,minlength,maxlength,initialText,mode,pokemon) + } + end + return ret +end + +def pbEnterPlayerName(helptext,minlength,maxlength,initialText="",nofadeout=false) + return pbEnterText(helptext,minlength,maxlength,initialText,1,nil,nofadeout) +end + +def pbEnterPokemonName(helptext,minlength,maxlength,initialText="",pokemon=nil,nofadeout=false) + return pbEnterText(helptext,minlength,maxlength,initialText,2,pokemon,nofadeout) +end + +def pbEnterNPCName(helptext,minlength,maxlength,initialText="",id=0,nofadeout=false) + return pbEnterText(helptext,minlength,maxlength,initialText,3,id,nofadeout) +end + +def pbEnterBoxName(helptext,minlength,maxlength,initialText="",nofadeout=false) + return pbEnterText(helptext,minlength,maxlength,initialText,4,nil,nofadeout) +end + +def pbFreeText(msgwindow,currenttext,passwordbox,maxlength,width=240) + window=Window_TextEntry_Keyboard.new(currenttext,0,0,width,64) + ret="" + window.maxlength=maxlength + window.visible=true + window.z=99999 + pbPositionNearMsgWindow(window,msgwindow,:right) + window.text=currenttext + window.passwordChar="*" if passwordbox + $fullInputUpdate = true + loop do + Graphics.update + Input.update + if Input.trigger?(Input::ESC) + ret=currenttext + break + elsif Input.trigger?(Input::ENTER) + ret=window.text + break + end + window.update + msgwindow.update if msgwindow + yield if block_given? + end + $fullInputUpdate = false + window.dispose + Input.update + return ret +end + +def pbMessageFreeText(message,currenttext,passwordbox,maxlength,width=240,&block) + msgwindow=pbCreateMessageWindow + retval=pbMessageDisplay(msgwindow,message,true, + proc { |msgwindow| + next pbFreeText(msgwindow,currenttext,passwordbox,maxlength,width,&block) + },&block) + pbDisposeMessageWindow(msgwindow) + return retval +end \ No newline at end of file diff --git a/Data/Scripts/008_Objects and windows/010_EventScene.rb b/Data/Scripts/008_Objects and windows/010_EventScene.rb new file mode 100644 index 000000000..0b9f16a9a --- /dev/null +++ b/Data/Scripts/008_Objects and windows/010_EventScene.rb @@ -0,0 +1,709 @@ +def getCubicPoint2(src,t) + x0 = src[0]; y0 = src[1] + cx0 = src[2]; cy0 = src[3] + cx1 = src[4]; cy1 = src[5] + x1 = src[6]; y1 = src[7] + + x1 = cx1+(x1-cx1)*t + x0 = x0+(cx0-x0)*t + cx0 = cx0+(cx1-cx0)*t + cx1 = cx0+(x1-cx0)*t + cx0 = x0+(cx0-x0)*t + cx = cx0+(cx1-cx0)*t + # a = x1 - 3 * cx1 + 3 * cx0 - x0 + # b = 3 * (cx1 - 2 * cx0 + x0) + # c = 3 * (cx0 - x0) + # d = x0 + # cx = a*t*t*t + b*t*t + c*t + d + y1 = cy1+(y1-cy1)*t + y0 = y0+(cy0-y0)*t + cy0 = cy0+(cy1-cy0)*t + cy1 = cy0+(y1-cy0)*t + cy0 = y0+(cy0-y0)*t + cy = cy0+(cy1-cy0)*t + # a = y1 - 3 * cy1 + 3 * cy0 - y0 + # b = 3 * (cy1 - 2 * cy0 + y0) + # c = 3 * (cy0 - y0) + # d = y0 + # cy = a*t*t*t + b*t*t + c*t + d + return [cx,cy] +end + + + +class Processes + XY = 0 + DeltaXY = 1 + Z = 2 + Curve = 3 + Zoom = 4 + Angle = 5 + Tone = 6 + Color = 7 + Hue = 8 + Opacity = 9 + Visible = 10 + BlendType = 11 + SE = 12 + Name = 13 + Origin = 14 + Src = 15 + SrcSize = 16 + CropBottom = 17 +end + + + +class PictureEx + attr_accessor :x # x-coordinate + attr_accessor :y # y-coordinate + attr_accessor :z # z value + attr_accessor :zoom_x # x directional zoom rate + attr_accessor :zoom_y # y directional zoom rate + attr_accessor :angle # rotation angle + attr_accessor :tone # tone + attr_accessor :color # color + attr_accessor :hue # filename hue + attr_accessor :opacity # opacity level + attr_accessor :visible # visibility boolean + attr_accessor :blend_type # blend method + attr_accessor :name # file name + attr_accessor :origin # starting point + attr_reader :src_rect # source rect + attr_reader :cropBottom # crops sprite to above this y-coordinate + attr_reader :frameUpdates # Array of processes updated in a frame + + def initialize(z) + # process: [type, delay, total_duration, frame_counter, cb, etc.] + @processes = [] + @x = 0.0 + @y = 0.0 + @z = z + @zoom_x = 100.0 + @zoom_y = 100.0 + @angle = 0 + @rotate_speed = 0 + @tone = Tone.new(0, 0, 0, 0) + @tone_duration = 0 + @color = Color.new(0, 0, 0, 0) + @hue = 0 + @opacity = 255.0 + @visible = true + @blend_type = 0 + @name = "" + @origin = PictureOrigin::TopLeft + @src_rect = Rect.new(0,0,-1,-1) + @cropBottom = -1 + @frameUpdates = [] + end + + def callback(cb) + if cb.is_a?(Proc); proc.call(self) + elsif cb.is_a?(Array); cb[0].method(cb[1]).call(self) + elsif cb.is_a?(Method); cb.call(self) + end + end + + def setCallback(delay, cb=nil) + delay = ensureDelayAndDuration(delay) + @processes.push([nil,delay,0,0,cb]) + end + + def running? + return @processes.length>0 + end + + def totalDuration + ret = 0 + for process in @processes + dur = process[1]+process[2] + ret = dur if dur>ret + end + ret *= 20.0/Graphics.frame_rate + return ret.to_i + end + + def ensureDelayAndDuration(delay, duration=nil) + delay = self.totalDuration if delay<0 + delay *= Graphics.frame_rate/20.0 + if !duration.nil? + duration *= Graphics.frame_rate/20.0 + return delay.to_i, duration.to_i + end + return delay.to_i + end + + def ensureDelay(delay) + return ensureDelayAndDuration(delay) + end + + # speed is the angle to change by in 1/20 of a second. @rotate_speed is the + # angle to change by per frame. + # NOTE: This is not compatible with manually changing the angle at a certain + # point. If you make a sprite auto-rotate, you should not try to alter + # the angle another way too. + def rotate(speed) + @rotate_speed = speed*20.0/Graphics.frame_rate + while @rotate_speed<0; @rotate_speed += 360; end + @rotate_speed %= 360 + end + + def erase + self.name = "" + end + + def clearProcesses + @processes = [] + end + + def adjustPosition(xOffset, yOffset) + for process in @processes + next if process[0]!=Processes::XY + process[5] += xOffset + process[6] += yOffset + process[7] += xOffset + process[8] += yOffset + end + end + + def move(delay, duration, origin, x, y, zoom_x=100.0, zoom_y=100.0, opacity=255) + setOrigin(delay,duration,origin) + moveXY(delay,duration,x,y) + moveZoomXY(delay,duration,zoom_x,zoom_y) + moveOpacity(delay,duration,opacity) + end + + def moveXY(delay, duration, x, y, cb=nil) + delay, duration = ensureDelayAndDuration(delay,duration) + @processes.push([Processes::XY,delay,duration,0,cb,@x,@y,x,y]) + end + + def setXY(delay, x, y, cb=nil) + moveXY(delay,0,x,y,cb) + end + + def moveCurve(delay, duration, x1, y1, x2, y2, x3, y3, cb=nil) + delay, duration = ensureDelayAndDuration(delay,duration) + @processes.push([Processes::Curve,delay,duration,0,cb,[@x,@y,x1,y1,x2,y2,x3,y3]]) + end + + def moveDelta(delay, duration, x, y, cb=nil) + delay, duration = ensureDelayAndDuration(delay,duration) + @processes.push([Processes::DeltaXY,delay,duration,0,cb,@x,@y,x,y]) + end + + def setDelta(delay, x, y, cb=nil) + moveDelta(delay,0,x,y,cb) + end + + def moveZ(delay, duration, z, cb=nil) + delay, duration = ensureDelayAndDuration(delay,duration) + @processes.push([Processes::Z,delay,duration,0,cb,@z,z]) + end + + def setZ(delay, z, cb=nil) + moveZ(delay,0,z,cb) + end + + def moveZoomXY(delay, duration, zoom_x, zoom_y, cb=nil) + delay, duration = ensureDelayAndDuration(delay,duration) + @processes.push([Processes::Zoom,delay,duration,0,cb,@zoom_x,@zoom_y,zoom_x,zoom_y]) + end + + def setZoomXY(delay, zoom_x, zoom_y, cb=nil) + moveZoomXY(delay,0,zoom_x,zoom_y,cb) + end + + def moveZoom(delay, duration, zoom, cb=nil) + moveZoomXY(delay,duration,zoom,zoom,cb) + end + + def setZoom(delay, zoom, cb=nil) + moveZoomXY(delay,0,zoom,zoom,cb) + end + + def moveAngle(delay, duration, angle, cb=nil) + delay, duration = ensureDelayAndDuration(delay,duration) + @processes.push([Processes::Angle,delay,duration,0,cb,@angle,angle]) + end + + def setAngle(delay, angle, cb=nil) + moveAngle(delay,0,angle,cb) + end + + def moveTone(delay, duration, tone, cb=nil) + delay, duration = ensureDelayAndDuration(delay,duration) + target = (tone) ? tone.clone : Tone.new(0,0,0,0) + @processes.push([Processes::Tone,delay,duration,0,cb,@tone.clone,target]) + end + + def setTone(delay, tone, cb=nil) + moveTone(delay,0,tone,cb) + end + + def moveColor(delay, duration, color, cb=nil) + delay, duration = ensureDelayAndDuration(delay,duration) + target = (color) ? color.clone : Color.new(0,0,0,0) + @processes.push([Processes::Color,delay,duration,0,cb,@color.clone,target]) + end + + def setColor(delay, color, cb=nil) + moveColor(delay,0,color,cb) + end + + # Hue changes don't actually work. + def moveHue(delay, duration, hue, cb=nil) + delay, duration = ensureDelayAndDuration(delay,duration) + @processes.push([Processes::Hue,delay,duration,0,cb,@hue,hue]) + end + + # Hue changes don't actually work. + def setHue(delay, hue, cb=nil) + moveHue(delay,0,hue,cb) + end + + def moveOpacity(delay, duration, opacity, cb=nil) + delay, duration = ensureDelayAndDuration(delay,duration) + @processes.push([Processes::Opacity,delay,duration,0,cb,@opacity,opacity]) + end + + def setOpacity(delay, opacity, cb=nil) + moveOpacity(delay,0,opacity,cb) + end + + def setVisible(delay, visible, cb=nil) + delay = ensureDelay(delay) + @processes.push([Processes::Visible,delay,0,0,cb,visible]) + end + + # Only values of 0 (normal), 1 (additive) and 2 (subtractive) are allowed. + def setBlendType(delay, blend, cb=nil) + delay = ensureDelayAndDuration(delay) + @processes.push([Processes::BlendType,delay,0,0,cb,blend]) + end + + def setSE(delay, seFile, volume=nil, cb=nil) + delay = ensureDelay(delay) + @processes.push([Processes::SE,delay,0,0,cb,seFile,volume]) + end + + def setName(delay, name, cb=nil) + delay = ensureDelay(delay) + @processes.push([Processes::Name,delay,0,0,cb,name]) + end + + def setOrigin(delay, origin, cb=nil) + delay = ensureDelay(delay) + @processes.push([Processes::Origin,delay,0,0,cb,origin]) + end + + def setSrc(delay, srcX, srcY, cb=nil) + delay = ensureDelay(delay) + @processes.push([Processes::Src,delay,0,0,cb,srcX,srcY]) + end + + def setSrcSize(delay, srcWidth, srcHeight, cb=nil) + delay = ensureDelay(delay) + @processes.push([Processes::SrcSize,delay,0,0,cb,srcWidth,srcHeight]) + end + + # Used to cut Pokémon sprites off when they faint and sink into the ground. + def setCropBottom(delay, y, cb=nil) + delay = ensureDelay(delay) + @processes.push([Processes::CropBottom,delay,0,0,cb,y]) + end + + def update + procEnded = false + @frameUpdates.clear + for i in 0...@processes.length + process = @processes[i] + # Decrease delay of processes that are scheduled to start later + if process[1]>=0 + # Set initial values if the process will start this frame + if process[1]==0 + case process[0] + when Processes::XY + process[5] = @x + process[6] = @y + when Processes::DeltaXY + process[5] = @x + process[6] = @y + process[7] += @x + process[8] += @y + when Processes::Curve + process[5][0] = @x + process[5][1] = @y + when Processes::Z + process[5] = @z + when Processes::Zoom + process[5] = @zoom_x + process[6] = @zoom_y + when Processes::Angle + process[5] = @angle + when Processes::Tone + process[5] = @tone.clone + when Processes::Color + process[5] = @color.clone + when Processes::Hue + process[5] = @hue + when Processes::Opacity + process[5] = @opacity + end + end + # Decrease delay counter + process[1] -= 1 + # Process hasn't started yet, skip to the next one + next if process[1]>=0 + end + # Update process + @frameUpdates.push(process[0]) if !@frameUpdates.include?(process[0]) + fra = (process[2]==0) ? 1 : process[3] # Frame counter + dur = (process[2]==0) ? 1 : process[2] # Total duration of process + case process[0] + when Processes::XY, Processes::DeltaXY + @x = process[5] + fra * (process[7] - process[5]) / dur + @y = process[6] + fra * (process[8] - process[6]) / dur + when Processes::Curve + @x, @y = getCubicPoint2(process[5],fra.to_f/dur) + when Processes::Z + @z = process[5] + fra * (process[6] - process[5]) / dur + when Processes::Zoom + @zoom_x = process[5] + fra * (process[7] - process[5]) / dur + @zoom_y = process[6] + fra * (process[8] - process[6]) / dur + when Processes::Angle + @angle = process[5] + fra * (process[6] - process[5]) / dur + when Processes::Tone + @tone.red = process[5].red + fra * (process[6].red - process[5].red) / dur + @tone.green = process[5].green + fra * (process[6].green - process[5].green) / dur + @tone.blue = process[5].blue + fra * (process[6].blue - process[5].blue) / dur + @tone.gray = process[5].gray + fra * (process[6].gray - process[5].gray) / dur + when Processes::Color + @color.red = process[5].red + fra * (process[6].red - process[5].red) / dur + @color.green = process[5].green + fra * (process[6].green - process[5].green) / dur + @color.blue = process[5].blue + fra * (process[6].blue - process[5].blue) / dur + @color.alpha = process[5].alpha + fra * (process[6].alpha - process[5].alpha) / dur + when Processes::Hue + @hue = (process[6] - process[5]).to_f / dur + when Processes::Opacity + @opacity = process[5] + fra * (process[6] - process[5]) / dur + when Processes::Visible + @visible = process[5] + when Processes::BlendType + @blend_type = process[5] + when Processes::SE + pbSEPlay(process[5],process[6]) + when Processes::Name + @name = process[5] + when Processes::Origin + @origin = process[5] + when Processes::Src + @src_rect.x = process[5] + @src_rect.y = process[6] + when Processes::SrcSize + @src_rect.width = process[5] + @src_rect.height = process[6] + when Processes::CropBottom + @cropBottom = process[5] + end + # Increase frame counter + process[3] += 1 + if process[3]>process[2] + # Process has ended, erase it + callback(process[4]) if process[4] + @processes[i] = nil + procEnded = true + end + end + # Clear out empty spaces in @processes array caused by finished processes + @processes.compact! if procEnded + # Add the constant rotation speed + if @rotate_speed != 0 + @frameUpdates.push(Processes::Angle) if !@frameUpdates.include?(Processes::Angle) + @angle += @rotate_speed + while @angle<0; @angle += 360; end + @angle %= 360 + end + end +end + + + +def setPictureSprite(sprite, picture, iconSprite=false) + return if picture.frameUpdates.length==0 + for i in 0...picture.frameUpdates.length + case picture.frameUpdates[i] + when Processes::XY, Processes::DeltaXY + sprite.x = picture.x.round + sprite.y = picture.y.round + when Processes::Z + sprite.z = picture.z + when Processes::Zoom + sprite.zoom_x = picture.zoom_x / 100.0 + sprite.zoom_y = picture.zoom_y / 100.0 + when Processes::Angle + sprite.angle = picture.angle + when Processes::Tone + sprite.tone = picture.tone + when Processes::Color + sprite.color = picture.color + when Processes::Hue + # This doesn't do anything. + when Processes::BlendType + sprite.blend_type = picture.blend_type + when Processes::Opacity + sprite.opacity = picture.opacity + when Processes::Visible + sprite.visible = picture.visible + when Processes::Name + sprite.name = picture.name if iconSprite && sprite.name != picture.name + when Processes::Origin + case picture.origin + when PictureOrigin::TopLeft, PictureOrigin::Left, PictureOrigin::BottomLeft + sprite.ox = 0 + when PictureOrigin::Top, PictureOrigin::Center, PictureOrigin::Bottom + sprite.ox = (sprite.bitmap && !sprite.bitmap.disposed?) ? sprite.src_rect.width/2 : 0 + when PictureOrigin::TopRight, PictureOrigin::Right, PictureOrigin::BottomRight + sprite.ox = (sprite.bitmap && !sprite.bitmap.disposed?) ? sprite.src_rect.width : 0 + end + case picture.origin + when PictureOrigin::TopLeft, PictureOrigin::Top, PictureOrigin::TopRight + sprite.oy = 0 + when PictureOrigin::Left, PictureOrigin::Center, PictureOrigin::Right + sprite.oy = (sprite.bitmap && !sprite.bitmap.disposed?) ? sprite.src_rect.height/2 : 0 + when PictureOrigin::BottomLeft, PictureOrigin::Bottom, PictureOrigin::BottomRight + sprite.oy = (sprite.bitmap && !sprite.bitmap.disposed?) ? sprite.src_rect.height : 0 + end + when Processes::Src + next unless iconSprite && sprite.src_rect + sprite.src_rect.x = picture.src_rect.x + sprite.src_rect.y = picture.src_rect.y + when Processes::SrcSize + next unless iconSprite && sprite.src_rect + sprite.src_rect.width = picture.src_rect.width + sprite.src_rect.height = picture.src_rect.height + end + end + if iconSprite && sprite.src_rect && picture.cropBottom>=0 + spriteBottom = sprite.y-sprite.oy+sprite.src_rect.height + if spriteBottom>picture.cropBottom + sprite.src_rect.height = [picture.cropBottom-sprite.y+sprite.oy,0].max + end + end +end + +def setPictureIconSprite(sprite, picture) + setPictureSprite(sprite,picture,true) +end + + + +class PictureOrigin + TopLeft = 0 + Center = 1 + TopRight = 2 + BottomLeft = 3 + LowerLeft = 3 + BottomRight = 4 + LowerRight = 4 + Top = 5 + Bottom = 6 + Left = 7 + Right = 8 +end + + + +def pbTextBitmap(text, maxwidth=Graphics.width) + dims = [] + tmp = Bitmap.new(maxwidth,Graphics.height) + pbSetSystemFont(tmp) + drawFormattedTextEx(tmp,0,0,maxwidth,text,Color.new(248,248,248),Color.new(168,184,184)) + return tmp +end + + + +class PictureSprite < SpriteWrapper + def initialize(viewport, picture) + super(viewport) + @picture = picture + @pictureBitmap = nil + @customBitmap = nil + @customBitmapIsBitmap = true + @hue = 0 + update + end + + def dispose + @pictureBitmap.dispose if @pictureBitmap + super + end + + # Doesn't free the bitmap + def setCustomBitmap(bitmap) + @customBitmap = bitmap + @customBitmapIsBitmap = @customBitmap.is_a?(Bitmap) + end + + def update + super + @pictureBitmap.update if @pictureBitmap + # If picture file name is different from current one + if @customBitmap && @picture.name=="" + self.bitmap = (@customBitmapIsBitmap) ? @customBitmap : @customBitmap.bitmap + elsif @picture_name != @picture.name || @picture.hue.to_i != @hue.to_i + # Remember file name to instance variables + @picture_name = @picture.name + @hue = @picture.hue.to_i + # If file name is not empty + if @picture_name == "" + @pictureBitmap.dispose if @pictureBitmap + @pictureBitmap = nil + self.visible = false + return + end + # Get picture graphic + @pictureBitmap.dispose if @pictureBitmap + @pictureBitmap = AnimatedBitmap.new(@picture_name, @hue) + self.bitmap = (@pictureBitmap) ? @pictureBitmap.bitmap : nil + elsif @picture_name == "" + # Set sprite to invisible + self.visible = false + return + end + setPictureSprite(self,@picture) + end +end + + + +class EventScene + attr_accessor :onCTrigger,:onBTrigger,:onUpdate + + def initialize(viewport=nil) + @viewport = viewport + @onCTrigger = Event.new + @onBTrigger = Event.new + @onUpdate = Event.new + @pictures = [] + @picturesprites = [] + @usersprites = [] + @disposed = false + end + + def dispose + return if disposed? + for sprite in @picturesprites + sprite.dispose + end + for sprite in @usersprites + sprite.dispose + end + @onCTrigger.clear + @onBTrigger.clear + @onUpdate.clear + @pictures.clear + @picturesprites.clear + @usersprites.clear + @disposed = true + end + + def disposed? + return @disposed + end + + def addBitmap(x, y, bitmap) + # _bitmap_ can be a Bitmap or an AnimatedBitmap + # (update method isn't called if it's animated) + # EventScene doesn't take ownership of the passed-in bitmap + num = @pictures.length + picture = PictureEx.new(num) + picture.setXY(0,x,y) + picture.setVisible(0,true) + @pictures[num] = picture + @picturesprites[num] = PictureSprite.new(@viewport,picture) + @picturesprites[num].setCustomBitmap(bitmap) + return picture + end + + def addLabel(x, y, width, text) + addBitmap(x,y,pbTextBitmap(text,width)) + end + + def addImage(x, y, name) + num = @pictures.length + picture = PictureEx.new(num) + picture.name = name + picture.setXY(0,x,y) + picture.setVisible(0,true) + @pictures[num] = picture + @picturesprites[num] = PictureSprite.new(@viewport,picture) + return picture + end + + def addUserSprite(sprite) + @usersprites.push(sprite) + end + + def getPicture(num) + return @pictures[num] + end + + def wait(frames) + frames.times { update } + end + + def pictureWait(extraframes=0) + loop do + hasRunning = false + for pic in @pictures + hasRunning = true if pic.running? + end + break if !hasRunning + update + end + extraframes.times { update } + end + + def update + return if disposed? + Graphics.update + Input.update + for picture in @pictures + picture.update + end + for sprite in @picturesprites + sprite.update + end + for sprite in @usersprites + next if !sprite || sprite.disposed? || !sprite.is_a?(Sprite) + sprite.update + end + @onUpdate.trigger(self) + if Input.trigger?(Input::B) + @onBTrigger.trigger(self) + elsif Input.trigger?(Input::C) + @onCTrigger.trigger(self) + end + end + + def main + while !disposed? + update + end + end +end + + + +def pbEventScreen(cls) + pbFadeOutIn { + viewport = Viewport.new(0,0,Graphics.width,Graphics.height) + viewport.z = 99999 + PBDebug.logonerr { + cls.new(viewport).main + } + viewport.dispose + } +end \ No newline at end of file diff --git a/Data/Scripts/008_Objects and windows/011_Interpolators.rb b/Data/Scripts/008_Objects and windows/011_Interpolators.rb new file mode 100644 index 000000000..8e69413ab --- /dev/null +++ b/Data/Scripts/008_Objects and windows/011_Interpolators.rb @@ -0,0 +1,172 @@ +class Interpolator + ZOOM_X = 1 + ZOOM_Y = 2 + X = 3 + Y = 4 + OPACITY = 5 + COLOR = 6 + WAIT = 7 + + def initialize + @tweening = false + @tweensteps = [] + @sprite = nil + @frames = 0 + @step = 0 + end + + def tweening? + return @tweening + end + + def tween(sprite,items,frames) + @tweensteps = [] + if sprite && !sprite.disposed? && frames>0 + @frames = frames + @step = 0 + @sprite = sprite + for item in items + case item[0] + when ZOOM_X + @tweensteps[item[0]] = [sprite.zoom_x,item[1]-sprite.zoom_x] + when ZOOM_Y + @tweensteps[item[0]] = [sprite.zoom_y,item[1]-sprite.zoom_y] + when X + @tweensteps[item[0]] = [sprite.x,item[1]-sprite.x] + when Y + @tweensteps[item[0]] = [sprite.y,item[1]-sprite.y] + when OPACITY + @tweensteps[item[0]] = [sprite.opacity,item[1]-sprite.opacity] + when COLOR + @tweensteps[item[0]] = [sprite.color.clone,Color.new( + item[1].red-sprite.color.red, + item[1].green-sprite.color.green, + item[1].blue-sprite.color.blue, + item[1].alpha-sprite.color.alpha + )] + end + end + @tweening = true + end + end + + def update + if @tweening + t = (@step*1.0)/@frames + for i in 0...@tweensteps.length + item = @tweensteps[i] + next if !item + case i + when ZOOM_X + @sprite.zoom_x = item[0]+item[1]*t + when ZOOM_Y + @sprite.zoom_y = item[0]+item[1]*t + when X + @sprite.x = item[0]+item[1]*t + when Y + @sprite.y = item[0]+item[1]*t + when OPACITY + @sprite.opacity = item[0]+item[1]*t + when COLOR + @sprite.color = Color.new( + item[0].red+item[1].red*t, + item[0].green+item[1].green*t, + item[0].blue+item[1].blue*t, + item[0].alpha+item[1].alpha*t + ) + end + end + @step += 1 + if @step==@frames + @step = 0 + @frames = 0 + @tweening = false + end + end + end +end + + + +class RectInterpolator + def initialize(oldrect,newrect,frames) + restart(oldrect,newrect,frames) + end + + def restart(oldrect,newrect,frames) + @oldrect = oldrect + @newrect = newrect + @frames = [frames,1].max + @curframe = 0 + @rect = oldrect.clone + end + + def set(rect) + rect.set(@rect.x,@rect.y,@rect.width,@rect.height) + end + + def done? + @curframe>@frames + end + + def update + return if done? + t = (@curframe*1.0/@frames) + x1 = @oldrect.x + x2 = @newrect.x + x = x1+t*(x2-x1) + y1 = @oldrect.y + y2 = @newrect.y + y = y1+t*(y2-y1) + rx1 = @oldrect.x+@oldrect.width + rx2 = @newrect.x+@newrect.width + rx = rx1+t*(rx2-rx1) + ry1 = @oldrect.y+@oldrect.height + ry2 = @newrect.y+@newrect.height + ry = ry1+t*(ry2-ry1) + minx = xrx ? x : rx + miny = yry ? y : ry + @rect.set(minx,miny,maxx-minx,maxy-miny) + @curframe += 1 + end +end + + + +class PointInterpolator + attr_reader :x + attr_reader :y + + def initialize(oldx,oldy,newx,newy,frames) + restart(oldx,oldy,newx,newy,frames) + end + + def restart(oldx,oldy,newx,newy,frames) + @oldx = oldx + @oldy = oldy + @newx = newx + @newy = newy + @frames = frames + @curframe = 0 + @x = oldx + @y = oldy + end + + def done? + @curframe>@frames + end + + def update + return if done? + t = (@curframe*1.0/@frames) + rx1 = @oldx + rx2 = @newx + @x = rx1+t*(rx2-rx1) + ry1 = @oldy + ry2 = @newy + @y = ry1+t*(ry2-ry1) + @curframe += 1 + end +end \ No newline at end of file diff --git a/Data/Scripts/009_Scenes/001_Scene_Map.rb b/Data/Scripts/009_Scenes/001_Scene_Map.rb new file mode 100644 index 000000000..52bb30121 --- /dev/null +++ b/Data/Scripts/009_Scenes/001_Scene_Map.rb @@ -0,0 +1,239 @@ +#=============================================================================== +# ** Modified Scene_Map class for Pokémon. +#------------------------------------------------------------------------------- +# +#=============================================================================== +class Scene_Map + attr_reader :spritesetGlobal + + def spriteset + for i in @spritesets.values + return i if i.map==$game_map + end + return @spritesets.values[0] + end + + def createSpritesets + @spritesetGlobal = Spriteset_Global.new + @spritesets = {} + for map in $MapFactory.maps + @spritesets[map.map_id] = Spriteset_Map.new(map) + end + $MapFactory.setSceneStarted(self) + updateSpritesets + end + + def createSingleSpriteset(map) + temp = $scene.spriteset.getAnimations + @spritesets[map] = Spriteset_Map.new($MapFactory.maps[map]) + $scene.spriteset.restoreAnimations(temp) + $MapFactory.setSceneStarted(self) + updateSpritesets + end + + def disposeSpritesets + return if !@spritesets + for i in @spritesets.keys + next if !@spritesets[i] + @spritesets[i].dispose + @spritesets[i] = nil + end + @spritesets.clear + @spritesets = {} + @spritesetGlobal.dispose + @spritesetGlobal = nil + end + + def autofade(mapid) + playingBGM = $game_system.playing_bgm + playingBGS = $game_system.playing_bgs + return if !playingBGM && !playingBGS + map = pbLoadRxData(sprintf("Data/Map%03d",mapid)) + if playingBGM && map.autoplay_bgm + if (PBDayNight.isNight? rescue false) + pbBGMFade(0.8) if playingBGM.name!=map.bgm.name && playingBGM.name!=map.bgm.name+"_n" + else + pbBGMFade(0.8) if playingBGM.name!=map.bgm.name + end + end + if playingBGS && map.autoplay_bgs + pbBGMFade(0.8) if playingBGS.name!=map.bgs.name + end + Graphics.frame_reset + end + + def transfer_player(cancelVehicles=true) + $game_temp.player_transferring = false + pbCancelVehicles($game_temp.player_new_map_id) if cancelVehicles + autofade($game_temp.player_new_map_id) + pbBridgeOff + if $game_map.map_id!=$game_temp.player_new_map_id + $MapFactory.setup($game_temp.player_new_map_id) + end + $game_player.moveto($game_temp.player_new_x, $game_temp.player_new_y) + case $game_temp.player_new_direction + when 2; $game_player.turn_down + when 4; $game_player.turn_left + when 6; $game_player.turn_right + when 8; $game_player.turn_up + end + $game_player.straighten + $game_map.update + disposeSpritesets + GC.start + createSpritesets + if $game_temp.transition_processing + $game_temp.transition_processing = false + Graphics.transition(20) + end + $game_map.autoplay + Graphics.frame_reset + Input.update + end + + def call_name + $game_temp.name_calling = false + $game_player.straighten + $game_map.update + end + + def call_menu + $game_temp.menu_calling = false + $game_temp.in_menu = true + $game_player.straighten + $game_map.update + sscene = PokemonPauseMenu_Scene.new + sscreen = PokemonPauseMenu.new(sscene) + sscreen.pbStartPokemonMenu + $game_temp.in_menu = false + end + + def call_debug + $game_temp.debug_calling = false + pbPlayDecisionSE + $game_player.straighten + pbFadeOutIn { pbDebugMenu } + end + + def miniupdate + $PokemonTemp.miniupdate = true + loop do + updateMaps + $game_player.update + $game_system.update + $game_screen.update + break unless $game_temp.player_transferring + transfer_player + break if $game_temp.transition_processing + end + updateSpritesets + $PokemonTemp.miniupdate = false + end + + def updateMaps + for map in $MapFactory.maps + map.update + end + $MapFactory.updateMaps(self) + end + + def updateSpritesets + @spritesets = {} if !@spritesets + keys = @spritesets.keys.clone + for i in keys + if !$MapFactory.hasMap?(i) + @spritesets[i].dispose if @spritesets[i] + @spritesets[i] = nil + @spritesets.delete(i) + else + @spritesets[i].update + end + end + @spritesetGlobal.update + for map in $MapFactory.maps + @spritesets[map.map_id] = Spriteset_Map.new(map) if !@spritesets[map.map_id] + end + Events.onMapUpdate.trigger(self) + end + + def update + loop do + updateMaps + pbMapInterpreter.update + $game_player.update + $game_system.update + $game_screen.update + break unless $game_temp.player_transferring + transfer_player + break if $game_temp.transition_processing + end + updateSpritesets + if $game_temp.to_title + $scene = pbCallTitle + return + end + if $game_temp.transition_processing + $game_temp.transition_processing = false + if $game_temp.transition_name == "" + Graphics.transition(20) + else + Graphics.transition(40, "Graphics/Transitions/" + $game_temp.transition_name) + end + end + return if $game_temp.message_window_showing + if !pbMapInterpreterRunning? + if Input.trigger?(Input::C) + $PokemonTemp.hiddenMoveEventCalling = true + elsif Input.trigger?(Input::B) + unless $game_system.menu_disabled or $game_player.moving? + $game_temp.menu_calling = true + $game_temp.menu_beep = true + end + elsif Input.trigger?(Input::F5) + unless $game_player.moving? + $PokemonTemp.keyItemCalling = true + end + elsif Input.trigger?(Input::A) + if $PokemonSystem.runstyle==1 + $PokemonGlobal.runtoggle = !$PokemonGlobal.runtoggle + end + elsif Input.press?(Input::F9) + $game_temp.debug_calling = true if $DEBUG + end + end + unless $game_player.moving? + if $game_temp.name_calling; call_name + elsif $game_temp.menu_calling; call_menu + elsif $game_temp.debug_calling; call_debug +# elsif $game_temp.battle_calling; call_battle +# elsif $game_temp.shop_calling; call_shop +# elsif $game_temp.save_calling; call_save + elsif $PokemonTemp.keyItemCalling + $PokemonTemp.keyItemCalling = false + $game_player.straighten + pbUseKeyItem + elsif $PokemonTemp.hiddenMoveEventCalling + $PokemonTemp.hiddenMoveEventCalling = false + $game_player.straighten + Events.onAction.trigger(self) + end + end + end + + def main + createSpritesets + Graphics.transition(20) + loop do + Graphics.update + Input.update + update + break if $scene != self + end + Graphics.freeze + disposeSpritesets + if $game_temp.to_title + Graphics.transition(20) + Graphics.freeze + end + end +end \ No newline at end of file diff --git a/Data/Scripts/009_Scenes/002_Scene_Intro.rb b/Data/Scripts/009_Scenes/002_Scene_Intro.rb new file mode 100644 index 000000000..90e99ac56 --- /dev/null +++ b/Data/Scripts/009_Scenes/002_Scene_Intro.rb @@ -0,0 +1,134 @@ +class IntroEventScene < EventScene + TICKS_PER_PIC = 40 # 20 ticks per second, so 2 seconds + TICKS_PER_ENTER_FLASH = 40 + FADE_TICKS = 8 + + def initialize(pics,splash,viewport=nil) + super(nil) + @pics = pics + @splash = splash + @pic = addImage(0,0,"") + @pic.setOpacity(0,0) # set opacity to 0 after waiting 0 frames + @pic2 = addImage(0,0,"") # flashing "Press Enter" picture + @pic2.setOpacity(0,0) + @index = 0 + data_system = pbLoadRxData("Data/System") + pbBGMPlay(data_system.title_bgm) + openPic(self,nil) + end + + def openPic(scene,args) + onCTrigger.clear + @pic.name = "Graphics/Titles/"+@pics[@index] + # fade to opacity 255 in FADE_TICKS ticks after waiting 0 frames + @pic.moveOpacity(0,FADE_TICKS,255) + pictureWait + @timer = 0 # reset the timer + onUpdate.set(method(:picUpdate)) # call picUpdate every frame + onCTrigger.set(method(:closePic)) # call closePic when C key is pressed + end + + def closePic(scene,args) + onUpdate.clear + onCTrigger.clear + @pic.moveOpacity(0,FADE_TICKS,0) + pictureWait + @index += 1 # Move to the next picture + if @index>=@pics.length + openSplash(scene,args) + else + openPic(scene,args) + end + end + + def picUpdate(scene,args) + @timer += 1 + if @timer>TICKS_PER_PIC*Graphics.frame_rate/20 + @timer = 0 + closePic(scene,args) # Close the picture + end + end + + def openSplash(scene,args) + onUpdate.clear + onCTrigger.clear + @pic.name = "Graphics/Titles/"+@splash + @pic.moveOpacity(0,FADE_TICKS,255) + @pic2.name = "Graphics/Titles/start" + @pic2.setXY(0,0,322) + @pic2.setVisible(0,true) + @pic2.moveOpacity(0,FADE_TICKS,255) + pictureWait + onUpdate.set(method(:splashUpdate)) # call splashUpdate every frame + onCTrigger.set(method(:closeSplash)) # call closeSplash when C key is pressed + end + + def closeSplash(scene,args) + onUpdate.clear + onCTrigger.clear + # Play random cry + cry = pbCryFile(1+rand(PBSpecies.maxValue)) + pbSEPlay(cry,80,100) if cry + @pic.moveXY(0,20,0,0) + pictureWait + # Fade out + @pic.moveOpacity(0,FADE_TICKS,0) + @pic2.clearProcesses + @pic2.moveOpacity(0,FADE_TICKS,0) + pbBGMStop(1.0) + pictureWait + scene.dispose # Close the scene + sscene = PokemonLoad_Scene.new + sscreen = PokemonLoadScreen.new(sscene) + sscreen.pbStartLoadScreen + end + + def closeSplashDelete(scene,args) + onUpdate.clear + onCTrigger.clear + # Play random cry + cry = pbCryFile(1+rand(PBSpecies.maxValue)) + pbSEPlay(cry,80,100) if cry + @pic.moveXY(0,20,0,0) + pictureWait + # Fade out + @pic.moveOpacity(0,FADE_TICKS,0) + @pic2.clearProcesses + @pic2.moveOpacity(0,FADE_TICKS,0) + pbBGMStop(1.0) + pictureWait + scene.dispose # Close the scene + sscene = PokemonLoad_Scene.new + sscreen = PokemonLoadScreen.new(sscene) + sscreen.pbStartDeleteScreen + end + + def splashUpdate(scene,args) + # Flashing of "Press Enter" picture + if !@pic2.running? + @pic2.moveOpacity(TICKS_PER_ENTER_FLASH*2/10,TICKS_PER_ENTER_FLASH*4/10,0) + @pic2.moveOpacity(TICKS_PER_ENTER_FLASH*6/10,TICKS_PER_ENTER_FLASH*4/10,255) + end + if Input.press?(Input::DOWN) && + Input.press?(Input::B) && + Input.press?(Input::CTRL) + closeSplashDelete(scene,args) + end + end +end + + + +class Scene_Intro + def initialize(pics, splash = nil) + @pics = pics + @splash = splash + end + + def main + Graphics.transition(0) + @eventscene = IntroEventScene.new(@pics,@splash) + @eventscene.main + Graphics.freeze + end +end \ No newline at end of file diff --git a/Data/Scripts/009_Scenes/003_Scene_Controls.rb b/Data/Scripts/009_Scenes/003_Scene_Controls.rb new file mode 100644 index 000000000..f323f8847 --- /dev/null +++ b/Data/Scripts/009_Scenes/003_Scene_Controls.rb @@ -0,0 +1,46 @@ +#============================================================================== +# * Scene_Controls +#------------------------------------------------------------------------------ +# Shows a help screen listing the keyboard controls. +# Display with: +# pbEventScreen(ButtonEventScene) +#============================================================================== +class ButtonEventScene < EventScene + def initialize(viewport=nil) + super + Graphics.freeze + addImage(0,0,"Graphics/Pictures/helpbg") + @labels=[ + addLabel(52*2,13*2,Graphics.width*3/4,_INTL("Moves the main character. Also used to scroll through list entries.")), + addLabel(52*2,53*2,Graphics.width*3/4,_INTL("Used to confirm a choice, check things, and talk to people.")), + addLabel(52*2,93*2,Graphics.width*3/4,_INTL("Used to exit, cancel a choice or mode, and open the pause menu.")), + addLabel(52*2,133*2,Graphics.width*3/4,_INTL("Hold down while walking to run.")), + addLabel(52*2,157*2,Graphics.width*3/4,_INTL("Press to use a registered Key Item.")) + ] + @keys=[ + addImage(26*2,18*2,"Graphics/Pictures/helpArrowKeys"), + addImage(26*2,59*2,"Graphics/Pictures/helpCkey"), + addImage(26*2,99*2,"Graphics/Pictures/helpXkey"), + addImage(26*2,130*2,"Graphics/Pictures/helpZkey"), + addImage(26*2,154*2,"Graphics/Pictures/helpFkey") + ] + for key in @keys + key.origin=PictureOrigin::Top + end + for i in 0...5 # Make everything show (almost) immediately + @keys[i].setOrigin(0,PictureOrigin::Top) + @keys[i].setOpacity(0,255) + end + pictureWait # Update event scene with the changes + Graphics.transition(20) + # Go to next screen when user presses C + onCTrigger.set(method(:pbOnScreen1)) + end + + def pbOnScreen1(scene,args) + # End scene + Graphics.freeze + scene.dispose + Graphics.transition(20) + end +end \ No newline at end of file diff --git a/Data/Scripts/009_Scenes/004_Scene_Movie.rb b/Data/Scripts/009_Scenes/004_Scene_Movie.rb new file mode 100644 index 000000000..b9f98e69a --- /dev/null +++ b/Data/Scripts/009_Scenes/004_Scene_Movie.rb @@ -0,0 +1,52 @@ +#=============================================================================== +# ** Scene_Movie class, created by SoundSpawn, fixed by Popper. +#------------------------------------------------------------------------------- +# Instruction +# 1) Movies must be in a new folder called "Movies" in your directory. +# 2) If you call this script from an event, e.g. +# Call Script: $scene = Scene_Movie.new("INTRO") +# 3) Have fun playing movies with this script! +#=============================================================================== +class Scene_Movie + def initialize(movie) + @movie_name = RTP.getPath("Movies\\"+movie+".avi").gsub(/\//,"\\") + end + + def main + @temp = Win32API.pbFindRgssWindow.to_s + movie = Win32API.new('winmm','mciSendString','%w(p,p,l,l)','V') + x=movie.call("open \""+@movie_name+ + "\" alias FILE style 1073741824 parent " + @temp.to_s,0,0,0) + @message = Win32API.new('user32','SendMessage','%w(l,l,l,l)','V') + @detector = Win32API.new('user32','GetSystemMetrics','%w(l)','L') + @width = @detector.call(0) + if @width == 640 + #fullscreen + Graphics.update + sleep(0.1) + Graphics.update + sleep(0.1) + Graphics.update + sleep(0.1) + #fullscreen + end + status = " " * 255 + x=movie.call("play FILE",0,0,0) + loop do + sleep(0.1) + @message.call(@temp.to_i,11,0,0) + Graphics.update + @message.call(@temp.to_i,11,1,0) + Input.update + movie.call("status FILE mode",status,255,0) + true_status = status.unpack("aaaa") + break if true_status.to_s != "play" + if Input.trigger?(Input::B) + movie.call("close FILE",0,0,0) + $scene = Scene_Map.new + break + end + end + $scene = Scene_Map.new + end +end \ No newline at end of file diff --git a/Data/Scripts/009_Scenes/005_Scene_Credits.rb b/Data/Scripts/009_Scenes/005_Scene_Credits.rb new file mode 100644 index 000000000..8773afe0d --- /dev/null +++ b/Data/Scripts/009_Scenes/005_Scene_Credits.rb @@ -0,0 +1,231 @@ +# Backgrounds to show in credits. Found in Graphics/Titles/ folder +CreditsBackgroundList = ["credits1","credits2","credits3","credits4","credits5"] +CreditsMusic = "Credits" +CreditsScrollSpeed = 2 +CreditsFrequency = 9 # Number of seconds per credits slide +CREDITS_OUTLINE = Color.new(0,0,128, 255) +CREDITS_SHADOW = Color.new(0,0,0, 100) +CREDITS_FILL = Color.new(255,255,255, 255) + +#============================================================================== +# * Scene_Credits +#------------------------------------------------------------------------------ +# Scrolls the credits you make below. Original Author unknown. +# +## Edited by MiDas Mike so it doesn't play over the Title, but runs by calling +# the following: +# $scene = Scene_Credits.new +# +## New Edit 3/6/2007 11:14 PM by AvatarMonkeyKirby. +# Ok, what I've done is changed the part of the script that was supposed to make +# the credits automatically end so that way they actually end! Yes, they will +# actually end when the credits are finished! So, that will make the people you +# should give credit to now is: Unknown, MiDas Mike, and AvatarMonkeyKirby. +# -sincerly yours, +# Your Beloved +# Oh yea, and I also added a line of code that fades out the BGM so it fades +# sooner and smoother. +# +## New Edit 24/1/2012 by Maruno. +# Added the ability to split a line into two halves with , with each half +# aligned towards the centre. Please also credit me if used. +# +## New Edit 22/2/2012 by Maruno. +# Credits now scroll properly when played with a zoom factor of 0.5. Music can +# now be defined. Credits can't be skipped during their first play. +# +## New Edit 25/3/2020 by Maruno. +# Scroll speed is now independent of frame rate. Now supports non-integer values +# for CreditsScrollSpeed. +# +## New Edit 21/8/2020 by Marin. +# Now automatically inserts the credits from the plugins that have been +# registered through the PluginManager module. +#============================================================================== + +class Scene_Credits + +# This next piece of code is the credits. +#Start Editing +CREDIT=<<_END_ + +Your credits go here. + +Your credits go here. + +Your credits go here. + +Your credits go here. + +Your credits go here. + +{INSERTS_PLUGIN_CREDITS_DO_NOT_REMOVE} +"Pokémon Essentials" was created by: +Flameguru +Poccil (Peter O.) +Maruno + +With contributions from: +AvatarMonkeyKirbyMarin +BoushyMiDas Mike +Brother1440Near Fantastica +FL.PinkMan +Genzai KawakamiPopper +help-14Rataime +IceGod64SoundSpawn +Jacob O. Wobbrockthe__end +KitsuneKoutaVenom12 +Lisa AnthonyWachunga +Luka S.J. +and everyone else who helped out + + + +"RPG Maker XP" by: +Enterbrain + +Pokémon is owned by: +The Pokémon Company +Nintendo +Affiliated with Game Freak + +This is a non-profit fan-made game. +No copyright infringements intended. +Please support the official games! + +_END_ +#Stop Editing + + def main +#------------------------------- +# Animated Background Setup +#------------------------------- + @sprite = IconSprite.new(0,0) + @backgroundList = CreditsBackgroundList + @frameCounter = 0 + # Number of game frames per background frame + @framesPerBackground = CreditsFrequency * Graphics.frame_rate + @sprite.setBitmap("Graphics/Titles/"+@backgroundList[0]) +#------------------ +# Credits text Setup +#------------------ + plugin_credits = "" + PluginManager.plugins.each do |plugin| + pcred = PluginManager.credits(plugin) + plugin_credits << "\"#{plugin}\" version #{PluginManager.version(plugin)}\n" + if pcred.size >= 5 + plugin_credits << pcred[0] + "\n" + i = 1 + until i >= pcred.size + plugin_credits << pcred[i] + "" + (pcred[i + 1] || "") + "\n" + i += 2 + end + else + pcred.each do |name| + plugin_credits << name + "\n" + end + end + plugin_credits << "\n" + end + CREDIT.gsub!(/{INSERTS_PLUGIN_CREDITS_DO_NOT_REMOVE}/, plugin_credits) + credit_lines = CREDIT.split(/\n/) + credit_bitmap = Bitmap.new(Graphics.width,32 * credit_lines.size) + credit_lines.each_index do |i| + line = credit_lines[i] + line = line.split("") + # LINE ADDED: If you use in your own game, you should remove this line + pbSetSystemFont(credit_bitmap) # <--- This line was added + x = 0 + xpos = 0 + align = 1 # Centre align + linewidth = Graphics.width + for j in 0...line.length + if line.length>1 + xpos = (j==0) ? 0 : 20 + Graphics.width/2 + align = (j==0) ? 2 : 0 # Right align : left align + linewidth = Graphics.width/2 - 20 + end + credit_bitmap.font.color = CREDITS_SHADOW + credit_bitmap.draw_text(xpos,i * 32 + 8,linewidth,32,line[j],align) + credit_bitmap.font.color = CREDITS_OUTLINE + credit_bitmap.draw_text(xpos + 2,i * 32 - 2,linewidth,32,line[j],align) + credit_bitmap.draw_text(xpos,i * 32 - 2,linewidth,32,line[j],align) + credit_bitmap.draw_text(xpos - 2,i * 32 - 2,linewidth,32,line[j],align) + credit_bitmap.draw_text(xpos + 2,i * 32,linewidth,32,line[j],align) + credit_bitmap.draw_text(xpos - 2,i * 32,linewidth,32,line[j],align) + credit_bitmap.draw_text(xpos + 2,i * 32 + 2,linewidth,32,line[j],align) + credit_bitmap.draw_text(xpos,i * 32 + 2,linewidth,32,line[j],align) + credit_bitmap.draw_text(xpos - 2,i * 32 + 2,linewidth,32,line[j],align) + credit_bitmap.font.color = CREDITS_FILL + credit_bitmap.draw_text(xpos,i * 32,linewidth,32,line[j],align) + end + end + @trim = Graphics.height/10 + @realOY = -(Graphics.height-@trim) # -430 + @oyChangePerFrame = CreditsScrollSpeed*20.0/Graphics.frame_rate + @credit_sprite = Sprite.new(Viewport.new(0,@trim,Graphics.width,Graphics.height-(@trim*2))) + @credit_sprite.bitmap = credit_bitmap + @credit_sprite.z = 9998 + @credit_sprite.oy = @realOY + @bg_index = 0 + @zoom_adjustment = 1.0/$ResizeFactor + @last_flag = false +#-------- +# Setup +#-------- + # Stops all audio but background music + previousBGM = $game_system.getPlayingBGM + pbMEStop + pbBGSStop + pbSEStop + pbBGMFade(2.0) + pbBGMPlay(CreditsMusic) + Graphics.transition(20) + loop do + Graphics.update + Input.update + update + break if $scene != self + end + Graphics.freeze + @sprite.dispose + @credit_sprite.dispose + $PokemonGlobal.creditsPlayed = true + pbBGMPlay(previousBGM) + end + + # Check if the credits should be cancelled + def cancel? + if Input.trigger?(Input::C) && $PokemonGlobal.creditsPlayed + $scene = Scene_Map.new + pbBGMFade(1.0) + return true + end + return false + end + + # Checks if credits bitmap has reached its ending point + def last? + if @realOY > @credit_sprite.bitmap.height + @trim + $scene = ($game_map) ? Scene_Map.new : nil + pbBGMFade(2.0) + return true + end + return false + end + + def update + @frameCounter += 1 + # Go to next slide + if @frameCounter >= @framesPerBackground + @frameCounter -= @framesPerBackground + @bg_index += 1 + @bg_index = 0 if @bg_index >= @backgroundList.length + @sprite.setBitmap("Graphics/Titles/"+@backgroundList[@bg_index]) + end + return if cancel? + return if last? + @realOY += @oyChangePerFrame + @credit_sprite.oy = @realOY + end +end \ No newline at end of file diff --git a/Data/Scripts/009_Scenes/006_Transitions.rb b/Data/Scripts/009_Scenes/006_Transitions.rb new file mode 100644 index 000000000..6b31eb27f --- /dev/null +++ b/Data/Scripts/009_Scenes/006_Transitions.rb @@ -0,0 +1,1617 @@ +module Graphics + @@transition = nil + STOP_WHILE_TRANSITION = true + + unless defined?(transition_KGC_SpecialTransition) + class << Graphics + alias transition_KGC_SpecialTransition transition + end + + class << Graphics + alias update_KGC_SpecialTransition update + end + end + + def self.transition(duration=8,filename="",vague=20) + duration = duration.floor + if judge_special_transition(duration,filename) + duration = 0 + filename = "" + end + begin + transition_KGC_SpecialTransition(duration,filename,vague) + rescue Exception + if filename!="" + transition_KGC_SpecialTransition(duration,"",vague) + end + end + if STOP_WHILE_TRANSITION && !@_interrupt_transition + while @@transition && !@@transition.disposed? + update + end + end + end + + def self.update + update_KGC_SpecialTransition +=begin + if Graphics.frame_count%40==0 + count=0 + ObjectSpace.each_object(Object) { |o| count += 1 } + echo("Objects: #{count}\r\n") + end +=end + @@transition.update if @@transition && !@@transition.disposed? + @@transition = nil if @@transition && @@transition.disposed? + end + + def self.judge_special_transition(duration,filename) + return false if @_interrupt_transition + ret = true + if @@transition && !@@transition.disposed? + @@transition.dispose + @@transition = nil + end + dc = File.basename(filename).downcase + case dc + # Other coded transitions + when "breakingglass"; @@transition = BreakingGlass.new(duration) + when "rotatingpieces"; @@transition = ShrinkingPieces.new(duration,true) + when "shrinkingpieces"; @@transition = ShrinkingPieces.new(duration,false) + when "splash"; @@transition = SplashTransition.new(duration) + when "random_stripe_v"; @@transition = RandomStripeTransition.new(duration,0) + when "random_stripe_h"; @@transition = RandomStripeTransition.new(duration,1) + when "zoomin"; @@transition = ZoomInTransition.new(duration) + when "scrolldown"; @@transition = ScrollScreen.new(duration,2) + when "scrollleft"; @@transition = ScrollScreen.new(duration,4) + when "scrollright"; @@transition = ScrollScreen.new(duration,6) + when "scrollup"; @@transition = ScrollScreen.new(duration,8) + when "scrolldownleft"; @@transition = ScrollScreen.new(duration,1) + when "scrolldownright"; @@transition = ScrollScreen.new(duration,3) + when "scrollupleft"; @@transition = ScrollScreen.new(duration,7) + when "scrollupright"; @@transition = ScrollScreen.new(duration,9) + when "mosaic"; @@transition = MosaicTransition.new(duration) + # HGSS transitions + when "snakesquares"; @@transition = SnakeSquares.new(duration) + when "diagonalbubbletl"; @@transition = DiagonalBubble.new(duration,0) + when "diagonalbubbletr"; @@transition = DiagonalBubble.new(duration,1) + when "diagonalbubblebl"; @@transition = DiagonalBubble.new(duration,2) + when "diagonalbubblebr"; @@transition = DiagonalBubble.new(duration,3) + when "risingsplash"; @@transition = RisingSplash.new(duration) + when "twoballpass"; @@transition = TwoBallPass.new(duration) + when "spinballsplit"; @@transition = SpinBallSplit.new(duration) + when "threeballdown"; @@transition = ThreeBallDown.new(duration) + when "balldown"; @@transition = BallDown.new(duration) + when "wavythreeballup"; @@transition = WavyThreeBallUp.new(duration) + when "wavyspinball"; @@transition = WavySpinBall.new(duration) + when "fourballburst"; @@transition = FourBallBurst.new(duration) + # Graphic transitions + when ""; @@transition = FadeTransition.new(duration) + else; ret=false + end + Graphics.frame_reset if ret + return ret + end +end + + + +#=============================================================================== +# +#=============================================================================== +class BreakingGlass + def initialize(numframes) + @disposed = false + @numframes = numframes + @opacitychange = (numframes<=0) ? 255 : 255.0/numframes + cx = 6 + cy = 5 + @bitmap = Graphics.snap_to_bitmap + if !@bitmap + @disposed = true + return + end + width = @bitmap.width/cx + height = @bitmap.height/cy + @numtiles = cx*cy + @viewport = Viewport.new(0,0,Graphics.width,Graphics.height) + @viewport.z = 99999 + @sprites = [] + @offset = [] + @y = [] + for i in 0...@numtiles + @sprites[i] = Sprite.new(@viewport) + @sprites[i].bitmap = @bitmap + @sprites[i].x = width*(i%cx) + @sprites[i].y = height*(i/cx) + @sprites[i].src_rect.set(@sprites[i].x,@sprites[i].y,width,height) + @offset[i] = (rand(100)+1)*3.0/100.0 + @y[i] = @sprites[i].y + end + end + + def disposed?; @disposed; end + + def dispose + if !disposed? + @bitmap.dispose + for i in 0...@numtiles + @sprites[i].visible = false + @sprites[i].dispose + end + @sprites.clear + @viewport.dispose if @viewport + @disposed = true + end + end + + def update + return if disposed? + continue = false + for i in 0...@numtiles + @sprites[i].opacity -= @opacitychange + @y[i] += @offset[i] + @sprites[i].y = @y[i] + continue = true if @sprites[i].opacity>0 + end + self.dispose if !continue + end +end + + + +#=============================================================================== +# +#=============================================================================== +class ShrinkingPieces + def initialize(numframes,rotation) + @disposed = false + @rotation = rotation + @numframes = numframes + @opacitychange = (numframes<=0) ? 255 : 255.0/numframes + cx = 6 + cy = 5 + @bitmap = Graphics.snap_to_bitmap + if !@bitmap + @disposed = true + return + end + width = @bitmap.width/cx + height = @bitmap.height/cy + @numtiles = cx*cy + @viewport = Viewport.new(0,0,Graphics.width,Graphics.height) + @viewport.z = 99999 + @sprites = [] + for i in 0...@numtiles + @sprites[i] = Sprite.new(@viewport) + @sprites[i].bitmap = @bitmap + @sprites[i].ox = width/2 + @sprites[i].oy = height/2 + @sprites[i].x = width*(i%cx)+@sprites[i].ox + @sprites[i].y = height*(i/cx)+@sprites[i].oy + @sprites[i].src_rect.set(width*(i%cx),height*(i/cx),width,height) + end + end + + def disposed?; @disposed; end + + def dispose + if !disposed? + @bitmap.dispose + for i in 0...@numtiles + @sprites[i].visible = false + @sprites[i].dispose + end + @sprites.clear + @viewport.dispose if @viewport + @disposed = true + end + end + + def update + return if disposed? + continue = false + for i in 0...@numtiles + @sprites[i].opacity -= @opacitychange + if @rotation + @sprites[i].angle += 40 + @sprites[i].angle %= 360 + end + @sprites[i].zoom_x = @sprites[i].opacity/255.0 + @sprites[i].zoom_y = @sprites[i].opacity/255.0 + continue = true if @sprites[i].opacity>0 + end + self.dispose if !continue + end +end + + + +#=============================================================================== +# +#=============================================================================== +class SplashTransition + SPLASH_SIZE = 32 + + def initialize(numframes,vague=9.6) + @duration = numframes + @numframes = numframes + @splash_dir = [] + @disposed = false + if @numframes<=0 + @disposed = true + return + end + @buffer = Graphics.snap_to_bitmap + if !@buffer + @disposed = true + return + end + @viewport = Viewport.new(0,0,Graphics.width,Graphics.height) + @viewport.z = 99999 + @sprite = Sprite.new(@viewport) + @sprite.bitmap = Bitmap.new(Graphics.width, Graphics.height) + size = SPLASH_SIZE + size = [size,1].max + cells = Graphics.width*Graphics.height/(size**2) + rows = Graphics.width/size + rect = Rect.new(0,0,size,size) + mag = 40.0/@numframes + cells.times { |i| + rect.x = i%rows*size + rect.y = i/rows*size + x = rect.x/size-(rows>>1) + y = rect.y/size-((cells/rows)>>1) + r = Math.sqrt(x**2+y**2)/vague + @splash_dir[i] = [] + if r!=0 + @splash_dir[i][0] = x/r + @splash_dir[i][1] = y/r + else + @splash_dir[i][0] = (x!= 0) ? x*1.5 : pmrand*vague + @splash_dir[i][1] = (y!= 0) ? y*1.5 : pmrand*vague + end + @splash_dir[i][0] += (rand-0.5)*vague + @splash_dir[i][1] += (rand-0.5)*vague + @splash_dir[i][0] *= mag + @splash_dir[i][1] *= mag + } + @sprite.bitmap.blt(0,0,@buffer,@buffer.rect) + end + + def disposed?; @disposed; end + + def dispose + return if disposed? + @buffer.dispose if @buffer + @buffer = nil + @sprite.visible = false + @sprite.bitmap.dispose + @sprite.dispose + @viewport.dispose if @viewport + @disposed = true + end + + def update + return if disposed? + if @duration==0 + dispose + else + size = SPLASH_SIZE + cells = Graphics.width*Graphics.height/(size**2) + rows = Graphics.width/size + rect = Rect.new(0,0,size,size) + buffer = @buffer + sprite = @sprite + phase = @numframes-@duration + sprite.bitmap.clear + cells.times { |i| + rect.x = (i%rows)*size + rect.y = (i/rows)*size + dx = rect.x+@splash_dir[i][0]*phase + dy = rect.y+@splash_dir[i][1]*phase + sprite.bitmap.blt(dx,dy,buffer,rect) + } + sprite.opacity = 384*@duration/@numframes + @duration -= 1 + end + end + + private + + def pmrand + return (rand(2)==0) ? 1 : -1 + end +end + + + +#=============================================================================== +# +#=============================================================================== +class RandomStripeTransition + RAND_STRIPE_SIZE = 2 + + def initialize(numframes,direction) + @duration = numframes + @numframes = numframes + @disposed = false + if @numframes<=0 + @disposed = true + return + end + @buffer = Graphics.snap_to_bitmap + if !@buffer + @disposed = true + return + end + @viewport = Viewport.new(0,0,Graphics.width,Graphics.height) + @viewport.z = 99999 + @sprite = Sprite.new(@viewport) + @sprite.bitmap = Bitmap.new(Graphics.width,Graphics.height) + ########## + @direction = direction + size = RAND_STRIPE_SIZE + bands = ((@direction==0) ? Graphics.width : Graphics.height)/size + @rand_stripe_deleted = [] + @rand_stripe_deleted_count = 0 + ary = (0...bands).to_a + @rand_stripe_index_array = ary.sort_by { rand } + ########## + @sprite.bitmap.blt(0,0,@buffer,@buffer.rect) + end + + def disposed?; @disposed; end + + def dispose + return if disposed? + @buffer.dispose if @buffer + @buffer = nil + @sprite.visible = false + @sprite.bitmap.dispose + @sprite.dispose + @viewport.dispose if @viewport + @disposed = true + end + + def update + return if disposed? + if @duration==0 + dispose + else + dir = @direction + size = RAND_STRIPE_SIZE + bands = ((dir==0) ? Graphics.width : Graphics.height)/size + rect = Rect.new(0,0,(dir==0) ? size : Graphics.width,(dir==0) ? Graphics.height : size) + buffer = @buffer + sprite = @sprite + phase = @numframes-@duration + count = (bands-bands*@duration/@numframes)-@rand_stripe_deleted_count + while count > 0 + @rand_stripe_deleted[@rand_stripe_index_array.pop] = true + @rand_stripe_deleted_count += 1 + count -= 1 + end + sprite.bitmap.clear + bands.to_i.times { |i| + unless @rand_stripe_deleted[i] + if dir==0 + rect.x = i*size + sprite.bitmap.blt(rect.x,0,buffer,rect) + else + rect.y = i*size + sprite.bitmap.blt(0,rect.y,buffer,rect) + end + end + } + @duration -= 1 + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +class ZoomInTransition + def initialize(numframes) + @duration = numframes + @numframes = numframes + @disposed = false + if @numframes<=0 + @disposed = true + return + end + @buffer = Graphics.snap_to_bitmap + if !@buffer + @disposed = true + return + end + @width = @buffer.width + @height = @buffer.height + @viewport = Viewport.new(0,0,@width,@height) + @viewport.z = 99999 + @sprite = Sprite.new(@viewport) + @sprite.bitmap = @buffer + @sprite.ox = @width/2 + @sprite.oy = @height/2 + @sprite.x = @width/2 + @sprite.y = @height/2 + end + + def disposed?; @disposed; end + + def dispose + return if disposed? + @buffer.dispose if @buffer + @buffer = nil + @sprite.dispose if @sprite + @viewport.dispose if @viewport + @disposed = true + end + + def update + return if disposed? + if @duration==0 + dispose + else + @sprite.zoom_x += 0.2 + @sprite.zoom_y += 0.2 + @sprite.opacity = (@duration-1)*255/@numframes + @duration -= 1 + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +class ScrollScreen + def initialize(numframes,direction) + @numframes = numframes + @duration = numframes + @dir = direction + @disposed = false + if @numframes<=0 + @disposed = true + return + end + @buffer = Graphics.snap_to_bitmap + if !@buffer + @disposed = true + return + end + @width = @buffer.width + @height = @buffer.height + @viewport = Viewport.new(0,0,@width,@height) + @viewport.z = 99999 + @sprite = Sprite.new(@viewport) + @sprite.bitmap = @buffer + end + + def disposed?; @disposed; end + + def dispose + return if disposed? + @buffer.dispose if @buffer + @buffer = nil + @sprite.dispose if @sprite + @viewport.dispose if @viewport + @disposed = true + end + + def update + return if disposed? + if @duration==0 + dispose + else + case @dir + when 1 # down left + @sprite.y += (@height/@numframes) + @sprite.x -= (@width/@numframes) + when 2 # down + @sprite.y += (@height/@numframes) + when 3 # down right + @sprite.y += (@height/@numframes) + @sprite.x += (@width/@numframes) + when 4 # left + @sprite.x -= (@width/@numframes) + when 6 # right + @sprite.x += (@width/@numframes) + when 7 # up left + @sprite.y -= (@height/@numframes) + @sprite.x -= (@width/@numframes) + when 8 # up + @sprite.y -= (@height/@numframes) + when 9 # up right + @sprite.y -= (@height/@numframes) + @sprite.x += (@width/@numframes) + end + @duration -= 1 + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +class MosaicTransition + def initialize(numframes) + @duration = numframes + @numframes = numframes + @disposed = false + if @numframes<=0 + @disposed = true + return + end + @buffer = Graphics.snap_to_bitmap + if !@buffer + @disposed = true + return + end + @viewport = Viewport.new(0,0,Graphics.width,Graphics.height) + @viewport.z = 99999 + @sprite = Sprite.new(@viewport) + @sprite.bitmap = @buffer + @bitmapclone = @buffer.clone + @bitmapclone2 = @buffer.clone + end + + def disposed?; @disposed; end + + def dispose + return if disposed? + @buffer.dispose if @buffer + @buffer = nil + @sprite.dispose if @sprite + @viewport.dispose if @viewport + @disposed = true + end + + def update + return if disposed? + if @duration==0 + dispose + else + @bitmapclone2.stretch_blt( + Rect.new(0,0,@buffer.width*@duration/@numframes,@buffer.height*@duration/@numframes), + @bitmapclone, + Rect.new(0,0,@buffer.width,@buffer.height)) + @buffer.stretch_blt( + Rect.new(0,0,@buffer.width,@buffer.height), + @bitmapclone2, + Rect.new(0,0,@buffer.width*@duration/@numframes,@buffer.height*@duration/@numframes)) + @duration -= 1 + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +class FadeTransition + def initialize(numframes) + @duration = numframes + @numframes = numframes + @disposed = false + if @duration<=0 + @disposed = true + return + end + @viewport = Viewport.new(0,0,Graphics.width,Graphics.height) + @viewport.z = 99999 + @sprite = BitmapSprite.new(Graphics.width,Graphics.height,@viewport) + @sprite.bitmap.fill_rect(0,0,Graphics.width,Graphics.height,Color.new(0,0,0)) + @sprite.opacity = 255 + end + + def disposed?; @disposed; end + + def dispose + return if disposed? + @sprite.dispose if @sprite + @viewport.dispose if @viewport + @disposed = true + end + + def update + return if disposed? + if @duration==0 + dispose + else + @sprite.opacity = (@duration-1)*255/@numframes + @duration -= 1 + end + end +end + + + +#=============================================================================== +# HGSS wild outdoor +#=============================================================================== +class SnakeSquares + def initialize(numframes) + @numframes = numframes + @duration = numframes + @disposed = false + @bitmap = BitmapCache.load_bitmap("Graphics/Transitions/black_square") + if !@bitmap + @disposed = true + return + end + width = @bitmap.width + height = @bitmap.height + cx = Graphics.width/width # 8 + cy = Graphics.height/height # 6 + @numtiles = cx*cy + @viewport = Viewport.new(0,0,Graphics.width,Graphics.height) + @viewport.z = 99999 + @sprites = [] + @frame = [] + @addzoom = 0.125*50/@numframes + for i in 0...cy + for j in 0...cx + k = i*cx+j + x = width*(j%cx) + x = (cx-1)*width-x if (i<3 && i%2==1) || (i>=3 && i%2==0) + @sprites[k] = Sprite.new(@viewport) + @sprites[k].x = x+width/2 + @sprites[k].y = height*i + @sprites[k].ox = width/2 + @sprites[k].visible = false + @sprites[k].bitmap = @bitmap + if k>=@numtiles/2 + @frame[k] = 2*(@numtiles-k-1)*(@numframes-1/@addzoom)/50 + else + @frame[k] = 2*k*(@numframes-1/@addzoom)/50 + end + end + end + end + + def disposed?; @disposed; end + + def dispose + if !disposed? + @bitmap.dispose + for i in 0...@numtiles + if @sprites[i] + @sprites[i].visible = false + @sprites[i].dispose + end + end + @sprites.clear + @viewport.dispose if @viewport + @disposed = true + end + end + + def update + return if disposed? + if @duration==0 + dispose + else + count = @numframes-@duration + for i in 0...@numtiles + if @frame[i]=count + @sprites[i].visible = true + @sprites[i].zoom_x = @addzoom*(count-@frame[i]) + @sprites[i].zoom_x = 1.0 if @sprites[i].zoom_x>1.0 + end + end + end + @duration -= 1 + end +end + + + +#=============================================================================== +# HGSS wild indoor day (origin=0) +# HGSS wild indoor night (origin=3) +# HGSS wild cave (origin=3) +#=============================================================================== +class DiagonalBubble + def initialize(numframes,origin=0) + @numframes = numframes + @duration = numframes + @disposed = false + @bitmap = BitmapCache.load_bitmap("Graphics/Transitions/black_square") + if !@bitmap + @disposed = true + return + end + width = @bitmap.width + height = @bitmap.height + cx = Graphics.width/width # 8 + cy = Graphics.height/height # 6 + @numtiles = cx*cy + @viewport = Viewport.new(0,0,Graphics.width,Graphics.height) + @viewport.z = 99999 + @sprites = [] + @frame = [] + # 1.2, 0.6 and 0.8 determined by trigonometry of default screen size + l = 1.2*Graphics.width/(@numtiles-8) + for i in 0...cy + for j in 0...cx + k = i*cx+j + @sprites[k] = Sprite.new(@viewport) + @sprites[k].x = width*j+width/2 + @sprites[k].y = height*i+height/2 + @sprites[k].ox = width/2 + @sprites[k].oy = height/2 + @sprites[k].visible = false + @sprites[k].bitmap = @bitmap + case origin + when 1; k = i*cx+(cx-1-j) # Top right + when 2; k = @numtiles-1-(i*cx+(cx-1-j)) # Bottom left + when 3; k = @numtiles-1-k # Bottom right + end + @frame[k] = ((0.6*j*width+0.8*i*height)*(@numframes/50)/l).floor + end + end + @addzoom = 0.125*50/@numframes + end + + def disposed?; @disposed; end + + def dispose + if !disposed? + @bitmap.dispose + for i in 0...@numtiles + if @sprites[i] + @sprites[i].visible = false + @sprites[i].dispose + end + end + @sprites.clear + @viewport.dispose if @viewport + @disposed = true + end + end + + def update + return if disposed? + if @duration==0 + dispose + else + count = @numframes-@duration + for i in 0...@numtiles + if @frame[i]=count + @sprites[i].visible = true + @sprites[i].zoom_x = @addzoom*(count-@frame[i]) + @sprites[i].zoom_x = 1.0 if @sprites[i].zoom_x>1.0 + @sprites[i].zoom_y = @sprites[i].zoom_x + end + end + end + @duration -= 1 + end +end + + + +#=============================================================================== +# HGSS wild water +#=============================================================================== +class RisingSplash + def initialize(numframes) + @numframes = numframes + @duration = numframes + @disposed = false + if @numframes<=0 + @disposed = true + return + end + @bubblebitmap = BitmapCache.load_bitmap("Graphics/Transitions/water_1") + @splashbitmap = BitmapCache.load_bitmap("Graphics/Transitions/water_2") + @blackbitmap = BitmapCache.load_bitmap("Graphics/Transitions/black_half") + @buffer = Graphics.snap_to_bitmap + if !@bubblebitmap || !@splashbitmap || !@blackbitmap || !@buffer + @disposed = true + return + end + @width = @buffer.width + @height = @buffer.height + @viewport = Viewport.new(0,0,@width,@height) + @viewport.z = 99999 + @rearsprite = Sprite.new(@viewport) + @rearsprite.z = 1 + @rearsprite.zoom_y = 2.0 + @rearsprite.bitmap = @blackbitmap + @bgsprites = [] + rect = Rect.new(0,0,@width,2) + for i in 0...@height/2 + @bgsprites[i] = Sprite.new(@viewport) + @bgsprites[i].y = i*2 + @bgsprites[i].z = 2 + @bgsprites[i].bitmap = @buffer + rect.y = i*2 + @bgsprites[i].src_rect = rect + end + @bubblesprite = Sprite.new(@viewport) + @bubblesprite.y = @height + @bubblesprite.z = 3 + @bubblesprite.bitmap = @bubblebitmap + @splashsprite = Sprite.new(@viewport) + @splashsprite.y = @height + @splashsprite.z = 4 + @splashsprite.bitmap = @splashbitmap + @blacksprite = Sprite.new(@viewport) + @blacksprite.y = @height + @blacksprite.z = 5 + @blacksprite.zoom_y = 2.0 + @blacksprite.bitmap = @blackbitmap + @bubblesuby = @height*2/@numframes + @splashsuby = @bubblesuby*2 + @blacksuby = @height/(@numframes*0.1).floor + @angmult = 2/(@numframes/50) + end + + def disposed?; @disposed; end + + def dispose + return if disposed? + @buffer.dispose if @buffer + @buffer = nil + @bubblebitmap.dispose if @bubblebitmap + @bubblebitmap = nil + @splashbitmap.dispose if @splashbitmap + @splashbitmap = nil + @blackbitmap.dispose if @blackbitmap + @blackbitmap = nil + @rearsprite.dispose if @rearsprite + for i in @bgsprites; i.dispose if i; end + @bgsprites.clear + @bubblesprite.dispose if @bubblesprite + @splashsprite.dispose if @splashsprite + @blacksprite.dispose if @blacksprite + @viewport.dispose if @viewport + @disposed = true + end + + def update + return if disposed? + if @duration==0 + dispose + else + angadd = (@numframes-@duration)*@angmult + amp = 6*angadd/8; amp = 6 if amp>6 + for i in 0...@bgsprites.length + @bgsprites[i].x = amp*Math.sin((i+angadd)*Math::PI/10) + end + @bubblesprite.x = (@width-@bubblebitmap.width)/2 + @bubblesprite.x -= 32*Math.sin((@numframes-@duration)/(@numframes/50)*3*Math::PI/60) + @bubblesprite.y -= @bubblesuby + if @duration<@numframes*0.5 + @splashsprite.y -= @splashsuby + end + if @duration<@numframes*0.1 + @blacksprite.y -= @blacksuby + @blacksprite.y = 0 if @blacksprite.y<0 + end + end + @duration -= 1 + end +end + + + +#=============================================================================== +# HGSS trainer outdoor day +#=============================================================================== +class TwoBallPass + def initialize(numframes) + @numframes = numframes + @duration = numframes + @disposed = false + if @numframes<=0 + @disposed = true + return + end + @blackbitmap = BitmapCache.load_bitmap("Graphics/Transitions/black_half") + @ballbitmap = BitmapCache.load_bitmap("Graphics/Transitions/ball_small") + @buffer = Graphics.snap_to_bitmap + if !@blackbitmap || !@ballbitmap || !@buffer + @disposed = true + return + end + @width = @buffer.width + @height = @buffer.height + @viewport = Viewport.new(0,0,@width,@height) + @viewport.z = 99999 + @bgsprite = Sprite.new(@viewport) + @bgsprite.x = @width/2 + @bgsprite.y = @height/2 + @bgsprite.ox = @width/2 + @bgsprite.oy = @height/2 + @bgsprite.bitmap = @buffer + @blacksprites = [] + @ballsprites = [] + for i in 0...2 + @blacksprites[i] = Sprite.new(@viewport) + @blacksprites[i].x = (1-i*2)*@width + @blacksprites[i].y = i*@height/2 + @blacksprites[i].z = 1 + @blacksprites[i].bitmap = @blackbitmap + @ballsprites[i] = Sprite.new(@viewport) + @ballsprites[i].x = (1-i)*@width + (1-i*2)*@ballbitmap.width/2 + @ballsprites[i].y = @height/2 + (i*2-1)*@ballbitmap.height/2 + @ballsprites[i].z = 2 + @ballsprites[i].ox = @ballbitmap.width/2 + @ballsprites[i].oy = @ballbitmap.height/2 + @ballsprites[i].bitmap = @ballbitmap + end + @blacksprites[2] = Sprite.new(@viewport) + @blacksprites[2].y = @height/2 + @blacksprites[2].z = 1 + @blacksprites[2].oy = @height/4 + @blacksprites[2].zoom_y = 0.0 + @blacksprites[2].bitmap = @blackbitmap + @addxmult = 2.0*@width/((@numframes*0.6)**2) + @addzoom = 0.02*50/@numframes + end + + def disposed?; @disposed; end + + def dispose + return if disposed? + @buffer.dispose if @buffer + @buffer = nil + @blackbitmap.dispose if @blackbitmap + @blackbitmap = nil + @ballbitmap.dispose if @ballbitmap + @ballbitmap = nil + @bgsprite.dispose if @bgsprite + for i in @blacksprites; i.dispose if i; end + @blacksprites.clear + for i in @ballsprites; i.dispose if i; end + @ballsprites.clear + @viewport.dispose if @viewport + @disposed = true + end + + def update + return if disposed? + if @duration==0 + dispose + elsif @duration>=@numframes*0.6 + for i in 0...2 + @ballsprites[i].x += (2*i-1)*((@width+@ballbitmap.width)/(0.4*@numframes)) + @ballsprites[i].angle += (2*i-1)*(360.0/(0.2*@numframes)) + end + else + addx = (@numframes*0.6-@duration)*@addxmult + for i in 0...2 + @bgsprite.zoom_x += @addzoom + @bgsprite.zoom_y += @addzoom + @blacksprites[i].x += (2*i-1)*addx + end + @blacksprites[2].zoom_y += 2*addx/@width + @blacksprites[0].x = 0 if @blacksprites[0].x<0 + @blacksprites[1].x = 0 if @blacksprites[1].x>0 + end + @duration -= 1 + end +end + + + +#=============================================================================== +# HGSS trainer outdoor night +#=============================================================================== +class SpinBallSplit + def initialize(numframes) + @numframes = numframes + @duration = numframes + @disposed = false + if @numframes<=0 + @disposed = true + return + end + @blackbitmap = BitmapCache.load_bitmap("Graphics/Transitions/black_half") + @ballbitmap = BitmapCache.load_bitmap("Graphics/Transitions/ball_large") + @buffer = Graphics.snap_to_bitmap + if !@blackbitmap || !@ballbitmap || !@buffer + @disposed = true + return + end + @width = @buffer.width + @height = @buffer.height + @viewport = Viewport.new(0,0,@width,@height) + @viewport.z = 99999 + @bgsprites = [] + @blacksprites = [] + @ballsprites = [] + for i in 0...2 + @bgsprites[i] = Sprite.new(@viewport) + @bgsprites[i].x = @width/2 + @bgsprites[i].y = @height/2 + @bgsprites[i].ox = @width/2 + @bgsprites[i].oy = (1-i)*@height/2 + @bgsprites[i].bitmap = @buffer + @bgsprites[i].src_rect.set(0,i*@height/2,@width,@height/2) + @blacksprites[i] = Sprite.new(@viewport) + @blacksprites[i].x = (1-i*2)*@width + @blacksprites[i].y = i*@height/2 + @blacksprites[i].z = 1 + @blacksprites[i].bitmap = @blackbitmap + @ballsprites[i] = Sprite.new(@viewport) + @ballsprites[i].x = @width/2 + @ballsprites[i].y = @height/2 + @ballsprites[i].z = 2 + @ballsprites[i].ox = @ballbitmap.width/2 + @ballsprites[i].oy = (1-i)*@ballbitmap.height/2 + @ballsprites[i].zoom_x = 0.0 + @ballsprites[i].zoom_y = 0.0 + @ballsprites[i].bitmap = @ballbitmap + end + @addxmult = 2.0*@width/((@numframes*0.5)**2) + @addzoom = 0.02*50/@numframes + end + + def disposed?; @disposed; end + + def dispose + return if disposed? + @buffer.dispose if @buffer + @buffer = nil + @blackbitmap.dispose if @blackbitmap + @blackbitmap = nil + @ballbitmap.dispose if @ballbitmap + @ballbitmap = nil + for i in @bgsprites; i.dispose if i; end + @bgsprites.clear + for i in @blacksprites; i.dispose if i; end + @blacksprites.clear + for i in @ballsprites; i.dispose if i; end + @ballsprites.clear + @viewport.dispose if @viewport + @disposed = true + end + + def update + return if disposed? + if @duration==0 + dispose + elsif @duration>=@numframes*0.6 + if @ballsprites[0].zoom_x<1.0 + @ballsprites[0].zoom_x += (1.0/(0.4*@numframes)) + @ballsprites[0].zoom_y += (1.0/(0.4*@numframes)) + @ballsprites[0].angle -= (360.0/(0.4*@numframes)) + if @ballsprites[0].zoom_x>=1.0 + for i in 0...2 + @ballsprites[i].src_rect.set(0,i*@ballbitmap.height/2, + @ballbitmap.width,@ballbitmap.height/2) + @ballsprites[i].zoom_x = @ballsprites[i].zoom_y = 1.0 + @ballsprites[i].angle = 0.0 + end + end + end + # Gap between 0.6*@numframes and 0.5*@numframes + elsif @duration<@numframes*0.5 + addx = (@numframes*0.5-@duration)*@addxmult + for i in 0...2 + @bgsprites[i].x += (2*i-1)*addx + @bgsprites[i].zoom_x += @addzoom + @bgsprites[i].zoom_y += @addzoom + @blacksprites[i].x += (2*i-1)*addx + @ballsprites[i].x += (2*i-1)*addx + end + @blacksprites[0].x = 0 if @blacksprites[0].x<0 + @blacksprites[1].x = 0 if @blacksprites[1].x>0 + end + @duration -= 1 + end +end + + + +#=============================================================================== +# HGSS trainer indoor day +#=============================================================================== +class ThreeBallDown + def initialize(numframes) + @numframes = numframes + @duration = numframes + @disposed = false + if @numframes<=0 + @disposed = true + return + end + @blackbitmap = BitmapCache.load_bitmap("Graphics/Transitions/black_square") + @ballbitmap = BitmapCache.load_bitmap("Graphics/Transitions/ball_small") + @buffer = Graphics.snap_to_bitmap + if !@blackbitmap || !@ballbitmap || !@buffer + @disposed = true + return + end + @width = @buffer.width + @height = @buffer.height + cx = Graphics.width/@blackbitmap.width # 8 + cy = Graphics.height/@blackbitmap.height # 6 + @numtiles = cx*cy + @viewport = Viewport.new(0,0,@width,@height) + @viewport.z = 99999 + @bgsprite = Sprite.new(@viewport) + @bgsprite.x = @width/2 + @bgsprite.y = @height/2 + @bgsprite.ox = @width/2 + @bgsprite.oy = @height/2 + @bgsprite.bitmap = @buffer + @frame = [] + @blacksprites = [] + for i in 0...cy + for j in 0...cx + k = i*cx+j + @blacksprites[k] = Sprite.new(@viewport) + @blacksprites[k].x = @blackbitmap.width*j + @blacksprites[k].y = @blackbitmap.height*i + @blacksprites[k].visible = false + @blacksprites[k].bitmap = @blackbitmap + @frame[k] = (((cy-i-1)*8+[0,4,1,6,7,2,5,3][j])*(@numframes*0.75)/@numtiles).floor + end + end + @ballsprites = [] + for i in 0...3 + @ballsprites[i] = Sprite.new(@viewport) + @ballsprites[i].x = 96+i*160 + @ballsprites[i].y = -@ballbitmap.height-[400,0,100][i] + @ballsprites[i].z = 2 + @ballsprites[i].ox = @ballbitmap.width/2 + @ballsprites[i].oy = @ballbitmap.height/2 + @ballsprites[i].bitmap = @ballbitmap + end + @addyball = (@height+400+@ballbitmap.height*2)/(0.25*@numframes) + @addangle = 1.5*360/(0.25*@numframes) + @addzoom = 0.02*50/@numframes + end + + def disposed?; @disposed; end + + def dispose + return if disposed? + @buffer.dispose if @buffer + @buffer = nil + @blackbitmap.dispose if @blackbitmap + @blackbitmap = nil + @ballbitmap.dispose if @ballbitmap + @ballbitmap = nil + @bgsprite.dispose if @bgsprite + for i in @blacksprites; i.dispose if i; end + @blacksprites.clear + for i in @ballsprites; i.dispose if i; end + @ballsprites.clear + @viewport.dispose if @viewport + @disposed = true + end + + def update + return if disposed? + if @duration==0 + dispose + elsif @duration>=@numframes*0.75 + for i in 0...@ballsprites.length + @ballsprites[i].y += @addyball + @ballsprites[i].angle -= @addangle*([1,-1][(i==2) ? 1 : 0]) + end + else + count = (@numframes*0.75).floor-@duration + for i in 0...@numtiles + @blacksprites[i].visible = true if @frame[i]<=count + end + @bgsprite.zoom_x += @addzoom + @bgsprite.zoom_y += @addzoom + end + @duration -= 1 + end +end + + + +#=============================================================================== +# HGSS trainer indoor night +# HGSS trainer cave +#=============================================================================== +class BallDown + def initialize(numframes) + @numframes = numframes + @duration = numframes + @disposed = false + if @numframes<=0 + @disposed = true + return + end + @blackbitmap = BitmapCache.load_bitmap("Graphics/Transitions/black_half") + @curvebitmap = BitmapCache.load_bitmap("Graphics/Transitions/black_curve") + @ballbitmap = BitmapCache.load_bitmap("Graphics/Transitions/ball_small") + @buffer = Graphics.snap_to_bitmap + if !@blackbitmap || !@curvebitmap || !@ballbitmap || !@buffer + @disposed = true + return + end + @width = @buffer.width + @height = @buffer.height + @viewport = Viewport.new(0,0,@width,@height) + @viewport.z = 99999 + @bgsprite = Sprite.new(@viewport) + @bgsprite.x = @width/2 + @bgsprite.y = @height/2 + @bgsprite.ox = @width/2 + @bgsprite.oy = @height/2 + @bgsprite.bitmap = @buffer + @blacksprites = [] + @blacksprites[0] = Sprite.new(@viewport) + @blacksprites[0].y = -@curvebitmap.height + @blacksprites[0].z = 1 + @blacksprites[0].oy = @blackbitmap.height + @blacksprites[0].zoom_y = 2.0 + @blacksprites[0].bitmap = @blackbitmap + @blacksprites[1] = Sprite.new(@viewport) + @blacksprites[1].y = -@curvebitmap.height + @blacksprites[1].z = 1 + @blacksprites[1].bitmap = @curvebitmap + @ballsprite = Sprite.new(@viewport) + @ballsprite.x = @width/2 + @ballsprite.y = -@ballbitmap.height/2 + @ballsprite.z = 2 + @ballsprite.ox = @ballbitmap.width/2 + @ballsprite.oy = @ballbitmap.height/2 + @ballsprite.zoom_x = 0.25 + @ballsprite.zoom_y = 0.25 + @ballsprite.bitmap = @ballbitmap + @addyball = (@height+@ballbitmap.height*2.5)/(0.5*@numframes) + @addangle = 1.5*360/(0.5*@numframes) + @addzoomball = 2.5/(0.5*@numframes) + @addy = (@height+@curvebitmap.height)/(@numframes*0.5) + @addzoom = 0.02*50/@numframes + end + + def disposed?; @disposed; end + + def dispose + return if disposed? + @buffer.dispose if @buffer + @buffer = nil + @blackbitmap.dispose if @blackbitmap + @blackbitmap = nil + @curvebitmap.dispose if @curvebitmap + @curvebitmap = nil + @ballbitmap.dispose if @ballbitmap + @ballbitmap = nil + @bgsprite.dispose if @bgsprite + for i in @blacksprites; i.dispose if i; end + @blacksprites.clear + @ballsprite.dispose + @viewport.dispose if @viewport + @disposed = true + end + + def update + return if disposed? + if @duration==0 + dispose + elsif @duration>=@numframes*0.5 + @ballsprite.y += @addyball + @ballsprite.angle -= @addangle + @ballsprite.zoom_x += @addzoomball + @ballsprite.zoom_y += @addzoomball + else + @blacksprites[1].y += @addy + @blacksprites[0].y = @blacksprites[1].y + @bgsprite.zoom_x += @addzoom + @bgsprite.zoom_y += @addzoom + end + @duration -= 1 + end +end + + + +#=============================================================================== +# HGSS trainer water day +#=============================================================================== +class WavyThreeBallUp + def initialize(numframes) + @numframes = numframes + @duration = numframes + @disposed = false + if @numframes<=0 + @disposed = true + return + end + @blackbitmap = BitmapCache.load_bitmap("Graphics/Transitions/black_half") + @ballbitmap = BitmapCache.load_bitmap("Graphics/Transitions/ball_small") + @buffer = Graphics.snap_to_bitmap + if !@blackbitmap || !@ballbitmap || !@buffer + @disposed = true + return + end + @width = @buffer.width + @height = @buffer.height + @viewport = Viewport.new(0,0,@width,@height) + @viewport.z = 99999 + @rearsprite = Sprite.new(@viewport) + @rearsprite.z = 1 + @rearsprite.zoom_y = 2.0 + @rearsprite.bitmap = @blackbitmap + @bgsprites = [] + rect = Rect.new(0,0,@width,2) + for i in 0...@height/2 + @bgsprites[i] = Sprite.new(@viewport) + @bgsprites[i].y = i*2 + @bgsprites[i].z = 2 + @bgsprites[i].bitmap = @buffer + rect.y = i*2 + @bgsprites[i].src_rect = rect + end + @blacksprites = [] + @ballsprites = [] + for i in 0...3 + @blacksprites[i] = Sprite.new(@viewport) + @blacksprites[i].x = (i-1)*@width*2/3 + @blacksprites[i].y = [@height*1.5,@height*3.25,@height*2.5][i] + @blacksprites[i].z = 3 + @blacksprites[i].zoom_y = 2.0 + @blacksprites[i].bitmap = @blackbitmap + @ballsprites[i] = Sprite.new(@viewport) + @ballsprites[i].x = (2*i+1)*@width/6 + @ballsprites[i].y = [@height*1.5,@height*3.25,@height*2.5][i] + @ballsprites[i].z = 4 + @ballsprites[i].ox = @ballbitmap.width/2 + @ballsprites[i].oy = @ballbitmap.height/2 + @ballsprites[i].bitmap = @ballbitmap + end + @suby = (@height*3.5)/(@numframes*0.6) + @angmult = 4/(@numframes/50) + end + + def disposed?; @disposed; end + + def dispose + return if disposed? + @buffer.dispose if @buffer + @buffer = nil + @blackbitmap.dispose if @blackbitmap + @blackbitmap = nil + @ballbitmap.dispose if @ballbitmap + @ballbitmap = nil + @rearsprite.dispose if @rearsprite + for i in @bgsprites; i.dispose if i; end + @bgsprites.clear + for i in @blacksprites; i.dispose if i; end + @blacksprites.clear + for i in @ballsprites; i.dispose if i; end + @ballsprites.clear + @viewport.dispose if @viewport + @disposed = true + end + + def update + return if disposed? + if @duration==0 + dispose + else + angadd = (@numframes-@duration)*@angmult + amp = 24*angadd/16; amp = 24 if amp>24 + for i in 0...@bgsprites.length + @bgsprites[i].x = amp*Math.sin((i+angadd)*Math::PI/48)*((i%2)*2-1) + end + if @duration<@numframes*0.6 + for i in 0...3 + @blacksprites[i].y -= @suby + @blacksprites[i].y = 0 if @blacksprites[i].y<0 + @ballsprites[i].y -= @suby + @ballsprites[i].angle += (2*(i%2)-1)*(360.0/(0.2*@numframes)) + end + end + end + @duration -= 1 + end +end + + + +#=============================================================================== +# HGSS trainer water night +#=============================================================================== +class WavySpinBall + def initialize(numframes) + @numframes = numframes + @duration = numframes + @disposed = false + if @numframes<=0 + @disposed = true + return + end + @blackbitmap = BitmapCache.load_bitmap("Graphics/Transitions/black_half") + @ballbitmap = BitmapCache.load_bitmap("Graphics/Transitions/ball_large") + @buffer = Graphics.snap_to_bitmap + if !@blackbitmap || !@ballbitmap || !@buffer + @disposed = true + return + end + @width = @buffer.width + @height = @buffer.height + @viewport = Viewport.new(0,0,@width,@height) + @viewport.z = 99999 + @rearsprite = Sprite.new(@viewport) + @rearsprite.z = 1 + @rearsprite.zoom_y = 2.0 + @rearsprite.bitmap = @blackbitmap + @bgsprites = [] + rect = Rect.new(0,0,@width,2) + for i in 0...@height/2 + @bgsprites[i] = Sprite.new(@viewport) + @bgsprites[i].y = i*2 + @bgsprites[i].z = 2 + @bgsprites[i].bitmap = @buffer + rect.y = i*2 + @bgsprites[i].src_rect = rect + end + @ballsprite = Sprite.new(@viewport) + @ballsprite.x = @width/2 + @ballsprite.y = @height/2 + @ballsprite.z = 3 + @ballsprite.ox = @ballbitmap.width/2 + @ballsprite.oy = @ballbitmap.height/2 + @ballsprite.visible = false + @ballsprite.bitmap = @ballbitmap + @blacksprite = Sprite.new(@viewport) + @blacksprite.x = @width/2 + @blacksprite.y = @height/2 + @blacksprite.z = 4 + @blacksprite.ox = @blackbitmap.width/2 + @blacksprite.oy = @blackbitmap.height/2 + @blacksprite.visible = false + @blacksprite.bitmap = @blackbitmap + @angmult = 4/(@numframes/50) + end + + def disposed?; @disposed; end + + def dispose + return if disposed? + @buffer.dispose if @buffer + @buffer = nil + @blackbitmap.dispose if @blackbitmap + @blackbitmap = nil + @ballbitmap.dispose if @ballbitmap + @ballbitmap = nil + @rearsprite.dispose if @rearsprite + for i in @bgsprites; i.dispose if i; end + @bgsprites.clear + @ballsprite.dispose if @ballsprite + @blacksprite.dispose if @blacksprite + @viewport.dispose if @viewport + @disposed = true + end + + def update + return if disposed? + if @duration==0 + dispose + else + angadd = (@numframes-@duration)*@angmult + amp = 24*angadd/16; amp = 24 if amp>24 + for i in 0...@bgsprites.length + @bgsprites[i].x = amp*Math.sin((i+angadd)*Math::PI/48)*((i%2)*2-1) + end + @ballsprite.visible = true + if @duration>=@numframes*0.6 + @ballsprite.opacity = 255*(@numframes-@duration)/(@numframes*0.4) + @ballsprite.angle = -360.0*(@numframes-@duration)/(@numframes*0.4) + elsif @duration<@numframes*0.5 + @blacksprite.visible = true + @blacksprite.zoom_x = (@numframes*0.5-@duration)/(@numframes*0.5) + @blacksprite.zoom_y = 2*(@numframes*0.5-@duration)/(@numframes*0.5) + end + end + @duration -= 1 + end +end + + + +#=============================================================================== +# HGSS double trainers +#=============================================================================== +class FourBallBurst + def initialize(numframes) + @numframes = numframes + @duration = numframes + @disposed = false + if @numframes<=0 + @disposed = true + return + end + @black1bitmap = BitmapCache.load_bitmap("Graphics/Transitions/black_wedge_1") + @black2bitmap = BitmapCache.load_bitmap("Graphics/Transitions/black_wedge_2") + @black3bitmap = BitmapCache.load_bitmap("Graphics/Transitions/black_wedge_3") + @black4bitmap = BitmapCache.load_bitmap("Graphics/Transitions/black_wedge_4") + @ballbitmap = BitmapCache.load_bitmap("Graphics/Transitions/ball_small") + if !@black1bitmap || !@black2bitmap || !@black3bitmap || !@black4bitmap || !@ballbitmap + @disposed = true + return + end + @width = Graphics.width + @height = Graphics.height + @viewport = Viewport.new(0,0,@width,@height) + @viewport.z = 99999 + @ballsprites = [] + for i in 0...4 + @ballsprites[i] = Sprite.new(@viewport) + @ballsprites[i].x = @width/2 + @ballsprites[i].y = @height/2 + @ballsprites[i].z = [2,1,3,0][i] + @ballsprites[i].ox = @ballbitmap.width/2 + @ballsprites[i].oy = @ballbitmap.height/2 + @ballsprites[i].bitmap = @ballbitmap + end + @blacksprites = [] + for i in 0...4 + b = [@black1bitmap,@black2bitmap,@black3bitmap,@black4bitmap][i] + @blacksprites[i] = Sprite.new(@viewport) + @blacksprites[i].x = (i==1) ? 0 : @width/2 + @blacksprites[i].y = (i==2) ? 0 : @height/2 + @blacksprites[i].ox = (i%2==0) ? b.width/2 : 0 + @blacksprites[i].oy = (i%2==0) ? 0 : b.height/2 + @blacksprites[i].zoom_x = (i%2==0) ? 0.0 : 1.0 + @blacksprites[i].zoom_y = (i%2==0) ? 1.0 : 0.0 + @blacksprites[i].visible = false + @blacksprites[i].bitmap = b + end + @addxball = (@width/2+@ballbitmap.width/2)/(@numframes*0.4) + @addyball = (@height/2+@ballbitmap.height/2)/(@numframes*0.4) + @addzoom = 1.0/(@numframes*0.6) + end + + def disposed?; @disposed; end + + def dispose + return if disposed? + @black1bitmap.dispose if @black1bitmap + @black1bitmap = nil + @black2bitmap.dispose if @black2bitmap + @black2bitmap = nil + @black3bitmap.dispose if @black3bitmap + @black3bitmap = nil + @black4bitmap.dispose if @black4bitmap + @black4bitmap = nil + @ballbitmap.dispose if @ballbitmap + @ballbitmap = nil + for i in @ballsprites; i.dispose if i; end + @ballsprites.clear + for i in @blacksprites; i.dispose if i; end + @blacksprites.clear + @viewport.dispose if @viewport + @disposed = true + end + + def update + return if disposed? + if @duration==0 + dispose + elsif @duration>=@numframes*0.6 + for i in 0...@ballsprites.length + @ballsprites[i].x += (i==1) ? @addxball : (i==3) ? -@addxball : 0 + @ballsprites[i].y += (i==0) ? @addyball : (i==2) ? -@addyball : 0 + end + else + for i in 0...@blacksprites.length + @blacksprites[i].visible = true + @blacksprites[i].zoom_x += (i%2==0) ? @addzoom : 0 + @blacksprites[i].zoom_y += (i%2==0) ? 0 : @addzoom + end + end + @duration -= 1 + end +end \ No newline at end of file diff --git a/Data/Scripts/010_Data/001_MiscData.rb b/Data/Scripts/010_Data/001_MiscData.rb new file mode 100644 index 000000000..71fd83680 --- /dev/null +++ b/Data/Scripts/010_Data/001_MiscData.rb @@ -0,0 +1,472 @@ +#=============================================================================== +# 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 + + + +module PhoneMsgType + Generic = 0 + Greeting = 1 + Body = 2 + BattleRequest = 3 +end + + + +#=============================================================================== +# Global and map metadata +#=============================================================================== +MetadataHome = 1 +MetadataWildBattleBGM = 2 +MetadataTrainerBattleBGM = 3 +MetadataWildVictoryME = 4 +MetadataTrainerVictoryME = 5 +MetadataWildCaptureME = 6 +MetadataSurfBGM = 7 +MetadataBicycleBGM = 8 +MetadataPlayerA = 9 +MetadataPlayerB = 10 +MetadataPlayerC = 11 +MetadataPlayerD = 12 +MetadataPlayerE = 13 +MetadataPlayerF = 14 +MetadataPlayerG = 15 +MetadataPlayerH = 16 + +MetadataOutdoor = 1 +MetadataShowArea = 2 +MetadataBicycle = 3 +MetadataBicycleAlways = 4 +MetadataHealingSpot = 5 +MetadataWeather = 6 +MetadataMapPosition = 7 +MetadataDiveMap = 8 +MetadataDarkMap = 9 +MetadataSafariMap = 10 +MetadataSnapEdges = 11 +MetadataDungeon = 12 +MetadataBattleBack = 13 +MetadataMapWildBattleBGM = 14 +MetadataMapTrainerBattleBGM = 15 +MetadataMapWildVictoryME = 16 +MetadataMapTrainerVictoryME = 17 +MetadataMapWildCaptureME = 18 +MetadataMapSize = 19 +MetadataEnvironment = 20 + + + +module PokemonMetadata + GlobalTypes = { + "Home" => [MetadataHome, "uuuu"], + "WildBattleBGM" => [MetadataWildBattleBGM, "s"], + "TrainerBattleBGM" => [MetadataTrainerBattleBGM, "s"], + "WildVictoryME" => [MetadataWildVictoryME, "s"], + "TrainerVictoryME" => [MetadataTrainerVictoryME, "s"], + "WildCaptureME" => [MetadataWildCaptureME, "s"], + "SurfBGM" => [MetadataSurfBGM, "s"], + "BicycleBGM" => [MetadataBicycleBGM, "s"], + "PlayerA" => [MetadataPlayerA, "esssssss",:PBTrainers], + "PlayerB" => [MetadataPlayerB, "esssssss",:PBTrainers], + "PlayerC" => [MetadataPlayerC, "esssssss",:PBTrainers], + "PlayerD" => [MetadataPlayerD, "esssssss",:PBTrainers], + "PlayerE" => [MetadataPlayerE, "esssssss",:PBTrainers], + "PlayerF" => [MetadataPlayerF, "esssssss",:PBTrainers], + "PlayerG" => [MetadataPlayerG, "esssssss",:PBTrainers], + "PlayerH" => [MetadataPlayerH, "esssssss",:PBTrainers] + } + NonGlobalTypes = { + "Outdoor" => [MetadataOutdoor, "b"], + "ShowArea" => [MetadataShowArea, "b"], + "Bicycle" => [MetadataBicycle, "b"], + "BicycleAlways" => [MetadataBicycleAlways, "b"], + "HealingSpot" => [MetadataHealingSpot, "uuu"], + "Weather" => [MetadataWeather, "eu",:PBFieldWeather], + "MapPosition" => [MetadataMapPosition, "uuu"], + "DiveMap" => [MetadataDiveMap, "u"], + "DarkMap" => [MetadataDarkMap, "b"], + "SafariMap" => [MetadataSafariMap, "b"], + "SnapEdges" => [MetadataSnapEdges, "b"], + "Dungeon" => [MetadataDungeon, "b"], + "BattleBack" => [MetadataBattleBack, "s"], + "WildBattleBGM" => [MetadataMapWildBattleBGM, "s"], + "TrainerBattleBGM" => [MetadataMapTrainerBattleBGM, "s"], + "WildVictoryME" => [MetadataMapWildVictoryME, "s"], + "TrainerVictoryME" => [MetadataMapTrainerVictoryME, "s"], + "WildCaptureME" => [MetadataMapWildCaptureME, "s"], + "MapSize" => [MetadataMapSize, "us"], + "Environment" => [MetadataEnvironment, "e",:PBEnvironment] + } +end + + + +#=============================================================================== +# Pokémon data +#=============================================================================== +SpeciesType1 = 0 +SpeciesType2 = 1 +SpeciesBaseStats = 2 +SpeciesGenderRate = 3 +SpeciesGrowthRate = 4 +SpeciesBaseExp = 5 +SpeciesEffortPoints = 6 +SpeciesRareness = 7 +SpeciesHappiness = 8 +SpeciesAbilities = 9 +SpeciesHiddenAbility = 10 +SpeciesCompatibility = 11 +SpeciesStepsToHatch = 12 +SpeciesHeight = 13 +SpeciesWeight = 14 +SpeciesColor = 15 +SpeciesShape = 16 +SpeciesHabitat = 17 +SpeciesWildItemCommon = 18 +SpeciesWildItemUncommon = 19 +SpeciesWildItemRare = 20 +SpeciesIncense = 21 +SpeciesPokedexForm = 22 # For alternate forms +SpeciesMegaStone = 23 # For alternate forms +SpeciesMegaMove = 24 # For alternate forms +SpeciesUnmegaForm = 25 # For alternate forms +SpeciesMegaMessage = 26 # For alternate forms + +MetricBattlerPlayerX = 0 +MetricBattlerPlayerY = 1 +MetricBattlerEnemyX = 2 +MetricBattlerEnemyY = 3 +MetricBattlerAltitude = 4 +MetricBattlerShadowX = 5 +MetricBattlerShadowSize = 6 + + + +module PokemonSpeciesData + def self.requiredValues(compilingForms=false) + ret = { + "Type1" => [SpeciesType1, "e",:PBTypes], + "BaseStats" => [SpeciesBaseStats, "vvvvvv"], + "BaseEXP" => [SpeciesBaseExp, "v"], + "EffortPoints" => [SpeciesEffortPoints, "uuuuuu"], + "Rareness" => [SpeciesRareness, "u"], + "Happiness" => [SpeciesHappiness, "u"], + "Compatibility" => [SpeciesCompatibility, "eE",:PBEggGroups,:PBEggGroups], + "StepsToHatch" => [SpeciesStepsToHatch, "v"], + "Height" => [SpeciesHeight, "f"], + "Weight" => [SpeciesWeight, "f"], + "Color" => [SpeciesColor, "e",:PBColors], + "Shape" => [SpeciesShape, "u"], + "Moves" => [0, "*ue",nil,:PBMoves], + "Kind" => [0, "s"], + "Pokedex" => [0, "q"] + } + if !compilingForms + ret["GenderRate"] = [SpeciesGenderRate, "e",:PBGenderRates] + ret["GrowthRate"] = [SpeciesGrowthRate, "e",:PBGrowthRates] + ret["Name"] = [0, "s"] + ret["InternalName"] = [0, "n"] + end + return ret + end + + def self.optionalValues(compilingForms=false) + ret = { + "Type2" => [SpeciesType2, "e",:PBTypes], + "Abilities" => [SpeciesAbilities, "eE",:PBAbilities,:PBAbilities], + "HiddenAbility" => [SpeciesHiddenAbility, "eEEE",:PBAbilities,:PBAbilities, + :PBAbilities,:PBAbilities], + "Habitat" => [SpeciesHabitat, "e",:PBHabitats], + "WildItemCommon" => [SpeciesWildItemCommon, "e",:PBItems], + "WildItemUncommon" => [SpeciesWildItemUncommon, "e",:PBItems], + "WildItemRare" => [SpeciesWildItemRare, "e",:PBItems], + "BattlerPlayerX" => [MetricBattlerPlayerX, "i"], + "BattlerPlayerY" => [MetricBattlerPlayerY, "i"], + "BattlerEnemyX" => [MetricBattlerEnemyX, "i"], + "BattlerEnemyY" => [MetricBattlerEnemyY, "i"], + "BattlerAltitude" => [MetricBattlerAltitude, "i"], + "BattlerShadowX" => [MetricBattlerShadowX, "i"], + "BattlerShadowSize" => [MetricBattlerShadowSize, "u"], + "EggMoves" => [0, "*e",:PBMoves], + "FormName" => [0, "q"], + "Evolutions" => [0, "*ses",nil,:PBEvolution,nil] + } + if compilingForms + ret["PokedexForm"] = [SpeciesPokedexForm, "u"] + ret["MegaStone"] = [SpeciesMegaStone, "e",:PBItems] + ret["MegaMove"] = [SpeciesMegaMove, "e",:PBMoves] + ret["UnmegaForm"] = [SpeciesUnmegaForm, "u"] + ret["MegaMessage"] = [SpeciesMegaMessage, "u"] + else + ret["Incense"] = [SpeciesIncense, "e",:PBItems] + ret["RegionalNumbers"] = [0, "*u"] + end + return ret + end +end + + + +#=============================================================================== +# Manipulation methods for metadata, phone data and Pokémon species data +#=============================================================================== +class PokemonTemp + attr_accessor :metadata + attr_accessor :townMapData + attr_accessor :encountersData + attr_accessor :phoneData + attr_accessor :regionalDexes + attr_accessor :speciesData + attr_accessor :speciesEggMoves + attr_accessor :speciesMetrics + attr_accessor :speciesMovesets + attr_accessor :speciesTMData + attr_accessor :speciesShadowMovesets + attr_accessor :pokemonFormToSpecies + attr_accessor :trainerTypesData + attr_accessor :trainersData + attr_accessor :moveToAnim + attr_accessor :battleAnims +end + + + +def pbLoadMetadata + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.metadata + $PokemonTemp.metadata = load_data("Data/metadata.dat") || [] + end + return $PokemonTemp.metadata +end + +def pbGetMetadata(mapid,metadataType) + meta = pbLoadMetadata + return meta[mapid][metadataType] if meta[mapid] + return nil +end + +def pbLoadTownMapData + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.townMapData + $PokemonTemp.townMapData = load_data("Data/town_map.dat") + end + return $PokemonTemp.townMapData +end + +def pbLoadEncountersData + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.encountersData + if pbRgssExists?("Data/encounters.dat") + $PokemonTemp.encountersData = load_data("Data/encounters.dat") + end + end + return $PokemonTemp.encountersData +end + +def pbLoadPhoneData + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.phoneData + if pbRgssExists?("Data/phone.dat") + $PokemonTemp.phoneData = load_data("Data/phone.dat") + end + end + return $PokemonTemp.phoneData +end + +def pbLoadRegionalDexes + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.regionalDexes + $PokemonTemp.regionalDexes = load_data("Data/regional_dexes.dat") + end + return $PokemonTemp.regionalDexes +end + +def pbLoadSpeciesData + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.speciesData + $PokemonTemp.speciesData = load_data("Data/species.dat") || [] + end + return $PokemonTemp.speciesData +end + +def pbGetSpeciesData(species,form=0,speciesDataType=-1) + species = getID(PBSpecies,species) + s = pbGetFSpeciesFromForm(species,form) + speciesData = pbLoadSpeciesData + if speciesDataType<0 + return speciesData[s] || [] + end + return speciesData[s][speciesDataType] if speciesData[s] && speciesData[s][speciesDataType] + case speciesDataType + when SpeciesType2; return nil + when SpeciesBaseStats; return [1,1,1,1,1,1] + when SpeciesEffortPoints; return [0,0,0,0,0,0] + when SpeciesStepsToHatch, SpeciesHeight, SpeciesWeight; return 1 + end + return 0 +end + +def pbLoadEggMovesData + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.speciesEggMoves + $PokemonTemp.speciesEggMoves = load_data("Data/species_eggmoves.dat") || [] + end + return $PokemonTemp.speciesEggMoves +end + +def pbGetSpeciesEggMoves(species,form=0) + species = getID(PBSpecies,species) + s = pbGetFSpeciesFromForm(species,form) + eggMovesData = pbLoadEggMovesData + return eggMovesData[s] if eggMovesData[s] + return [] +end + +def pbLoadSpeciesMetrics + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.speciesMetrics + $PokemonTemp.speciesMetrics = load_data("Data/species_metrics.dat") || [] + end + return $PokemonTemp.speciesMetrics +end + +def pbLoadMovesetsData + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.speciesMovesets + $PokemonTemp.speciesMovesets = load_data("Data/species_movesets.dat") || [] + end + return $PokemonTemp.speciesMovesets +end + +def pbGetSpeciesMoveset(species,form=0) + species = getID(PBSpecies,species) + s = pbGetFSpeciesFromForm(species,form) + movesetsData = pbLoadMovesetsData + return movesetsData[s] if movesetsData[s] + return [] +end + +def pbLoadSpeciesTMData + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.speciesTMData + $PokemonTemp.speciesTMData = load_data("Data/tm.dat") || [] + end + return $PokemonTemp.speciesTMData +end + +def pbLoadShadowMovesets + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.speciesShadowMovesets + $PokemonTemp.speciesShadowMovesets = load_data("Data/shadow_movesets.dat") || [] + end + return $PokemonTemp.speciesShadowMovesets +end + +def pbLoadFormToSpecies + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.pokemonFormToSpecies + $PokemonTemp.pokemonFormToSpecies = load_data("Data/form2species.dat") + end + return $PokemonTemp.pokemonFormToSpecies +end + +def pbLoadTrainerTypesData + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.trainerTypesData + $PokemonTemp.trainerTypesData = load_data("Data/trainer_types.dat") || [] + end + return $PokemonTemp.trainerTypesData +end + +def pbGetTrainerTypeData(type) + data = pbLoadTrainerTypesData + return data[type] if data + return nil +end + +def pbLoadTrainersData + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.trainersData + $PokemonTemp.trainersData = load_data("Data/trainers.dat") || [] + end + return $PokemonTemp.trainersData +end + +def pbGetTrainerData(trainerID,trainerName,partyID=0) + trainersData = pbLoadTrainersData + ret = nil + for t in trainersData + next if t[0]!=trainerID || t[1]!=trainerName || t[4]!=partyID + ret = t + break + end + return ret +end + +def pbLoadMoveToAnim + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.moveToAnim + $PokemonTemp.moveToAnim = load_data("Data/move2anim.dat") || [] + end + return $PokemonTemp.moveToAnim +end + +def pbLoadBattleAnimations + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.battleAnims + if pbRgssExists?("Data/PkmnAnimations.rxdata") + $PokemonTemp.battleAnims = load_data("Data/PkmnAnimations.rxdata") + end + end + return $PokemonTemp.battleAnims +end + +def pbClearData + if $PokemonTemp + $PokemonTemp.metadata = nil + $PokemonTemp.townMapData = nil + $PokemonTemp.encountersData = nil + $PokemonTemp.phoneData = nil + $PokemonTemp.regionalDexes = nil + $PokemonTemp.speciesData = nil + $PokemonTemp.speciesEggMoves = nil + $PokemonTemp.speciesMetrics = nil + $PokemonTemp.speciesMovesets = nil + $PokemonTemp.speciesTMData = nil + $PokemonTemp.speciesShadowMovesets = nil + $PokemonTemp.pokemonFormToSpecies = nil + $PokemonTemp.trainerTypesData = nil + $PokemonTemp.trainersData = nil + $PokemonTemp.moveToAnim = nil + $PokemonTemp.battleAnims = nil + end + MapFactoryHelper.clear + $PokemonEncounters.setup($game_map.map_id) if $game_map && $PokemonEncounters + if pbRgssExists?("Data/Tilesets.rxdata") + $data_tilesets = load_data("Data/Tilesets.rxdata") + end + if pbRgssExists?("Data/Tilesets.rvdata") + $data_tilesets = load_data("Data/Tilesets.rvdata") + end +end \ No newline at end of file diff --git a/Data/Scripts/010_Data/002_PBMove.rb b/Data/Scripts/010_Data/002_PBMove.rb new file mode 100644 index 000000000..24da3b49f --- /dev/null +++ b/Data/Scripts/010_Data/002_PBMove.rb @@ -0,0 +1,104 @@ +MOVE_ID = 0 +MOVE_INTERNAL_NAME = 1 +MOVE_NAME = 2 +MOVE_FUNCTION_CODE = 3 +MOVE_BASE_DAMAGE = 4 +MOVE_TYPE = 5 +MOVE_CATEGORY = 6 +MOVE_ACCURACY = 7 +MOVE_TOTAL_PP = 8 +MOVE_EFFECT_CHANCE = 9 +MOVE_TARGET = 10 +MOVE_PRIORITY = 11 +MOVE_FLAGS = 12 +MOVE_DESCRIPTION = 13 + + + +class PokemonTemp + attr_accessor :movesData +end + + + +def pbLoadMovesData + $PokemonTemp = PokemonTemp.new if !$PokemonTemp + if !$PokemonTemp.movesData + if pbRgssExists?("Data/moves.dat") + $PokemonTemp.movesData = load_data("Data/moves.dat") + else + $PokemonTemp.movesData = [] + end + end + return $PokemonTemp.movesData +end + +def pbGetMoveData(moveID,moveDataType=-1) + meta = pbLoadMovesData + if moveDataType<0 + return meta[moveID] || [] + end + return meta[moveID][moveDataType] if meta[moveID] + return nil +end + +alias __moveData__pbClearData pbClearData +def pbClearData + $PokemonTemp.movesData = nil if $PokemonTemp + __moveData__pbClearData +end + + + +class PBMoveData + attr_reader :function,:basedamage,:type,:accuracy,:category + attr_reader :totalpp,:addlEffect,:target,:priority,:flags + + def initialize(moveid) + moveData = pbGetMoveData(moveID) + @function = moveData[MOVE_FUNCTION_CODE] + @basedamage = moveData[MOVE_BASE_DAMAGE] + @type = moveData[MOVE_TYPE] + @category = moveData[MOVE_CATEGORY] + @accuracy = moveData[MOVE_ACCURACY] + @totalpp = moveData[MOVE_TOTAL_PP] + @addlEffect = moveData[MOVE_EFFECT_CHANCE] + @target = moveData[MOVE_TARGET] + @priority = moveData[MOVE_PRIORITY] + @flags = moveData[MOVE_FLAGS] + end +end + + + +class PBMove + attr_reader(:id) # This move's ID + attr_accessor(:pp) # The amount of PP remaining for this move + attr_accessor(:ppup) # The number of PP Ups used for this move + + # Initializes this object to the specified move ID. + def initialize(moveID) + @id = moveID + @pp = pbGetMoveData(moveID,MOVE_TOTAL_PP) || 0 + @ppup = 0 + end + + # Changes this move's ID, and caps the PP amount if it is now greater than the + # new move's total PP. + def id=(value) + oldID = @id + @id = value + @pp = [@pp,self.totalpp].min if oldID>0 + end + + # Gets this move's type. + def type + return pbGetMoveData(@id,MOVE_TYPE) || 0 + end + + # Gets the maximum PP for this move. + def totalpp + maxPP = pbGetMoveData(@id,MOVE_TOTAL_PP) || 0 + return maxPP+maxPP*@ppup/5 + end +end \ No newline at end of file diff --git a/Data/Scripts/010_Data/003_PBStatuses.rb b/Data/Scripts/010_Data/003_PBStatuses.rb new file mode 100644 index 000000000..17c62e7a2 --- /dev/null +++ b/Data/Scripts/010_Data/003_PBStatuses.rb @@ -0,0 +1,28 @@ +#70925035 +begin + module PBStatuses + NONE = 0 + SLEEP = 1 + POISON = 2 + BURN = 3 + PARALYSIS = 4 + FROZEN = 5 + + def self.getName(id) + id = getID(PBStatuses,id) + names = [ + _INTL("healthy"), + _INTL("asleep"), + _INTL("poisoned"), + _INTL("burned"), + _INTL("paralyzed"), + _INTL("frozen") + ] + return names[id] + end end + +rescue Exception + if $!.is_a?(SystemExit) || "#{$!.class}"=="Reset" + raise $! + end +end \ No newline at end of file diff --git a/Data/Scripts/010_Data/004_PBTypes_Extra.rb b/Data/Scripts/010_Data/004_PBTypes_Extra.rb new file mode 100644 index 000000000..8641766a9 --- /dev/null +++ b/Data/Scripts/010_Data/004_PBTypes_Extra.rb @@ -0,0 +1,90 @@ +module PBTypeEffectiveness + INEFFECTIVE = 0 + NOT_EFFECTIVE_ONE = 1 + NORMAL_EFFECTIVE_ONE = 2 + SUPER_EFFECTIVE_ONE = 4 + NORMAL_EFFECTIVE = NORMAL_EFFECTIVE_ONE ** 3 +end + + + +class PBTypes + @@TypeData = nil + + def PBTypes.loadTypeData + if !@@TypeData + @@TypeData = load_data("Data/types.dat") + @@TypeData[0].freeze + @@TypeData[1].freeze + @@TypeData[2].freeze + @@TypeData.freeze + end + return @@TypeData + end + + def PBTypes.regularTypesCount + ret = 0 + for i in 0..PBTypes.maxValue + next if PBTypes.isPseudoType?(i) || isConst?(i,PBTypes,:SHADOW) + ret += 1 + end + return ret + end + + def PBTypes.isPseudoType?(type) + return PBTypes.loadTypeData[0].include?(type) + end + + def PBTypes.isSpecialType?(type) + return PBTypes.loadTypeData[1].include?(type) + end + + def PBTypes.getEffectiveness(attackType,targetType) + return PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE if !targetType || targetType<0 + return PBTypes.loadTypeData[2][attackType*(PBTypes.maxValue+1)+targetType] + end + + def PBTypes.getCombinedEffectiveness(attackType,targetType1,targetType2=nil,targetType3=nil) + mod1 = PBTypes.getEffectiveness(attackType,targetType1) + mod2 = PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE + mod3 = PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE + if targetType2!=nil && targetType2>=0 && targetType1!=targetType2 + mod2 = PBTypes.getEffectiveness(attackType,targetType2) + end + if targetType3!=nil && targetType3>=0 && + targetType1!=targetType3 && targetType2!=targetType3 + mod3 = PBTypes.getEffectiveness(attackType,targetType3) + end + return mod1*mod2*mod3 + end + + def PBTypes.ineffective?(attackType,targetType1=nil,targetType2=nil,targetType3=nil) + return attackType==PBTypeEffectiveness::INEFFECTIVE if !targetType1 + e = PBTypes.getCombinedEffectiveness(attackType,targetType1,targetType2,targetType3) + return e==PBTypeEffectiveness::INEFFECTIVE + end + + def PBTypes.notVeryEffective?(attackType,targetType1=nil,targetType2=nil,targetType3=nil) + return attackType>PBTypeEffectiveness::INEFFECTIVE && attackTypePBTypeEffectiveness::INEFFECTIVE && ePBTypeEffectiveness::NORMAL_EFFECTIVE if !targetType1 + e = PBTypes.getCombinedEffectiveness(attackType,targetType1,targetType2,targetType3) + return e>PBTypeEffectiveness::NORMAL_EFFECTIVE + end +end \ No newline at end of file diff --git a/Data/Scripts/010_Data/005_PBNatures.rb b/Data/Scripts/010_Data/005_PBNatures.rb new file mode 100644 index 000000000..bcd501a44 --- /dev/null +++ b/Data/Scripts/010_Data/005_PBNatures.rb @@ -0,0 +1,87 @@ +module PBNatures + HARDY = 0 + LONELY = 1 + BRAVE = 2 + ADAMANT = 3 + NAUGHTY = 4 + BOLD = 5 + DOCILE = 6 + RELAXED = 7 + IMPISH = 8 + LAX = 9 + TIMID = 10 + HASTY = 11 + SERIOUS = 12 + JOLLY = 13 + NAIVE = 14 + MODEST = 15 + MILD = 16 + QUIET = 17 + BASHFUL = 18 + RASH = 19 + CALM = 20 + GENTLE = 21 + SASSY = 22 + CAREFUL = 23 + QUIRKY = 24 + + def self.maxValue; 24; end + def self.getCount; 25; end + + def self.getName(id) + id = getID(PBNatures,id) + names = [ + _INTL("Hardy"), + _INTL("Lonely"), + _INTL("Brave"), + _INTL("Adamant"), + _INTL("Naughty"), + _INTL("Bold"), + _INTL("Docile"), + _INTL("Relaxed"), + _INTL("Impish"), + _INTL("Lax"), + _INTL("Timid"), + _INTL("Hasty"), + _INTL("Serious"), + _INTL("Jolly"), + _INTL("Naive"), + _INTL("Modest"), + _INTL("Mild"), + _INTL("Quiet"), + _INTL("Bashful"), + _INTL("Rash"), + _INTL("Calm"), + _INTL("Gentle"), + _INTL("Sassy"), + _INTL("Careful"), + _INTL("Quirky") + ] + return names[id] + end + + def self.getStatRaised(id) + m = (id%25)/5 # 25 here is (number of stats)**2, not PBNatures.getCount + return [PBStats::ATTACK,PBStats::DEFENSE,PBStats::SPEED, + PBStats::SPATK,PBStats::SPDEF][m] + end + + def self.getStatLowered(id) + m = id%5 # Don't need to %25 here because 25 is a multiple of 5 + return [PBStats::ATTACK,PBStats::DEFENSE,PBStats::SPEED, + PBStats::SPATK,PBStats::SPDEF][m] + end + + def self.getStatChanges(id) + id = getID(PBNatures,id) + up = PBNatures.getStatRaised(id) + dn = PBNatures.getStatLowered(id) + ret = [] + PBStats.eachStat do |s| + ret[s] = 100 + ret[s] += 10 if s==up + ret[s] -= 10 if s==dn + end + return ret + end +end \ No newline at end of file diff --git a/Data/Scripts/010_Data/006_PBGenderRates.rb b/Data/Scripts/010_Data/006_PBGenderRates.rb new file mode 100644 index 000000000..0dffe8f34 --- /dev/null +++ b/Data/Scripts/010_Data/006_PBGenderRates.rb @@ -0,0 +1,24 @@ +module PBGenderRates + Genderless = 0 + AlwaysMale = 1 + FemaleOneEighth = 2 + Female25Percent = 3 + Female50Percent = 4 + Female75Percent = 5 + FemaleSevenEighths = 6 + AlwaysFemale = 7 + + def self.genderByte(gender) + case gender + when AlwaysMale; return 0 + when FemaleOneEighth; return 32 + when Female25Percent; return 64 + when Female50Percent; return 128 + when Female75Percent; return 192 + when FemaleSevenEighths; return 224 + when AlwaysFemale; return 254 + when Genderless; return 255 + end + return 255 # Default value (genderless) + end +end \ No newline at end of file diff --git a/Data/Scripts/010_Data/007_PBExperience.rb b/Data/Scripts/010_Data/007_PBExperience.rb new file mode 100644 index 000000000..cd2acb5b5 --- /dev/null +++ b/Data/Scripts/010_Data/007_PBExperience.rb @@ -0,0 +1,197 @@ +module PBGrowthRates + Medium = MediumFast = 0 + Erratic = 1 + Fluctuating = 2 + Parabolic = MediumSlow = 3 + Fast = 4 + Slow = 5 + + def self.maxValue; return 5; end +end + + + +module PBExperience + @PBExpTable = [] + @PBExpTable[PBGrowthRates::Medium] = [ + -1, 0, 8, 27, 64, 125, 216, 343, 512, 729, + 1000, 1331, 1728, 2197, 2744, 3375, 4096, 4913, 5832, 6859, + 8000, 9261, 10648, 12167, 13824, 15625, 17576, 19683, 21952, 24389, + 27000, 29791, 32768, 35937, 39304, 42875, 46656, 50653, 54872, 59319, + 64000, 68921, 74088, 79507, 85184, 91125, 97336, 103823, 110592, 117649, + 125000, 132651, 140608, 148877, 157464, 166375, 175616, 185193, 195112, 205379, + 216000, 226981, 238328, 250047, 262144, 274625, 287496, 300763, 314432, 328509, + 343000, 357911, 373248, 389017, 405224, 421875, 438976, 456533, 474552, 493039, + 512000, 531441, 551368, 571787, 592704, 614125, 636056, 658503, 681472, 704969, + 729000, 753571, 778688, 804357, 830584, 857375, 884736, 912673, 941192, 970299, + 1000000] + @PBExpTable[PBGrowthRates::Erratic] = [ + -1, 0, 15, 52, 122, 237, 406, 637, 942, 1326, + 1800, 2369, 3041, 3822, 4719, 5737, 6881, 8155, 9564, 11111, + 12800, 14632, 16610, 18737, 21012, 23437, 26012, 28737, 31610, 34632, + 37800, 41111, 44564, 48155, 51881, 55737, 59719, 63822, 68041, 72369, + 76800, 81326, 85942, 90637, 95406, 100237, 105122, 110052, 115015, 120001, + 125000, 131324, 137795, 144410, 151165, 158056, 165079, 172229, 179503, 186894, + 194400, 202013, 209728, 217540, 225443, 233431, 241496, 249633, 257834, 267406, + 276458, 286328, 296358, 305767, 316074, 326531, 336255, 346965, 357812, 367807, + 378880, 390077, 400293, 411686, 423190, 433572, 445239, 457001, 467489, 479378, + 491346, 501878, 513934, 526049, 536557, 548720, 560922, 571333, 583539, 591882, + 600000] + @PBExpTable[PBGrowthRates::Fluctuating] = [ + -1, 0, 4, 13, 32, 65, 112, 178, 276, 393, + 540, 745, 967, 1230, 1591, 1957, 2457, 3046, 3732, 4526, + 5440, 6482, 7666, 9003, 10506, 12187, 14060, 16140, 18439, 20974, + 23760, 26811, 30146, 33780, 37731, 42017, 46656, 50653, 55969, 60505, + 66560, 71677, 78533, 84277, 91998, 98415, 107069, 114205, 123863, 131766, + 142500, 151222, 163105, 172697, 185807, 196322, 210739, 222231, 238036, 250562, + 267840, 281456, 300293, 315059, 335544, 351520, 373744, 390991, 415050, 433631, + 459620, 479600, 507617, 529063, 559209, 582187, 614566, 639146, 673863, 700115, + 737280, 765275, 804997, 834809, 877201, 908905, 954084, 987754, 1035837, 1071552, + 1122660, 1160499, 1214753, 1254796, 1312322, 1354652, 1415577, 1460276, 1524731, 1571884, + 1640000] + @PBExpTable[PBGrowthRates::Parabolic] = [ + -1, 0, 9, 57, 96, 135, 179, 236, 314, 419, + 560, 742, 973, 1261, 1612, 2035, 2535, 3120, 3798, 4575, + 5460, 6458, 7577, 8825, 10208, 11735, 13411, 15244, 17242, 19411, + 21760, 24294, 27021, 29949, 33084, 36435, 40007, 43808, 47846, 52127, + 56660, 61450, 66505, 71833, 77440, 83335, 89523, 96012, 102810, 109923, + 117360, 125126, 133229, 141677, 150476, 159635, 169159, 179056, 189334, 199999, + 211060, 222522, 234393, 246681, 259392, 272535, 286115, 300140, 314618, 329555, + 344960, 360838, 377197, 394045, 411388, 429235, 447591, 466464, 485862, 505791, + 526260, 547274, 568841, 590969, 613664, 636935, 660787, 685228, 710266, 735907, + 762160, 789030, 816525, 844653, 873420, 902835, 932903, 963632, 995030, 1027103, + 1059860] + @PBExpTable[PBGrowthRates::Fast] = [ + -1, 0, 6, 21, 51, 100, 172, 274, 409, 583, + 800, 1064, 1382, 1757, 2195, 2700, 3276, 3930, 4665, 5487, + 6400, 7408, 8518, 9733, 11059, 12500, 14060, 15746, 17561, 19511, + 21600, 23832, 26214, 28749, 31443, 34300, 37324, 40522, 43897, 47455, + 51200, 55136, 59270, 63605, 68147, 72900, 77868, 83058, 88473, 94119, + 100000, 106120, 112486, 119101, 125971, 133100, 140492, 148154, 156089, 164303, + 172800, 181584, 190662, 200037, 209715, 219700, 229996, 240610, 251545, 262807, + 274400, 286328, 298598, 311213, 324179, 337500, 351180, 365226, 379641, 394431, + 409600, 425152, 441094, 457429, 474163, 491300, 508844, 526802, 545177, 563975, + 583200, 602856, 622950, 643485, 664467, 685900, 707788, 730138, 752953, 776239, + 800000] + @PBExpTable[PBGrowthRates::Slow] = [ + -1, 0, 10, 33, 80, 156, 270, 428, 640, 911, + 1250, 1663, 2160, 2746, 3430, 4218, 5120, 6141, 7290, 8573, + 10000, 11576, 13310, 15208, 17280, 19531, 21970, 24603, 27440, 30486, + 33750, 37238, 40960, 44921, 49130, 53593, 58320, 63316, 68590, 74148, + 80000, 86151, 92610, 99383, 106480, 113906, 121670, 129778, 138240, 147061, + 156250, 165813, 175760, 186096, 196830, 207968, 219520, 231491, 243890, 256723, + 270000, 283726, 297910, 312558, 327680, 343281, 359370, 375953, 393040, 410636, + 428750, 447388, 466560, 486271, 506530, 527343, 548720, 570666, 593190, 616298, + 640000, 664301, 689210, 714733, 740880, 767656, 795070, 823128, 851840, 881211, + 911250, 941963, 973360, 1005446, 1038230, 1071718, 1105920, 1140841, 1176490, 1212873, + 1250000] + + # Returns the maximum level a Pokémon can attain. If you want to make it vary, + # here's where you put your formulae. Note that this is also called by the + # Compiler, which happens before anything (e.g. Game Switches/Variables, the + # player's data) is loaded, so make sure they exist before using them, and + # make this method return the most maximum ever level if they don't. + def self.maxLevel + return MAXIMUM_LEVEL + end + + # Erratic (600000): + # For levels 0-50: n**3([100-n]/50) + # For levels 51-68: n**3([150-n]/100) + # For levels 69-98: n**3(1.274-[1/50][n/3]-p(n mod 3)) + # where p(x) = array(0.000,0.008,0.014)[x] + # For levels 99-100: n**3([160-n]/100) + # Fluctuating (1640000): + # For levels 0-15 : n**3([24+{(n+1)/3}]/50) + # For levels 16-35: n**3([14+n]/50) + # For levels 36-100: n**3([32+{n/2}]/50) + + def self.pbGetExpInternal(level,growth) + if level <= 100 + # Refer to experience table for levels 100 and less + return @PBExpTable[growth][level] + end + # Level 101+, use formulae + case growth + when PBGrowthRates::Medium # 1000000 + return level ** 3 + when PBGrowthRates::Erratic # 600000 + # Different formula that causes 600000 EXP at level 100 + return ((level ** 4) * 0.6 / 100).floor + when PBGrowthRates::Fluctuating # 1640000 + # Different formula that causes 1640000 EXP at level 100 + rate = 82 + if level > 100 + # Slow rate with increasing level + rate -= (level - 100) / 2 + rate = 40 if rate < 40 + end + return ((level ** 3) * (level * rate / 100) / 50.0).floor + when PBGrowthRates::Parabolic # 1059860 + return ((level ** 3) * 6 / 5) - 15 * (level ** 2) + 100 * level - 140 + when PBGrowthRates::Fast # 800000 + return (level ** 3) * 4 / 5 + when PBGrowthRates::Slow # 1250000 + return (level ** 3) * 5 / 4 + end + return 0 + end + + # Gets the maximum Exp Points possible for the given growth rate. + # growth -- Growth rate. + def self.pbGetMaxExperience(growth) + if growth<0 || growth>PBGrowthRates.maxValue + return ArgumentError.new("The growth rate is invalid.") + end + return pbGetExpInternal(maxLevel,growth) + end + + # Gets the number of Exp Points needed to reach the given + # level with the given growth rate. + # growth -- Growth rate. + def self.pbGetStartExperience(level,growth) + if growth<0 || growth>PBGrowthRates.maxValue + return ArgumentError.new("The growth rate is invalid.") + end + if level<0 + return ArgumentError.new("The level is invalid.") + end + mLevel = maxLevel + level = mLevel if level>mLevel + return pbGetExpInternal(level,growth) + end + + # Adds experience points ensuring that the new total doesn't + # exceed the maximum Exp. Points for the given growth rate. + # currexp -- Current Exp Points. + # expgain -- Exp. Points to add + # growth -- Growth rate. + def self.pbAddExperience(currexp,expgain,growth) + if growth<0 || growth>PBGrowthRates.maxValue + return ArgumentError.new("The growth rate is invalid.") + end + exp = currexp+expgain + maxexp = pbGetExpInternal(maxLevel,growth) + exp = maxexp if exp>maxexp + return exp + end + + # Calculates a level given the number of Exp Points and growth rate. + # growth -- Growth rate. + def self.pbGetLevelFromExperience(exp,growth) + if growth<0 || growth>PBGrowthRates.maxValue + return ArgumentError.new("The growth rate is invalid.") + end + mLevel = maxLevel + maxexp = pbGetExpInternal(mLevel,growth) + exp = maxexp if exp>maxexp + i = 0 + for j in 0..mLevel + currentExp = pbGetExpInternal(i,growth) + return i if exp==currentExp + return i-1 if exp0 + return @defense + end + + attr_writer :defense + + def spdef + return @defense if @battle.field.effects[PBEffects::WonderRoom]>0 + return @spdef + end + + attr_writer :spdef + + attr_reader :hp + + def hp=(value) + @hp = value.to_i + @pokemon.hp = value.to_i if @pokemon + end + + def fainted?; return @hp<=0; end + alias isFainted? fainted? + + attr_reader :status + + def status=(value) + @effects[PBEffects::Truant] = false if @status==PBStatuses::SLEEP && value!=PBStatuses::SLEEP + @effects[PBEffects::Toxic] = 0 if value!=PBStatuses::POISON + @status = value + @pokemon.status = value if @pokemon + self.statusCount = 0 if value!=PBStatuses::POISON && value!=PBStatuses::SLEEP + @battle.scene.pbRefreshOne(@index) + end + + attr_reader :statusCount + + def statusCount=(value) + @statusCount = value + @pokemon.statusCount = value if @pokemon + end + + #============================================================================= + # Properties from Pokémon + #============================================================================= + def happiness; return @pokemon ? @pokemon.happiness : 0; end + def nature; return @pokemon ? @pokemon.nature : 0; end + def pokerusStage; return @pokemon ? @pokemon.pokerusStage : 0; end + + #============================================================================= + # Mega Evolution, Primal Reversion, Shadow Pokémon + #============================================================================= + def hasMega? + return false if @effects[PBEffects::Transform] + return @pokemon && @pokemon.hasMegaForm? + end + + def mega?; return @pokemon && @pokemon.mega?; end + alias isMega? mega? + + def hasPrimal? + return false if @effects[PBEffects::Transform] + return @pokemon && @pokemon.hasPrimalForm? + end + + def primal?; return @pokemon && @pokemon.primal?; end + alias isPrimal? primal? + + def shadowPokemon?; return false; end + alias isShadow? shadowPokemon? + + def inHyperMode?; return false; end + + #============================================================================= + # Display-only properties + #============================================================================= + def name + return @effects[PBEffects::Illusion].name if @effects[PBEffects::Illusion] + return @name + end + + attr_writer :name + + def displayPokemon + return @effects[PBEffects::Illusion] if @effects[PBEffects::Illusion] + return self.pokemon + end + + def displaySpecies + return @effects[PBEffects::Illusion].species if @effects[PBEffects::Illusion] + return self.species + end + + def displayGender + return @effects[PBEffects::Illusion].gender if @effects[PBEffects::Illusion] + return self.gender + end + + def displayForm + return @effects[PBEffects::Illusion].form if @effects[PBEffects::Illusion] + return self.form + end + + def shiny? + return @effects[PBEffects::Illusion].shiny? if @effects[PBEffects::Illusion] + return @pokemon && @pokemon.shiny? + end + alias isShiny? shiny? + + def owned? + return false if !@battle.wildBattle? + return $Trainer.owned[displaySpecies] + end + alias owned owned? + + def abilityName; return PBAbilities.getName(@ability); end + def itemName; return PBItems.getName(@item); end + + def pbThis(lowerCase=false) + if opposes? + if @battle.trainerBattle? + return lowerCase ? _INTL("the opposing {1}",name) : _INTL("The opposing {1}",name) + else + return lowerCase ? _INTL("the wild {1}",name) : _INTL("The wild {1}",name) + end + elsif !pbOwnedByPlayer? + return lowerCase ? _INTL("the ally {1}",name) : _INTL("The ally {1}",name) + end + return name + end + + def pbTeam(lowerCase=false) + if opposes? + return lowerCase ? _INTL("the opposing team") : _INTL("The opposing team") + end + return lowerCase ? _INTL("your team") : _INTL("Your team") + end + + def pbOpposingTeam(lowerCase=false) + if opposes? + return lowerCase ? _INTL("your team") : _INTL("Your team") + end + return lowerCase ? _INTL("the opposing team") : _INTL("The opposing team") + end + + #============================================================================= + # Calculated properties + #============================================================================= + def pbSpeed + return 1 if fainted? + stageMul = [2,2,2,2,2,2, 2, 3,4,5,6,7,8] + stageDiv = [8,7,6,5,4,3, 2, 2,2,2,2,2,2] + stage = @stages[PBStats::SPEED] + 6 + speed = @speed*stageMul[stage]/stageDiv[stage] + speedMult = 0x1000 + # Ability effects that alter calculated Speed + if abilityActive? + speedMult = BattleHandlers.triggerSpeedCalcAbility(@ability,self,speedMult) + end + # Item effects that alter calculated Speed + if itemActive? + speedMult = BattleHandlers.triggerSpeedCalcItem(@item,self,speedMult) + end + # Other effects + speedMult *= 2 if pbOwnSide.effects[PBEffects::Tailwind]>0 + speedMult /= 2 if pbOwnSide.effects[PBEffects::Swamp]>0 + # Paralysis + if status==PBStatuses::PARALYSIS && !hasActiveAbility?(:QUICKFEET) + speedMult /= (NEWEST_BATTLE_MECHANICS) ? 2 : 4 + end + # Badge multiplier + if @battle.internalBattle && pbOwnedByPlayer? && + @battle.pbPlayer.numbadges>=NUM_BADGES_BOOST_SPEED + speedMult *= 1.1 + end + # Calculation + return [(speed.to_f*speedMult/0x1000).round,1].max + end + + def pbWeight + ret = (@pokemon) ? @pokemon.weight : 500 + ret += @effects[PBEffects::WeightChange] + ret = 1 if ret<1 + if abilityActive? && !@battle.moldBreaker + ret = BattleHandlers.triggerWeightCalcAbility(@ability,self,ret) + end + if itemActive? + ret = BattleHandlers.triggerWeightCalcItem(@item,self,ret) + end + return [ret,1].max + end + + #============================================================================= + # Queries about what the battler has + #============================================================================= + def plainStats + ret = [] + ret[PBStats::ATTACK] = self.attack + ret[PBStats::DEFENSE] = self.defense + ret[PBStats::SPATK] = self.spatk + ret[PBStats::SPDEF] = self.spdef + ret[PBStats::SPEED] = self.speed + return ret + end + + # Returns the active types of this Pokémon. The array should not include the + # same type more than once, and should not include any invalid type numbers + # (e.g. -1). + def pbTypes(withType3=false) + ret = [@type1] + ret.push(@type2) if @type2!=@type1 + # Burn Up erases the Fire-type. + if @effects[PBEffects::BurnUp] + ret.reject! { |type| isConst?(type,PBTypes,:FIRE) } + end + # Roost erases the Flying-type. If there are no types left, adds the Normal- + # type. + if @effects[PBEffects::Roost] + ret.reject! { |type| isConst?(type,PBTypes,:FLYING) } + ret.push(getConst(PBTypes,:NORMAL) || 0) if ret.length==0 + end + # Add the third type specially. + if withType3 && @effects[PBEffects::Type3]>=0 + ret.push(@effects[PBEffects::Type3]) if !ret.include?(@effects[PBEffects::Type3]) + end + return ret + end + + def pbHasType?(type) + type = getConst(PBTypes,type) if type.is_a?(Symbol) || type.is_a?(String) + return false if !type || type<0 + activeTypes = pbTypes(true) + return activeTypes.include?(type) + end + + def pbHasOtherType?(type) + type = getConst(PBTypes,type) if type.is_a?(Symbol) || type.is_a?(String) + return false if !type || type<0 + activeTypes = pbTypes(true) + activeTypes.reject! { |t| t==type } + return activeTypes.length>0 + end + + # NOTE: Do not create any held item which affects whether a Pokémon's ability + # is active. The ability Klutz affects whether a Pokémon's item is + # active, and the code for the two combined would cause an infinite loop + # (regardless of whether any Pokémon actualy has either the ability or + # the item - the code existing is enough to cause the loop). + def abilityActive?(ignoreFainted=false) + return false if fainted? && !ignoreFainted + return false if @effects[PBEffects::GastroAcid] + return true + end + + def hasActiveAbility?(ability,ignoreFainted=false) + return false if !abilityActive?(ignoreFainted) + if ability.is_a?(Array) + ability.each do |a| + a = getID(PBAbilities,a) + return true if a!=0 && a==@ability + end + return false + end + ability = getID(PBAbilities,ability) + return ability!=0 && ability==@ability + end + alias hasWorkingAbility hasActiveAbility? + + def nonNegatableAbility? + abilityBlacklist = [ + # Form-changing abilities + :BATTLEBOND, + :DISGUISE, +# :FLOWERGIFT, # This can be negated +# :FORECAST, # This can be negated + :MULTITYPE, + :POWERCONSTRUCT, + :SCHOOLING, + :SHIELDSDOWN, + :STANCECHANGE, + :ZENMODE, + # Abilities intended to be inherent properties of a certain species + :COMATOSE, + :RKSSYSTEM + ] + failed = false + abilityBlacklist.each do |abil| + return true if isConst?(@ability,PBAbilities,abil) + end + return false + end + + def itemActive?(ignoreFainted=false) + return false if fainted? && !ignoreFainted + return false if @effects[PBEffects::Embargo]>0 + return false if @battle.field.effects[PBEffects::MagicRoom]>0 + return false if hasActiveAbility?(:KLUTZ,ignoreFainted) + return true + end + + def hasActiveItem?(item,ignoreFainted=false) + return false if !itemActive?(ignoreFainted) + if item.is_a?(Array) + item.each do |i| + i = getID(PBItems,i) + return true if i!=0 && i==@item + end + return false + end + item = getID(PBItems,item) + return item!=0 && item==@item + end + alias hasWorkingItem hasActiveItem? + + # Returns whether the specified item will be unlosable for this Pokémon. + def unlosableItem?(item) + return false if item<=0 + return true if pbIsMail?(item) + return false if @effects[PBEffects::Transform] + # Items that change a Pokémon's form + return true if @pokemon && @pokemon.getMegaForm(true)>0 # Mega Stone + return pbIsUnlosableItem?(item,@species,@ability) + end + + def eachMove + @moves.each { |m| yield m if m && m.id!=0 } + end + + def eachMoveWithIndex + @moves.each_with_index { |m,i| yield m,i if m && m.id!=0 } + end + + def pbHasMove?(id) + id = getID(PBMoves,id) + return false if !id || id<=0 + eachMove { |m| return true if m.id==id } + return false + end + + def pbHasMoveType?(type) + type = getConst(PBTypes,type) + return false if !type || type<0 + eachMove { |m| return true if m.type==type } + return false + end + + def pbHasMoveFunction?(*arg) + return false if !code + eachMove do |m| + arg.each { |code| return true if m.function==code } + end + return false + end + + def hasMoldBreaker? + return hasActiveAbility?([:MOLDBREAKER,:TERAVOLT,:TURBOBLAZE]) + end + + def canChangeType? + return false if isConst?(@ability,PBAbilities,:MULTITYPE) || + isConst?(@ability,PBAbilities,:RKSSYSTEM) + return true + end + + def airborne? + return false if hasActiveItem?(:IRONBALL) + return false if @effects[PBEffects::Ingrain] + return false if @effects[PBEffects::SmackDown] + return false if @battle.field.effects[PBEffects::Gravity]>0 + return true if pbHasType?(:FLYING) + return true if hasActiveAbility?(:LEVITATE) && !@battle.moldBreaker + return true if hasActiveItem?(:AIRBALLOON) + return true if @effects[PBEffects::MagnetRise]>0 + return true if @effects[PBEffects::Telekinesis]>0 + return false + end + + def affectedByTerrain? + return false if airborne? + return false if semiInvulnerable? + return true + end + + def takesIndirectDamage?(showMsg=false) + return false if fainted? + if hasActiveAbility?(:MAGICGUARD) + if showMsg + @battle.pbShowAbilitySplash(self) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} is unaffected!",pbThis)) + else + @battle.pbDisplay(_INTL("{1} is unaffected because of its {2}!",pbThis,abilityName)) + end + @battle.pbHideAbilitySplash(self) + end + return false + end + return true + end + + def takesSandstormDamage? + return false if !takesIndirectDamage? + return false if pbHasType?(:GROUND) || pbHasType?(:ROCK) || pbHasType?(:STEEL) + return false if inTwoTurnAttack?("0CA","0CB") # Dig, Dive + return false if hasActiveAbility?([:OVERCOAT,:SANDFORCE,:SANDRUSH,:SANDVEIL]) + return false if hasActiveItem?(:SAFETYGOGGLES) + return true + end + + def takesHailDamage? + return false if !takesIndirectDamage? + return false if pbHasType?(:ICE) + return false if inTwoTurnAttack?("0CA","0CB") # Dig, Dive + return false if hasActiveAbility?([:OVERCOAT,:ICEBODY,:SNOWCLOAK]) + return false if hasActiveItem?(:SAFETYGOGGLES) + return true + end + + def takesShadowSkyDamage? + return false if fainted? + return false if shadowPokemon? + return true + end + + def affectedByPowder?(showMsg=false) + return false if fainted? + return true if !NEWEST_BATTLE_MECHANICS + if pbHasType?(:GRASS) + @battle.pbDisplay(_INTL("{1} is unaffected!",pbThis)) if showMsg + return false + end + if hasActiveAbility?(:OVERCOAT) && !@battle.moldBreaker + if showMsg + @battle.pbShowAbilitySplash(self) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} is unaffected!",pbThis)) + else + @battle.pbDisplay(_INTL("{1} is unaffected because of its {2}!",pbThis,abilityName)) + end + @battle.pbHideAbilitySplash(self) + end + return false + end + if hasActiveItem?(:SAFETYGOGGLES) + if showMsg + @battle.pbDisplay(_INTL("{1} is unaffected because of its {2}!",pbThis,itemName)) + end + return false + end + return true + end + + def canHeal? + return false if fainted? || @hp>=@totalhp + return false if @effects[PBEffects::HealBlock]>0 + return true + end + + def affectedByContactEffect?(showMsg=false) + return false if fainted? + if hasActiveItem?(:PROTECTIVEPADS) + @battle.pbDisplay(_INTL("{1} protected itself with the {2}!",pbThis,itemName)) if showMsg + return false + end + return true + end + + def movedThisRound? + return @lastRoundMoved && @lastRoundMoved==@battle.turnCount + end + + def usingMultiTurnAttack? + return true if @effects[PBEffects::TwoTurnAttack]>0 + return true if @effects[PBEffects::HyperBeam]>0 + return true if @effects[PBEffects::Rollout]>0 + return true if @effects[PBEffects::Outrage]>0 + return true if @effects[PBEffects::Uproar]>0 + return true if @effects[PBEffects::Bide]>0 + return false + end + + def inTwoTurnAttack?(*arg) + return false if @effects[PBEffects::TwoTurnAttack]==0 + ttaFunction = pbGetMoveData(@effects[PBEffects::TwoTurnAttack],MOVE_FUNCTION_CODE) + arg.each { |a| return true if a==ttaFunction } + return false + end + + def semiInvulnerable? + return inTwoTurnAttack?("0C9","0CA","0CB","0CC","0CD","0CE","14D") + end + + def pbEncoredMoveIndex + return -1 if @effects[PBEffects::Encore]==0 || @effects[PBEffects::EncoreMove]==0 + ret = -1 + eachMoveWithIndex do |m,i| + next if m.id!=@effects[PBEffects::EncoreMove] + ret = i + break + end + return ret + end + + def initialItem + return @battle.initialItems[@index&1][@pokemonIndex] + end + + def setInitialItem(newItem) + @battle.initialItems[@index&1][@pokemonIndex] = newItem + end + + def recycleItem + return @battle.recycleItems[@index&1][@pokemonIndex] + end + + def setRecycleItem(newItem) + @battle.recycleItems[@index&1][@pokemonIndex] = newItem + end + + def belched? + return @battle.belch[@index&1][@pokemonIndex] + end + + def setBelched + @battle.belch[@index&1][@pokemonIndex] = true + end + + #============================================================================= + # Methods relating to this battler's position on the battlefield + #============================================================================= + # Returns whether the given position belongs to the opposing Pokémon's side. + def opposes?(i=0) + i = i.index if i.respond_to?("index") + return (@index&1)!=(i&1) + end + + # Returns whether the given position/battler is near to self. + def near?(i) + i = i.index if i.respond_to?("index") + return @battle.nearBattlers?(@index,i) + end + + # Returns whether self is owned by the player. + def pbOwnedByPlayer? + return @battle.pbOwnedByPlayer?(@index) + end + + # Returns 0 if self is on the player's side, or 1 if self is on the opposing + # side. + def idxOwnSide + return @index&1 + end + + # Returns 1 if self is on the player's side, or 0 if self is on the opposing + # side. + def idxOpposingSide + return (@index&1)^1 + end + + # Returns the data structure for this battler's side. + def pbOwnSide + return @battle.sides[idxOwnSide] + end + + # Returns the data structure for the opposing Pokémon's side. + def pbOpposingSide + return @battle.sides[idxOpposingSide] + end + + # Yields each unfainted ally Pokémon. + def eachAlly + @battle.battlers.each do |b| + yield b if b && !b.fainted? && !b.opposes?(@index) && b.index!=@index + end + end + + # Yields each unfainted opposing Pokémon. + def eachOpposing + @battle.battlers.each { |b| yield b if b && !b.fainted? && b.opposes?(@index) } + end + + # Returns the battler that is most directly opposite to self. unfaintedOnly is + # whether it should prefer to return a non-fainted battler. + def pbDirectOpposing(unfaintedOnly=false) + @battle.pbGetOpposingIndicesInOrder(@index).each do |i| + next if !@battle.battlers[i] + break if unfaintedOnly && @battle.battlers[i].fainted? + return @battle.battlers[i] + end + # Wanted an unfainted battler but couldn't find one; make do with a fainted + # battler + @battle.pbGetOpposingIndicesInOrder(@index).each do |i| + return @battle.battlers[i] if @battle.battlers[i] + end + return @battle.battlers[(@index^1)] + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/001_Battler/002_Battler_Initialize.rb b/Data/Scripts/011_Battle/001_Battler/002_Battler_Initialize.rb new file mode 100644 index 000000000..36ce6381c --- /dev/null +++ b/Data/Scripts/011_Battle/001_Battler/002_Battler_Initialize.rb @@ -0,0 +1,326 @@ +class PokeBattle_Battler + #============================================================================= + # Creating a battler + #============================================================================= + def initialize(btl,idxBattler) + @battle = btl + @index = idxBattler + @captured = false + @dummy = false + @stages = [] + @effects = [] + @damageState = PokeBattle_DamageState.new + pbInitBlank + pbInitEffects(false) + end + + def pbInitBlank + @name = "" + @species = 0 + @form = 0 + @level = 0 + @hp = @totalhp = 0 + @type1 = @type2 = 0 + @ability = 0 + @item = 0 + @gender = 0 + @attack = @defense = @spatk = @spdef = @speed = 0 + @status = PBStatuses::NONE + @statusCount = 0 + @pokemon = nil + @pokemonIndex = -1 + @participants = [] + @moves = [] + @iv = [0,0,0,0,0,0] + end + + # Used by Future Sight only, when Future Sight's user is no longer in battle. + def pbInitDummyPokemon(pkmn,idxParty) + raise _INTL("An egg can't be an active Pokémon.") if pkmn.egg? + @name = pkmn.name + @species = pkmn.species + @form = pkmn.form + @level = pkmn.level + @hp = pkmn.hp + @totalhp = pkmn.totalhp + @type1 = pkmn.type1 + @type2 = pkmn.type2 + # ability and item intentionally not copied across here + @gender = pkmn.gender + @attack = pkmn.attack + @defense = pkmn.defense + @spatk = pkmn.spatk + @spdef = pkmn.spdef + @speed = pkmn.speed + @status = pkmn.status + @statusCount = pkmn.statusCount + @pokemon = pkmn + @pokemonIndex = idxParty + @participants = [] + # moves intentionally not copied across here + @iv = pkmn.iv.clone + @dummy = true + end + + def pbInitialize(pkmn,idxParty,batonPass=false) + pbInitPokemon(pkmn,idxParty) + pbInitEffects(batonPass) + end + + def pbInitPokemon(pkmn,idxParty) + raise _INTL("An egg can't be an active Pokémon.") if pkmn.egg? + @name = pkmn.name + @species = pkmn.species + @form = pkmn.form + @level = pkmn.level + @hp = pkmn.hp + @totalhp = pkmn.totalhp + @type1 = pkmn.type1 + @type2 = pkmn.type2 + @ability = pkmn.ability + @item = pkmn.item + @gender = pkmn.gender + @attack = pkmn.attack + @defense = pkmn.defense + @spatk = pkmn.spatk + @spdef = pkmn.spdef + @speed = pkmn.speed + @status = pkmn.status + @statusCount = pkmn.statusCount + @pokemon = pkmn + @pokemonIndex = idxParty + @participants = [] # Participants earn Exp. if this battler is defeated + @moves = [] + pkmn.moves.each_with_index do |m,i| + @moves[i] = PokeBattle_Move.pbFromPBMove(@battle,m) + end + @iv = pkmn.iv.clone + end + + def pbInitEffects(batonPass) + if batonPass + # These effects are passed on if Baton Pass is used, but they need to be + # reapplied + @effects[PBEffects::LaserFocus] = (@effects[PBEffects::LaserFocus]>0) ? 2 : 0 + @effects[PBEffects::LockOn] = (@effects[PBEffects::LockOn]>0) ? 2 : 0 + if @effects[PBEffects::PowerTrick] + @attack,@defense = @defense,@attack + end + # These effects are passed on if Baton Pass is used, but they need to be + # cancelled in certain circumstances anyway + @effects[PBEffects::Telekinesis] = 0 if isConst?(@species,PBSpecies,:GENGAR) && mega? + @effects[PBEffects::GastroAcid] = false if nonNegatableAbility? + else + # These effects are passed on if Baton Pass is used + @stages[PBStats::ATTACK] = 0 + @stages[PBStats::DEFENSE] = 0 + @stages[PBStats::SPEED] = 0 + @stages[PBStats::SPATK] = 0 + @stages[PBStats::SPDEF] = 0 + @stages[PBStats::EVASION] = 0 + @stages[PBStats::ACCURACY] = 0 + @effects[PBEffects::AquaRing] = false + @effects[PBEffects::Confusion] = 0 + @effects[PBEffects::Curse] = false + @effects[PBEffects::Embargo] = 0 + @effects[PBEffects::FocusEnergy] = 0 + @effects[PBEffects::GastroAcid] = false + @effects[PBEffects::HealBlock] = 0 + @effects[PBEffects::Ingrain] = false + @effects[PBEffects::LaserFocus] = 0 + @effects[PBEffects::LeechSeed] = -1 + @effects[PBEffects::LockOn] = 0 + @effects[PBEffects::LockOnPos] = -1 + @effects[PBEffects::MagnetRise] = 0 + @effects[PBEffects::PerishSong] = 0 + @effects[PBEffects::PerishSongUser] = -1 + @effects[PBEffects::PowerTrick] = false + @effects[PBEffects::Substitute] = 0 + @effects[PBEffects::Telekinesis] = 0 + end + @damageState.reset + @fainted = (@hp==0) + @initialHP = 0 + @lastAttacker = [] + @lastFoeAttacker = [] + @lastHPLost = 0 + @lastHPLostFromFoe = 0 + @tookDamage = false + @tookPhysicalHit = false + @lastMoveUsed = -1 + @lastMoveUsedType = -1 + @lastRegularMoveUsed = -1 + @lastRegularMoveTarget = -1 + @lastRoundMoved = -1 + @lastMoveFailed = false + @lastRoundMoveFailed = false + @movesUsed = [] + @turnCount = 0 + @effects[PBEffects::Attract] = -1 + @battle.eachBattler do |b| # Other battlers no longer attracted to self + b.effects[PBEffects::Attract] = -1 if b.effects[PBEffects::Attract]==@index + end + @effects[PBEffects::BanefulBunker] = false + @effects[PBEffects::BeakBlast] = false + @effects[PBEffects::Bide] = 0 + @effects[PBEffects::BideDamage] = 0 + @effects[PBEffects::BideTarget] = -1 + @effects[PBEffects::BurnUp] = false + @effects[PBEffects::Charge] = 0 + @effects[PBEffects::ChoiceBand] = -1 + @effects[PBEffects::Counter] = -1 + @effects[PBEffects::CounterTarget] = -1 + @effects[PBEffects::Dancer] = false + @effects[PBEffects::DefenseCurl] = false + @effects[PBEffects::DestinyBond] = false + @effects[PBEffects::DestinyBondPrevious] = false + @effects[PBEffects::DestinyBondTarget] = -1 + @effects[PBEffects::Disable] = 0 + @effects[PBEffects::DisableMove] = 0 + @effects[PBEffects::Electrify] = false + @effects[PBEffects::Encore] = 0 + @effects[PBEffects::EncoreMove] = 0 + @effects[PBEffects::Endure] = false + @effects[PBEffects::FirstPledge] = 0 + @effects[PBEffects::FlashFire] = false + @effects[PBEffects::Flinch] = false + @effects[PBEffects::FocusPunch] = false + @effects[PBEffects::FollowMe] = 0 + @effects[PBEffects::Foresight] = false + @effects[PBEffects::FuryCutter] = 0 + @effects[PBEffects::GemConsumed] = 0 + @effects[PBEffects::Grudge] = false + @effects[PBEffects::HelpingHand] = false + @effects[PBEffects::HyperBeam] = 0 + @effects[PBEffects::Illusion] = nil + if hasActiveAbility?(:ILLUSION) + idxLastParty = @battle.pbLastInTeam(@index) + if idxLastParty!=@pokemonIndex + @effects[PBEffects::Illusion] = @battle.pbParty(@index)[idxLastParty] + end + end + @effects[PBEffects::Imprison] = false + @effects[PBEffects::Instruct] = false + @effects[PBEffects::Instructed] = false + @effects[PBEffects::KingsShield] = false + @battle.eachBattler do |b| # Other battlers lose their lock-on against self + next if b.effects[PBEffects::LockOn]==0 + next if b.effects[PBEffects::LockOnPos]!=@index + b.effects[PBEffects::LockOn] = 0 + b.effects[PBEffects::LockOnPos] = -1 + end + @effects[PBEffects::MagicBounce] = false + @effects[PBEffects::MagicCoat] = false + @effects[PBEffects::MeanLook] = -1 + @battle.eachBattler do |b| # Other battlers no longer blocked by self + b.effects[PBEffects::MeanLook] = -1 if b.effects[PBEffects::MeanLook]==@index + end + @effects[PBEffects::MeFirst] = false + @effects[PBEffects::Metronome] = 0 + @effects[PBEffects::MicleBerry] = false + @effects[PBEffects::Minimize] = false + @effects[PBEffects::MiracleEye] = false + @effects[PBEffects::MirrorCoat] = -1 + @effects[PBEffects::MirrorCoatTarget] = -1 + @effects[PBEffects::MoveNext] = false + @effects[PBEffects::MudSport] = false + @effects[PBEffects::Nightmare] = false + @effects[PBEffects::Outrage] = 0 + @effects[PBEffects::ParentalBond] = 0 + @effects[PBEffects::PickupItem] = 0 + @effects[PBEffects::PickupUse] = 0 + @effects[PBEffects::Pinch] = false + @effects[PBEffects::Powder] = false + @effects[PBEffects::Prankster] = false + @effects[PBEffects::PriorityAbility] = false + @effects[PBEffects::PriorityItem] = false + @effects[PBEffects::Protect] = false + @effects[PBEffects::ProtectRate] = 1 + @effects[PBEffects::Pursuit] = false + @effects[PBEffects::Quash] = 0 + @effects[PBEffects::Rage] = false + @effects[PBEffects::RagePowder] = false + @effects[PBEffects::Revenge] = 0 + @effects[PBEffects::Rollout] = 0 + @effects[PBEffects::Roost] = false + @effects[PBEffects::SkyDrop] = -1 + @battle.eachBattler do |b| # Other battlers no longer Sky Dropped by self + b.effects[PBEffects::SkyDrop] = -1 if b.effects[PBEffects::SkyDrop]==@index + end + @effects[PBEffects::SlowStart] = 0 + @effects[PBEffects::SmackDown] = false + @effects[PBEffects::Snatch] = 0 + @effects[PBEffects::SpikyShield] = false + @effects[PBEffects::Spotlight] = 0 + @effects[PBEffects::Stockpile] = 0 + @effects[PBEffects::StockpileDef] = 0 + @effects[PBEffects::StockpileSpDef] = 0 + @effects[PBEffects::Taunt] = 0 + @effects[PBEffects::ThroatChop] = 0 + @effects[PBEffects::Torment] = false + @effects[PBEffects::Toxic] = 0 + @effects[PBEffects::Transform] = false + @effects[PBEffects::TransformSpecies] = 0 + @effects[PBEffects::Trapping] = 0 + @effects[PBEffects::TrappingMove] = 0 + @effects[PBEffects::TrappingUser] = -1 + @battle.eachBattler do |b| # Other battlers no longer trapped by self + next if b.effects[PBEffects::TrappingUser]!=@index + b.effects[PBEffects::Trapping] = 0 + b.effects[PBEffects::TrappingUser] = -1 + end + @effects[PBEffects::Truant] = false + @effects[PBEffects::TwoTurnAttack] = 0 + @effects[PBEffects::Type3] = -1 + @effects[PBEffects::Unburden] = false + @effects[PBEffects::Uproar] = 0 + @effects[PBEffects::WaterSport] = false + @effects[PBEffects::WeightChange] = 0 + @effects[PBEffects::Yawn] = 0 + end + + #============================================================================= + # Refreshing a battler's properties + #============================================================================= + def pbUpdate(fullChange=false) + return if !@pokemon + @pokemon.calcStats + @level = @pokemon.level + @hp = @pokemon.hp + @totalhp = @pokemon.totalhp + if !@effects[PBEffects::Transform] + @attack = @pokemon.attack + @defense = @pokemon.defense + @spatk = @pokemon.spatk + @spdef = @pokemon.spdef + @speed = @pokemon.speed + if fullChange + @type1 = @pokemon.type1 + @type2 = @pokemon.type2 + @ability = @pokemon.ability + end + end + end + + # Used only to erase the battler of a Shadow Pokémon that has been snagged. + def pbReset + @pokemon = nil + @pokemonIndex = -1 + @hp = 0 + pbInitEffects(false) + @participants = [] + # Reset status + @status = PBStatuses::NONE + @statusCount = 0 + # Reset choice + @battle.pbClearChoice(@index) + end + + # Update which Pokémon will gain Exp if this battler is defeated. + def pbUpdateParticipants + return if fainted? || !@battle.opposes?(@index) + eachOpposing do |b| + @participants.push(b.pokemonIndex) if !@participants.include?(b.pokemonIndex) + end + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/001_Battler/003_Battler_ChangeSelf.rb b/Data/Scripts/011_Battle/001_Battler/003_Battler_ChangeSelf.rb new file mode 100644 index 000000000..2b789180a --- /dev/null +++ b/Data/Scripts/011_Battle/001_Battler/003_Battler_ChangeSelf.rb @@ -0,0 +1,301 @@ +class PokeBattle_Battler + #============================================================================= + # Change HP + #============================================================================= + def pbReduceHP(amt,anim=true,registerDamage=true,anyAnim=true) + amt = amt.round + amt = @hp if amt>@hp + amt = 1 if amt<1 && !fainted? + oldHP = @hp + self.hp -= amt + PBDebug.log("[HP change] #{pbThis} lost #{amt} HP (#{oldHP}=>#{@hp})") + raise _INTL("HP less than 0") if @hp<0 + raise _INTL("HP greater than total HP") if @hp>@totalhp + @battle.scene.pbHPChanged(self,oldHP,anim) if anyAnim && amt>0 + @tookDamage = true if amt>0 && registerDamage + return amt + end + + def pbRecoverHP(amt,anim=true,anyAnim=true) + amt = amt.round + amt = @totalhp-@hp if amt>@totalhp-@hp + amt = 1 if amt<1 && @hp<@totalhp + oldHP = @hp + self.hp += amt + PBDebug.log("[HP change] #{pbThis} gained #{amt} HP (#{oldHP}=>#{@hp})") + raise _INTL("HP less than 0") if @hp<0 + raise _INTL("HP greater than total HP") if @hp>@totalhp + @battle.scene.pbHPChanged(self,oldHP,anim) if anyAnim && amt>0 + return amt + end + + def pbRecoverHPFromDrain(amt,target,msg=nil) + if target.hasActiveAbility?(:LIQUIDOOZE) + oldHP = @hp + @battle.pbShowAbilitySplash(target) + pbReduceHP(amt) + @battle.pbDisplay(_INTL("{1} sucked up the liquid ooze!",pbThis)) + @battle.pbHideAbilitySplash(target) + pbItemHPHealCheck + else + msg = _INTL("{1} had its energy drained!",target.pbThis) if !msg || msg=="" + @battle.pbDisplay(msg) + if canHeal? + amt = (amt*1.3).floor if hasActiveItem?(:BIGROOT) + pbRecoverHP(amt) + end + end + end + + def pbFaint(showMessage=true) + if !fainted? + PBDebug.log("!!!***Can't faint with HP greater than 0") + return + end + return if @fainted # Has already fainted properly + @battle.pbDisplayBrief(_INTL("{1} fainted!",pbThis)) if showMessage + PBDebug.log("[Pokémon fainted] #{pbThis} (#{@index})") if !showMessage + @battle.scene.pbFaintBattler(self) + pbInitEffects(false) + # Reset status + self.status = PBStatuses::NONE + self.statusCount = 0 + # Lose happiness + if @pokemon && @battle.internalBattle + badLoss = false + @battle.eachOtherSideBattler(@index) do |b| + badLoss = true if b.level>=self.level+30 + end + @pokemon.changeHappiness((badLoss) ? "faintbad" : "faint") + end + # Reset form + @battle.peer.pbOnLeavingBattle(@battle,@pokemon,@battle.usedInBattle[idxOwnSide][@index/2]) + @pokemon.makeUnmega if mega? + @pokemon.makeUnprimal if primal? + # Do other things + @battle.pbClearChoice(@index) # Reset choice + pbOwnSide.effects[PBEffects::LastRoundFainted] = @battle.turnCount + # Check other battlers' abilities that trigger upon a battler fainting + pbAbilitiesOnFainting + # Check for end of primordial weather + @battle.pbEndPrimordialWeather + end + + #============================================================================= + # Move PP + #============================================================================= + def pbSetPP(move,pp) + move.pp = pp + # No need to care about @effects[PBEffects::Mimic], since Mimic can't copy + # Mimic + if move.realMove && move.id==move.realMove.id && !@effects[PBEffects::Transform] + move.realMove.pp = pp + end + end + + def pbReducePP(move) + return true if usingMultiTurnAttack? + return true if move.pp<0 # Don't reduce PP for special calls of moves + return true if move.totalpp<=0 # Infinite PP, can always be used + return false if move.pp==0 # Ran out of PP, couldn't reduce + pbSetPP(move,move.pp-1) if move.pp>0 + return true + end + + def pbReducePPOther(move) + pbSetPP(move,move.pp-1) if move.pp>0 + end + + #============================================================================= + # Change type + #============================================================================= + def pbChangeTypes(newType) + if newType.is_a?(PokeBattle_Battler) + newTypes = newType.pbTypes + newTypes.push(getConst(PBTypes,:NORMAL) || 0) if newTypes.length==0 + newType3 = newType.effects[PBEffects::Type3] + newType3 = -1 if newTypes.include?(newType3) + @type1 = newTypes[0] + @type2 = (newTypes.length==1) ? newTypes[0] : newTypes[1] + @effects[PBEffects::Type3] = newType3 + else + newType = getConst(PBTypes,newType) if newType.is_a?(Symbol) || newType.is_a?(String) + @type1 = newType + @type2 = newType + @effects[PBEffects::Type3] = -1 + end + @effects[PBEffects::BurnUp] = false + @effects[PBEffects::Roost] = false + end + + #============================================================================= + # Forms + #============================================================================= + def pbChangeForm(newForm,msg) + return if fainted? || @effects[PBEffects::Transform] || @form==newForm + oldForm = @form + oldDmg = @totalhp-@hp + self.form = newForm + pbUpdate(true) + @hp = @totalhp-oldDmg + @effects[PBEffects::WeightChange] = 0 if NEWEST_BATTLE_MECHANICS + @battle.scene.pbChangePokemon(self,@pokemon) + @battle.scene.pbRefreshOne(@index) + @battle.pbDisplay(msg) if msg && msg!="" + PBDebug.log("[Form changed] #{pbThis} changed from form #{oldForm} to form #{newForm}") + @battle.pbSetSeen(self) + end + + def pbCheckFormOnStatusChange + return if fainted? || @effects[PBEffects::Transform] + # Shaymin - reverts if frozen + if isConst?(@species,PBSpecies,:SHAYMIN) && frozen? + pbChangeForm(0,_INTL("{1} transformed!",pbThis)) + end + end + + def pbCheckFormOnMovesetChange + return if fainted? || @effects[PBEffects::Transform] + # Keldeo - knowing Secret Sword + if isConst?(@species,PBSpecies,:KELDEO) + newForm = 0 + newForm = 1 if pbHasMove?(:SECRETSWORD) + pbChangeForm(newForm,_INTL("{1} transformed!",pbThis)) + end + end + + def pbCheckFormOnWeatherChange + return if fainted? || @effects[PBEffects::Transform] + # Castform - Forecast + if isConst?(@species,PBSpecies,:CASTFORM) + if hasActiveAbility?(:FORECAST) + newForm = 0 + case @battle.pbWeather + when PBWeather::Sun, PBWeather::HarshSun; newForm = 1 + when PBWeather::Rain, PBWeather::HeavyRain; newForm = 2 + when PBWeather::Hail; newForm = 3 + end + if @form!=newForm + @battle.pbShowAbilitySplash(self,true) + @battle.pbHideAbilitySplash(self) + pbChangeForm(newForm,_INTL("{1} transformed!",pbThis)) + end + else + pbChangeForm(0,_INTL("{1} transformed!",pbThis)) + end + end + # Cherrim - Flower Gift + if isConst?(@species,PBSpecies,:CHERRIM) + if hasActiveAbility?(:FLOWERGIFT) + newForm = 0 + case @battle.pbWeather + when PBWeather::Sun, PBWeather::HarshSun; newForm = 1 + end + if @form!=newForm + @battle.pbShowAbilitySplash(self,true) + @battle.pbHideAbilitySplash(self) + pbChangeForm(newForm,_INTL("{1} transformed!",pbThis)) + end + else + pbChangeForm(0,_INTL("{1} transformed!",pbThis)) + end + end + end + + # Checks the Pokémon's form and updates it if necessary. Used for when a + # Pokémon enters battle (endOfRound=false) and at the end of each round + # (endOfRound=true). + def pbCheckForm(endOfRound=false) + return if fainted? || @effects[PBEffects::Transform] + # Form changes upon entering battle and when the weather changes + pbCheckFormOnWeatherChange if !endOfRound + # Darmanitan - Zen Mode + if isConst?(@species,PBSpecies,:DARMANITAN) && isConst?(@ability,PBAbilities,:ZENMODE) + if @hp<=@totalhp/2 + if @form!=1 + @battle.pbShowAbilitySplash(self,true) + @battle.pbHideAbilitySplash(self) + pbChangeForm(1,_INTL("{1} triggered!",abilityName)) + end + elsif @form!=0 + @battle.pbShowAbilitySplash(self,true) + @battle.pbHideAbilitySplash(self) + pbChangeForm(0,_INTL("{1} triggered!",abilityName)) + end + end + # Minior - Shields Down + if isConst?(@species,PBSpecies,:MINIOR) && isConst?(@ability,PBAbilities,:SHIELDSDOWN) + if @hp>@totalhp/2 # Turn into Meteor form + newForm = (@form>=7) ? @form-7 : @form + if @form!=newForm + @battle.pbShowAbilitySplash(self,true) + @battle.pbHideAbilitySplash(self) + pbChangeForm(newForm,_INTL("{1} deactivated!",abilityName)) + elsif !endOfRound + @battle.pbDisplay(_INTL("{1} deactivated!",abilityName)) + end + elsif @form<7 # Turn into Core form + @battle.pbShowAbilitySplash(self,true) + @battle.pbHideAbilitySplash(self) + pbChangeForm(@form+7,_INTL("{1} activated!",abilityName)) + end + end + # Wishiwashi - Schooling + if isConst?(@species,PBSpecies,:WISHIWASHI) && isConst?(@ability,PBAbilities,:SCHOOLING) + if @level>=20 && @hp>@totalhp/4 + if @form!=1 + @battle.pbShowAbilitySplash(self,true) + @battle.pbHideAbilitySplash(self) + pbChangeForm(1,_INTL("{1} formed a school!",pbThis)) + end + elsif @form!=0 + @battle.pbShowAbilitySplash(self,true) + @battle.pbHideAbilitySplash(self) + pbChangeForm(0,_INTL("{1} stopped schooling!",pbThis)) + end + end + # Zygarde - Power Construct + if isConst?(@species,PBSpecies,:ZYGARDE) && isConst?(@ability,PBAbilities,:POWERCONSTRUCT) && + endOfRound + if @hp<=@totalhp/2 && @form<2 # Turn into Complete Forme + newForm = @form+2 + @battle.pbDisplay(_INTL("You sense the presence of many!")) + @battle.pbShowAbilitySplash(self,true) + @battle.pbHideAbilitySplash(self) + pbChangeForm(newForm,_INTL("{1} transformed into its Complete Forme!",pbThis)) + end + end + end + + def pbTransform(target) + oldAbil = @ability + @effects[PBEffects::Transform] = true + @effects[PBEffects::TransformSpecies] = target.species + pbChangeTypes(target) + @ability = target.ability + @attack = target.attack + @defense = target.defense + @spatk = target.spatk + @spdef = target.spdef + @speed = target.speed + PBStats.eachBattleStat { |s| @stages[s] = target.stages[s] } + if NEWEST_BATTLE_MECHANICS + @effects[PBEffects::FocusEnergy] = target.effects[PBEffects::FocusEnergy] + @effects[PBEffects::LaserFocus] = target.effects[PBEffects::LaserFocus] + end + @moves.clear + target.moves.each_with_index do |m,i| + @moves[i] = PokeBattle_Move.pbFromPBMove(@battle,PBMove.new(m.id)) + @moves[i].pp = 5 + @moves[i].totalpp = 5 + end + @effects[PBEffects::Disable] = 0 + @effects[PBEffects::DisableMove] = 0 + @effects[PBEffects::WeightChange] = target.effects[PBEffects::WeightChange] + @battle.scene.pbRefreshOne(@index) + @battle.pbDisplay(_INTL("{1} transformed into {2}!",pbThis,target.pbThis(true))) + pbOnAbilityChanged(oldAbil) + end + + def pbHyperMode; end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/001_Battler/004_Battler_Statuses.rb b/Data/Scripts/011_Battle/001_Battler/004_Battler_Statuses.rb new file mode 100644 index 000000000..907b406e9 --- /dev/null +++ b/Data/Scripts/011_Battle/001_Battler/004_Battler_Statuses.rb @@ -0,0 +1,573 @@ +class PokeBattle_Battler + #============================================================================= + # Generalised checks for whether a status problem can be inflicted + #============================================================================= + # NOTE: Not all "does it have this status?" checks use this method. If the + # check is leading up to curing self of that status condition, then it + # will look at the value of @status directly instead - if it is that + # PBStatuses value then it is curable. This method only checks for + # "counts as having that status", which includes Comatose which can't be + # cured. + def pbHasStatus?(checkStatus) + if BattleHandlers.triggerStatusCheckAbilityNonIgnorable(@ability,self,checkStatus) + return true + end + return @status==checkStatus + end + + def pbHasAnyStatus? + if BattleHandlers.triggerStatusCheckAbilityNonIgnorable(@ability,self,nil) + return true + end + return @status!=PBStatuses::NONE + end + + def pbCanInflictStatus?(newStatus,user,showMessages,move=nil,ignoreStatus=false) + return false if fainted? + selfInflicted = (user && user.index==@index) + # Already have that status problem + if self.status==newStatus && !ignoreStatus + if showMessages + msg = "" + case self.status + when PBStatuses::SLEEP; msg = _INTL("{1} is already asleep!",pbThis) + when PBStatuses::POISON; msg = _INTL("{1} is already poisoned!",pbThis) + when PBStatuses::BURN; msg = _INTL("{1} already has a burn!",pbThis) + when PBStatuses::PARALYSIS; msg = _INTL("{1} is already paralyzed!",pbThis) + when PBStatuses::FROZEN; msg = _INTL("{1} is already frozen solid!",pbThis) + end + @battle.pbDisplay(msg) + end + return false + end + # Trying to replace a status problem with another one + if self.status!=PBStatuses::NONE && !ignoreStatus && !selfInflicted + @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 + @battle.pbDisplay(_INTL("It doesn't affect {1}...",pbThis(true))) if showMessages + return false + end + # Weather immunity + if newStatus==PBStatuses::FROZEN && + (@battle.pbWeather==PBWeather::Sun || @battle.pbWeather==PBWeather::HarshSun) + @battle.pbDisplay(_INTL("It doesn't affect {1}...",pbThis(true))) if showMessages + return false + end + # Terrains immunity + if affectedByTerrain? + case @battle.field.terrain + when PBBattleTerrains::Electric + if newStatus==PBStatuses::SLEEP + @battle.pbDisplay(_INTL("{1} surrounds itself with electrified terrain!", + pbThis(true))) if showMessages + return false + end + when PBBattleTerrains::Misty + @battle.pbDisplay(_INTL("{1} surrounds itself with misty terrain!",pbThis(true))) if showMessages + return false + end + end + # Uproar immunity + if newStatus==PBStatuses::SLEEP && + !(hasActiveAbility?(:SOUNDPROOF) && !@battle.moldBreaker) + @battle.eachBattler do |b| + next if b.effects[PBEffects::Uproar]==0 + @battle.pbDisplay(_INTL("But the uproar kept {1} awake!",pbThis(true))) if showMessages + return false + end + end + # Type immunities + hasImmuneType = false + case newStatus + when PBStatuses::SLEEP + # No type is immune to sleep + when PBStatuses::POISON + if !(user && user.hasActiveAbility?(:CORROSION)) + hasImmuneType |= pbHasType?(:POISON) + hasImmuneType |= pbHasType?(:STEEL) + end + when PBStatuses::BURN + hasImmuneType |= pbHasType?(:FIRE) + when PBStatuses::PARALYSIS + hasImmuneType |= pbHasType?(:ELECTRIC) && NEWEST_BATTLE_MECHANICS + when PBStatuses::FROZEN + hasImmuneType |= pbHasType?(:ICE) + end + if hasImmuneType + @battle.pbDisplay(_INTL("It doesn't affect {1}...",pbThis(true))) if showMessages + return false + end + # Ability immunity + immuneByAbility = false; immAlly = nil + if BattleHandlers.triggerStatusImmunityAbilityNonIgnorable(@ability,self,newStatus) + immuneByAbility = true + elsif selfInflicted || !@battle.moldBreaker + if abilityActive? && BattleHandlers.triggerStatusImmunityAbility(@ability,self,newStatus) + immuneByAbility = true + else + eachAlly do |b| + next if !b.abilityActive? + next if !BattleHandlers.triggerStatusImmunityAllyAbility(b.ability,self,newStatus) + immuneByAbility = true + immAlly = b + break + end + end + end + if immuneByAbility + if showMessages + @battle.pbShowAbilitySplash(immAlly || self) + msg = "" + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + case newStatus + when PBStatuses::SLEEP; msg = _INTL("{1} stays awake!",pbThis) + when PBStatuses::POISON; msg = _INTL("{1} cannot be poisoned!",pbThis) + when PBStatuses::BURN; msg = _INTL("{1} cannot be burned!",pbThis) + when PBStatuses::PARALYSIS; msg = _INTL("{1} cannot be paralyzed!",pbThis) + when PBStatuses::FROZEN; msg = _INTL("{1} cannot be frozen solid!",pbThis) + end + elsif immAlly + case newStatus + when PBStatuses::SLEEP + msg = _INTL("{1} stays awake because of {2}'s {3}!", + pbThis,immAlly.pbThis(true),immAlly.abilityName) + when PBStatuses::POISON + msg = _INTL("{1} cannot be poisoned because of {2}'s {3}!", + pbThis,immAlly.pbThis(true),immAlly.abilityName) + when PBStatuses::BURN + msg = _INTL("{1} cannot be burned because of {2}'s {3}!", + pbThis,immAlly.pbThis(true),immAlly.abilityName) + when PBStatuses::PARALYSIS + msg = _INTL("{1} cannot be paralyzed because of {2}'s {3}!", + pbThis,immAlly.pbThis(true),immAlly.abilityName) + when PBStatuses::FROZEN + msg = _INTL("{1} cannot be frozen solid because of {2}'s {3}!", + pbThis,immAlly.pbThis(true),immAlly.abilityName) + end + else + case newStatus + when PBStatuses::SLEEP; msg = _INTL("{1} stays awake because of its {2}!",pbThis,abilityName) + when PBStatuses::POISON; msg = _INTL("{1}'s {2} prevents poisoning!",pbThis,abilityName) + when PBStatuses::BURN; msg = _INTL("{1}'s {2} prevents burns!",pbThis,abilityName) + when PBStatuses::PARALYSIS; msg = _INTL("{1}'s {2} prevents paralysis!",pbThis,abilityName) + when PBStatuses::FROZEN; msg = _INTL("{1}'s {2} prevents freezing!",pbThis,abilityName) + end + end + @battle.pbDisplay(msg) + @battle.pbHideAbilitySplash(immAlly || self) + end + return false + end + # Safeguard immunity + if pbOwnSide.effects[PBEffects::Safeguard]>0 && !selfInflicted && move && + !(user && user.hasActiveAbility?(:INFILTRATOR)) + @battle.pbDisplay(_INTL("{1}'s team is protected by Safeguard!",pbThis)) if showMessages + return false + end + return true + end + + def pbCanSynchronizeStatus?(status,target) + return false if fainted? + # Trying to replace a status problem with another one + return false if self.status!=PBStatuses::NONE + # Terrain immunity + return false if @battle.field.terrain==PBBattleTerrains::Misty && affectedByTerrain? + # Type immunities + hasImmuneType = false + case self.status + when PBStatuses::POISON + # NOTE: target will have Synchronize, so it can't have Corrosion. + if !(target && target.hasActiveAbility?(:CORROSION)) + hasImmuneType |= pbHasType?(:POISON) + hasImmuneType |= pbHasType?(:STEEL) + end + when PBStatuses::BURN + hasImmuneType |= pbHasType?(:FIRE) + when PBStatuses::PARALYSIS + hasImmuneType |= pbHasType?(:ELECTRIC) && NEWEST_BATTLE_MECHANICS + end + return false if hasImmuneType + # Ability immunity + if BattleHandlers.triggerStatusImmunityAbilityNonIgnorable(@ability,self,status) + return false + end + if abilityActive? && BattleHandlers.triggerStatusImmunityAbility(@ability,self,status) + return false + end + eachAlly do |b| + next if !b.abilityActive? + next if !BattleHandlers.triggerStatusImmunityAllyAbility(b.ability,self,status) + return false + end + # Safeguard immunity + if pbOwnSide.effects[PBEffects::Safeguard]>0 && + !(user && user.hasActiveAbility?(:INFILTRATOR)) + return false + end + return true + end + + #============================================================================= + # Generalised infliction of status problem + #============================================================================= + def pbInflictStatus(newStatus,newStatusCount=0,msg=nil,user=nil) + # Inflict the new status + self.status = newStatus + self.statusCount = newStatusCount + @effects[PBEffects::Toxic] = 0 + # Record status change in debug log, generate default message, show animation + case newStatus + when PBStatuses::SLEEP + @battle.pbCommonAnimation("Sleep",self) + msg = _INTL("{1} fell asleep!",pbThis) if !msg || msg=="" + when PBStatuses::POISON + if newStatusCount>0 + @battle.pbCommonAnimation("Toxic",self) + msg = _INTL("{1} was badly poisoned!",pbThis) if !msg || msg=="" + else + @battle.pbCommonAnimation("Poison",self) + msg = _INTL("{1} was poisoned!",pbThis) if !msg || msg=="" + end + when PBStatuses::BURN + @battle.pbCommonAnimation("Burn",self) + msg = _INTL("{1} was burned!",pbThis) if !msg || msg=="" + when PBStatuses::PARALYSIS + @battle.pbCommonAnimation("Paralysis",self) + msg = _INTL("{1} is paralyzed! It may be unable to move!",pbThis) if !msg || msg=="" + when PBStatuses::FROZEN + @battle.pbCommonAnimation("Frozen",self) + msg = _INTL("{1} was frozen solid!",pbThis) if !msg || msg=="" + end + # Show message + @battle.pbDisplay(msg) if msg && msg!="" + PBDebug.log("[Status change] #{pbThis}'s sleep count is #{newStatusCount}") if newStatus==PBStatuses::SLEEP + pbCheckFormOnStatusChange + # Synchronize + if abilityActive? + BattleHandlers.triggerAbilityOnStatusInflicted(@ability,self,user,newStatus) + end + # Status cures + pbItemStatusCureCheck + pbAbilityStatusCureCheck + # Petal Dance/Outrage/Thrash get cancelled immediately by falling asleep + # NOTE: I don't know why this applies only to Outrage and only to falling + # asleep (i.e. it doesn't cancel Rollout/Uproar/other multi-turn + # moves, and it doesn't cancel any moves if self becomes frozen/ + # disabled/anything else). This behaviour was tested in Gen 5. + if @status==PBStatuses::SLEEP && @effects[PBEffects::Outrage]>0 + @effects[PBEffects::Outrage] = 0 + @currentMove = 0 + end + end + + #============================================================================= + # Sleep + #============================================================================= + def asleep? + return pbHasStatus?(PBStatuses::SLEEP) + end + + def pbCanSleep?(user,showMessages,move=nil,ignoreStatus=false) + return pbCanInflictStatus?(PBStatuses::SLEEP,user,showMessages,move,ignoreStatus) + end + + def pbCanSleepYawn? + return false if self.status!=PBStatuses::NONE + if affectedByTerrain? + return false if @battle.field.terrain==PBBattleTerrains::Electric + return false if @battle.field.terrain==PBBattleTerrains::Misty + end + if !hasActiveAbility?(:SOUNDPROOF) + @battle.eachBattler do |b| + return false if b.effects[PBEffects::Uproar]>0 + end + end + if BattleHandlers.triggerStatusImmunityAbilityNonIgnorable(@ability,self,PBStatuses::SLEEP) + return false + end + # NOTE: Bulbapedia claims that Flower Veil shouldn't prevent sleep due to + # drowsiness, but I disagree because that makes no sense. Also, the + # comparable Sweet Veil does prevent sleep due to drowsiness. + if abilityActive? && BattleHandlers.triggerStatusImmunityAbility(@ability,self,PBStatuses::SLEEP) + return false + end + eachAlly do |b| + next if !b.abilityActive? + next if !BattleHandlers.triggerStatusImmunityAllyAbility(b.ability,self,PBStatuses::SLEEP) + return false + end + # NOTE: Bulbapedia claims that Safeguard shouldn't prevent sleep due to + # drowsiness. I disagree with this too. Compare with the other sided + # effects Misty/Electric Terrain, which do prevent it. + return false if pbOwnSide.effects[PBEffects::Safeguard]>0 + return true + end + + def pbSleep(msg=nil) + pbInflictStatus(PBStatuses::SLEEP,pbSleepDuration,msg) + end + + def pbSleepSelf(msg=nil,duration=-1) + pbInflictStatus(PBStatuses::SLEEP,pbSleepDuration(duration),msg) + end + + def pbSleepDuration(duration=-1) + duration = 2+@battle.pbRandom(3) if duration<=0 + duration = (duration/2).floor if hasActiveAbility?(:EARLYBIRD) + return duration + end + + #============================================================================= + # Poison + #============================================================================= + def poisoned? + return pbHasStatus?(PBStatuses::POISON) + end + + def pbCanPoison?(user,showMessages,move=nil) + return pbCanInflictStatus?(PBStatuses::POISON,user,showMessages,move) + end + + def pbCanPoisonSynchronize?(target) + return pbCanSynchronizeStatus?(PBStatuses::POISON,target) + end + + def pbPoison(user=nil,msg=nil,toxic=false) + pbInflictStatus(PBStatuses::POISON,(toxic) ? 1 : 0,msg,user) + end + + #============================================================================= + # Burn + #============================================================================= + def burned? + return pbHasStatus?(PBStatuses::BURN) + end + + def pbCanBurn?(user,showMessages,move=nil) + return pbCanInflictStatus?(PBStatuses::BURN,user,showMessages,move) + end + + def pbCanBurnSynchronize?(target) + return pbCanSynchronizeStatus?(PBStatuses::BURN,target) + end + + def pbBurn(user=nil,msg=nil) + pbInflictStatus(PBStatuses::BURN,0,msg,user) + end + + #============================================================================= + # Paralyze + #============================================================================= + def paralyzed? + return pbHasStatus?(PBStatuses::PARALYSIS) + end + + def pbCanParalyze?(user,showMessages,move=nil) + return pbCanInflictStatus?(PBStatuses::PARALYSIS,user,showMessages,move) + end + + def pbCanParalyzeSynchronize?(target) + return pbCanSynchronizeStatus?(PBStatuses::PARALYSIS,target) + end + + def pbParalyze(user=nil,msg=nil) + pbInflictStatus(PBStatuses::PARALYSIS,0,msg,user) + end + + #============================================================================= + # Freeze + #============================================================================= + def frozen? + return pbHasStatus?(PBStatuses::FROZEN) + end + + def pbCanFreeze?(user,showMessages,move=nil) + return pbCanInflictStatus?(PBStatuses::FROZEN,user,showMessages,move) + end + + def pbFreeze(msg=nil) + pbInflictStatus(PBStatuses::FROZEN,0,msg) + end + + #============================================================================= + # Generalised status displays + #============================================================================= + def pbContinueStatus + anim = ""; msg = "" + case self.status + when PBStatuses::SLEEP + anim = "Sleep"; msg = _INTL("{1} is fast asleep.",pbThis) + when PBStatuses::POISON + anim = (@statusCount>0) ? "Toxic" : "Poison" + msg = _INTL("{1} was hurt by poison!",pbThis) + when PBStatuses::BURN + anim = "Burn"; msg = _INTL("{1} was hurt by its burn!",pbThis) + when PBStatuses::PARALYSIS + anim = "Paralysis"; msg = _INTL("{1} is paralyzed! It can't move!",pbThis) + when PBStatuses::FROZEN + anim = "Frozen"; msg = _INTL("{1} is frozen solid!",pbThis) + end + @battle.pbCommonAnimation(anim,self) if anim!="" + yield if block_given? + @battle.pbDisplay(msg) if msg!="" + PBDebug.log("[Status continues] #{pbThis}'s sleep count is #{@statusCount}") if self.status==PBStatuses::SLEEP + end + + def pbCureStatus(showMessages=true) + oldStatus = status + self.status = PBStatuses::NONE + if showMessages + case oldStatus + when PBStatuses::SLEEP; @battle.pbDisplay(_INTL("{1} woke up!",pbThis)) + when PBStatuses::POISON; @battle.pbDisplay(_INTL("{1} was cured of its poisoning.",pbThis)) + when PBStatuses::BURN; @battle.pbDisplay(_INTL("{1}'s burn was healed.",pbThis)) + when PBStatuses::PARALYSIS; @battle.pbDisplay(_INTL("{1} was cured of paralysis.",pbThis)) + when PBStatuses::FROZEN; @battle.pbDisplay(_INTL("{1} thawed out!",pbThis)) + end + end + PBDebug.log("[Status change] #{pbThis}'s status was cured") if !showMessages + end + + #============================================================================= + # Confusion + #============================================================================= + def pbCanConfuse?(user=nil,showMessages=true,move=nil,selfInflicted=false) + return false if fainted? + if @effects[PBEffects::Confusion]>0 + @battle.pbDisplay(_INTL("{1} is already confused.",pbThis)) if showMessages + return false + end + if @effects[PBEffects::Substitute]>0 && !(move && move.ignoresSubstitute?(user)) && + !selfInflicted + @battle.pbDisplay(_INTL("But it failed!")) if showMessages + return false + end + # Terrains immunity + if affectedByTerrain? && @battle.field.terrain==PBBattleTerrains::Misty + @battle.pbDisplay(_INTL("{1} surrounds itself with misty terrain!",pbThis(true))) if showMessages + return false + end + if selfInflicted || !@battle.moldBreaker + if hasActiveAbility?(:OWNTEMPO) + if showMessages + @battle.pbShowAbilitySplash(self) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} doesn't become confused!",pbThis)) + else + @battle.pbDisplay(_INTL("{1}'s {2} prevents confusion!",pbThis,abilityName)) + end + @battle.pbHideAbilitySplash(self) + end + return false + end + end + if pbOwnSide.effects[PBEffects::Safeguard]>0 && !selfInflicted && + !(user && user.hasActiveAbility?(:INFILTRATOR)) + @battle.pbDisplay(_INTL("{1}'s team is protected by Safeguard!",pbThis)) if showMessages + return false + end + return true + end + + def pbCanConfuseSelf?(showMessages) + return pbCanConfuse?(nil,showMessages,nil,true) + end + + def pbConfuse(msg=nil) + @effects[PBEffects::Confusion] = pbConfusionDuration + @battle.pbCommonAnimation("Confusion",self) + msg = _INTL("{1} became confused!",pbThis) if !msg || msg=="" + @battle.pbDisplay(msg) + PBDebug.log("[Lingering effect] #{pbThis}'s confusion count is #{@effects[PBEffects::Confusion]}") + # Confusion cures + pbItemStatusCureCheck + pbAbilityStatusCureCheck + end + + def pbConfusionDuration(duration=-1) + duration = 2+@battle.pbRandom(4) if duration<=0 + return duration + end + + def pbCureConfusion + @effects[PBEffects::Confusion] = 0 + end + + #============================================================================= + # Attraction + #============================================================================= + def pbCanAttract?(user,showMessages=true) + return false if fainted? + return false if !user || user.fainted? + if @effects[PBEffects::Attract]>=0 + @battle.pbDisplay(_INTL("{1} is unaffected!",pbThis)) if showMessages + return false + end + agender = user.gender + ogender = gender + if agender==2 || ogender==2 || agender==ogender + @battle.pbDisplay(_INTL("{1} is unaffected!",pbThis)) if showMessages + return false + end + if !@battle.moldBreaker + if hasActiveAbility?([:AROMAVEIL,:OBLIVIOUS]) + if showMessages + @battle.pbShowAbilitySplash(self) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} is unaffected!",pbThis)) + else + @battle.pbDisplay(_INTL("{1}'s {2} prevents romance!",pbThis,abilityName)) + end + @battle.pbHideAbilitySplash(self) + end + return false + else + eachAlly do |b| + next if !b.hasActiveAbility?(:AROMAVEIL) + if showMessages + @battle.pbShowAbilitySplash(self) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} is unaffected!",pbThis)) + else + @battle.pbDisplay(_INTL("{1}'s {2} prevents romance!",b.pbThis,b.abilityName)) + end + @battle.pbHideAbilitySplash(self) + end + return true + end + end + end + return true + end + + def pbAttract(user,msg=nil) + @effects[PBEffects::Attract] = user.index + @battle.pbCommonAnimation("Attract",self) + msg = _INTL("{1} fell in love!",pbThis) if !msg || msg=="" + @battle.pbDisplay(msg) + # Destiny Knot + if hasActiveItem?(:DESTINYKNOT) && user.pbCanAttract?(self,false) + user.pbAttract(self,_INTL("{1} fell in love from the {2}!",user.pbThis(true),itemName)) + end + # Attraction cures + pbItemStatusCureCheck + pbAbilityStatusCureCheck + end + + def pbCureAttract + @effects[PBEffects::Attract] = -1 + end + + #============================================================================= + # Flinching + #============================================================================= + def pbFlinch(user=nil) + return if hasActiveAbility?(:INNERFOCUS) && !@battle.moldBreaker + @effects[PBEffects::Flinch] = true + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/001_Battler/005_Battler_StatStages.rb b/Data/Scripts/011_Battle/001_Battler/005_Battler_StatStages.rb new file mode 100644 index 000000000..761c7497a --- /dev/null +++ b/Data/Scripts/011_Battle/001_Battler/005_Battler_StatStages.rb @@ -0,0 +1,310 @@ +class PokeBattle_Battler + #============================================================================= + # Increase stat stages + #============================================================================= + def statStageAtMax?(stat) + return @stages[stat]>=6 + end + + def pbCanRaiseStatStage?(stat,user=nil,move=nil,showFailMsg=false,ignoreContrary=false) + return false if fainted? + # Contrary + if hasActiveAbility?(:CONTRARY) && !ignoreContrary && !@battle.moldBreaker + return pbCanLowerStatStage?(stat,user,move,showFailMsg,true) + end + # Check the stat stage + if statStageAtMax?(stat) + @battle.pbDisplay(_INTL("{1}'s {2} won't go any higher!", + pbThis,PBStats.getName(stat))) if showFailMsg + return false + end + return true + end + + def pbRaiseStatStageBasic(stat,increment,ignoreContrary=false) + if !@battle.moldBreaker + # Contrary + if hasActiveAbility?(:CONTRARY) && !ignoreContrary + return pbLowerStatStageBasic(stat,increment,true) + end + # Simple + increment *= 2 if hasActiveAbility?(:SIMPLE) + end + # Change the stat stage + increment = [increment,6-@stages[stat]].min + if increment>0 + s = PBStats.getName(stat); new = @stages[stat]+increment + PBDebug.log("[Stat change] #{pbThis}'s #{s}: #{@stages[stat]} -> #{new} (+#{increment})") + @stages[stat] += increment + end + return increment + end + + def pbRaiseStatStage(stat,increment,user,showAnim=true,ignoreContrary=false) + return false if !PBStats.validBattleStat?(stat) + # Contrary + if hasActiveAbility?(:CONTRARY) && !ignoreContrary && !@battle.moldBreaker + return pbLowerStatStage(stat,increment,user,showAnim,true) + end + # Perform the stat stage change + increment = pbRaiseStatStageBasic(stat,increment,ignoreContrary) + return false if increment<=0 + # Stat up animation and message + @battle.pbCommonAnimation("StatUp",self) if showAnim + arrStatTexts = [ + _INTL("{1}'s {2} rose!",pbThis,PBStats.getName(stat)), + _INTL("{1}'s {2} rose sharply!",pbThis,PBStats.getName(stat)), + _INTL("{1}'s {2} rose drastically!",pbThis,PBStats.getName(stat))] + @battle.pbDisplay(arrStatTexts[[increment-1,2].min]) + # Trigger abilities upon stat gain + if abilityActive? + BattleHandlers.triggerAbilityOnStatGain(@ability,self,stat,user) + end + return true + end + + def pbRaiseStatStageByCause(stat,increment,user,cause,showAnim=true,ignoreContrary=false) + return false if !PBStats.validBattleStat?(stat) + # Contrary + if hasActiveAbility?(:CONTRARY) && !ignoreContrary && !@battle.moldBreaker + return pbLowerStatStageByCause(stat,increment,user,cause,showAnim,true) + end + # Perform the stat stage change + increment = pbRaiseStatStageBasic(stat,increment,ignoreContrary) + return false if increment<=0 + # Stat up animation and message + @battle.pbCommonAnimation("StatUp",self) if showAnim + if user.index==@index + arrStatTexts = [ + _INTL("{1}'s {2} raised its {3}!",pbThis,cause,PBStats.getName(stat)), + _INTL("{1}'s {2} sharply raised its {3}!",pbThis,cause,PBStats.getName(stat)), + _INTL("{1}'s {2} drastically raised its {3}!",pbThis,cause,PBStats.getName(stat))] + else + arrStatTexts = [ + _INTL("{1}'s {2} raised {3}'s {4}!",user.pbThis,cause,pbThis(true),PBStats.getName(stat)), + _INTL("{1}'s {2} sharply raised {3}'s {4}!",user.pbThis,cause,pbThis(true),PBStats.getName(stat)), + _INTL("{1}'s {2} drastically raised {3}'s {4}!",user.pbThis,cause,pbThis(true),PBStats.getName(stat))] + end + @battle.pbDisplay(arrStatTexts[[increment-1,2].min]) + # Trigger abilities upon stat gain + if abilityActive? + BattleHandlers.triggerAbilityOnStatGain(@ability,self,stat,user) + end + return true + end + + def pbRaiseStatStageByAbility(stat,increment,user,splashAnim=true) + return false if fainted? + ret = false + @battle.pbShowAbilitySplash(user) if splashAnim + if pbCanRaiseStatStage?(stat,user,nil,PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + ret = pbRaiseStatStage(stat,increment,user) + else + ret = pbRaiseStatStageByCause(stat,increment,user,user.abilityName) + end + end + @battle.pbHideAbilitySplash(user) if splashAnim + return ret + end + + #============================================================================= + # Decrease stat stages + #============================================================================= + def statStageAtMin?(stat) + return @stages[stat]<=-6 + end + + def pbCanLowerStatStage?(stat,user=nil,move=nil,showFailMsg=false,ignoreContrary=false) + return false if fainted? + # Contrary + if hasActiveAbility?(:CONTRARY) && !ignoreContrary && !@battle.moldBreaker + return pbCanRaiseStatStage?(stat,user,move,showFailMsg,true) + end + if !user || user.index!=@index # Not self-inflicted + if @effects[PBEffects::Substitute]>0 && !(move && move.ignoresSubstitute?(user)) + @battle.pbDisplay(_INTL("{1} is protected by its substitute!",pbThis)) if showFailMsg + return false + end + if pbOwnSide.effects[PBEffects::Mist]>0 && + !(user && user.hasActiveAbility?(:INFILTRATOR)) + @battle.pbDisplay(_INTL("{1} is protected by Mist!",pbThis)) if showFailMsg + return false + end + if abilityActive? + return false if BattleHandlers.triggerStatLossImmunityAbility( + @ability,self,stat,@battle,showFailMsg) if !@battle.moldBreaker + return false if BattleHandlers.triggerStatLossImmunityAbilityNonIgnorable( + @ability,self,stat,@battle,showFailMsg) + end + if !@battle.moldBreaker + eachAlly do |b| + next if !b.abilityActive? + return false if BattleHandlers.triggerStatLossImmunityAllyAbility( + b.ability,b,self,stat,@battle,showFailMsg) + end + end + end + # Check the stat stage + if statStageAtMin?(stat) + @battle.pbDisplay(_INTL("{1}'s {2} won't go any lower!", + pbThis,PBStats.getName(stat))) if showFailMsg + return false + end + return true + end + + def pbLowerStatStageBasic(stat,increment,ignoreContrary=false) + if !@battle.moldBreaker + # Contrary + if hasActiveAbility?(:CONTRARY) && !ignoreContrary + return pbRaiseStatStageBasic(stat,increment,true) + end + # Simple + increment *= 2 if hasActiveAbility?(:SIMPLE) + end + # Change the stat stage + increment = [increment,6+@stages[stat]].min + if increment>0 + s = PBStats.getName(stat); new = @stages[stat]-increment + PBDebug.log("[Stat change] #{pbThis}'s #{s}: #{@stages[stat]} -> #{new} (-#{increment})") + @stages[stat] -= increment + end + return increment + end + + def pbLowerStatStage(stat,increment,user,showAnim=true,ignoreContrary=false) + return false if !PBStats.validBattleStat?(stat) + # Contrary + if hasActiveAbility?(:CONTRARY) && !ignoreContrary && !@battle.moldBreaker + return pbRaiseStatStage(stat,increment,user,showAnim,true) + end + # Perform the stat stage change + increment = pbLowerStatStageBasic(stat,increment,ignoreContrary) + return false if increment<=0 + # Stat down animation and message + @battle.pbCommonAnimation("StatDown",self) if showAnim + arrStatTexts = [ + _INTL("{1}'s {2} fell!",pbThis,PBStats.getName(stat)), + _INTL("{1}'s {2} harshly fell!",pbThis,PBStats.getName(stat)), + _INTL("{1}'s {2} severely fell!",pbThis,PBStats.getName(stat))] + @battle.pbDisplay(arrStatTexts[[increment-1,2].min]) + # Trigger abilities upon stat loss + if abilityActive? + BattleHandlers.triggerAbilityOnStatLoss(@ability,self,stat,user) + end + return true + end + + def pbLowerStatStageByCause(stat,increment,user,cause,showAnim=true,ignoreContrary=false) + return false if !PBStats.validBattleStat?(stat) + # Contrary + if hasActiveAbility?(:CONTRARY) && !ignoreContrary && !@battle.moldBreaker + return pbRaiseStatStageByCause(stat,increment,user,cause,showAnim,true) + end + # Perform the stat stage change + increment = pbLowerStatStageBasic(stat,increment,ignoreContrary) + return false if increment<=0 + # Stat down animation and message + @battle.pbCommonAnimation("StatDown",self) if showAnim + if user.index==@index + arrStatTexts = [ + _INTL("{1}'s {2} lowered its {3}!",pbThis,cause,PBStats.getName(stat)), + _INTL("{1}'s {2} harshly lowered its {3}!",pbThis,cause,PBStats.getName(stat)), + _INTL("{1}'s {2} severely lowered its {3}!",pbThis,cause,PBStats.getName(stat))] + else + arrStatTexts = [ + _INTL("{1}'s {2} lowered {3}'s {4}!",user.pbThis,cause,pbThis(true),PBStats.getName(stat)), + _INTL("{1}'s {2} harshly lowered {3}'s {4}!",user.pbThis,cause,pbThis(true),PBStats.getName(stat)), + _INTL("{1}'s {2} severely lowered {3}'s {4}!",user.pbThis,cause,pbThis(true),PBStats.getName(stat))] + end + @battle.pbDisplay(arrStatTexts[[increment-1,2].min]) + # Trigger abilities upon stat loss + if abilityActive? + BattleHandlers.triggerAbilityOnStatLoss(@ability,self,stat,user) + end + return true + end + + def pbLowerStatStageByAbility(stat,increment,user,splashAnim=true,checkContact=false) + ret = false + @battle.pbShowAbilitySplash(user) if splashAnim + if pbCanLowerStatStage?(stat,user,nil,PokeBattle_SceneConstants::USE_ABILITY_SPLASH) && + (!checkContact || affectedByContactEffect?(PokeBattle_SceneConstants::USE_ABILITY_SPLASH)) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + ret = pbLowerStatStage(stat,increment,user) + else + ret = pbLowerStatStageByCause(stat,increment,user,user.abilityName) + end + end + @battle.pbHideAbilitySplash(user) if splashAnim + return ret + end + + def pbLowerAttackStatStageIntimidate(user) + return false if fainted? + # NOTE: Substitute intentially blocks Intimidate even if self has Contrary. + if @effects[PBEffects::Substitute]>0 + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} is protected by its substitute!",pbThis)) + else + @battle.pbDisplay(_INTL("{1}'s substitute protected it from {2}'s {3}!", + pbThis,user.pbThis(true),user.abilityName)) + end + return false + end + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + return pbLowerStatStageByAbility(PBStats::ATTACK,1,user,false) + end + # NOTE: These checks exist to ensure appropriate messages are shown if + # Intimidate is blocked somehow (i.e. the messages should mention the + # Intimidate ability by name). + if !hasActiveAbility?(:CONTRARY) + if pbOwnSide.effects[PBEffects::Mist]>0 + @battle.pbDisplay(_INTL("{1} is protected from {2}'s {3} by Mist!", + pbThis,user.pbThis(true),user.abilityName)) + return false + end + if abilityActive? + if BattleHandlers.triggerStatLossImmunityAbility(@ability,self,PBStats::ATTACK,@battle,false) || + BattleHandlers.triggerStatLossImmunityAbilityNonIgnorable(@ability,self,PBStats::ATTACK,@battle,false) + @battle.pbDisplay(_INTL("{1}'s {2} prevented {3}'s {4} from working!", + pbThis,abilityName,user.pbThis(true),user.abilityName)) + return false + end + end + eachAlly do |b| + next if !b.abilityActive? + if BattleHandlers.triggerStatLossImmunityAllyAbility(b.ability,b,self,PBStats::ATTACK,@battle,false) + @battle.pbDisplay(_INTL("{1} is protected from {2}'s {3} by {4}'s {5}!", + pbThis,user.pbThis(true),user.abilityName,b.pbThis(true),b.abilityName)) + return false + end + end + end + return false if !pbCanLowerStatStage?(PBStats::ATTACK,user) + return pbLowerStatStageByCause(PBStats::ATTACK,1,user,user.abilityName) + end + + #============================================================================= + # Reset stat stages + #============================================================================= + def hasAlteredStatStages? + PBStats.eachBattleStat { |s| return true if @stages[s]!=0 } + return false + end + + def hasRaisedStatStages? + PBStats.eachBattleStat { |s| return true if @stages[s]>0 } + return false + end + + def hasLoweredStatStages? + PBStats.eachBattleStat { |s| return true if @stages[s]<0 } + return false + end + + def pbResetStatStages + PBStats.eachBattleStat { |s| @stages[s] = 0 } + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/001_Battler/006_Battler_AbilityAndItem.rb b/Data/Scripts/011_Battle/001_Battler/006_Battler_AbilityAndItem.rb new file mode 100644 index 000000000..ed7a0403d --- /dev/null +++ b/Data/Scripts/011_Battle/001_Battler/006_Battler_AbilityAndItem.rb @@ -0,0 +1,322 @@ +class PokeBattle_Battler + #============================================================================= + # Called when a Pokémon (self) is sent into battle or its ability changes. + #============================================================================= + def pbEffectsOnSwitchIn(switchIn=false) + # Healing Wish/Lunar Dance/entry hazards + @battle.pbOnActiveOne(self) if switchIn + # Primal Revert upon entering battle + @battle.pbPrimalReversion(@index) if !fainted? + # Ending primordial weather, checking Trace + pbContinualAbilityChecks(true) + # Abilities that trigger upon switching in + if (!fainted? && nonNegatableAbility?) || abilityActive? + BattleHandlers.triggerAbilityOnSwitchIn(@ability,self,@battle) + end + # Check for end of primordial weather + @battle.pbEndPrimordialWeather + # Items that trigger upon switching in (Air Balloon message) + if switchIn && itemActive? + BattleHandlers.triggerItemOnSwitchIn(@item,self,@battle) + end + # Berry check, status-curing ability check + pbHeldItemTriggerCheck if switchIn + pbAbilityStatusCureCheck + end + + #============================================================================= + # Ability effects + #============================================================================= + def pbAbilitiesOnSwitchOut + if abilityActive? + BattleHandlers.triggerAbilityOnSwitchOut(@ability,self,false) + end + # Reset form + @battle.peer.pbOnLeavingBattle(@battle,@pokemon,@battle.usedInBattle[idxOwnSide][@index/2]) + # Treat self as fainted + @hp = 0 + @fainted = true + # Check for end of primordial weather + @battle.pbEndPrimordialWeather + end + + def pbAbilitiesOnFainting + # Self fainted; check all other battlers to see if their abilities trigger + @battle.pbPriority(true).each do |b| + next if !b || !b.abilityActive? + BattleHandlers.triggerAbilityChangeOnBattlerFainting(b.ability,b,self,@battle) + end + @battle.pbPriority(true).each do |b| + next if !b || !b.abilityActive? + BattleHandlers.triggerAbilityOnBattlerFainting(b.ability,b,self,@battle) + end + end + + # Used for Emergency Exit/Wimp Out. + def pbAbilitiesOnDamageTaken(oldHP,newHP=-1) + return false if !abilityActive? + newHP = @hp if newHP<0 + return false if oldHP<@totalhp/2 || newHP>=@totalhp/2 # Didn't drop below half + ret = BattleHandlers.triggerAbilityOnHPDroppedBelowHalf(@ability,self,@battle) + return ret # Whether self has switched out + end + + # Called when a Pokémon (self) enters battle, at the end of each move used, + # and at the end of each round. + def pbContinualAbilityChecks(onSwitchIn=false) + # Check for end of primordial weather + @battle.pbEndPrimordialWeather + # Trace + if hasActiveAbility?(:TRACE) + # NOTE: In Gen 5 only, Trace only triggers upon the Trace bearer switching + # in and not at any later times, even if a traceable ability turns + # up later. Essentials ignores this, and allows Trace to trigger + # whenever it can even in the old battle mechanics. + abilityBlacklist = [ + # Replaces self with another ability + :POWEROFALCHEMY, + :RECEIVER, + :TRACE, + # Form-changing abilities + :BATTLEBOND, + :DISGUISE, + :FLOWERGIFT, + :FORECAST, + :MULTITYPE, + :POWERCONSTRUCT, + :SCHOOLING, + :SHIELDSDOWN, + :STANCECHANGE, + :ZENMODE, + # Appearance-changing abilities + :ILLUSION, + :IMPOSTER, + # Abilities intended to be inherent properties of a certain species + :COMATOSE, + :RKSSYSTEM + ] + choices = [] + @battle.eachOtherSideBattler(@index) do |b| + abilityBlacklist.each do |abil| + next if !isConst?(b.ability,PBAbilities,abil) + choices.push(b) + break + end + end + if choices.length>0 + choice = choices[@battle.pbRandom(choices.length)] + @battle.pbShowAbilitySplash(self) + @ability = choice.ability + @battle.pbDisplay(_INTL("{1} traced {2}'s {3}!",pbThis,choice.pbThis(true),choice.abilityName)) + @battle.pbHideAbilitySplash(self) + if !onSwitchIn && (nonNegatableAbility? || abilityActive?) + BattleHandlers.triggerAbilityOnSwitchIn(@ability,self,@battle) + end + end + end + end + + #============================================================================= + # Ability curing + #============================================================================= + # Cures status conditions, confusion and infatuation. + def pbAbilityStatusCureCheck + if abilityActive? + BattleHandlers.triggerStatusCureAbility(@ability,self) + end + end + + #============================================================================= + # Ability change + #============================================================================= + def pbOnAbilityChanged(oldAbil) + if @effects[PBEffects::Illusion] && isConst?(oldAbil,PBAbilities,:ILLUSION) + @effects[PBEffects::Illusion] = nil + if !@effects[PBEffects::Transform] + @battle.scene.pbChangePokemon(self,@pokemon) + @battle.pbDisplay(_INTL("{1}'s {2} wore off!",pbThis,PBAbilities.getName(oldAbil))) + @battle.pbSetSeen(self) + end + end + @effects[PBEffects::GastroAcid] = false if nonNegatableAbility? + @effects[PBEffects::SlowStart] = 0 if !isConst?(@ability,PBAbilities,:SLOWSTART) + # Revert form if Flower Gift/Forecast was lost + pbCheckFormOnWeatherChange + # Check for end of primordial weather + @battle.pbEndPrimordialWeather + end + + #============================================================================= + # Held item consuming/removing + #============================================================================= + def pbCanConsumeBerry?(item,alwaysCheckGluttony=true) + return false if @battle.pbCheckOpposingAbility(:UNNERVE,@index) + return true if @hp<=@totalhp/4 + if alwaysCheckGluttony || NEWEST_BATTLE_MECHANICS + return true if @hp<=@totalhp/2 && hasActiveAbility?(:GLUTTONY) + end + return false + end + + # permanent is whether the item is lost even after battle. Is false for Knock + # Off. + def pbRemoveItem(permanent=true) + @effects[PBEffects::ChoiceBand] = -1 + @effects[PBEffects::Unburden] = true if @item>0 + setInitialItem(0) if self.initialItem==@item && permanent + self.item = 0 + end + + def pbConsumeItem(recoverable=true,symbiosis=true,belch=true) + PBDebug.log("[Item consumed] #{pbThis} consumed its held #{PBItems.getName(@item)}") + if recoverable + setRecycleItem(@item) + @effects[PBEffects::PickupItem] = @item + @effects[PBEffects::PickupUse] = @battle.nextPickupUse + end + setBelched if belch && pbIsBerry?(@item) + pbRemoveItem + pbSymbiosis if symbiosis + end + + def pbSymbiosis + return if fainted? + return if @item!=0 + @battle.pbPriority(true).each do |b| + next if b.opposes? + next if !b.hasActiveAbility?(:SYMBIOSIS) + next if b.item==0 || b.unlosableItem?(b.item) + next if unlosableItem?(b.item) + @battle.pbShowAbilitySplash(b) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} shared its {2} with {3}!", + b.pbThis,b.itemName,pbThis(true))) + else + @battle.pbDisplay(_INTL("{1}'s {2} let it share its {3} with {4}!", + b.pbThis,b.abilityName,b.itemName,pbThis(true))) + end + self.item = b.item + b.item = 0 + b.effects[PBEffects::Unburden] = true + @battle.pbHideAbilitySplash(b) + pbHeldItemTriggerCheck + break + end + end + + def pbHeldItemTriggered(thisItem,forcedItem=0,fling=false) + # Cheek Pouch + if hasActiveAbility?(:CHEEKPOUCH) && pbIsBerry?(thisItem) && canHeal? + @battle.pbShowAbilitySplash(self) + pbRecoverHP(@totalhp/3) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1}'s HP was restored.",pbThis)) + else + @battle.pbDisplay(_INTL("{1}'s {2} restored its HP.",pbThis,abilityName)) + end + @battle.pbHideAbilitySplash(self) + end + pbConsumeItem if forcedItem<=0 + pbSymbiosis if forcedItem>0 && !fling # Bug Bite/Pluck users trigger Symbiosis + end + + #============================================================================= + # Held item trigger checks + #============================================================================= + # NOTE: A Pokémon using Bug Bite/Pluck, and a Pokémon having an item thrown at + # it via Fling, will gain the effect of the item even if the Pokémon is + # affected by item-negating effects. + # If forcedItem is -1, the Pokémon's held item is forced to be consumed. If it + # is greater than 0, a different item (of that ID) is forced to be consumed + # (not the Pokémon's held one). + def pbHeldItemTriggerCheck(forcedItem=0,fling=false) + return if fainted? + return if forcedItem==0 && !itemActive? + pbItemHPHealCheck(forcedItem,fling) + pbItemStatusCureCheck(forcedItem,fling) + pbItemEndOfMoveCheck(forcedItem,fling) + # For Enigma Berry, Kee Berry and Maranga Berry, which have their effects + # when forcibly consumed by Pluck/Fling. + if forcedItem!=0 + thisItem = (forcedItem>0) ? forcedItem : @item + if BattleHandlers.triggerTargetItemOnHitPositiveBerry(thisItem,self,@battle,true) + pbHeldItemTriggered(thisItem,forcedItem,fling) + end + end + end + + # forcedItem is an item ID for Pluck/Fling, and 0 otherwise. fling is for + # Fling only. + def pbItemHPHealCheck(forcedItem=0,fling=false) + return if !canHeal? + return if forcedItem==0 && !itemActive? + thisItem = (forcedItem>0) ? forcedItem : @item + if BattleHandlers.triggerHPHealItem(thisItem,self,@battle,(forcedItem!=0)) + pbHeldItemTriggered(thisItem,forcedItem,fling) + elsif forcedItem==0 + pbItemTerrainStatBoostCheck + end + end + + # Cures status conditions, confusion, infatuation and the other effects cured + # by Mental Herb. + # forcedItem is an item ID for Pluck/Fling, and 0 otherwise. fling is for + # Fling only. + def pbItemStatusCureCheck(forcedItem=0,fling=false) + return if fainted? + return if forcedItem==0 && !itemActive? + thisItem = (forcedItem>0) ? forcedItem : @item + if BattleHandlers.triggerStatusCureItem(thisItem,self,@battle,(forcedItem!=0)) + pbHeldItemTriggered(thisItem,forcedItem,fling) + end + end + + # Called at the end of using a move. + # forcedItem is an item ID for Pluck/Fling, and 0 otherwise. fling is for + # Fling only. + def pbItemEndOfMoveCheck(forcedItem=0,fling=false) + return if fainted? + return if forcedItem==0 && !itemActive? + thisItem = (forcedItem>0) ? forcedItem : @item + if BattleHandlers.triggerEndOfMoveItem(thisItem,self,@battle,(forcedItem!=0)) + pbHeldItemTriggered(thisItem,forcedItem,fling) + elsif BattleHandlers.triggerEndOfMoveStatRestoreItem(thisItem,self,@battle,(forcedItem!=0)) + pbHeldItemTriggered(thisItem,forcedItem,fling) + end + end + + # Used for White Herb (restore lowered stats). Only called by Moody and Sticky + # Web, as all other stat reduction happens because of/during move usage and + # this handler is also called at the end of each move's usage. + # forcedItem is an item ID for Pluck/Fling, and 0 otherwise. fling is for + # Fling only. + def pbItemStatRestoreCheck(forcedItem=0,fling=false) + return if fainted? + return if forcedItem==0 && !itemActive? + thisItem = (forcedItem>0) ? forcedItem : @item + if BattleHandlers.triggerEndOfMoveStatRestoreItem(thisItem,self,@battle,(forcedItem!=0)) + pbHeldItemTriggered(thisItem,forcedItem,fling) + end + end + + # Called when the battle terrain changes and when a Pokémon loses HP. + # forcedItem is an item ID for Pluck/Fling, and 0 otherwise. fling is for + # Fling only. + def pbItemTerrainStatBoostCheck + return if !itemActive? + if BattleHandlers.triggerTerrainStatBoostItem(@item,self,@battle) + pbHeldItemTriggered(@item) + end + end + + # Used for Adrenaline Orb. Called when Intimidate is triggered (even if + # Intimidate has no effect on the Pokémon). + # forcedItem is an item ID for Pluck/Fling, and 0 otherwise. fling is for + # Fling only. + def pbItemOnIntimidatedCheck + return if !itemActive? + if BattleHandlers.triggerItemOnIntimidated(@item,self,@battle) + pbHeldItemTriggered(@item) + end + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/001_Battler/007_Battler_UseMove.rb b/Data/Scripts/011_Battle/001_Battler/007_Battler_UseMove.rb new file mode 100644 index 000000000..aa32fd603 --- /dev/null +++ b/Data/Scripts/011_Battle/001_Battler/007_Battler_UseMove.rb @@ -0,0 +1,729 @@ +class PokeBattle_Battler + #============================================================================= + # Turn processing + #============================================================================= + def pbProcessTurn(choice,tryFlee=true) + return false if fainted? + # Wild roaming Pokémon always flee if possible + if tryFlee && @battle.wildBattle? && opposes? && + @battle.rules["alwaysflee"] && @battle.pbCanRun?(@index) + pbBeginTurn(choice) + @battle.pbDisplay(_INTL("{1} fled from battle!",pbThis)) { pbSEPlay("Battle flee") } + @battle.decision = 3 + pbEndTurn(choice) + return true + end + # Shift with the battler next to this one + if choice[0]==:Shift + idxOther = -1 + case @battle.pbSideSize(@index) + when 2 + idxOther = (@index+2)%4 + when 3 + if @index!=2 && @index!=3 # If not in middle spot already + idxOther = ((@index%2)==0) ? 2 : 3 + end + end + if idxOther>=0 + @battle.pbSwapBattlers(@index,idxOther) + case @battle.pbSideSize(@index) + when 2 + @battle.pbDisplay(_INTL("{1} moved across!",pbThis)) + when 3 + @battle.pbDisplay(_INTL("{1} moved to the center!",pbThis)) + end + end + pbBeginTurn(choice) + pbCancelMoves + @lastRoundMoved = @battle.turnCount # Done something this round + return true + end + # If this battler's action for this round wasn't "use a move" + if choice[0]!=:UseMove + # Clean up effects that end at battler's turn + pbBeginTurn(choice) + pbEndTurn(choice) + return false + end + # Turn is skipped if Pursuit was used during switch + if @effects[PBEffects::Pursuit] + @effects[PBEffects::Pursuit] = false + pbCancelMoves + pbEndTurn(choice) + @battle.pbJudge + return false + end + # Use the move + PBDebug.log("[Move usage] #{pbThis} started using #{choice[2].name}") + PBDebug.logonerr{ + pbUseMove(choice,choice[2]==@battle.struggle) + } + @battle.pbJudge + # Update priority order +# @battle.pbCalculatePriority if NEWEST_BATTLE_MECHANICS + return true + end + + #============================================================================= + # + #============================================================================= + def pbBeginTurn(choice) + # Cancel some lingering effects which only apply until the user next moves + @effects[PBEffects::BeakBlast] = false + @effects[PBEffects::DestinyBondPrevious] = @effects[PBEffects::DestinyBond] + @effects[PBEffects::DestinyBond] = false + @effects[PBEffects::Grudge] = false + @effects[PBEffects::MoveNext] = false + @effects[PBEffects::Quash] = 0 + @effects[PBEffects::ShellTrap] = false + # Encore's effect ends if the encored move is no longer available + if @effects[PBEffects::Encore]>0 && pbEncoredMoveIndex<0 + @effects[PBEffects::Encore] = 0 + @effects[PBEffects::EncoreMove] = 0 + end + end + + # Called when the usage of various multi-turn moves is disrupted due to + # failing pbTryUseMove, being ineffective against all targets, or because + # Pursuit was used specially to intercept a switching foe. + # Cancels the use of multi-turn moves and counters thereof. Note that Hyper + # Beam's effect is NOT cancelled. + def pbCancelMoves + # Outragers get confused anyway if they are disrupted during their final + # turn of using the move + if @effects[PBEffects::Outrage]==1 && pbCanConfuseSelf?(false) + pbConfuse(_INTL("{1} became confused due to fatigue!",pbThis)) + end + # Cancel usage of most multi-turn moves + @effects[PBEffects::TwoTurnAttack] = 0 + @effects[PBEffects::Rollout] = 0 + @effects[PBEffects::Outrage] = 0 + @effects[PBEffects::Uproar] = 0 + @effects[PBEffects::Bide] = 0 + @currentMove = 0 + # Reset counters for moves which increase them when used in succession + @effects[PBEffects::FuryCutter] = 0 + end + + def pbEndTurn(choice) + @lastRoundMoved = @battle.turnCount # Done something this round + if @effects[PBEffects::ChoiceBand]<0 && + hasActiveItem?([:CHOICEBAND,:CHOICESPECS,:CHOICESCARF]) + if @lastMoveUsed>=0 && pbHasMove?(@lastMoveUsed) + @effects[PBEffects::ChoiceBand] = @lastMoveUsed + elsif @lastRegularMoveUsed>=0 && pbHasMove?(@lastRegularMoveUsed) + @effects[PBEffects::ChoiceBand] = @lastRegularMoveUsed + end + end + @effects[PBEffects::Charge] = 0 if @effects[PBEffects::Charge]==1 + @effects[PBEffects::GemConsumed] = 0 + @battle.eachBattler { |b| b.pbContinualAbilityChecks } # Trace, end primordial weathers + end + + def pbConfusionDamage(msg) + @damageState.reset + @damageState.initialHP = @hp + confusionMove = PokeBattle_Confusion.new(@battle,nil) + confusionMove.calcType = confusionMove.pbCalcType(self) # -1 + @damageState.typeMod = confusionMove.pbCalcTypeMod(confusionMove.calcType,self,self) # 8 + confusionMove.pbCheckDamageAbsorption(self,self) + confusionMove.pbCalcDamage(self,self) + confusionMove.pbReduceDamage(self,self) + self.hp -= @damageState.hpLost + confusionMove.pbAnimateHitAndHPLost(self,[self]) + @battle.pbDisplay(msg) # "It hurt itself in its confusion!" + confusionMove.pbRecordDamageLost(self,self) + confusionMove.pbEndureKOMessage(self) + pbFaint if fainted? + pbItemHPHealCheck + end + + #============================================================================= + # Simple "use move" method, used when a move calls another move and for Future + # Sight's attack + #============================================================================= + def pbUseMoveSimple(moveID,target=-1,idxMove=-1,specialUsage=true) + choice = [] + choice[0] = :UseMove # "Use move" + choice[1] = idxMove # Index of move to be used in user's moveset + if idxMove>=0 + choice[2] = @moves[idxMove] + else + choice[2] = PokeBattle_Move.pbFromPBMove(@battle,PBMove.new(moveID)) # PokeBattle_Move object + choice[2].pp = -1 + end + choice[3] = target # Target (-1 means no target yet) + PBDebug.log("[Move usage] #{pbThis} started using the called/simple move #{choice[2].name}") + pbUseMove(choice,specialUsage) + end + + #============================================================================= + # Master "use move" method + #============================================================================= + def pbUseMove(choice,specialUsage=false) + # NOTE: This is intentionally determined before a multi-turn attack can + # set specialUsage to true. + skipAccuracyCheck = (specialUsage && choice[2]!=@battle.struggle) + # Start using the move + pbBeginTurn(choice) + # Force the use of certain moves if they're already being used + if usingMultiTurnAttack? + choice[2] = PokeBattle_Move.pbFromPBMove(@battle,PBMove.new(@currentMove)) + specialUsage = true + elsif @effects[PBEffects::Encore]>0 && choice[1]>=0 && + @battle.pbCanShowCommands?(@index) + idxEncoredMove = pbEncoredMoveIndex + if idxEncoredMove>=0 && @battle.pbCanChooseMove?(@index,idxEncoredMove,false) + if choice[1]!=idxEncoredMove # Change move if battler was Encored mid-round + choice[1] = idxEncoredMove + choice[2] = @moves[idxEncoredMove] + choice[3] = -1 # No target chosen + end + end + end + # Labels the move being used as "move" + move = choice[2] + return if !move || move.id==0 # if move was not chosen somehow + # Try to use the move (inc. disobedience) + @lastMoveFailed = false + if !pbTryUseMove(choice,move,specialUsage,skipAccuracyCheck) + @lastMoveUsed = -1 + @lastMoveUsedType = -1 + if !specialUsage + @lastRegularMoveUsed = -1 + @lastRegularMoveTarget = -1 + end + @battle.pbGainExp # In case self is KO'd due to confusion + pbCancelMoves + pbEndTurn(choice) + return + end + move = choice[2] # In case disobedience changed the move to be used + return if !move || move.id==0 # if move was not chosen somehow + # Subtract PP + if !specialUsage + if !pbReducePP(move) + @battle.pbDisplay(_INTL("{1} used {2}!",pbThis,move.name)) + @battle.pbDisplay(_INTL("But there was no PP left for the move!")) + @lastMoveUsed = -1 + @lastMoveUsedType = -1 + @lastRegularMoveUsed = -1 + @lastRegularMoveTarget = -1 + @lastMoveFailed = true + pbCancelMoves + pbEndTurn(choice) + return + end + end + # Stance Change + if isConst?(@species,PBSpecies,:AEGISLASH) && isConst?(@ability,PBAbilities,:STANCECHANGE) + if move.damagingMove? + pbChangeForm(1,_INTL("{1} changed to Blade Forme!",pbThis)) + elsif isConst?(move.id,PBMoves,:KINGSSHIELD) + pbChangeForm(0,_INTL("{1} changed to Shield Forme!",pbThis)) + end + end + # Calculate the move's type during this usage + move.calcType = move.pbCalcType(self) + # Start effect of Mold Breaker + @battle.moldBreaker = hasMoldBreaker? + # Remember that user chose a two-turn move + if move.pbIsChargingTurn?(self) + # Beginning the use of a two-turn attack + @effects[PBEffects::TwoTurnAttack] = move.id + @currentMove = move.id + else + @effects[PBEffects::TwoTurnAttack] = 0 # Cancel use of two-turn attack + end + # Add to counters for moves which increase them when used in succession + move.pbChangeUsageCounters(self,specialUsage) + # Charge up Metronome item + if hasActiveItem?(:METRONOME) && !move.callsAnotherMove? + if @lastMoveUsed==move.id && !@lastMoveFailed + @effects[PBEffects::Metronome] += 1 + else + @effects[PBEffects::Metronome] = 0 + end + end + # Record move as having been used + @lastMoveUsed = move.id + @lastMoveUsedType = move.calcType # For Conversion 2 + if !specialUsage + @lastRegularMoveUsed = move.id # For Disable, Encore, Instruct, Mimic, Mirror Move, Sketch, Spite + @lastRegularMoveTarget = choice[3] # For Instruct (remembering original target is fine) + @movesUsed.push(move.id) if !@movesUsed.include?(move.id) # For Last Resort + end + @battle.lastMoveUsed = move.id # For Copycat + @battle.lastMoveUser = @index # For "self KO" battle clause to avoid draws + @battle.successStates[@index].useState = 1 # Battle Arena - assume failure + # Find the default user (self or Snatcher) and target(s) + user = pbFindUser(choice,move) + user = pbChangeUser(choice,move,user) + targets = pbFindTargets(choice,move,user) + targets = pbChangeTargets(move,user,targets) + # Pressure + if !specialUsage + targets.each do |b| + next unless b.opposes?(user) && b.hasActiveAbility?(:PRESSURE) + PBDebug.log("[Ability triggered] #{b.pbThis}'s #{b.abilityName}") + user.pbReducePP(move) + end + if PBTargets.targetsFoeSide?(move.pbTarget(user)) + @battle.eachOtherSideBattler(user) do |b| + next unless b.hasActiveAbility?(:PRESSURE) + PBDebug.log("[Ability triggered] #{b.pbThis}'s #{b.abilityName}") + user.pbReducePP(move) + end + end + end + # Dazzling/Queenly Majesty make the move fail here + @battle.pbPriority(true).each do |b| + next if !b || !b.abilityActive? + if BattleHandlers.triggerMoveBlockingAbility(b.ability,b,user,targets,move,@battle) + @battle.pbDisplayBrief(_INTL("{1} used {2}!",user.pbThis,move.name)) + @battle.pbShowAbilitySplash(b) + @battle.pbDisplay(_INTL("{1} cannot use {2}!",user.pbThis,move.name)) + @battle.pbHideAbilitySplash(b) + user.lastMoveFailed = true + pbCancelMoves + pbEndTurn(choice) + return + end + end + # "X used Y!" message + # Can be different for Bide, Fling, Focus Punch and Future Sight + # NOTE: This intentionally passes self rather than user. The user is always + # self except if Snatched, but this message should state the original + # user (self) even if the move is Snatched. + move.pbDisplayUseMessage(self) + # Snatch's message (user is the new user, self is the original user) + if move.snatched + @lastMoveFailed = true # Intentionally applies to self, not user + @battle.pbDisplay(_INTL("{1} snatched {2}'s move!",user.pbThis,pbThis(true))) + end + # "But it failed!" checks + if move.pbMoveFailed?(user,targets) + PBDebug.log(sprintf("[Move failed] In function code %s's def pbMoveFailed?",move.function)) + user.lastMoveFailed = true + pbCancelMoves + pbEndTurn(choice) + return + end + # Perform set-up actions and display messages + # Messages include Magnitude's number and Pledge moves' "it's a combo!" + move.pbOnStartUse(user,targets) + # Self-thawing due to the move + if user.status==PBStatuses::FROZEN && move.thawsUser? + user.pbCureStatus(false) + @battle.pbDisplay(_INTL("{1} melted the ice!",user.pbThis)) + end + # Powder + if user.effects[PBEffects::Powder] && isConst?(move.calcType,PBTypes,:FIRE) + @battle.pbCommonAnimation("Powder",user) + @battle.pbDisplay(_INTL("When the flame touched the powder on the Pokémon, it exploded!")) + user.lastMoveFailed = true + w = @battle.pbWeather + if w!=PBWeather.RAINDANCE && w!=PBWeather.HEAVYRAIN && user.takesIndirectDamage? + oldHP = user.hp + user.pbReduceHP((user.totalhp/4.0).round,false) + user.pbFaint if user.fainted? + @battle.pbGainExp # In case user is KO'd by this + user.pbItemHPHealCheck + if user.pbAbilitiesOnDamageTaken(oldHP) + user.pbEffectsOnSwitchIn(true) + end + end + pbCancelMoves + pbEndTurn(choice) + return + end + # Primordial Sea, Desolate Land + if move.damagingMove? + case @battle.pbWeather + when PBWeather::HeavyRain + if isConst?(move.calcType,PBTypes,:FIRE) + @battle.pbDisplay(_INTL("The Fire-type attack fizzled out in the heavy rain!")) + user.lastMoveFailed = true + pbCancelMoves + pbEndTurn(choice) + return + end + when PBWeather::HarshSun + if isConst?(move.calcType,PBTypes,:WATER) + @battle.pbDisplay(_INTL("The Water-type attack evaporated in the harsh sunlight!")) + user.lastMoveFailed = true + pbCancelMoves + pbEndTurn(choice) + return + end + end + end + # Protean + if user.hasActiveAbility?(:PROTEAN) && !move.callsAnotherMove? && !move.snatched + if user.pbHasOtherType?(moveType) && !PBTypes.isPseudoType?(move.calcType) + @battle.pbShowAbilitySplash(user) + user.pbChangeTypes(move.calcType) + typeName = PBTypes.getName(move.calcType) + @battle.pbDisplay(_INTL("{1} transformed into the {2} type!",user.pbThis,typeName)) + @battle.pbHideAbilitySplash(user) + # NOTE: The GF games say that if Curse is used by a non-Ghost-type + # Pokémon which becomes Ghost-type because of Protean, it should + # target and curse itself. I think this is silly, so I'm making it + # choose a random opponent to curse instead. + if move.function=="10D" && targets.length==0 # Curse + choice[3] = -1 + targets = pbFindTargets(choice,move,user) + end + end + end + #--------------------------------------------------------------------------- + magicCoater = -1 + magicBouncer = -1 + if targets.length==0 && !PBTargets.noTargets?(move.pbTarget(user)) && + !move.worksWithNoTargets? + # def pbFindTargets should have found a target(s), but it didn't because + # they were all fainted + # All target types except: None, User, UserSide, FoeSide, BothSides + @battle.pbDisplay(_INTL("But there was no target...")) + user.lastMoveFailed = true + else # We have targets, or move doesn't use targets + # Reset whole damage state, perform various success checks (not accuracy) + user.initialHP = user.hp + targets.each do |b| + b.damageState.reset + b.damageState.initialHP = b.hp + if !pbSuccessCheckAgainstTarget(move,user,b) + b.damageState.unaffected = true + end + end + # Magic Coat/Magic Bounce checks (for moves which don't target Pokémon) + if targets.length==0 && move.canMagicCoat? + @battle.pbPriority(true).each do |b| + next if b.fainted? || !b.opposes?(user) + next if b.semiInvulnerable? + if b.effects[PBEffects::MagicCoat] + magicCoater = b.index + b.effects[PBEffects::MagicCoat] = false + break + elsif b.hasActiveAbility?(:MAGICBOUNCE) && !@battle.moldBreaker && + !b.effects[PBEffects::MagicBounce] + magicBouncer = b.index + b.effects[PBEffects::MagicBounce] = true + break + end + end + end + # Get the number of hits + numHits = move.pbNumHits(user,targets) + # Process each hit in turn + realNumHits = 0 + for i in 0...numHits + break if magicCoater>=0 || magicBouncer>=0 + success = pbProcessMoveHit(move,user,targets,i,skipAccuracyCheck) + if !success + if i==0 && targets.length>0 + hasFailed = false + targets.each do |t| + next if t.damageState.protected + hasFailed = t.damageState.unaffected + break if !t.damageState.unaffected + end + user.lastMoveFailed = hasFailed + end + break + end + realNumHits += 1 + break if user.fainted? + break if user.status==PBStatuses::SLEEP || user.status==PBStatuses::FROZEN + # NOTE: If a multi-hit move becomes disabled partway through doing those + # hits (e.g. by Cursed Body), the rest of the hits continue as + # normal. + notFainted = false + break if !targets.any? { |t| !t.fainted? } # All targets are fainted + end + # Battle Arena only - attack is successful + @battle.successStates[user.index].useState = 2 + if targets.length>0 + @battle.successStates[user.index].typeMod = 0 + targets.each do |b| + next if b.damageState.unaffected + @battle.successStates[user.index].typeMod += b.damageState.typeMod + end + end + # Effectiveness message for multi-hit moves + # NOTE: No move is both multi-hit and multi-target, and the messages below + # aren't quite right for such a hypothetical move. + if numHits>1 + if move.damagingMove? + targets.each do |b| + next if b.damageState.unaffected || b.damageState.substitute + move.pbEffectivenessMessage(user,b,targets.length) + end + end + if realNumHits==1 + @battle.pbDisplay(_INTL("Hit 1 time!")) + elsif realNumHits>1 + @battle.pbDisplay(_INTL("Hit {1} times!",realNumHits)) + end + end + # Magic Coat's bouncing back (move has targets) + targets.each do |b| + next if b.fainted? + next if !b.damageState.magicCoat && !b.damageState.magicBounce + @battle.pbShowAbilitySplash(b) if b.damageState.magicBounce + @battle.pbDisplay(_INTL("{1} bounced the {2} back!",b.pbThis,move.name)) + @battle.pbHideAbilitySplash(b) if b.damageState.magicBounce + newChoice = choice.clone + newChoice[3] = user.index + newTargets = pbFindTargets(newChoice,move,b) + newTargets = pbChangeTargets(move,b,newTargets) + success = pbProcessMoveHit(move,b,newTargets,0,false) + b.lastMoveFailed = true if !success + targets.each { |b| b.pbFaint if b && b.fainted? } + user.pbFaint if user.fainted? + end + # Magic Coat's bouncing back (move has no targets) + if magicCoater>=0 || magicBouncer>=0 + mc = @battle.battlers[(magicCoater>=0) ? magicCoater : magicBouncer] + if !mc.fainted? + user.lastMoveFailed = true + @battle.pbShowAbilitySplash(mc) if magicBouncer>=0 + @battle.pbDisplay(_INTL("{1} bounced the {2} back!",mc.pbThis,move.name)) + @battle.pbHideAbilitySplash(mc) if magicBouncer>=0 + success = pbProcessMoveHit(move,mc,[],0,false) + mc.lastMoveFailed = true if !success + targets.each { |b| b.pbFaint if b && b.fainted? } + user.pbFaint if user.fainted? + end + end + # Move-specific effects after all hits + targets.each { |b| move.pbEffectAfterAllHits(user,b) } + # Faint if 0 HP + targets.each { |b| b.pbFaint if b && b.fainted? } + user.pbFaint if user.fainted? + # External/general effects after all hits. Eject Button, Shell Bell, etc. + pbEffectsAfterMove(user,targets,move,realNumHits) + end + # End effect of Mold Breaker + @battle.moldBreaker = false + # Gain Exp + @battle.pbGainExp + # Battle Arena only - update skills + @battle.eachBattler { |b| @battle.successStates[b.index].updateSkill } + # Shadow Pokémon triggering Hyper Mode + pbHyperMode if @battle.choices[@index][0]!=:None # Not if self is replaced + # End of move usage + pbEndTurn(choice) + # Instruct + @battle.eachBattler do |b| + next if !b.effects[PBEffects::Instruct] + b.effects[PBEffects::Instruct] = false + idxMove = -1 + b.eachMoveWithIndex { |m,i| idxMove = i if m.id==b.lastMoveUsed } + next if idxMove<0 + oldLastRoundMoved = b.lastRoundMoved + @battle.pbDisplay(_INTL("{1} used the move instructed by {2}!",b.pbThis,user.pbThis(true))) + PBDebug.logonerr{ + b.effects[PBEffects::Instructed] = true + b.pbUseMoveSimple(b.lastMoveUsed,b.lastRegularMoveTarget,idxMove,false) + b.effects[PBEffects::Instructed] = false + } + b.lastRoundMoved = oldLastRoundMoved + @battle.pbJudge + return if @battle.decision>0 + end + # Dancer + if !@effects[PBEffects::Dancer] && !user.lastMoveFailed && realNumHits>0 && + !move.snatched && magicCoater<0 && @battle.pbCheckGlobalAbility(:DANCER) + dancers = [] + @battle.pbPriority(true).each do |b| + dancers.push(b) if b.index!=user.index && b.hasActiveAbility?(:DANCER) + end + while dancers.length>0 + nextUser = dancers.pop + oldLastRoundMoved = nextUser.lastRoundMoved + # NOTE: Petal Dance being used because of Dancer shouldn't lock the + # Dancer into using that move, and shouldn't contribute to its + # turn counter if it's already locked into Petal Dance. + oldOutrage = nextUser.effects[PBEffects::Outrage] + nextUser.effects[PBEffects::Outrage] += 1 if nextUser.effects[PBEffects::Outrage]>0 + oldCurrentMove = nextUser.currentMove + preTarget = choice[3] + preTarget = user.index if nextUser.opposes?(user) || !nextUser.opposes?(preTarget) + @battle.pbShowAbilitySplash(nextUser,true) + @battle.pbHideAbilitySplash(nextUser) + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} kept the dance going with {2}!", + nextUser.pbThis,nextUser.abilityName)) + end + PBDebug.logonerr{ + nextUser.effects[PBEffects::Dancer] = true + nextUser.pbUseMoveSimple(move.id,preTarget) + nextUser.effects[PBEffects::Dancer] = false + } + nextUser.lastRoundMoved = oldLastRoundMoved + nextUser.effects[PBEffects::Outrage] = oldOutrage + nextUser.currentMove = oldCurrentMove + @battle.pbJudge + return if @battle.decision>0 + end + end + end + + #============================================================================= + # Attack a single target + #============================================================================= + def pbProcessMoveHit(move,user,targets,hitNum,skipAccuracyCheck) + return false if user.fainted? + # For two-turn attacks being used in a single turn + move.pbInitialEffect(user,targets,hitNum) + numTargets = 0 # Number of targets that are affected by this hit + targets.each { |b| b.damageState.resetPerHit } + # Count a hit for Parental Bond (if it applies) + user.effects[PBEffects::ParentalBond] -= 1 if user.effects[PBEffects::ParentalBond]>0 + # Accuracy check (accuracy/evasion calc) + if hitNum==0 || move.successCheckPerHit? + targets.each do |b| + next if b.damageState.unaffected + if pbSuccessCheckPerHit(move,user,b,skipAccuracyCheck) + numTargets += 1 + else + b.damageState.missed = true + b.damageState.unaffected = true + end + end + # If failed against all targets + if targets.length>0 && numTargets==0 && !move.worksWithNoTargets? + targets.each do |b| + next if !b.damageState.missed || b.damageState.magicCoat + pbMissMessage(move,user,b) + end + move.pbCrashDamage(user) + user.pbItemHPHealCheck + pbCancelMoves + return false + end + end + # If we get here, this hit will happen and do something + #--------------------------------------------------------------------------- + # Calculate damage to deal + if move.pbDamagingMove? + targets.each do |b| + next if b.damageState.unaffected + # Check whether Substitute/Disguise will absorb the damage + move.pbCheckDamageAbsorption(user,b) + # Calculate the damage against b + # pbCalcDamage shows the "eat berry" animation for SE-weakening + # berries, although the message about it comes after the additional + # effect below + move.pbCalcDamage(user,b,targets.length) # Stored in damageState.calcDamage + # Lessen damage dealt because of False Swipe/Endure/etc. + move.pbReduceDamage(user,b) # Stored in damageState.hpLost + end + end + # Show move animation (for this hit) + move.pbShowAnimation(move.id,user,targets,hitNum) + # Type-boosting Gem consume animation/message + if user.effects[PBEffects::GemConsumed]>0 && hitNum==0 + # NOTE: The consume animation and message for Gems are shown now, but the + # actual removal of the item happens in def pbEffectsAfterMove. + @battle.pbCommonAnimation("UseItem",user) + @battle.pbDisplay(_INTL("The {1} strengthened {2}'s power!", + PBItems.getName(user.effects[PBEffects::GemConsumed]),move.name)) + end + # Messages about missed target(s) (relevant for multi-target moves only) + targets.each do |b| + next if !b.damageState.missed + pbMissMessage(move,user,b) + end + # Deal the damage (to all allies first simultaneously, then all foes + # simultaneously) + if move.pbDamagingMove? + # This just changes the HP amounts and does nothing else + targets.each do |b| + next if b.damageState.unaffected + move.pbInflictHPDamage(b) + end + # Animate the hit flashing and HP bar changes + move.pbAnimateHitAndHPLost(user,targets) + end + # Self-Destruct/Explosion's damaging and fainting of user + move.pbSelfKO(user) if hitNum==0 + user.pbFaint if user.fainted? + if move.pbDamagingMove? + targets.each do |b| + next if b.damageState.unaffected + # NOTE: This method is also used for the OKHO special message. + move.pbHitEffectivenessMessages(user,b,targets.length) + # Record data about the hit for various effects' purposes + move.pbRecordDamageLost(user,b) + end + # Close Combat/Superpower's stat-lowering, Flame Burst's splash damage, + # and Incinerate's berry destruction + targets.each do |b| + next if b.damageState.unaffected + move.pbEffectWhenDealingDamage(user,b) + end + # Ability/item effects such as Static/Rocky Helmet, and Grudge, etc. + targets.each do |b| + next if b.damageState.unaffected + pbEffectsOnMakingHit(move,user,b) + end + # Disguise/Endure/Sturdy/Focus Sash/Focus Band messages + targets.each do |b| + next if b.damageState.unaffected + move.pbEndureKOMessage(b) + end + # HP-healing held items (checks all battlers rather than just targets + # because Flame Burst's splash damage affects non-targets) + @battle.pbPriority(true).each { |b| b.pbItemHPHealCheck } + # Animate battlers fainting (checks all battlers rather than just targets + # because Flame Burst's splash damage affects non-targets) + @battle.pbPriority(true).each { |b| b.pbFaint if b && b.fainted? } + end + @battle.pbJudgeCheckpoint(user,move) + # Main effect (recoil/drain, etc.) + targets.each do |b| + next if b.damageState.unaffected + move.pbEffectAgainstTarget(user,b) + end + move.pbEffectGeneral(user) + targets.each { |b| b.pbFaint if b && b.fainted? } + user.pbFaint if user.fainted? + # Additional effect + if !user.hasActiveAbility?(:SHEERFORCE) + targets.each do |b| + next if b.damageState.calcDamage==0 + chance = move.pbAdditionalEffectChance(user,b) + next if chance<=0 + if @battle.pbRandom(100)=strength + next if b.effects[PBEffects::SkyDrop]>=0 + newUser = b + strength = b.effects[PBEffects::Snatch] + end + if newUser + user = newUser + user.effects[PBEffects::Snatch] = 0 + move.snatched = true + @battle.moldBreaker = user.hasMoldBreaker? + choice[3] = -1 # Clear pre-chosen target + end + end + return user + end + + #============================================================================= + # Get move's default target(s) + #============================================================================= + def pbFindTargets(choice,move,user) + preTarget = choice[3] # A target that was already chosen + targets = [] + # Get list of targets + case move.pbTarget(user) # Curse can change its target type + when PBTargets::NearAlly + targetBattler = (preTarget>=0) ? @battle.battlers[preTarget] : nil + if !pbAddTarget(targets,user,targetBattler,move) + pbAddTargetRandomAlly(targets,user,move) + end + when PBTargets::UserOrNearAlly + targetBattler = (preTarget>=0) ? @battle.battlers[preTarget] : nil + if !pbAddTarget(targets,user,targetBattler,move,true,true) + pbAddTarget(targets,user,user,move,true,true) + end + when PBTargets::NearFoe, PBTargets::NearOther + targetBattler = (preTarget>=0) ? @battle.battlers[preTarget] : nil + if !pbAddTarget(targets,user,targetBattler,move) + if preTarget>=0 && !user.opposes?(preTarget) + pbAddTargetRandomAlly(targets,user,move) + else + pbAddTargetRandomFoe(targets,user,move) + end + end + when PBTargets::AllNearFoes + @battle.eachOtherSideBattler(user.index) { |b| pbAddTarget(targets,user,b,move) } + when PBTargets::RandomNearFoe + pbAddTargetRandomFoe(targets,user,move) + when PBTargets::AllNearOthers + @battle.eachBattler { |b| pbAddTarget(targets,user,b,move) } + when PBTargets::Other + targetBattler = (preTarget>=0) ? @battle.battlers[preTarget] : nil + if !pbAddTarget(targets,user,targetBattler,move,false) + if preTarget>=0 && !user.opposes?(preTarget) + pbAddTargetRandomAlly(targets,user,move,false) + else + pbAddTargetRandomFoe(targets,user,move,false) + end + end + when PBTargets::UserAndAllies + pbAddTarget(targets,user,user,move,true,true) + @battle.eachSameSideBattler(user.index) { |b| pbAddTarget(targets,user,b,move,false,true) } + when PBTargets::AllFoes + @battle.eachOtherSideBattler(user.index) { |b| pbAddTarget(targets,user,b,move,false) } + when PBTargets::AllBattlers + @battle.eachBattler { |b| pbAddTarget(targets,user,b,move,false,true) } + else + # Used by Counter/Mirror Coat/Metal Burst/Bide + move.pbAddTarget(targets,user) # Move-specific pbAddTarget, not the def below + end + return targets + end + + #============================================================================= + # Redirect attack to another target + #============================================================================= + def pbChangeTargets(move,user,targets) + targetType = move.pbTarget(user) + return targets if @battle.switching # For Pursuit interrupting a switch + return targets if move.cannotRedirect? + return targets if !PBTargets.canChooseOneFoeTarget?(targetType) || targets.length!=1 + priority = @battle.pbPriority(true) + nearOnly = !PBTargets.canChooseDistantTarget?(move.target) + # Spotlight (takes priority over Follow Me/Rage Powder/Lightning Rod/Storm Drain) + newTarget = nil; strength = 100 # Lower strength takes priority + priority.each do |b| + next if b.fainted? || b.effects[PBEffects::SkyDrop]>=0 + next if b.effects[PBEffects::Spotlight]==0 || + b.effects[PBEffects::Spotlight]>=strength + next if !b.opposes?(user) + next if nearOnly && !b.near?(user) + newTarget = b + strength = b.effects[PBEffects::Spotlight] + end + if newTarget + PBDebug.log("[Move target changed] #{newTarget.pbThis}'s Spotlight made it the target") + targets = [] + pbAddTarget(targets,user,newTarget,move,nearOnly) + return targets + end + # Follow Me/Rage Powder (takes priority over Lightning Rod/Storm Drain) + newTarget = nil; strength = 100 # Lower strength takes priority + priority.each do |b| + next if b.fainted? || b.effects[PBEffects::SkyDrop]>=0 + next if b.effects[PBEffects::RagePowder] && !user.affectedByPowder? + next if b.effects[PBEffects::FollowMe]==0 || + b.effects[PBEffects::FollowMe]>=strength + next if !b.opposes?(user) + next if nearOnly && !b.near?(user) + newTarget = b + strength = b.effects[PBEffects::FollowMe] + end + if newTarget + PBDebug.log("[Move target changed] #{newTarget.pbThis}'s Follow Me/Rage Powder made it the target") + targets = [] + pbAddTarget(targets,user,newTarget,move,nearOnly) + return targets + end + # Lightning Rod + targets = pbChangeTargetByAbility(:LIGHTNINGROD,:ELECTRIC,move,user,targets,priority,nearOnly) + # Storm Drain + targets = pbChangeTargetByAbility(:STORMDRAIN,:WATER,move,user,targets,priority,nearOnly) + return targets + end + + def pbChangeTargetByAbility(drawingAbility,drawnType,move,user,targets,priority,nearOnly) + return targets if !isConst?(move.calcType,PBTypes,drawnType) + return targets if targets[0].hasActiveAbility?(drawingAbility) + priority.each do |b| + next if b.index==user.index || b.index==targets[0].index + next if !b.hasActiveAbility?(drawingAbility) + next if nearOnly && !b.near?(user) + @battle.pbShowAbilitySplash(b) + targets.clear + pbAddTarget(targets,user,b,move,nearOnly) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} took the attack!",b.pbThis)) + else + @battle.pbDisplay(_INTL("{1} took the attack with its {2}!",b.pbThis,b.abilityName)) + end + @battle.pbHideAbilitySplash(b) + break + end + return targets + end + + #============================================================================= + # Register target + #============================================================================= + def pbAddTarget(targets,user,target,move,nearOnly=true,allowUser=false) + return false if !target || (target.fainted? && !move.cannotRedirect?) + return false if !(allowUser && user==target) && nearOnly && !user.near?(target) + targets.each { |b| return true if b.index==target.index } # Already added + targets.push(target) + return true + end + + def pbAddTargetRandomAlly(targets,user,move,nearOnly=true) + choices = [] + user.eachAlly do |b| + next if nearOnly && !user.near?(b) + pbAddTarget(choices,user,b,nearOnly) + end + if choices.length>0 + pbAddTarget(targets,user,choices[@battle.pbRandom(choices.length)],nearOnly) + end + end + + def pbAddTargetRandomFoe(targets,user,move,nearOnly=true) + choices = [] + user.eachOpposing do |b| + next if nearOnly && !user.near?(b) + pbAddTarget(choices,user,b,nearOnly) + end + if choices.length>0 + pbAddTarget(targets,user,choices[@battle.pbRandom(choices.length)],nearOnly) + end + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/001_Battler/009_Battler_UseMove_SuccessChecks.rb b/Data/Scripts/011_Battle/001_Battler/009_Battler_UseMove_SuccessChecks.rb new file mode 100644 index 000000000..f842a364a --- /dev/null +++ b/Data/Scripts/011_Battle/001_Battler/009_Battler_UseMove_SuccessChecks.rb @@ -0,0 +1,538 @@ +class PokeBattle_Battler + #============================================================================= + # Decide whether the trainer is allowed to tell the Pokémon to use the given + # move. Called when choosing a command for the round. + # Also called when processing the Pokémon's action, because these effects also + # prevent Pokémon action. Relevant because these effects can become active + # earlier in the same round (after choosing the command but before using the + # move) or an unusable move may be called by another move such as Metronome. + #============================================================================= + def pbCanChooseMove?(move,commandPhase,showMessages=true,specialUsage=false) + # Disable + if @effects[PBEffects::DisableMove]==move.id && !specialUsage + if showMessages + msg = _INTL("{1}'s {2} is disabled!",pbThis,move.name) + (commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg) + end + return false + end + # Heal Block + if @effects[PBEffects::HealBlock]>0 && move.healingMove? + if showMessages + msg = _INTL("{1} can't use {2} because of Heal Block!",pbThis,move.name) + (commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg) + end + return false + end + # Gravity + if @battle.field.effects[PBEffects::Gravity]>0 && move.unusableInGravity? + if showMessages + msg = _INTL("{1} can't use {2} because of gravity!",pbThis,move.name) + (commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg) + end + return false + end + # Throat Chop + if @effects[PBEffects::ThroatChop]>0 && move.soundMove? + if showMessages + msg = _INTL("{1} can't use {2} because of Throat Chop!",pbThis,move.name) + (commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg) + end + return false + end + # Choice Band + if @effects[PBEffects::ChoiceBand]>=0 + if hasActiveItem?([:CHOICEBAND,:CHOICESPECS,:CHOICESCARF]) && + pbHasMove?(@effects[PBEffects::ChoiceBand]) + if move.id!=@effects[PBEffects::ChoiceBand] + if showMessages + msg = _INTL("{1} allows the use of only {2}!",itemName, + PBMoves.getName(@effects[PBEffects::ChoiceBand])) + (commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg) + end + return false + end + else + @effects[PBEffects::ChoiceBand] = -1 + end + end + # Taunt + if @effects[PBEffects::Taunt]>0 && move.statusMove? + if showMessages + msg = _INTL("{1} can't use {2} after the taunt!",pbThis,move.name) + (commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg) + end + return false + end + # Torment + if @effects[PBEffects::Torment] && !@effects[PBEffects::Instructed] && + move.id==@lastMoveUsed && move.id!=@battle.struggle.id + if showMessages + msg = _INTL("{1} can't use the same move twice in a row due to the torment!",pbThis) + (commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg) + end + return false + end + # Imprison + @battle.eachOtherSideBattler(@index) do |b| + next if !b.effects[PBEffects::Imprison] || !b.pbHasMove?(move.id) + if showMessages + msg = _INTL("{1} can't use its sealed {2}!",pbThis,move.name) + (commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg) + end + return false + end + # Assault Vest (prevents choosing status moves but doesn't prevent + # executing them) + if hasActiveItem?(:ASSAULTVEST) && move.statusMove? && commandPhase + if showMessages + msg = _INTL("The effects of the {1} prevent status moves from being used!", + itemName) + (commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg) + end + return false + end + # Belch + return false if !move.pbCanChooseMove?(self,commandPhase,showMessages) + return true + end + + #============================================================================= + # Obedience check + #============================================================================= + # Return true if Pokémon continues attacking (although it may have chosen to + # use a different move in disobedience), or false if attack stops. + def pbObedienceCheck?(choice) + return true if usingMultiTurnAttack? + return true if choice[0]!=:UseMove + return true if !@battle.internalBattle + return true if !@battle.pbOwnedByPlayer?(@index) + disobedient = false + # Pokémon may be disobedient; calculate if it is + badgeLevel = 10*(@battle.pbPlayer.numbadges+1) + badgeLevel = PBExperience.maxLevel if @battle.pbPlayer.numbadges>=8 + if @pokemon.foreign?(@battle.pbPlayer) && @level>badgeLevel + a = ((@level+badgeLevel)*@battle.pbRandom(256)/256).floor + disobedient |= (a>=badgeLevel) + end + disobedient |= !pbHyperModeObedience(choice[2]) + return true if !disobedient + # Pokémon is disobedient; make it do something else + return pbDisobey(choice,badgeLevel) + end + + def pbDisobey(choice,badgeLevel) + move = choice[2] + PBDebug.log("[Disobedience] #{pbThis} disobeyed") + @effects[PBEffects::Rage] = false + # Do nothing if using Snore/Sleep Talk + if @status==PBStatuses::SLEEP && move.usableWhenAsleep? + @battle.pbDisplay(_INTL("{1} ignored orders and kept sleeping!",pbThis)) + return false + end + b = ((@level+badgeLevel)*@battle.pbRandom(256)/256).floor + # Use another move + if b=0 # Intentionally no message here + PBDebug.log("[Move failed] #{pbThis} can't use #{move.name} because of being Sky Dropped") + return false + end + if @effects[PBEffects::HyperBeam]>0 # Intentionally before Truant + @battle.pbDisplay(_INTL("{1} must recharge!",pbThis)) + return false + end + if choice[1]==-2 # Battle Palace + @battle.pbDisplay(_INTL("{1} appears incapable of using its power!",pbThis)) + return false + end + # Skip checking all applied effects that could make self fail doing something + return true if skipAccuracyCheck + # Check status problems and continue their effects/cure them + case @status + when PBStatuses::SLEEP + self.statusCount -= 1 + if @statusCount<=0 + pbCureStatus + else + pbContinueStatus + if !move.usableWhenAsleep? # Snore/Sleep Talk + @lastMoveFailed = true + return false + end + end + when PBStatuses::FROZEN + if !move.thawsUser? + if @battle.pbRandom(100)<20 + pbCureStatus + else + pbContinueStatus + @lastMoveFailed = true + return false + end + end + end + # Obedience check + return false if !pbObedienceCheck?(choice) + # Truant + if hasActiveAbility?(:TRUANT) + @effects[PBEffects::Truant] = !@effects[PBEffects::Truant] + if !@effects[PBEffects::Truant] # True means loafing, but was just inverted + @battle.pbShowAbilitySplash(self) + @battle.pbDisplay(_INTL("{1} is loafing around!",pbThis)) + @lastMoveFailed = true + @battle.pbHideAbilitySplash(self) + return false + end + end + # Flinching + if @effects[PBEffects::Flinch] + @battle.pbDisplay(_INTL("{1} flinched and couldn't move!",pbThis)) + if abilityActive? + BattleHandlers.triggerAbilityOnFlinch(@ability,self,@battle) + end + @lastMoveFailed = true + return false + end + # Confusion + if @effects[PBEffects::Confusion]>0 + @effects[PBEffects::Confusion] -= 1 + if @effects[PBEffects::Confusion]<=0 + pbCureConfusion + @battle.pbDisplay(_INTL("{1} snapped out of its confusion.",pbThis)) + else + @battle.pbCommonAnimation("Confusion",self) + @battle.pbDisplay(_INTL("{1} is confused!",pbThis)) + threshold = (NEWEST_BATTLE_MECHANICS) ? 33 : 50 # % chance + if @battle.pbRandom(100)=0 + @battle.pbCommonAnimation("Attract",self) + @battle.pbDisplay(_INTL("{1} is in love with {2}!",pbThis, + @battle.battlers[@effects[PBEffects::Attract]].pbThis(true))) + if @battle.pbRandom(100)<50 + @battle.pbDisplay(_INTL("{1} is immobilized by love!",pbThis)) + @lastMoveFailed = true + return false + end + end + return true + end + + #============================================================================= + # Initial success check against the target. Done once before the first hit. + # Includes move-specific failure conditions, protections and type immunities. + #============================================================================= + def pbSuccessCheckAgainstTarget(move,user,target) + typeMod = move.pbCalcTypeMod(move.calcType,user,target) + target.damageState.typeMod = typeMod + # Two-turn attacks can't fail here in the charging turn + return true if user.effects[PBEffects::TwoTurnAttack]>0 + # Move-specific failures + return false if move.pbFailsAgainstTarget?(user,target) + # Immunity to priority moves because of Psychic Terrain + if @battle.field.terrain==PBBattleTerrains::Psychic && target.affectedByTerrain? && + target.opposes?(user) && + @battle.choices[user.index][4]>0 # Move priority saved from pbCalculatePriority + @battle.pbDisplay(_INTL("{1} surrounds itself with psychic terrain!",target.pbThis)) + return false + end + # Crafty Shield + if target.pbOwnSide.effects[PBEffects::CraftyShield] && user.index!=target.index && + move.statusMove? && move.pbTarget(user)!=PBTargets::AllBattlers + @battle.pbCommonAnimation("CraftyShield",target) + @battle.pbDisplay(_INTL("Crafty Shield protected {1}!",target.pbThis(true))) + target.damageState.protected = true + @battle.successStates[user.index].protected = true + return false + end + # Wide Guard + if target.pbOwnSide.effects[PBEffects::WideGuard] && user.index!=target.index && + PBTargets.multipleTargets?(move.pbTarget(user)) && + (NEWEST_BATTLE_MECHANICS || move.damagingMove?) + @battle.pbCommonAnimation("WideGuard",target) + @battle.pbDisplay(_INTL("Wide Guard protected {1}!",target.pbThis(true))) + target.damageState.protected = true + @battle.successStates[user.index].protected = true + return false + end + if move.canProtectAgainst? + # Quick Guard + if target.pbOwnSide.effects[PBEffects::QuickGuard] && + @battle.choices[user.index][4]>0 # Move priority saved from pbCalculatePriority + @battle.pbCommonAnimation("QuickGuard",target) + @battle.pbDisplay(_INTL("Quick Guard protected {1}!",target.pbThis(true))) + target.damageState.protected = true + @battle.successStates[user.index].protected = true + return false + end + # Protect + if target.effects[PBEffects::Protect] + @battle.pbCommonAnimation("Protect",target) + @battle.pbDisplay(_INTL("{1} protected itself!",target.pbThis)) + target.damageState.protected = true + @battle.successStates[user.index].protected = true + return false + end + # King's Shield + if target.effects[PBEffects::KingsShield] && move.damagingMove? + @battle.pbCommonAnimation("KingsShield",target) + @battle.pbDisplay(_INTL("{1} protected itself!",target.pbThis)) + target.damageState.protected = true + @battle.successStates[user.index].protected = true + if move.pbContactMove?(user) && user.affectedByContactEffect? + if user.pbCanLowerStatStage?(PBStats::ATTACK) + user.pbLowerStatStage(PBStats::ATTACK,2,nil) + end + end + return false + end + # Spiky Shield + if target.effects[PBEffects::SpikyShield] + @battle.pbCommonAnimation("SpikyShield",target) + @battle.pbDisplay(_INTL("{1} protected itself!",target.pbThis)) + target.damageState.protected = true + @battle.successStates[user.index].protected = true + if move.pbContactMove?(user) && user.affectedByContactEffect? + @battle.scene.pbDamageAnimation(user) + user.pbReduceHP(user.totalhp/8,false) + @battle.pbDisplay(_INTL("{1} was hurt!",user.pbThis)) + user.pbItemHPHealCheck + end + return false + end + # Baneful Bunker + if target.effects[PBEffects::BanefulBunker] + @battle.pbCommonAnimation("BanefulBunker",target) + @battle.pbDisplay(_INTL("{1} protected itself!",target.pbThis)) + target.damageState.protected = true + @battle.successStates[user.index].protected = true + if move.pbContactMove?(user) && user.affectedByContactEffect? + user.pbPoison(target) if user.pbCanPoison?(target,false) + end + return false + end + # Mat Block + if target.pbOwnSide.effects[PBEffects::MatBlock] && move.damagingMove? + # NOTE: Confirmed no common animation for this effect. + @battle.pbDisplay(_INTL("{1} was blocked by the kicked-up mat!",move.name)) + target.damageState.protected = true + @battle.successStates[user.index].protected = true + return false + end + end + # Magic Coat/Magic Bounce + if move.canMagicCoat? && !target.semiInvulnerable? && target.opposes?(user) + if target.effects[PBEffects::MagicCoat] + target.damageState.magicCoat = true + target.effects[PBEffects::MagicCoat] = false + return false + end + if target.hasActiveAbility?(:MAGICBOUNCE) && !@battle.moldBreaker && + !target.effects[PBEffects::MagicBounce] + target.damageState.magicBounce = true + target.effects[PBEffects::MagicBounce] = true + return false + end + end + # Immunity because of ability (intentionally before type immunity check) + return false if move.pbImmunityByAbility(user,target) + # Type immunity + if move.pbDamagingMove? && PBTypes.ineffective?(typeMod) + PBDebug.log("[Target immune] #{target.pbThis}'s type immunity") + @battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true))) + return false + end + # Dark-type immunity to moves made faster by Prankster + if NEWEST_BATTLE_MECHANICS && user.effects[PBEffects::Prankster] && + target.pbHasType?(:DARK) && target.opposes?(user) + PBDebug.log("[Target immune] #{target.pbThis} is Dark-type and immune to Prankster-boosted moves") + @battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true))) + return false + end + # Airborne-based immunity to Ground moves + if move.damagingMove? && isConst?(move.calcType,PBTypes,:GROUND) && + target.airborne? && !move.hitsFlyingTargets? + if target.hasActiveAbility?(:LEVITATE) && !@battle.moldBreaker + @battle.pbShowAbilitySplash(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} avoided the attack!",target.pbThis)) + else + @battle.pbDisplay(_INTL("{1} avoided the attack with {2}!",target.pbThis,target.abilityName)) + end + @battle.pbHideAbilitySplash(target) + return false + end + if target.hasActiveItem?(:AIRBALLOON) + @battle.pbDisplay(_INTL("{1}'s {2} makes Ground moves miss!",target.pbThis,target.itemName)) + return false + end + if target.effects[PBEffects::MagnetRise]>0 + @battle.pbDisplay(_INTL("{1} makes Ground moves miss with Magnet Rise!",target.pbThis)) + return false + end + if target.effects[PBEffects::Telekinesis]>0 + @battle.pbDisplay(_INTL("{1} makes Ground moves miss with Telekinesis!",target.pbThis)) + return false + end + end + # Immunity to powder-based moves + if NEWEST_BATTLE_MECHANICS && move.powderMove? + if target.pbHasType?(:GRASS) + PBDebug.log("[Target immune] #{target.pbThis} is Grass-type and immune to powder-based moves") + @battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true))) + return false + end + if target.hasActiveAbility?(:OVERCOAT) && !@battle.moldBreaker + @battle.pbShowAbilitySplash(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true))) + else + @battle.pbDisplay(_INTL("It doesn't affect {1} because of its {2}.",target.pbThis(true),target.abilityName)) + end + @battle.pbHideAbilitySplash(target) + return false + end + if target.hasActiveItem?(:SAFETYGOGGLES) + PBDebug.log("[Item triggered] #{target.pbThis} has Safety Goggles and is immune to powder-based moves") + @battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true))) + return false + end + end + # Substitute + if target.effects[PBEffects::Substitute]>0 && move.statusMove? && + !move.ignoresSubstitute?(user) && user.index!=target.index + PBDebug.log("[Target immune] #{target.pbThis} is protected by its Substitute") + @battle.pbDisplay(_INTL("{1} avoided the attack!",target.pbThis(true))) + return false + end + return true + end + + #============================================================================= + # Per-hit success check against the target. + # Includes semi-invulnerable move use and accuracy calculation. + #============================================================================= + def pbSuccessCheckPerHit(move,user,target,skipAccuracyCheck) + # Two-turn attacks can't fail here in the charging turn + return true if user.effects[PBEffects::TwoTurnAttack]>0 + # Lock-On + return true if user.effects[PBEffects::LockOn]>0 && + user.effects[PBEffects::LockOnPos]==target.index + # Toxic + return true if move.pbOverrideSuccessCheckPerHit(user,target) + miss = false; hitsInvul = false + # No Guard + hitsInvul = true if user.hasActiveAbility?(:NOGUARD) || + target.hasActiveAbility?(:NOGUARD) + # Future Sight + hitsInvul = true if @battle.futureSight + # Helping Hand + hitsInvul = true if move.function=="09C" + if !hitsInvul + # Semi-invulnerable moves + if target.effects[PBEffects::TwoTurnAttack]>0 + if target.inTwoTurnAttack?("0C9","0CC","0CE") # Fly, Bounce, Sky Drop + miss = true if !move.hitsFlyingTargets? + elsif target.inTwoTurnAttack?("0CA") # Dig + miss = true if !move.hitsDiggingTargets? + elsif target.inTwoTurnAttack?("0CB") # Dive + miss = true if !move.hitsDivingTargets? + elsif target.inTwoTurnAttack?("0CD","14D") # Shadow Force, Phantom Force + miss = true + end + end + if target.effects[PBEffects::SkyDrop]>=0 && + target.effects[PBEffects::SkyDrop]!=user.index + miss = true if !move.hitsFlyingTargets? + end + end + if !miss + # Called by another move + return true if skipAccuracyCheck + # Accuracy check + return true if move.pbAccuracyCheck(user,target) # Includes Counter/Mirror Coat + end + # Missed + PBDebug.log("[Move failed] Failed pbAccuracyCheck or target is semi-invulnerable") + return false + end + + #============================================================================= + # Message shown when a move fails the per-hit success check above. + #============================================================================= + def pbMissMessage(move,user,target) + tar = move.pbTarget(user) + if PBTargets.multipleTargets?(tar) + @battle.pbDisplay(_INTL("{1} avoided the attack!",target.pbThis)) + elsif target.effects[PBEffects::TwoTurnAttack]>0 + @battle.pbDisplay(_INTL("{1} avoided the attack!",target.pbThis)) + elsif !move.pbMissMessage(user,target) + @battle.pbDisplay(_INTL("{1}'s attack missed!",user.pbThis)) + end + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/001_Battler/010_Battler_UseMove_TriggerEffects.rb b/Data/Scripts/011_Battle/001_Battler/010_Battler_UseMove_TriggerEffects.rb new file mode 100644 index 000000000..4bc3527b4 --- /dev/null +++ b/Data/Scripts/011_Battle/001_Battler/010_Battler_UseMove_TriggerEffects.rb @@ -0,0 +1,188 @@ +class PokeBattle_Battler + #============================================================================= + # Effect per hit + #============================================================================= + def pbEffectsOnMakingHit(move,user,target) + if target.damageState.calcDamage>0 && !target.damageState.substitute + # Target's ability + if target.abilityActive?(true) + oldHP = user.hp + BattleHandlers.triggerTargetAbilityOnHit(target.ability,user,target,move,@battle) + user.pbItemHPHealCheck if user.hp0 && !target.damageState.substitute && move.physicalMove? + target.tookPhysicalHit = true + target.effects[PBEffects::MoveNext] = true + target.effects[PBEffects::Quash] = 0 + end + end + # Grudge + if target.effects[PBEffects::Grudge] && target.fainted? + move.pp = 0 + @battle.pbDisplay(_INTL("{1}'s {2} lost all of its PP due to the grudge!", + user.pbThis,move.name)) + end + # Destiny Bond (recording that it should apply) + if target.effects[PBEffects::DestinyBond] && target.fainted? + if user.effects[PBEffects::DestinyBondTarget]<0 + user.effects[PBEffects::DestinyBondTarget] = target.index + end + end + end + end + + #============================================================================= + # Effects after all hits (i.e. at end of move usage) + #============================================================================= + def pbEffectsAfterMove(user,targets,move,numHits) + # Defrost + if move.damagingMove? + targets.each do |b| + next if b.damageState.unaffected || b.damageState.substitute + next if b.status!=PBStatuses::FROZEN + # NOTE: Non-Fire-type moves that thaw the user will also thaw the + # target (in Gen 6+). + if isConst?(move.calcType,PBTypes,:FIRE) || + (NEWEST_BATTLE_MECHANICS && move.thawsUser?) + b.pbCureStatus + end + end + end + # Destiny Bond + # NOTE: Although Destiny Bond is similar to Grudge, they don't apply at + # the same time (although Destiny Bond does check whether it's going + # to trigger at the same time as Grudge). + if user.effects[PBEffects::DestinyBondTarget]>=0 && !user.fainted? + dbName = @battle.battlers[user.effects[PBEffects::DestinyBondTarget]].pbThis + @battle.pbDisplay(_INTL("{1} took its attacker down with it!",dbName)) + user.pbReduceHP(user.hp,false) + user.pbItemHPHealCheck + user.pbFaint + @battle.pbJudgeCheckpoint(user) + end + # User's ability + if user.abilityActive? + BattleHandlers.triggerUserAbilityEndOfMove(user.ability,user,targets,move,@battle) + end + # Greninja - Battle Bond + if !user.fainted? && !user.effects[PBEffects::Transform] && + isConst?(user.species,PBSpecies,:GRENINJA) && + isConst?(user.ability,PBAbilities,:BATTLEBOND) + if !@battle.pbAllFainted?(user.idxOpposingSide) && + !@battle.battleBond[user.index&1][user.pokemonIndex] + numFainted = 0 + targets.each { |b| numFainted += 1 if b.damageState.fainted } + if numFainted>0 && user.form!=1 + @battle.battleBond[user.index&1][user.pokemonIndex] = true + @battle.pbDisplay(_INTL("{1} became fully charged due to its bond with its Trainer!",user.pbThis)) + @battle.pbShowAbilitySplash(user,true) + @battle.pbHideAbilitySplash(user) + user.pbChangeForm(1,_INTL("{1} became Ash-Greninja!",user.pbThis)) + end + end + end + # Consume user's Gem + if user.effects[PBEffects::GemConsumed]>0 + # NOTE: The consume animation and message for Gems are shown immediately + # after the move's animation, but the item is only consumed now. + user.pbConsumeItem + end + # Pokémon switching caused by Roar, Whirlwind, Circle Throw, Dragon Tail + switchedBattlers = [] + move.pbSwitchOutTargetsEffect(user,targets,numHits,switchedBattlers) + # Target's item, user's item, target's ability (all negated by Sheer Force) + if move.addlEffect==0 || !user.hasActiveAbility?(:SHEERFORCE) + pbEffectsAfterMove2(user,targets,move,numHits,switchedBattlers) + end + # Some move effects that need to happen here, i.e. U-turn/Volt Switch + # switching, Baton Pass switching, Parting Shot switching, Relic Song's form + # changing, Fling/Natural Gift consuming item. + if !switchedBattlers.include?(user.index) + move.pbEndOfMoveUsageEffect(user,targets,numHits,switchedBattlers) + end + if numHits>0 + @battle.eachBattler { |b| b.pbItemEndOfMoveCheck } + end + end + + # Everything in this method is negated by Sheer Force. + def pbEffectsAfterMove2(user,targets,move,numHits,switchedBattlers) + hpNow = user.hp # Intentionally determined now, before Shell Bell + # Target's held item (Eject Button, Red Card) + switchByItem = [] + @battle.pbPriority(true).each do |b| + next if !targets.any? { |targetB| targetB.index==b.index } + next if b.damageState.unaffected || b.damageState.calcDamage==0 || + switchedBattlers.include?(b.index) + next if !b.itemActive? + BattleHandlers.triggerTargetItemAfterMoveUse(b.item,b,user,move,switchByItem,@battle) + end + @battle.moldBreaker = false if switchByItem.include?(user.index) + @battle.pbPriority(true).each do |b| + b.pbEffectsOnSwitchIn(true) if switchByItem.include?(b.index) + end + switchByItem.each { |idxB| switchedBattlers.push(idxB) } + # User's held item (Life Orb, Shell Bell) + if !switchedBattlers.include?(user.index) && user.itemActive? + BattleHandlers.triggerUserItemAfterMoveUse(user.item,user,targets,move,numHits,@battle) + end + # Target's ability (Berserk, Color Change, Emergency Exit, Pickpocket, Wimp Out) + switchWimpOut = [] + @battle.pbPriority(true).each do |b| + next if !targets.any? { |targetB| targetB.index==b.index } + next if b.damageState.unaffected || switchedBattlers.include?(b.index) + next if !b.abilityActive? + BattleHandlers.triggerTargetAbilityAfterMoveUse(b.ability,b,user,move,switchedBattlers,@battle) + if !switchedBattlers.include?(b.index) && move.damagingMove? + if b.pbAbilitiesOnDamageTaken(b.damageState.initialHP) # Emergency Exit, Wimp Out + switchWimpOut.push(b.index) + end + end + end + @battle.moldBreaker = false if switchWimpOut.include?(user.index) + @battle.pbPriority(true).each do |b| + next if b.index==user.index + b.pbEffectsOnSwitchIn(true) if switchWimpOut.include?(b.index) + end + switchWimpOut.each { |idxB| switchedBattlers.push(idxB) } + # User's ability (Emergency Exit, Wimp Out) + if !switchedBattlers.include?(user.index) && move.damagingMove? + hpNow = user.hp if user.hp0 # Usually undefined + return @realMove.totalpp if @realMove + return 0 + end + + # NOTE: This method is only ever called while using a move (and also by the + # AI), so using @calcType here is acceptable. + def physicalMove?(thisType=nil) + return (@category==0) if MOVE_CATEGORY_PER_MOVE + thisType ||= @calcType if @calcType>=0 + thisType = @type if !thisType + return !PBTypes.isSpecialType?(thisType) + end + + # NOTE: This method is only ever called while using a move (and also by the + # AI), so using @calcType here is acceptable. + def specialMove?(thisType=nil) + return (@category==1) if MOVE_CATEGORY_PER_MOVE + thisType ||= @calcType if @calcType>=0 + thisType = @type if !thisType + return PBTypes.isSpecialType?(thisType) + end + + def damagingMove?; return @category!=2; end + def statusMove?; return @category==2; end + + def usableWhenAsleep?; return false; end + def unusableInGravity?; return false; end + def healingMove?; return false; end + def recoilMove?; return false; end + def flinchingMove?; return false; end + def callsAnotherMove?; return false; end + # Whether the move can/will hit more than once in the same turn (including + # Beat Up which may instead hit just once). Not the same as pbNumHits>1. + def multiHitMove?; return false; end + def chargingTurnMove?; return false; end + def successCheckPerHit?; return false; end + def hitsFlyingTargets?; return false; end + def hitsDiggingTargets?; return false; end + def hitsDivingTargets?; return false; end + def ignoresReflect?; return false; end # For Brick Break + def cannotRedirect?; return false; end # For Future Sight/Doom Desire + def worksWithNoTargets?; return false; end # For Explosion + def damageReducedByBurn?; return true; end # For Facade + def triggersHyperMode?; return false; end + + def contactMove?; return @flags[/a/]; end + def canProtectAgainst?; return @flags[/b/]; end + def canMagicCoat?; return @flags[/c/]; end + def canSnatch?; return @flags[/d/]; end + def canMirrorMove?; return @flags[/e/]; end + def canKingsRock?; return @flags[/f/]; end + def thawsUser?; return @flags[/g/]; end + def highCriticalRate?; return @flags[/h/]; end + def bitingMove?; return @flags[/i/]; end + def punchingMove?; return @flags[/j/]; end + def soundMove?; return @flags[/k/]; end + def powderMove?; return @flags[/l/]; end + def pulseMove?; return @flags[/m/]; end + def bombMove?; return @flags[/n/]; end + def danceMove?; return @flags[/o/]; end + + # Causes perfect accuracy (param=1) and double damage (param=2). + def tramplesMinimize?(param=1); return false; end + def nonLethal?(user,target); return false; end # For False Swipe + + def ignoresSubstitute?(user) # user is the Pokémon using this move + if NEWEST_BATTLE_MECHANICS + return true if soundMove? + return true if user && user.hasActiveAbility?(:INFILTRATOR) + end + return false + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/002_Move/002_Move_Usage.rb b/Data/Scripts/011_Battle/002_Move/002_Move_Usage.rb new file mode 100644 index 000000000..59238a99c --- /dev/null +++ b/Data/Scripts/011_Battle/002_Move/002_Move_Usage.rb @@ -0,0 +1,350 @@ +class PokeBattle_Move + #============================================================================= + # Effect methods per move usage + #============================================================================= + def pbCanChooseMove?(user,commandPhase,showMessages); return true; end # For Belch + def pbDisplayChargeMessage(user); end # For Focus Punch/shell Trap/Beak Blast + def pbOnStartUse(user,targets); end + def pbAddTarget(targets,user); end # For Counter, etc. and Bide + + # Reset move usage counters (child classes can increment them). + def pbChangeUsageCounters(user,specialUsage) + user.effects[PBEffects::FuryCutter] = 0 + user.effects[PBEffects::ParentalBond] = 0 + user.effects[PBEffects::ProtectRate] = 1 + @battle.field.effects[PBEffects::FusionBolt] = false + @battle.field.effects[PBEffects::FusionFlare] = false + end + + def pbDisplayUseMessage(user) + @battle.pbDisplayBrief(_INTL("{1} used {2}!",user.pbThis,@name)) + end + + def pbMissMessage(user,target); return false; end + + #============================================================================= + # + #============================================================================= + # Whether the move is currently in the "charging" turn of a two turn attack. + # Is false if Power Herb or another effect lets a two turn move charge and + # attack in the same turn. + # user.effects[PBEffects::TwoTurnAttack] is set to the move's ID during the + # charging turn, and is 0 during the attack turn. + def pbIsChargingTurn?(user); return false; end + def pbDamagingMove?; return damagingMove?; end + + def pbContactMove?(user) + return false if user.hasActiveAbility?(:LONGREACH) + return contactMove? + end + + # The maximum number of hits in a round this move will actually perform. This + # can be 1 for Beat Up, and can be 2 for any moves affected by Parental Bond. + def pbNumHits(user,targets) + if user.hasActiveAbility?(:PARENTALBOND) && pbDamagingMove? && + !chargingTurnMove? && targets.length==1 + # Record that Parental Bond applies, to weaken the second attack + user.effects[PBEffects::ParentalBond] = 3 + return 2 + end + return 1 + end + + #============================================================================= + # Effect methods per hit + #============================================================================= + def pbOverrideSuccessCheckPerHit(user,target); return false; end + def pbCrashDamage(user); end + def pbInitialEffect(user,targets,hitNum); end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + return if !showAnimation + if user.effects[PBEffects::ParentalBond]==1 + @battle.pbCommonAnimation("ParentalBond",user,targets) + else + @battle.pbAnimation(id,user,targets,hitNum) + end + end + + def pbSelfKO(user); end + def pbEffectWhenDealingDamage(user,target); end + def pbEffectAgainstTarget(user,target); end + def pbEffectGeneral(user); end + def pbAdditionalEffect(user,target); end + def pbEffectAfterAllHits(user,target); end # Move effects that occur after all hits + def pbSwitchOutTargetsEffect(user,targets,numHits,switchedBattlers); end + def pbEndOfMoveUsageEffect(user,targets,numHits,switchedBattlers); end + + #============================================================================= + # Check if target is immune to the move because of its ability + #============================================================================= + def pbImmunityByAbility(user,target) + return false if @battle.moldBreaker + ret = false + if target.abilityActive? + ret = BattleHandlers.triggerMoveImmunityTargetAbility(target.ability, + user,target,self,@calcType,@battle) + end + return ret + end + + #============================================================================= + # Move failure checks + #============================================================================= + # Check whether the move fails completely due to move-specific requirements. + def pbMoveFailed?(user,targets); return false; end + # Checks whether the move will be ineffective against the target. + def pbFailsAgainstTarget?(user,target); return false; end + + def pbMoveFailedLastInRound?(user) + unmoved = false + @battle.eachBattler do |b| + next if b.index==user.index + next if @battle.choices[b.index][0]!=:UseMove && @battle.choices[b.index][0]!=:Shift + next if b.movedThisRound? + unmoved = true + break + end + if !unmoved + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbMoveFailedTargetAlreadyMoved?(target) + if (@battle.choices[target.index][0]!=:UseMove && + @battle.choices[target.index][0]!=:Shift) || target.movedThisRound? + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbMoveFailedAromaVeil?(user,target,showMessage=true) + return false if @battle.moldBreaker + if target.hasActiveAbility?(:AROMAVEIL) + if showMessage + @battle.pbShowAbilitySplash(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} is unaffected!",target.pbThis)) + else + @battle.pbDisplay(_INTL("{1} is unaffected because of its {2}!", + target.pbThis,target.abilityName)) + end + @battle.pbHideAbilitySplash(target) + end + return true + end + target.eachAlly do |b| + next if !b.hasActiveAbility?(:AROMAVEIL) + if showMessage + @battle.pbShowAbilitySplash(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} is unaffected!",target.pbThis)) + else + @battle.pbDisplay(_INTL("{1} is unaffected because of {2}'s {3}!", + target.pbThis,b.pbThis(true),b.abilityName)) + end + @battle.pbHideAbilitySplash(target) + end + return true + end + return false + end + + #============================================================================= + # Weaken the damage dealt (doesn't actually change a battler's HP) + #============================================================================= + def pbCheckDamageAbsorption(user,target) + # Substitute will take the damage + if target.effects[PBEffects::Substitute]>0 && !ignoresSubstitute?(user) && + (!user || user.index!=target.index) + target.damageState.substitute = true + return + end + # Disguise will take the damage + if !@battle.moldBreaker && isConst?(target.species,PBSpecies,:MIMIKYU) && + target.form==0 && isConst?(target.ability,PBAbilities,:DISGUISE) + target.damageState.disguise = true + return + end + end + + def pbReduceDamage(user,target) + damage = target.damageState.calcDamage + # Substitute takes the damage + if target.damageState.substitute + damage = target.effects[PBEffects::Substitute] if damage>target.effects[PBEffects::Substitute] + target.damageState.hpLost = damage + target.damageState.totalHPLost += damage + return + end + # Disguise takes the damage + return if target.damageState.disguise + # Target takes the damage + if damage>=target.hp + damage = target.hp + # Survive a lethal hit with 1 HP effects + if nonLethal?(user,target) + damage -= 1 + elsif target.effects[PBEffects::Endure] + target.damageState.endured = true + damage -= 1 + elsif damage==target.totalhp + if target.hasActiveAbility?(:STURDY) && !@battle.moldBreaker + target.damageState.sturdy = true + damage -= 1 + elsif target.hasActiveItem?(:FOCUSSASH) && target.hp==target.totalhp + target.damageState.focusSash = true + damage -= 1 + elsif target.hasActiveItem?(:FOCUSBAND) && @battle.pbRandom(100)<10 + target.damageState.focusBand = true + damage -= 1 + end + end + end + damage = 0 if damage<0 + target.damageState.hpLost = damage + target.damageState.totalHPLost += damage + end + + #============================================================================= + # Change the target's HP by the amount calculated above + #============================================================================= + def pbInflictHPDamage(target) + if target.damageState.substitute + target.effects[PBEffects::Substitute] -= target.damageState.hpLost + else + target.hp -= target.damageState.hpLost + end + end + + #============================================================================= + # Animate the damage dealt, including lowering the HP + #============================================================================= + # Animate being damaged and losing HP (by a move) + def pbAnimateHitAndHPLost(user,targets) + # Animate allies first, then foes + animArray = [] + for side in 0...2 # side here means "allies first, then foes" + targets.each do |b| + next if b.damageState.unaffected || b.damageState.hpLost==0 + next if (side==0 && b.opposes?(user)) || (side==1 && !b.opposes?(user)) + oldHP = b.hp+b.damageState.hpLost + PBDebug.log("[Move damage] #{b.pbThis} lost #{b.damageState.hpLost} HP (#{oldHP}=>#{b.hp})") + effectiveness = 0 + if PBTypes.resistant?(b.damageState.typeMod); effectiveness = 1 + elsif PBTypes.superEffective?(b.damageState.typeMod); effectiveness = 2 + end + animArray.push([b,oldHP,effectiveness]) + end + if animArray.length>0 + @battle.scene.pbHitAndHPLossAnimation(animArray) + animArray.clear + end + end + end + + #============================================================================= + # Messages upon being hit + #============================================================================= + def pbEffectivenessMessage(user,target,numTargets=1) + return if target.damageState.disguise + if PBTypes.superEffective?(target.damageState.typeMod) + if numTargets>1 + @battle.pbDisplay(_INTL("It's super effective on {1}!",target.pbThis(true))) + else + @battle.pbDisplay(_INTL("It's super effective!")) + end + elsif PBTypes.notVeryEffective?(target.damageState.typeMod) + if numTargets>1 + @battle.pbDisplay(_INTL("It's not very effective on {1}...",target.pbThis(true))) + else + @battle.pbDisplay(_INTL("It's not very effective...")) + end + end + end + + def pbHitEffectivenessMessages(user,target,numTargets=1) + return if target.damageState.disguise + if target.damageState.substitute + @battle.pbDisplay(_INTL("The substitute took damage for {1}!",target.pbThis(true))) + end + if target.damageState.critical + if numTargets>1 + @battle.pbDisplay(_INTL("A critical hit on {1}!",target.pbThis(true))) + else + @battle.pbDisplay(_INTL("A critical hit!")) + end + end + # Effectiveness message, for moves with 1 hit + if !multiHitMove? && user.effects[PBEffects::ParentalBond]==0 + pbEffectivenessMessage(user,target,numTargets) + end + if target.damageState.substitute && target.effects[PBEffects::Substitute]==0 + target.effects[PBEffects::Substitute] = 0 + @battle.pbDisplay(_INTL("{1}'s substitute faded!",target.pbThis)) + end + end + + def pbEndureKOMessage(target) + if target.damageState.disguise + @battle.pbShowAbilitySplash(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("Its disguise served it as a decoy!")) + else + @battle.pbDisplay(_INTL("{1}'s disguise served it as a decoy!",target.pbThis)) + end + @battle.pbHideAbilitySplash(target) + target.pbChangeForm(1,_INTL("{1}'s disguise was busted!",target.pbThis)) + elsif target.damageState.endured + @battle.pbDisplay(_INTL("{1} endured the hit!",target.pbThis)) + elsif target.damageState.sturdy + @battle.pbShowAbilitySplash(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} endured the hit!",target.pbThis)) + else + @battle.pbDisplay(_INTL("{1} hung on with Sturdy!",target.pbThis)) + end + @battle.pbHideAbilitySplash(target) + elsif target.damageState.focusSash + @battle.pbCommonAnimation("UseItem",target) + @battle.pbDisplay(_INTL("{1} hung on using its Focus Sash!",target.pbThis)) + target.pbConsumeItem + elsif target.damageState.focusBand + @battle.pbCommonAnimation("UseItem",target) + @battle.pbDisplay(_INTL("{1} hung on using its Focus Band!",target.pbThis)) + end + end + + # Used by Counter/Mirror Coat/Metal Burst/Revenge/Focus Punch/Bide/Assurance. + def pbRecordDamageLost(user,target) + damage = target.damageState.hpLost + # NOTE: In Gen 3 where a move's category depends on its type, Hidden Power + # is for some reason countered by Counter rather than Mirror Coat, + # regardless of its calculated type. Hence the following two lines of + # code. + moveType = nil + moveType = getID(PBTypes,:NORMAL) if @function=="090" # Hidden Power + if physicalMove?(moveType) + target.effects[PBEffects::Counter] = damage + target.effects[PBEffects::CounterTarget] = user.index + elsif specialMove?(moveType) + target.effects[PBEffects::MirrorCoat] = damage + target.effects[PBEffects::MirrorCoatTarget] = user.index + end + if target.effects[PBEffects::Bide]>0 + target.effects[PBEffects::BideDamage] += damage + target.effects[PBEffects::BideTarget] = user.index + end + target.damageState.fainted = true if target.fainted? + target.lastHPLost = damage # For Focus Punch + target.tookDamage = true if damage>0 # For Assurance + target.lastAttacker.push(user.index) # For Revenge + if target.opposes?(user) + target.lastHPLostFromFoe = damage # For Metal Burst + target.lastFoeAttacker.push(user.index) # For Metal Burst + end + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/002_Move/003_Move_Usage_Calculations.rb b/Data/Scripts/011_Battle/002_Move/003_Move_Usage_Calculations.rb new file mode 100644 index 000000000..497e7322b --- /dev/null +++ b/Data/Scripts/011_Battle/002_Move/003_Move_Usage_Calculations.rb @@ -0,0 +1,491 @@ +class PokeBattle_Move + #============================================================================= + # Move's type calculation + #============================================================================= + def pbBaseType(user) + ret = @type + return ret if !ret || ret<0 + if user.abilityActive? + ret = BattleHandlers.triggerMoveBaseTypeModifierAbility(user.ability,user,self,ret) + end + return ret + end + + def pbCalcType(user) + @powerBoost = false + ret = pbBaseType(user) + return ret if !ret || ret<0 + if hasConst?(PBTypes,:ELECTRIC) + if @battle.field.effects[PBEffects::IonDeluge] && isConst?(ret,PBTypes,:NORMAL) + ret = getConst(PBTypes,:ELECTRIC) + @powerBoost = false + end + if user.effects[PBEffects::Electrify] + ret = getConst(PBTypes,:ELECTRIC) + @powerBoost = false + end + end + return ret + end + + #============================================================================= + # Type effectiveness calculation + #============================================================================= + def pbCalcTypeModSingle(moveType,defType,user,target) + ret = PBTypes.getEffectiveness(moveType,defType) + # Ring Target + if target.hasActiveItem?(:RINGTARGET) + ret = PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE if PBTypes.ineffective?(moveType,defType) + end + # Foresight + if user.hasActiveAbility?(:SCRAPPY) || target.effects[PBEffects::Foresight] + ret = PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE if isConst?(defType,PBTypes,:GHOST) && + PBTypes.ineffective?(moveType,defType) + end + # Miracle Eye + if target.effects[PBEffects::MiracleEye] + ret = PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE if isConst?(defType,PBTypes,:DARK) && + PBTypes.ineffective?(moveType,defType) + end + # Delta Stream's weather + if @battle.pbWeather==PBWeather::StrongWinds + ret = PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE if isConst?(defType,PBTypes,:FLYING) && + PBTypes.superEffective?(moveType,defType) + end + # Grounded Flying-type Pokémon become susceptible to Ground moves + if !target.airborne? + ret = PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE if isConst?(defType,PBTypes,:FLYING) && + isConst?(moveType,PBTypes,:GROUND) + end + return ret + end + + def pbCalcTypeMod(moveType,user,target) + return PBTypeEffectiveness::NORMAL_EFFECTIVE if moveType<0 + return PBTypeEffectiveness::NORMAL_EFFECTIVE if isConst?(moveType,PBTypes,:GROUND) && + target.pbHasType?(:FLYING) && target.hasActiveItem?(:IRONBALL) + # Determine types + tTypes = target.pbTypes(true) + # Get effectivenesses + typeMods = [PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE] * 3 # 3 types max + tTypes.each_with_index do |type,i| + typeMods[i] = pbCalcTypeModSingle(moveType,type,user,target) + end + # Multiply all effectivenesses together + ret = 1 + typeMods.each { |m| ret *= m } + return ret + end + + #============================================================================= + # Accuracy check + #============================================================================= + def pbBaseAccuracy(user,target); return @accuracy; end + + # Accuracy calculations for one-hit KO moves and "always hit" moves are + # handled elsewhere. + def pbAccuracyCheck(user,target) + # "Always hit" effects and "always hit" accuracy + return true if target.effects[PBEffects::Telekinesis]>0 + return true if target.effects[PBEffects::Minimize] && tramplesMinimize?(1) + baseAcc = pbBaseAccuracy(user,target) + return true if baseAcc==0 + # Calculate all multiplier effects + modifiers = [] + modifiers[BASE_ACC] = baseAcc + modifiers[ACC_STAGE] = user.stages[PBStats::ACCURACY] + modifiers[EVA_STAGE] = target.stages[PBStats::EVASION] + modifiers[ACC_MULT] = 0x1000 + modifiers[EVA_MULT] = 0x1000 + pbCalcAccuracyModifiers(user,target,modifiers) + # Check if move can't miss + return true if modifiers[BASE_ACC]==0 + # Calculation + accStage = [[modifiers[ACC_STAGE],-6].max,6].min + 6 + evaStage = [[modifiers[EVA_STAGE],-6].max,6].min + 6 + stageMul = [3,3,3,3,3,3, 3, 4,5,6,7,8,9] + stageDiv = [9,8,7,6,5,4, 3, 3,3,3,3,3,3] + accuracy = 100.0 * stageMul[accStage] / stageDiv[accStage] + evasion = 100.0 * stageMul[evaStage] / stageDiv[evaStage] + accuracy = (accuracy * modifiers[ACC_MULT] / 0x1000).round + evasion = (evasion * modifiers[EVA_MULT] / 0x1000).round + evasion = 1 if evasion<1 + # Calculation + return @battle.pbRandom(100) < modifiers[BASE_ACC] * accuracy / evasion + end + + def pbCalcAccuracyModifiers(user,target,modifiers) + # Ability effects that alter accuracy calculation + if user.abilityActive? + BattleHandlers.triggerAccuracyCalcUserAbility(user.ability, + modifiers,user,target,self,@calcType) + end + user.eachAlly do |b| + next if !b.abilityActive? + BattleHandlers.triggerAccuracyCalcUserAllyAbility(b.ability, + modifiers,user,target,self,@calcType) + end + if target.abilityActive? && !@battle.moldBreaker + BattleHandlers.triggerAccuracyCalcTargetAbility(target.ability, + modifiers,user,target,self,@calcType) + end + # Item effects that alter accuracy calculation + if user.itemActive? + BattleHandlers.triggerAccuracyCalcUserItem(user.item, + modifiers,user,target,self,@calcType) + end + if target.itemActive? + BattleHandlers.triggerAccuracyCalcTargetItem(target.item, + modifiers,user,target,self,@calcType) + end + # Other effects, inc. ones that set ACC_MULT or EVA_STAGE to specific values + if @battle.field.effects[PBEffects::Gravity]>0 + modifiers[ACC_MULT] = (modifiers[ACC_MULT]*5/3).round + end + if user.effects[PBEffects::MicleBerry] + user.effects[PBEffects::MicleBerry] = false + modifiers[ACC_MULT] = (modifiers[ACC_MULT]*1.2).round + end + modifiers[EVA_STAGE] = 0 if target.effects[PBEffects::Foresight] && modifiers[EVA_STAGE]>0 + modifiers[EVA_STAGE] = 0 if target.effects[PBEffects::MiracleEye] && modifiers[EVA_STAGE]>0 + end + + #============================================================================= + # Critical hit check + #============================================================================= + # Return values: + # -1: Never a critical hit. + # 0: Calculate normally. + # 1: Always a critical hit. + def pbCritialOverride(user,target); return 0; end + + # Returns whether the move will be a critical hit. + def pbIsCritical?(user,target) + return false if target.pbOwnSide.effects[PBEffects::LuckyChant]>0 + # Set up the critical hit ratios + ratios = (NEWEST_BATTLE_MECHANICS) ? [24,8,2,1] : [16,8,4,3,2] + c = 0 + # Ability effects that alter critical hit rate + if c>=0 && user.abilityActive? + c = BattleHandlers.triggerCriticalCalcUserAbility(user.ability,user,target,c) + end + if c>=0 && target.abilityActive? && !@battle.moldBreaker + c = BattleHandlers.triggerCriticalCalcTargetAbility(target.ability,user,target,c) + end + # Item effects that alter critical hit rate + if c>=0 && user.itemActive? + c = BattleHandlers.triggerCriticalCalcUserItem(user.item,user,target,c) + end + if c>=0 && target.itemActive? + c = BattleHandlers.triggerCriticalCalcTargetItem(target.item,user,target,c) + end + return false if c<0 + # Move-specific "always/never a critical hit" effects + case pbCritialOverride(user,target) + when 1; return true + when -1; return false + end + # Other effects + return true if c>50 # Merciless + return true if user.effects[PBEffects::LaserFocus]>0 + c += 1 if highCriticalRate? + c += user.effects[PBEffects::FocusEnergy] + c += 1 if user.inHyperMode? && isConst?(@type,PBTypes,:SHADOW) + c = ratios.length-1 if c>=ratios.length + # Calculation + return @battle.pbRandom(ratios[c])==0 + end + + #============================================================================= + # Damage calculation + #============================================================================= + def pbBaseDamage(baseDmg,user,target); return baseDmg; end + def pbBaseDamageMultiplier(damageMult,user,target); return damageMult; end + def pbModifyDamage(damageMult,user,target); return damageMult; end + + def pbGetAttackStats(user,target) + if specialMove? + return user.spatk, user.stages[PBStats::SPATK]+6 + end + return user.attack, user.stages[PBStats::ATTACK]+6 + end + + def pbGetDefenseStats(user,target) + if specialMove? + return target.spdef, target.stages[PBStats::SPDEF]+6 + end + return target.defense, target.stages[PBStats::DEFENSE]+6 + end + + def pbCalcDamage(user,target,numTargets=1) + return if statusMove? + if target.damageState.disguise + target.damageState.calcDamage = 1 + return + end + stageMul = [2,2,2,2,2,2, 2, 3,4,5,6,7,8] + stageDiv = [8,7,6,5,4,3, 2, 2,2,2,2,2,2] + # Get the move's type + type = @calcType # -1 is treated as physical + # Calculate whether this hit deals critical damage + target.damageState.critical = pbIsCritical?(user,target) + # Calcuate base power of move + baseDmg = pbBaseDamage(@baseDamage,user,target) + # Calculate user's attack stat + atk, atkStage = pbGetAttackStats(user,target) + if !target.hasActiveAbility?(:UNAWARE) || @battle.moldBreaker + atkStage = 6 if target.damageState.critical && atkStage<6 + atk = (atk.to_f*stageMul[atkStage]/stageDiv[atkStage]).floor + end + # Calculate target's defense stat + defense, defStage = pbGetDefenseStats(user,target) + if !user.hasActiveAbility?(:UNAWARE) + defStage = 6 if target.damageState.critical && defStage>6 + defense = (defense.to_f*stageMul[defStage]/stageDiv[defStage]).floor + end + # Calculate all multiplier effects + multipliers = [0x1000,0x1000,0x1000,0x1000] + pbCalcDamageMultipliers(user,target,numTargets,type,baseDmg,multipliers) + # Main damage calculation + baseDmg = [(baseDmg * multipliers[BASE_DMG_MULT] / 0x1000).round,1].max + atk = [(atk * multipliers[ATK_MULT] / 0x1000).round,1].max + defense = [(defense * multipliers[DEF_MULT] / 0x1000).round,1].max + damage = (((2.0*user.level/5+2).floor*baseDmg*atk/defense).floor/50).floor+2 + damage = [(damage * multipliers[FINAL_DMG_MULT] / 0x1000).round,1].max + target.damageState.calcDamage = damage + end + + def pbCalcDamageMultipliers(user,target,numTargets,baseDmg,type,multipliers) + # Global abilities + if (@battle.pbCheckGlobalAbility(:DARKAURA) && isConst?(type,PBTypes,:DARK)) || + (@battle.pbCheckGlobalAbility(:FAIRYAURA) && isConst?(type,PBTypes,:FAIRY)) + if @battle.pbCheckGlobalAbility(:AURABREAK) + multipliers[BASE_DMG_MULT] *= 2/3.0 + else + multipliers[BASE_DMG_MULT] *= 4/3.0 + end + end + # Ability effects that alter damage + if user.abilityActive? + BattleHandlers.triggerDamageCalcUserAbility(user.ability, + user,target,self,multipliers,baseDmg,type) + end + if !@battle.moldBreaker + # NOTE: It's odd that the user's Mold Breaker prevents its partner's + # beneficial abilities (i.e. Flower Gift boosting Atk), but that's + # how it works. + user.eachAlly do |b| + next if !b.abilityActive? + BattleHandlers.triggerDamageCalcUserAllyAbility(b.ability, + user,target,self,multipliers,baseDmg,type) + end + if target.abilityActive? + BattleHandlers.triggerDamageCalcTargetAbility(target.ability, + user,target,self,multipliers,baseDmg,type) if !@battle.moldBreaker + BattleHandlers.triggerDamageCalcTargetAbilityNonIgnorable(target.ability, + user,target,self,multipliers,baseDmg,type) + end + target.eachAlly do |b| + next if !b.abilityActive? + BattleHandlers.triggerDamageCalcTargetAllyAbility(b.ability, + user,target,self,multipliers,baseDmg,type) + end + end + # Item effects that alter damage + if user.itemActive? + BattleHandlers.triggerDamageCalcUserItem(user.item, + user,target,self,multipliers,baseDmg,type) + end + if target.itemActive? + BattleHandlers.triggerDamageCalcTargetItem(target.item, + user,target,self,multipliers,baseDmg,type) + end + # Parental Bond's second attack + if user.effects[PBEffects::ParentalBond]==1 + multipliers[BASE_DMG_MULT] /= 4 + end + # Other + if user.effects[PBEffects::MeFirst] + multipliers[BASE_DMG_MULT] = (multipliers[BASE_DMG_MULT]*1.5).round + end + if user.effects[PBEffects::HelpingHand] && !self.is_a?(PokeBattle_Confusion) + multipliers[BASE_DMG_MULT] = (multipliers[BASE_DMG_MULT]*1.5).round + end + if user.effects[PBEffects::Charge]>0 && isConst?(type,PBTypes,:ELECTRIC) + multipliers[BASE_DMG_MULT] *= 2 + end + # Mud Sport + if isConst?(type,PBTypes,:ELECTRIC) + @battle.eachBattler do |b| + next if !b.effects[PBEffects::MudSport] + multipliers[BASE_DMG_MULT] /= 3 + break + end + if @battle.field.effects[PBEffects::MudSportField]>0 + multipliers[BASE_DMG_MULT] /= 3 + end + end + # Water Sport + if isConst?(type,PBTypes,:FIRE) + @battle.eachBattler do |b| + next if !b.effects[PBEffects::WaterSport] + multipliers[BASE_DMG_MULT] /= 3 + break + end + if @battle.field.effects[PBEffects::WaterSportField]>0 + multipliers[BASE_DMG_MULT] /= 3 + end + end + # Terrain moves + if user.affectedByTerrain? + case @battle.field.terrain + when PBBattleTerrains::Electric + if isConst?(type,PBTypes,:ELECTRIC) + multipliers[BASE_DMG_MULT] = (multipliers[BASE_DMG_MULT]*1.5).round + end + when PBBattleTerrains::Grassy + if isConst?(type,PBTypes,:GRASS) + multipliers[BASE_DMG_MULT] = (multipliers[BASE_DMG_MULT]*1.5).round + end + when PBBattleTerrains::Psychic + if isConst?(type,PBTypes,:PSYCHIC) + multipliers[BASE_DMG_MULT] = (multipliers[BASE_DMG_MULT]*1.5).round + end + end + end + if @battle.field.terrain==PBBattleTerrains::Misty && target.affectedByTerrain? && + isConst?(type,PBTypes,:DRAGON) + multipliers[BASE_DMG_MULT] /= 2 + end + # Badge multipliers + if @battle.internalBattle + if user.pbOwnedByPlayer? + if physicalMove? && @battle.pbPlayer.numbadges>=NUM_BADGES_BOOST_ATTACK + multipliers[ATK_MULT] = (multipliers[ATK_MULT]*1.1).round + elsif specialMove? && @battle.pbPlayer.numbadges>=NUM_BADGES_BOOST_SPATK + multipliers[ATK_MULT] = (multipliers[ATK_MULT]*1.1).round + end + end + if target.pbOwnedByPlayer? + if physicalMove? && @battle.pbPlayer.numbadges>=NUM_BADGES_BOOST_DEFENSE + multipliers[DEF_MULT] = (multipliers[DEF_MULT]*1.1).round + elsif specialMove? && @battle.pbPlayer.numbadges>=NUM_BADGES_BOOST_SPDEF + multipliers[DEF_MULT] = (multipliers[DEF_MULT]*1.1).round + end + end + end + # Multi-targeting attacks + if numTargets>1 + multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*0.75).round + end + # Weather + case @battle.pbWeather + when PBWeather::Sun, PBWeather::HarshSun + if isConst?(type,PBTypes,:FIRE) + multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*1.5).round + elsif isConst?(type,PBTypes,:WATER) + multipliers[FINAL_DMG_MULT] /= 2 + end + when PBWeather::Rain, PBWeather::HeavyRain + if isConst?(type,PBTypes,:FIRE) + multipliers[FINAL_DMG_MULT] /= 2 + elsif isConst?(type,PBTypes,:WATER) + multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*1.5).round + end + when PBWeather::Sandstorm + if target.pbHasType?(:ROCK) && specialMove? && @function!="122" # Psyshock + multipliers[DEF_MULT] = (multipliers[DEF_MULT]*1.5).round + end + end + # Critical hits + if target.damageState.critical + if NEWEST_BATTLE_MECHANICS + multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*1.5).round + else + multipliers[FINAL_DMG_MULT] *= 2 + end + end + # Random variance + if !self.is_a?(PokeBattle_Confusion) + random = 85+@battle.pbRandom(16) + multipliers[FINAL_DMG_MULT] *= random/100.0 + end + # STAB + if type>=0 && user.pbHasType?(type) + if user.hasActiveAbility?(:ADAPTABILITY) + multipliers[FINAL_DMG_MULT] *= 2 + else + multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*1.5).round + end + end + # Type effectiveness + multipliers[FINAL_DMG_MULT] *= target.damageState.typeMod.to_f/PBTypeEffectiveness::NORMAL_EFFECTIVE + multipliers[FINAL_DMG_MULT] = multipliers[FINAL_DMG_MULT].round + # Burn + if user.status==PBStatuses::BURN && physicalMove? && damageReducedByBurn? && + !user.hasActiveAbility?(:GUTS) + multipliers[FINAL_DMG_MULT] /= 2 + end + # Aurora Veil, Reflect, Light Screen + if !ignoresReflect? && !target.damageState.critical && + !user.hasActiveAbility?(:INFILTRATOR) + if target.pbOwnSide.effects[PBEffects::AuroraVeil]>0 + if @battle.pbSideBattlerCount(target)>1 + multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*2/3).round + else + multipliers[FINAL_DMG_MULT] /= 2 + end + elsif target.pbOwnSide.effects[PBEffects::Reflect]>0 && physicalMove? + if @battle.pbSideBattlerCount(target)>1 + multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*2/3).round + else + multipliers[FINAL_DMG_MULT] /= 2 + end + elsif target.pbOwnSide.effects[PBEffects::LightScreen]>0 && specialMove? + if @battle.pbSideBattlerCount(target)>1 + multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*2/3).round + else + multipliers[FINAL_DMG_MULT] /= 2 + end + end + end + # Minimize + if target.effects[PBEffects::Minimize] && tramplesMinimize?(2) + multipliers[FINAL_DMG_MULT] *= 2 + end + # Move-specific base damage modifiers + multipliers[BASE_DMG_MULT] = pbBaseDamageMultiplier(multipliers[BASE_DMG_MULT],user,target) + # Move-specific final damage modifiers + multipliers[FINAL_DMG_MULT] = pbModifyDamage(multipliers[FINAL_DMG_MULT],user,target) + end + + #============================================================================= + # Additional effect chance + #============================================================================= + def pbAdditionalEffectChance(user,target,effectChance=0) + return 0 if target.hasActiveAbility?(:SHIELDDUST) && !@battle.moldBreaker + ret = (effectChance>0) ? effectChance : @addlEffect + if NEWEST_BATTLE_MECHANICS || @function!="0A4" # Secret Power + ret *= 2 if user.hasActiveAbility?(:SERENEGRACE) || + user.pbOwnSide.effects[PBEffects::Rainbow]>0 + end + ret = 100 if $DEBUG && Input.press?(Input::CTRL) + return ret + end + + # NOTE: Flinching caused by a move's effect is applied in that move's code, + # not here. + def pbFlinchChance(user,target) + return 0 if flinchingMove? + return 0 if target.hasActiveAbility?(:SHIELDDUST) && !@battle.moldBreaker + ret = 0 + if user.hasActiveAbility?(:STENCH,true) + ret = 10 + elsif user.hasActiveItem?([:KINGSROCK,:RAZORFANG],true) + ret = 10 + end + ret *= 2 if user.hasActiveAbility?(:SERENEGRACE) || + user.pbOwnSide.effects[PBEffects::Rainbow]>0 + return ret + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/002_Move/004_Move_Effects_Generic.rb b/Data/Scripts/011_Battle/002_Move/004_Move_Effects_Generic.rb new file mode 100644 index 000000000..c3c02d709 --- /dev/null +++ b/Data/Scripts/011_Battle/002_Move/004_Move_Effects_Generic.rb @@ -0,0 +1,714 @@ +#=============================================================================== +# Superclass that handles moves using a non-existent function code. +# Damaging moves just do damage with no additional effect. +# Status moves always fail. +#=============================================================================== +class PokeBattle_UnimplementedMove < PokeBattle_Move + def pbMoveFailed?(user,targets) + if statusMove? + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end +end + + + +#=============================================================================== +# Pseudomove for confusion damage. +#=============================================================================== +class PokeBattle_Confusion < PokeBattle_Move + def initialize(battle,move) + @battle = battle + @realMove = move + @id = 0 + @name = "" + @function = "000" + @baseDamage = 40 + @type = -1 + @category = 0 + @accuracy = 100 + @pp = -1 + @target = 0 + @priority = 0 + @flags = "" + @addlEffect = 0 + @calcType = -1 + @powerBoost = false + @snatched = false + end + + def physicalMove?(thisType=nil); return true; end + def specialMove?(thisType=nil); return false; end + def pbCritialOverride(user,target); return -1; end +end + + + +#=============================================================================== +# Implements the move Struggle. +# For cases where the real move named Struggle is not defined. +#=============================================================================== +class PokeBattle_Struggle < PokeBattle_Move + def initialize(battle,move) + @battle = battle + @realMove = nil # Not associated with a move + @id = (move) ? move.id : -1 # Doesn't work if 0 + @name = (move) ? PBMoves.getName(@id) : _INTL("Struggle") + @function = "002" + @baseDamage = 50 + @type = -1 + @category = 0 + @accuracy = 0 + @pp = -1 + @target = 0 + @priority = 0 + @flags = "" + @addlEffect = 0 + @calcType = -1 + @powerBoost = false + @snatched = false + end + + def physicalMove?(thisType=nil); return true; end + def specialMove?(thisType=nil); return false; end + + def pbEffectAfterAllHits(user,target) + return if target.damageState.unaffected + user.pbReduceHP((user.totalhp/4.0).round,false) + @battle.pbDisplay(_INTL("{1} is damaged by recoil!",user.pbThis)) + user.pbItemHPHealCheck + end +end + + + +#=============================================================================== +# Generic status problem-inflicting classes. +#=============================================================================== +class PokeBattle_SleepMove < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + return false if damagingMove? + return !target.pbCanSleep?(user,true,self) + end + + def pbEffectAgainstTarget(user,target) + return if damagingMove? + target.pbSleep + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + target.pbSleep if target.pbCanSleep?(user,false,self) + end +end + + + +class PokeBattle_PoisonMove < PokeBattle_Move + def initialize(battle,move) + super + @toxic = false + end + + def pbFailsAgainstTarget?(user,target) + return false if damagingMove? + return !target.pbCanPoison?(user,true,self) + end + + def pbEffectAgainstTarget(user,target) + return if damagingMove? + target.pbPoison(user,nil,@toxic) + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + target.pbPoison(user,nil,@toxic) if target.pbCanPoison?(user,false,self) + end +end + + + +class PokeBattle_ParalysisMove < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + return false if damagingMove? + return !target.pbCanParalyze?(user,true,self) + end + + def pbEffectAgainstTarget(user,target) + return if damagingMove? + target.pbParalyze(user) + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + target.pbParalyze(user) if target.pbCanParalyze?(user,false,self) + end +end + + + +class PokeBattle_BurnMove < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + return false if damagingMove? + return !target.pbCanBurn?(user,true,self) + end + + def pbEffectAgainstTarget(user,target) + return if damagingMove? + target.pbBurn(user) + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + target.pbBurn(user) if target.pbCanBurn?(user,false,self) + end +end + + + +class PokeBattle_FreezeMove < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + return false if damagingMove? + return !target.pbCanFreeze?(user,true,self) + end + + def pbEffectAgainstTarget(user,target) + return if damagingMove? + target.pbFreeze + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + target.pbFreeze if target.pbCanFreeze?(user,false,self) + end +end + + + +#=============================================================================== +# Other problem-causing classes. +#=============================================================================== +class PokeBattle_FlinchMove < PokeBattle_Move + def flinchingMove?; return true; end + + def pbEffectAgainstTarget(user,target) + return if damagingMove? + target.pbFlinch(user) + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + target.pbFlinch(user) + end +end + + + +class PokeBattle_ConfuseMove < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + return false if damagingMove? + return !target.pbCanConfuse?(user,true,self) + end + + def pbEffectAgainstTarget(user,target) + return if damagingMove? + target.pbConfuse + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + return if !target.pbCanConfuse?(user,false,self) + target.pbConfuse + end +end + + + +#=============================================================================== +# Generic user's stat increase/decrease classes. +#=============================================================================== +class PokeBattle_StatUpMove < PokeBattle_Move + def pbMoveFailed?(user,targets) + return false if damagingMove? + return !user.pbCanRaiseStatStage?(@statUp[0],user,self,true) + end + + def pbEffectGeneral(user) + return if damagingMove? + user.pbRaiseStatStage(@statUp[0],@statUp[1],user) + end + + def pbAdditionalEffect(user,target) + if user.pbCanRaiseStatStage?(@statUp[0],user,self) + user.pbRaiseStatStage(@statUp[0],@statUp[1],user) + end + end +end + + + +class PokeBattle_MultiStatUpMove < PokeBattle_Move + def pbMoveFailed?(user,targets) + return false if damagingMove? + failed = true + for i in 0...@statUp.length/2 + next if !user.pbCanRaiseStatStage?(@statUp[i*2],user,self) + failed = false + break + end + if failed + @battle.pbDisplay(_INTL("{1}'s stats won't go any higher!",user.pbThis)) + return true + end + return false + end + + def pbEffectGeneral(user) + return if damagingMove? + showAnim = true + for i in 0...@statUp.length/2 + next if !user.pbCanRaiseStatStage?(@statUp[i*2],user,self) + if user.pbRaiseStatStage(@statUp[i*2],@statUp[i*2+1],user,showAnim) + showAnim = false + end + end + end + + def pbAdditionalEffect(user,target) + showAnim = true + for i in 0...@statUp.length/2 + next if !user.pbCanRaiseStatStage?(@statUp[i*2],user,self) + if user.pbRaiseStatStage(@statUp[i*2],@statUp[i*2+1],user,showAnim) + showAnim = false + end + end + end +end + + + +class PokeBattle_StatDownMove < PokeBattle_Move + def pbEffectWhenDealingDamage(user,target) + return if @battle.pbAllFainted?(target.idxOwnSide) + showAnim = true + for i in 0...@statDown.length/2 + next if !user.pbCanLowerStatStage?(@statDown[i*2],user,self) + if user.pbLowerStatStage(@statDown[i*2],@statDown[i*2+1],user,showAnim) + showAnim = false + end + end + end +end + + + +#=============================================================================== +# Generic target's stat increase/decrease classes. +#=============================================================================== +class PokeBattle_TargetStatDownMove < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + return false if damagingMove? + return !target.pbCanLowerStatStage?(@statDown[0],user,self,true) + end + + def pbEffectAgainstTarget(user,target) + return if damagingMove? + target.pbLowerStatStage(@statDown[0],@statDown[1],user) + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + return if !target.pbCanLowerStatStage?(@statDown[0],user,self) + target.pbLowerStatStage(@statDown[0],@statDown[1],user) + end +end + + + +class PokeBattle_TargetMultiStatDownMove < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + return false if damagingMove? + failed = true + for i in 0...@statDown.length/2 + next if !target.pbCanLowerStatStage?(@statDown[i*2],user,self) + failed = false + break + end + if failed + # NOTE: It's a bit of a faff to make sure the appropriate failure message + # is shown here, I know. + canLower = false + if target.hasActiveAbility?(:CONTRARY) && !@battle.moldBreaker + for i in 0...@statDown.length/2 + next if target.statStageAtMax?(@statDown[i*2]) + canLower = true + break + end + @battle.pbDisplay(_INTL("{1}'s stats won't go any higher!",user.pbThis)) if !canLower + else + for i in 0...@statDown.length/2 + next if target.statStageAtMin?(@statDown[i*2]) + canLower = true + break + end + @battle.pbDisplay(_INTL("{1}'s stats won't go any lower!",user.pbThis)) if !canLower + end + if canLower + target.pbCanLowerStatStage?(@statDown[0],user,self,true) + end + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + return if damagingMove? + showAnim = true + for i in 0...@statDown.length/2 + next if !target.pbCanLowerStatStage?(@statDown[i*2],user,self) + if target.pbLowerStatStage(@statDown[i*2],@statDown[i*2+1],user,showAnim) + showAnim = false + end + end + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + showAnim = true + for i in 0...@statDown.length/2 + next if !target.pbCanLowerStatStage?(@statDown[i*2],user,self) + if target.pbLowerStatStage(@statDown[i*2],@statDown[i*2+1],user,showAnim) + showAnim = false + end + end + end +end + + + +#=============================================================================== +# Fixed damage-inflicting move. +#=============================================================================== +class PokeBattle_FixedDamageMove < PokeBattle_Move + def pbFixedDamage(user,target); return 1; end + + def pbCalcDamage(user,target,numTargets=1) + target.damageState.critical = false + target.damageState.calcDamage = pbFixedDamage(user,target) + target.damageState.calcDamage = 1 if target.damageState.calcDamage<1 + end +end + + + +#=============================================================================== +# Two turn move. +#=============================================================================== +class PokeBattle_TwoTurnMove < PokeBattle_Move + def chargingTurnMove?; return true; end + + # user.effects[PBEffects::TwoTurnAttack] is set to the move's ID if this + # method returns true, or 0 if false. + # Non-zero means the charging turn. 0 means the attacking turn. + def pbIsChargingTurn?(user) + @powerHerb = false + @chargingTurn = false # Assume damaging turn by default + @damagingTurn = true + # 0 at start of charging turn, move's ID at start of damaging turn + if user.effects[PBEffects::TwoTurnAttack]==0 + @powerHerb = user.hasActiveItem?(:POWERHERB) + @chargingTurn = true + @damagingTurn = @powerHerb + end + return !@damagingTurn # Deliberately not "return @chargingTurn" + end + + def pbDamagingMove? # Stops damage being dealt in the first (charging) turn + return false if !@damagingTurn + return super + end + + def pbAccuracyCheck(user,target) + return true if !@damagingTurn + return super + end + + def pbInitialEffect(user,targets,hitNum) + pbChargingTurnMessage(user,targets) if @chargingTurn + if @chargingTurn && @damagingTurn # Move only takes one turn to use + pbShowAnimation(@id,user,targets,1) # Charging anim + targets.each { |b| pbChargingTurnEffect(user,b) } + if @powerHerb + # Moves that would make the user semi-invulnerable will hide the user + # after the charging animation, so the "UseItem" animation shouldn't show + # for it + if !["0C9","0CA","0CB","0CC","0CD","0CE","14D"].include?(@function) + @battle.pbCommonAnimation("UseItem",user) + end + @battle.pbDisplay(_INTL("{1} became fully charged due to its Power Herb!",user.pbThis)) + user.pbConsumeItem + end + end + pbAttackingTurnMessage(user,targets) if @damagingTurn + end + + def pbChargingTurnMessage(user,targets) + @battle.pbDisplay(_INTL("{1} began charging up!",user.pbThis)) + end + + def pbAttackingTurnMessage(user,targets) + end + + def pbChargingTurnEffect(user,target) + # Skull Bash/Sky Drop are the only two-turn moves with an effect here, and + # the latter just records the target is being Sky Dropped + end + + def pbAttackingTurnEffect(user,target) + end + + def pbEffectAgainstTarget(user,target) + if @damagingTurn; pbAttackingTurnEffect(user,target) + elsif @chargingTurn; pbChargingTurnEffect(user,target) + end + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + hitNum = 1 if @chargingTurn && !@damagingTurn # Charging anim + super + end +end + + + +#=============================================================================== +# Healing move. +#=============================================================================== +class PokeBattle_HealingMove < PokeBattle_Move + def healingMove?; return true; end + def pbHealAmount(user); return 1; end + + def pbMoveFailed?(user,targets) + if user.hp==user.totalhp + @battle.pbDisplay(_INTL("{1}'s HP is full!",user.pbThis)) + return true + end + return false + end + + def pbEffectGeneral(user) + amt = pbHealAmount(user) + user.pbRecoverHP(amt) + @battle.pbDisplay(_INTL("{1}'s HP was restored.",user.pbThis)) + end +end + + + +#=============================================================================== +# Recoil move. +#=============================================================================== +class PokeBattle_RecoilMove < PokeBattle_Move + def recoilMove?; return true; end + def pbRecoilDamage(user,target); return 1; end + + def pbEffectAfterAllHits(user,target) + return if target.damageState.unaffected + return if !user.takesIndirectDamage? + return if user.hasActiveAbility?(:ROCKHEAD) + amt = pbRecoilDamage(user,target) + amt = 1 if amt<1 + user.pbReduceHP(amt,false) + @battle.pbDisplay(_INTL("{1} is damaged by recoil!",user.pbThis)) + user.pbItemHPHealCheck + end +end + + + +#=============================================================================== +# Protect move. +#=============================================================================== +class PokeBattle_ProtectMove < PokeBattle_Move + def initialize(battle,move) + super + @sidedEffect = false + end + + def pbChangeUsageCounters(user,specialUsage) + oldVal = user.effects[PBEffects::ProtectRate] + super + user.effects[PBEffects::ProtectRate] = oldVal + end + + def pbMoveFailed?(user,targets) + if @sidedEffect + if user.pbOwnSide.effects[@effect] + user.effects[PBEffects::ProtectRate] = 1 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + elsif user.effects[@effect] + user.effects[PBEffects::ProtectRate] = 1 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if !(@sidedEffect && NEWEST_BATTLE_MECHANICS) && + user.effects[PBEffects::ProtectRate]>1 && + @battle.pbRandom(user.effects[PBEffects::ProtectRate])!=0 + user.effects[PBEffects::ProtectRate] = 1 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if pbMoveFailedLastInRound?(user) + user.effects[PBEffects::ProtectRate] = 1 + return true + end + return false + end + + def pbEffectGeneral(user) + if @sidedEffect + user.pbOwnSide.effects[@effect] = true + else + user.effects[@effect] = true + end + user.effects[PBEffects::ProtectRate] *= (NEWEST_BATTLE_MECHANICS) ? 3 : 2 + pbProtectMessage(user) + end + + def pbProtectMessage(user) + if @sidedEffect + @battle.pbDisplay(_INTL("{1} protected {2}!",@name,user.pbTeam(true))) + else + @battle.pbDisplay(_INTL("{1} protected itself!",user.pbThis)) + end + end +end + + + +#=============================================================================== +# Weather-inducing move. +#=============================================================================== +class PokeBattle_WeatherMove < PokeBattle_Move + def initialize(battle,move) + super + @weatherType = PBWeather::None + end + def pbMoveFailed?(user,targets) + case @battle.field.weather + when PBWeather::HarshSun + @battle.pbDisplay(_INTL("The extremely harsh sunlight was not lessened at all!")) + return true + when PBWeather::HeavyRain + @battle.pbDisplay(_INTL("There is no relief from this heavy rain!")) + return true + when PBWeather::StrongWinds + @battle.pbDisplay(_INTL("The mysterious air current blows on regardless!")) + return true + when @weatherType + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + @battle.pbStartWeather(user,@weatherType,true,false) + end +end + + + +#=============================================================================== +# Pledge move. +#=============================================================================== +class PokeBattle_PledgeMove < PokeBattle_Move + def pbOnStartUse(user,targets) + @pledgeSetup = false; @pledgeCombo = false; @pledgeOtherUser = nil + @comboEffect = nil; @overrideType = nil; @overrideAnim = nil + # Check whether this is the use of a combo move + @combos.each do |i| + next if i[0]!=user.effects[PBEffects::FirstPledge] + @battle.pbDisplay(_INTL("The two moves have become one! It's a combined move!")) + @pledgeCombo = true + @comboEffect = i[1]; @overrideType = i[2]; @overrideAnim = i[3] + break + end + return if @pledgeCombo + # Check whether this is the setup of a combo move + user.eachAlly do |b| + next if @battle.choices[b.index][0]!=:UseMove || b.movedThisRound? + move = @battle.choices[b.index][2] + next if !move || move.id<=0 + @combos.each do |i| + next if i[0]!=move.function + @pledgeSetup = true + @pledgeOtherUser = b + break + end + break if @pledgeSetup + end + end + + def pbDamagingMove? + return false if @pledgeSetup + return super + end + + def pbBaseType(user) + return @overrideType if @overrideType!=nil + return super + end + + def pbBaseDamage(baseDmg,user,target) + baseDmg *= 2 if @pledgeCombo + return baseDmg + end + + def pbEffectGeneral(user) + user.effects[PBEffects::FirstPledge] = 0 + return if !@pledgeSetup + @battle.pbDisplay(_INTL("{1} is waiting for {2}'s move...", + user.pbThis,@pledgeOtherUser.pbThis(true))) + @pledgeOtherUser.effects[PBEffects::FirstPledge] = @function + @pledgeOtherUser.effects[PBEffects::MoveNext] = true + user.lastMoveFailed = true # Treated as a failure for Stomping Tantrum + end + + def pbEffectAfterAllHits(user,target) + return if !@pledgeCombo + msg = nil; animName = nil + case @comboEffect + when :SeaOfFire # Grass + Fire + if user.pbOpposingSide.effects[PBEffects::SeaOfFire]==0 + user.pbOpposingSide.effects[PBEffects::SeaOfFire] = 4 + msg = _INTL("A sea of fire enveloped {1}!",user.pbOpposingTeam(true)) + animName = (user.opposes?) ? "SeaOfFire" : "SeaOfFireOpp" + end + when :Rainbow # Fire + Water + if user.pbOwnSide.effects[PBEffects::Rainbow]==0 + user.pbOwnSide.effects[PBEffects::Rainbow] = 4 + msg = _INTL("A rainbow appeared in the sky on {1}'s side!",user.pbTeam(true)) + animName = (user.opposes?) ? "RainbowOpp" : "Rainbow" + end + when :Swamp # Water + Grass + if user.pbOpposingSide.effects[PBEffects::Swamp]==0 + user.pbOpposingSide.effects[PBEffects::Swamp] = 4 + msg = _INTL("A swamp enveloped {1}!",user.pbOpposingTeam(true)) + animName = (user.opposes?) ? "Swamp" : "SwampOpp" + end + end + @battle.pbDisplay(msg) if msg + @battle.pbCommonAnimation(animName) if animName + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + return if @pledgeSetup # No animation for setting up + id = @overrideAnim if @overrideAnim!=nil + return super + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/002_Move/005_Move_Effects_000-07F.rb b/Data/Scripts/011_Battle/002_Move/005_Move_Effects_000-07F.rb new file mode 100644 index 000000000..e8a489828 --- /dev/null +++ b/Data/Scripts/011_Battle/002_Move/005_Move_Effects_000-07F.rb @@ -0,0 +1,2924 @@ +#=============================================================================== +# No additional effect. +#=============================================================================== +class PokeBattle_Move_000 < PokeBattle_Move +end + + + +#=============================================================================== +# Does absolutely nothing. (Splash) +#=============================================================================== +class PokeBattle_Move_001 < PokeBattle_Move + def unusableInGravity?; return true; end + + def pbEffectGeneral(user) + @battle.pbDisplay(_INTL("But nothing happened!")) + end +end + + + +#=============================================================================== +# Struggle, if defined as a move in moves.txt. Typically it won't be. +#=============================================================================== +class PokeBattle_Move_002 < PokeBattle_Struggle +end + + + +#=============================================================================== +# Puts the target to sleep. +#=============================================================================== +class PokeBattle_Move_003 < PokeBattle_SleepMove + def pbMoveFailed?(user,targets) + if NEWEST_BATTLE_MECHANICS && isConst?(@id,PBMoves,:DARKVOID) + if !isConst?(user.species,PBSpecies,:DARKRAI) && + !isConst?(user.effects[PBEffects::TransformSpecies],PBSpecies,:DARKRAI) + @battle.pbDisplay(_INTL("But {1} can't use the move!",user.pbThis)) + return true + end + end + return false + end + + def pbEndOfMoveUsageEffect(user,targets,numHits,switchedBattlers) + return if numHits==0 + return if user.fainted? || user.effects[PBEffects::Transform] + return if !isConst?(@id,PBMoves,:RELICSONG) + return if !isConst?(user.species,PBSpecies,:MELOETTA) + return if user.hasActiveAbility?(:SHEERFORCE) && @addlEffect>0 + newForm = (oldForm+1)%2 + user.pbChangeForm(newForm,_INTL("{1} transformed!",user.pbThis)) + end +end + + + +#=============================================================================== +# Makes the target drowsy; it falls asleep at the end of the next turn. (Yawn) +#=============================================================================== +class PokeBattle_Move_004 < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + if target.effects[PBEffects::Yawn]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return true if !target.pbCanSleep?(user,true,self) + return false + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::Yawn] = 2 + @battle.pbDisplay(_INTL("{1} made {2} drowsy!",user.pbThis,target.pbThis(true))) + end +end + + + +#=============================================================================== +# Poisons the target. +#=============================================================================== +class PokeBattle_Move_005 < PokeBattle_PoisonMove +end + + + +#=============================================================================== +# Badly poisons the target. (Poison Fang, Toxic) +#=============================================================================== +class PokeBattle_Move_006 < PokeBattle_PoisonMove + def initialize(battle,move) + super + @toxic = true + end + + def pbOverrideSuccessCheckPerHit(user,target) + return (NEWEST_BATTLE_MECHANICS && statusMove? && user.pbHasType?(:POISON)) + end +end + + + +#=============================================================================== +# Paralyzes the target. +# Thunder Wave: Doesn't affect target if move's type has no effect on it. +# Body Slam: Does double damage and has perfect accuracy if target is Minimized. +#=============================================================================== +class PokeBattle_Move_007 < PokeBattle_ParalysisMove + def tramplesMinimize?(param=1) + # Perfect accuracy and double damage (for Body Slam only) + return NEWEST_BATTLE_MECHANICS if isConst?(@id,PBMoves,:BODYSLAM) + return super + end + + def pbFailsAgainstTarget?(user,target) + if isConst?(@id,PBMoves,:THUNDERWAVE) && PBTypes.ineffective?(target.damageState.typeMod) + @battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true))) + return true + end + return super + end +end + + + +#=============================================================================== +# Paralyzes the target. Accuracy perfect in rain, 50% in sunshine. Hits some +# semi-invulnerable targets. (Thunder) +#=============================================================================== +class PokeBattle_Move_008 < PokeBattle_ParalysisMove + def hitsFlyingTargets?; return true; end + + def pbBaseAccuracy(user,target) + case @battle.pbWeather + when PBWeather::Sun, PBWeather::HarshSun + return 50 + when PBWeather::Rain, PBWeather::HeavyRain + return 0 + end + return super + end +end + + + +#=============================================================================== +# Paralyzes the target. May cause the target to flinch. (Thunder Fang) +#=============================================================================== +class PokeBattle_Move_009 < PokeBattle_Move + def flinchingMove?; return true; end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + chance = pbAdditionalEffectChance(user,target,10) + return if chance==0 + if @battle.pbRandom(100)1 || user.lastRoundMoved>=0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end +end + + + +#=============================================================================== +# Confuses the target. +#=============================================================================== +class PokeBattle_Move_013 < PokeBattle_ConfuseMove +end + + + +#=============================================================================== +# Confuses the target. Chance of causing confusion depends on the cry's volume. +# Confusion chance is 0% if user doesn't have a recorded cry. (Chatter) +#=============================================================================== +class PokeBattle_Move_014 < PokeBattle_ConfuseMove + def pbOnStartUse(user,targets) + @chatterChance = 0 + if user.pokemon && user.pokemon.chatter + # Intensity can be 0-127, so return value is 0-10 + @chatterChance = 10*user.pokemon.chatter.intensity/127 + end + end + + def addlEffect + return @chatterChance if !NEWEST_BATTLE_MECHANICS + return super + end +end + + + +#=============================================================================== +# Confuses the target. Accuracy perfect in rain, 50% in sunshine. Hits some +# semi-invulnerable targets. (Hurricane) +#=============================================================================== +class PokeBattle_Move_015 < PokeBattle_ConfuseMove + def hitsFlyingTargets?; return true; end + + def pbBaseAccuracy(user,target) + case @battle.pbWeather + when PBWeather::Sun, PBWeather::HarshSun + return 50 + when PBWeather::Rain, PBWeather::HeavyRain + return 0 + end + return super + end +end + + + +#=============================================================================== +# Attracts the target. (Attract) +#=============================================================================== +class PokeBattle_Move_016 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbFailsAgainstTarget?(user,target) + return false if damagingMove? + return true if !target.pbCanAttract?(user) + return true if pbMoveFailedAromaVeil?(user,target) + return false + end + + def pbEffectAgainstTarget(user,target) + return if damagingMove? + target.pbAttract(user) + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + target.pbAttract(user) if target.pbCanAttract?(user,false) + end +end + + + +#=============================================================================== +# Burns, freezes or paralyzes the target. (Tri Attack) +#=============================================================================== +class PokeBattle_Move_017 < PokeBattle_Move + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + case @battle.pbRandom(3) + when 0; target.pbBurn(user) if target.pbCanBurn?(user,false,self) + when 1; target.pbFreeze if target.pbCanFreeze?(user,false,self) + when 2; target.pbParalyze(user) if target.pbCanParalyze?(user,false,self) + end + end +end + + + +#=============================================================================== +# Cures user of burn, poison and paralysis. (Refresh) +#=============================================================================== +class PokeBattle_Move_018 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.status!=PBStatuses::BURN && + user.status!=PBStatuses::POISON && + user.status!=PBStatuses::PARALYSIS + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + t = user.status + user.pbCureStatus(false) + case t + when PBStatuses::BURN + @battle.pbDisplay(_INTL("{1} healed its burn!",user.pbThis)) + when PBStatuses::POISON + @battle.pbDisplay(_INTL("{1} cured its poisoning!",user.pbThis)) + when PBStatuses::PARALYSIS + @battle.pbDisplay(_INTL("{1} cured its paralysis!",user.pbThis)) + end + end +end + + + +#=============================================================================== +# Cures all party Pokémon of permanent status problems. (Aromatherapy, Heal Bell) +#=============================================================================== +# NOTE: In Gen 5, this move should have a target of UserSide, while in Gen 6+ it +# should have a target of UserAndAllies. This is because, in Gen 5, this +# move shouldn't call def pbSuccessCheckAgainstTarget for each Pokémon +# currently in battle that will be affected by this move (i.e. allies +# aren't protected by their substitute/ability/etc., but they are in Gen +# 6+). We achieve this by not targeting any battlers in Gen 5, since +# pbSuccessCheckAgainstTarget is only called for targeted battlers. +class PokeBattle_Move_019 < PokeBattle_Move + def worksWithNoTargets?; return true; end + + def pbMoveFailed?(user,targets) + failed = true + @battle.eachSameSideBattler(user) do |b| + next if b.status==PBStatuses::NONE + failed = false + break + end + if !failed + @battle.pbParty(user.index).each do |pkmn| + next if !pkmn || !pkmn.able? || pkmn.status==PBStatuses::NONE + failed = false + break + end + end + if failed + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + return target.status==PBStatuses::NONE + end + + def pbAromatherapyHeal(pkmn,battler=nil) + oldStatus = (battler) ? battler.status : pkmn.status + curedName = (battler) ? battler.pbThis : pkmn.name + if battler + battler.pbCureStatus(false) + else + pkmn.status = PBStatuses::NONE + pkmn.statusCount = 0 + end + case oldStatus + when PBStatuses::SLEEP + @battle.pbDisplay(_INTL("{1} was woken from sleep.",curedName)) + when PBStatuses::POISON + @battle.pbDisplay(_INTL("{1} was cured of its poisoning.",curedName)) + when PBStatuses::BURN + @battle.pbDisplay(_INTL("{1}'s burn was healed.",curedName)) + when PBStatuses::PARALYSIS + @battle.pbDisplay(_INTL("{1} was cured of paralysis.",curedName)) + when PBStatuses::FROZEN + @battle.pbDisplay(_INTL("{1} was thawed out.",curedName)) + end + end + + def pbEffectAgainstTarget(user,target) + # Cure all Pokémon in battle on the user's side. + pbAromatherapyHeal(target.pokemon,target) + end + + def pbEffectGeneral(user) + # Cure all Pokémon in battle on the user's side. For the benefit of the Gen + # 5 version of this move, to make Pokémon out in battle get cured first. + if pbTarget(user)!=PBTargets::UserAndAllies + @battle.eachSameSideBattler(user) do |b| + next if b.status==PBStatuses::NONE + pbAromatherapyHeal(b.pokemon,b) + end + end + # Cure all Pokémon in the user's and partner trainer's party. + # NOTE: This intentionally affects the partner trainer's inactive Pokémon + # too. + @battle.pbParty(user.index).each_with_index do |pkmn,i| + next if !pkmn || !pkmn.able? || pkmn.status==PBStatuses::NONE + next if @battle.pbFindBattler(i,user) # Skip Pokémon in battle + pbAromatherapyHeal(pkmn) + end + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + super + if isConst?(@id,PBMoves,:AROMATHERAPY) + @battle.pbDisplay(_INTL("A soothing aroma wafted through the area!")) + elsif isConst?(@id,PBMoves,:HEALBELL) + @battle.pbDisplay(_INTL("A bell chimed!")) + end + end +end + + + +#=============================================================================== +# Safeguards the user's side from being inflicted with status problems. +# (Safeguard) +#=============================================================================== +class PokeBattle_Move_01A < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.pbOwnSide.effects[PBEffects::Safeguard]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.pbOwnSide.effects[PBEffects::Safeguard] = 5 + @battle.pbDisplay(_INTL("{1} became cloaked in a mystical veil!",user.pbTeam)) + end +end + + + +#=============================================================================== +# User passes its status problem to the target. (Psycho Shift) +#=============================================================================== +class PokeBattle_Move_01B < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.status==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + if !target.pbCanInflictStatus?(user.status,user,false,self) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + msg = "" + case user.status + when PBStatuses::SLEEP + target.pbSleep + msg = _INTL("{1} woke up.",user.pbThis) + when PBStatuses::POISON + target.pbPoison(user,nil,user.statusCount!=0) + msg = _INTL("{1} was cured of its poisoning.",user.pbThis) + when PBStatuses::BURN + target.pbBurn(user) + msg = _INTL("{1}'s burn was healed.",user.pbThis) + when PBStatuses::PARALYSIS + target.pbParalyze(user) + msg = _INTL("{1} was cured of paralysis.",user.pbThis) + when PBStatuses::FROZEN + target.pbFreeze + msg = _INTL("{1} was thawed out.",user.pbThis) + end + if msg!="" + user.pbCureStatus(false) + @battle.pbDisplay(msg) + end + end +end + + + +#=============================================================================== +# Increases the user's Attack by 1 stage. +#=============================================================================== +class PokeBattle_Move_01C < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::ATTACK,1] + end +end + + + +#=============================================================================== +# Increases the user's Defense by 1 stage. (Harden, Steel Wing, Withdraw) +#=============================================================================== +class PokeBattle_Move_01D < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::DEFENSE,1] + end +end + + + +#=============================================================================== +# Increases the user's Defense by 1 stage. User curls up. (Defense Curl) +#=============================================================================== +class PokeBattle_Move_01E < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::DEFENSE,1] + end + + def pbEffectGeneral(user) + user.effects[PBEffects::DefenseCurl] = true + super + end +end + + + +#=============================================================================== +# Increases the user's Speed by 1 stage. (Flame Charge) +#=============================================================================== +class PokeBattle_Move_01F < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::SPEED,1] + end +end + + + +#=============================================================================== +# Increases the user's Special Attack by 1 stage. (Charge Beam, Fiery Dance) +#=============================================================================== +class PokeBattle_Move_020 < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::SPATK,1] + end +end + + + +#=============================================================================== +# Increases the user's Special Defense by 1 stage. +# Charges up user's next attack if it is Electric-type. (Charge) +#=============================================================================== +class PokeBattle_Move_021 < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::SPDEF,1] + end + + def pbEffectGeneral(user) + user.effects[PBEffects::Charge] = 2 + @battle.pbDisplay(_INTL("{1} began charging power!",user.pbThis)) + super + end +end + + + +#=============================================================================== +# Increases the user's evasion by 1 stage. (Double Team) +#=============================================================================== +class PokeBattle_Move_022 < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::EVASION,1] + end +end + + + +#=============================================================================== +# Increases the user's critical hit rate. (Focus Energy) +#=============================================================================== +class PokeBattle_Move_023 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.effects[PBEffects::FocusEnergy]>=2 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.effects[PBEffects::FocusEnergy] = 2 + @battle.pbDisplay(_INTL("{1} is getting pumped!",user.pbThis)) + end +end + + + +#=============================================================================== +# Increases the user's Attack and Defense by 1 stage each. (Bulk Up) +#=============================================================================== +class PokeBattle_Move_024 < PokeBattle_MultiStatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::ATTACK,1,PBStats::DEFENSE,1] + end +end + + + +#=============================================================================== +# Increases the user's Attack, Defense and accuracy by 1 stage each. (Coil) +#=============================================================================== +class PokeBattle_Move_025 < PokeBattle_MultiStatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::ATTACK,1,PBStats::DEFENSE,1,PBStats::ACCURACY,1] + end +end + + + +#=============================================================================== +# Increases the user's Attack and Speed by 1 stage each. (Dragon Dance) +#=============================================================================== +class PokeBattle_Move_026 < PokeBattle_MultiStatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::ATTACK,1,PBStats::SPEED,1] + end +end + + + +#=============================================================================== +# Increases the user's Attack and Special Attack by 1 stage each. (Work Up) +#=============================================================================== +class PokeBattle_Move_027 < PokeBattle_MultiStatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::ATTACK,1,PBStats::SPATK,1] + end +end + + + +#=============================================================================== +# Increases the user's Attack and Sp. Attack by 1 stage each. +# In sunny weather, increases are 2 stages each instead. (Growth) +#=============================================================================== +class PokeBattle_Move_028 < PokeBattle_MultiStatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::ATTACK,1,PBStats::SPATK,1] + end + + def pbOnStartUse(user,targets) + increment = 1 + if @battle.pbWeather==PBWeather::Sun || + @battle.pbWeather==PBWeather::HarshSun + increment = 2 + end + @statUp[1] = @statUp[3] = increment + end +end + + + +#=============================================================================== +# Increases the user's Attack and accuracy by 1 stage each. (Hone Claws) +#=============================================================================== +class PokeBattle_Move_029 < PokeBattle_MultiStatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::ATTACK,1,PBStats::ACCURACY,1] + end +end + + + +#=============================================================================== +# Increases the user's Defense and Special Defense by 1 stage each. +# (Cosmic Power, Defend Order) +#=============================================================================== +class PokeBattle_Move_02A < PokeBattle_MultiStatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::DEFENSE,1,PBStats::SPDEF,1] + end +end + + + +#=============================================================================== +# Increases the user's Sp. Attack, Sp. Defense and Speed by 1 stage each. +# (Quiver Dance) +#=============================================================================== +class PokeBattle_Move_02B < PokeBattle_MultiStatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::SPATK,1,PBStats::SPDEF,1,PBStats::SPEED,1] + end +end + + + +#=============================================================================== +# Increases the user's Sp. Attack and Sp. Defense by 1 stage each. (Calm Mind) +#=============================================================================== +class PokeBattle_Move_02C < PokeBattle_MultiStatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::SPATK,1,PBStats::SPDEF,1] + end +end + + + +#=============================================================================== +# Increases the user's Attack, Defense, Speed, Special Attack and Special Defense +# by 1 stage each. (Ancient Power, Ominous Wind, Silver Wind) +#=============================================================================== +class PokeBattle_Move_02D < PokeBattle_MultiStatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::ATTACK,1,PBStats::DEFENSE,1, + PBStats::SPATK,1,PBStats::SPDEF,1, + PBStats::SPEED,1] + end +end + + + +#=============================================================================== +# Increases the user's Attack by 2 stages. (Swords Dance) +#=============================================================================== +class PokeBattle_Move_02E < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::ATTACK,2] + end +end + + + +#=============================================================================== +# Increases the user's Defense by 2 stages. (Acid Armor, Barrier, Iron Defense) +#=============================================================================== +class PokeBattle_Move_02F < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::DEFENSE,2] + end +end + + + +#=============================================================================== +# Increases the user's Speed by 2 stages. (Agility, Rock Polish) +#=============================================================================== +class PokeBattle_Move_030 < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::SPEED,2] + end +end + + + +#=============================================================================== +# Increases the user's Speed by 2 stages. Lowers user's weight by 100kg. +# (Autotomize) +#=============================================================================== +class PokeBattle_Move_031 < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::SPEED,2] + end + + def pbEffectGeneral(user) + if user.pbWeight+user.effects[PBEffects::WeightChange]>1 + user.effects[PBEffects::WeightChange] -= 1000 + @battle.pbDisplay(_INTL("{1} became nimble!",user.pbThis)) + end + super + end +end + + + +#=============================================================================== +# Increases the user's Special Attack by 2 stages. (Nasty Plot) +#=============================================================================== +class PokeBattle_Move_032 < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::SPATK,2] + end +end + + + +#=============================================================================== +# Increases the user's Special Defense by 2 stages. (Amnesia) +#=============================================================================== +class PokeBattle_Move_033 < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::SPDEF,2] + end +end + + + +#=============================================================================== +# Increases the user's evasion by 2 stages. Minimizes the user. (Minimize) +#=============================================================================== +class PokeBattle_Move_034 < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::EVASION,2] + end + + def pbEffectGeneral(user) + user.effects[PBEffects::Minimize] = true + super + end +end + + + +#=============================================================================== +# Decreases the user's Defense and Special Defense by 1 stage each. +# Increases the user's Attack, Speed and Special Attack by 2 stages each. +# (Shell Smash) +#=============================================================================== +class PokeBattle_Move_035 < PokeBattle_Move + def initialize(battle,move) + super + @statUp = [PBStats::ATTACK,2,PBStats::SPATK,2,PBStats::SPEED,2] + @statDown = [PBStats::DEFENSE,1,PBStats::SPDEF,1] + end + + def pbMoveFailed?(user,targets) + failed = true + for i in 0...@statUp.length/2 + if user.pbCanRaiseStatStage?(@statUp[i*2],user,self) + failed = false; break + end + end + for i in 0...@statDown.length/2 + if user.pbCanLowerStatStage?(@statDown[i*2],user,self) + failed = false; break + end + end + if failed + @battle.pbDisplay(_INTL("{1}'s stats can't be changed further!",user.pbThis)) + return true + end + return false + end + + def pbEffectGeneral(user) + showAnim = true + for i in 0...@statDown.length/2 + next if !user.pbCanLowerStatStage?(@statDown[i*2],user,self) + if user.pbLowerStatStage(@statDown[i*2],@statDown[i*2+1],user,showAnim) + showAnim = false + end + end + showAnim = true + for i in 0...@statUp.length/2 + next if !user.pbCanRaiseStatStage?(@statUp[i*2],user,self) + if user.pbRaiseStatStage(@statUp[i*2],@statUp[i*2+1],user,showAnim) + showAnim = false + end + end + end +end + + + +#=============================================================================== +# Increases the user's Speed by 2 stages, and its Attack by 1 stage. (Shift Gear) +#=============================================================================== +class PokeBattle_Move_036 < PokeBattle_MultiStatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::SPEED,2,PBStats::ATTACK,1] + end +end + + + +#=============================================================================== +# Increases one random stat of the target by 2 stages (except HP). (Acupressure) +#=============================================================================== +class PokeBattle_Move_037 < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + @statArray = [] + PBStats.eachBattleStat do |s| + @statArray.push(s) if target.pbCanRaiseStatStage?(s,user,self) + end + if @statArray.length==0 + @battle.pbDisplay(_INTL("{1}'s stats won't go any higher!",target.pbThis)) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + stat = @statArray[@battle.pbRandom(@statArray.length)] + target.pbRaiseStatStage(stat,2,user) + end +end + + + +#=============================================================================== +# Increases the user's Defense by 3 stages. (Cotton Guard) +#=============================================================================== +class PokeBattle_Move_038 < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::DEFENSE,3] + end +end + + + +#=============================================================================== +# Increases the user's Special Attack by 3 stages. (Tail Glow) +#=============================================================================== +class PokeBattle_Move_039 < PokeBattle_StatUpMove + def initialize(battle,move) + super + @statUp = [PBStats::SPATK,3] + end +end + + + +#=============================================================================== +# Reduces the user's HP by half of max, and sets its Attack to maximum. +# (Belly Drum) +#=============================================================================== +class PokeBattle_Move_03A < PokeBattle_Move + def pbMoveFailed?(user,targets) + hpLoss = [user.totalhp/2,1].max + if user.hp<=hpLoss + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return true if !user.pbCanRaiseStatStage?(PBStats::ATTACK,user,self,true) + return false + end + + def pbEffectGeneral(user) + hpLoss = [user.totalhp/2,1].max + user.pbReduceHP(hpLoss,false) + if user.hasActiveAbility?(:CONTRARY) + user.stages[PBStats::ATTACK] = -6 + @battle.pbCommonAnimation("StatDown",user) + @battle.pbDisplay(_INTL("{1} cut its own HP and minimized its Attack!",user.pbThis)) + else + user.stages[PBStats::ATTACK] = 6 + @battle.pbCommonAnimation("StatUp",user) + @battle.pbDisplay(_INTL("{1} cut its own HP and maximized its Attack!",user.pbThis)) + end + user.pbItemHPHealCheck + end +end + + + +#=============================================================================== +# Decreases the user's Attack and Defense by 1 stage each. (Superpower) +#=============================================================================== +class PokeBattle_Move_03B < PokeBattle_StatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::ATTACK,1,PBStats::DEFENSE,1] + end +end + + + +#=============================================================================== +# Decreases the user's Defense and Special Defense by 1 stage each. +# (Close Combat, Dragon Ascent) +#=============================================================================== +class PokeBattle_Move_03C < PokeBattle_StatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::DEFENSE,1,PBStats::SPDEF,1] + end +end + + + +#=============================================================================== +# Decreases the user's Defense, Special Defense and Speed by 1 stage each. +# (V-create) +#=============================================================================== +class PokeBattle_Move_03D < PokeBattle_StatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::SPEED,1,PBStats::DEFENSE,1,PBStats::SPDEF,1] + end +end + + + +#=============================================================================== +# Decreases the user's Speed by 1 stage. (Hammer Arm, Ice Hammer) +#=============================================================================== +class PokeBattle_Move_03E < PokeBattle_StatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::SPEED,1] + end +end + + + +#=============================================================================== +# Decreases the user's Special Attack by 2 stages. +#=============================================================================== +class PokeBattle_Move_03F < PokeBattle_StatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::SPATK,2] + end +end + + + +#=============================================================================== +# Increases the target's Special Attack by 1 stage. Confuses the target. (Flatter) +#=============================================================================== +class PokeBattle_Move_040 < PokeBattle_Move + def pbMoveFailed?(user,targets) + failed = true + targets.each do |b| + next if !b.pbCanRaiseStatStage?(PBStats::SPATK,user,self) && + !b.pbCanConfuse?(user,false,self) + failed = false + break + end + if failed + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + if target.pbCanRaiseStatStage?(PBStats::SPATK,user,self) + target.pbRaiseStatStage(PBStats::SPATK,1,user) + end + target.pbConfuse if target.pbCanConfuse?(user,false,self) + end +end + + + +#=============================================================================== +# Increases the target's Attack by 2 stages. Confuses the target. (Swagger) +#=============================================================================== +class PokeBattle_Move_041 < PokeBattle_Move + def pbMoveFailed?(user,targets) + failed = true + targets.each do |b| + next if !b.pbCanRaiseStatStage?(PBStats::ATTACK,user,self) && + !b.pbCanConfuse?(user,false,self) + failed = false + break + end + if failed + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + if target.pbCanRaiseStatStage?(PBStats::ATTACK,user,self) + target.pbRaiseStatStage(PBStats::ATTACK,2,user) + end + target.pbConfuse if target.pbCanConfuse?(user,false,self) + end +end + + + +#=============================================================================== +# Decreases the target's Attack by 1 stage. +#=============================================================================== +class PokeBattle_Move_042 < PokeBattle_TargetStatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::ATTACK,1] + end +end + + +#=============================================================================== +# Decreases the target's Defense by 1 stage. +#=============================================================================== +class PokeBattle_Move_043 < PokeBattle_TargetStatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::DEFENSE,1] + end +end + + + +#=============================================================================== +# Decreases the target's Speed by 1 stage. +#=============================================================================== +class PokeBattle_Move_044 < PokeBattle_TargetStatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::SPEED,1] + end + + def pbBaseDamage(baseDmg,user,target) + if isConst?(@id,PBMoves,:BULLDOZE) && @battle.field.terrain==PBBattleTerrains::Grassy + baseDmg = (baseDmg/2.0).round + end + return baseDmg + end +end + + + +#=============================================================================== +# Decreases the target's Special Attack by 1 stage. +#=============================================================================== +class PokeBattle_Move_045 < PokeBattle_TargetStatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::SPATK,1] + end +end + + + +#=============================================================================== +# Decreases the target's Special Defense by 1 stage. +#=============================================================================== +class PokeBattle_Move_046 < PokeBattle_TargetStatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::SPDEF,1] + end +end + + + +#=============================================================================== +# Decreases the target's accuracy by 1 stage. +#=============================================================================== +class PokeBattle_Move_047 < PokeBattle_TargetStatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::ACCURACY,1] + end +end + + + +#=============================================================================== +# Decreases the target's evasion by 1 stage OR 2 stages. (Sweet Scent) +#=============================================================================== +class PokeBattle_Move_048 < PokeBattle_TargetStatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::EVASION,(NEWEST_BATTLE_MECHANICS) ? 2 : 1] + end +end + + + +#=============================================================================== +# Decreases the target's evasion by 1 stage. Ends all barriers and entry +# hazards for the target's side OR on both sides. (Defog) +#=============================================================================== +class PokeBattle_Move_049 < PokeBattle_TargetStatDownMove + def ignoresSubstitute?(user); return true; end + + def initialize(battle,move) + super + @statDown = [PBStats::EVASION,1] + end + + def pbFailsAgainstTarget?(user,target) + targetSide = target.pbOwnSide + targetOpposingSide = target.pbOpposingSide + return false if targetSide.effects[PBEffects::AuroraVeil]>0 || + targetSide.effects[PBEffects::LightScreen]>0 || + targetSide.effects[PBEffects::Reflect]>0 || + targetSide.effects[PBEffects::Mist]>0 || + targetSide.effects[PBEffects::Safeguard]>0 + return false if targetSide.effects[PBEffects::StealthRock] || + targetSide.effects[PBEffects::Spikes]>0 || + targetSide.effects[PBEffects::ToxicSpikes]>0 || + targetSide.effects[PBEffects::StickyWeb] + return false if NEWEST_BATTLE_MECHANICS && + (targetOpposingSide.effects[PBEffects::StealthRock] || + targetOpposingSide.effects[PBEffects::Spikes]>0 || + targetOpposingSide.effects[PBEffects::ToxicSpikes]>0 || + targetOpposingSide.effects[PBEffects::StickyWeb]) + return super + end + + def pbEffectAgainstTarget(user,target) + if target.pbCanLowerStatStage?(@statDown[0],user,self) + target.pbLowerStatStage(@statDown[0],@statDown[1],user) + end + if target.pbOwnSide.effects[PBEffects::AuroraVeil]>0 + target.pbOwnSide.effects[PBEffects::AuroraVeil] = 0 + @battle.pbDisplay(_INTL("{1}'s Aurora Veil wore off!",target.pbTeam)) + end + if target.pbOwnSide.effects[PBEffects::LightScreen]>0 + target.pbOwnSide.effects[PBEffects::LightScreen] = 0 + @battle.pbDisplay(_INTL("{1}'s Light Screen wore off!",target.pbTeam)) + end + if target.pbOwnSide.effects[PBEffects::Reflect]>0 + target.pbOwnSide.effects[PBEffects::Reflect] = 0 + @battle.pbDisplay(_INTL("{1}'s Reflect wore off!",target.pbTeam)) + end + if target.pbOwnSide.effects[PBEffects::Mist]>0 + target.pbOwnSide.effects[PBEffects::Mist] = 0 + @battle.pbDisplay(_INTL("{1}'s Mist faded!",target.pbTeam)) + end + if target.pbOwnSide.effects[PBEffects::Safeguard]>0 + target.pbOwnSide.effects[PBEffects::Safeguard] = 0 + @battle.pbDisplay(_INTL("{1} is no longer protected by Safeguard!!",target.pbTeam)) + end + if target.pbOwnSide.effects[PBEffects::StealthRock] || + (NEWEST_BATTLE_MECHANICS && + target.pbOpposingSide.effects[PBEffects::StealthRock]) + target.pbOwnSide.effects[PBEffects::StealthRock] = false + target.pbOpposingSide.effects[PBEffects::StealthRock] = false if NEWEST_BATTLE_MECHANICS + @battle.pbDisplay(_INTL("{1} blew away stealth rocks!",user.pbThis)) + end + if target.pbOwnSide.effects[PBEffects::Spikes]>0 || + (NEWEST_BATTLE_MECHANICS && + target.pbOpposingSide.effects[PBEffects::Spikes]>0) + target.pbOwnSide.effects[PBEffects::Spikes] = 0 + target.pbOpposingSide.effects[PBEffects::Spikes] = 0 if NEWEST_BATTLE_MECHANICS + @battle.pbDisplay(_INTL("{1} blew away spikes!",user.pbThis)) + end + if target.pbOwnSide.effects[PBEffects::ToxicSpikes]>0 || + (NEWEST_BATTLE_MECHANICS && + target.pbOpposingSide.effects[PBEffects::ToxicSpikes]>0) + target.pbOwnSide.effects[PBEffects::ToxicSpikes] = 0 + target.pbOpposingSide.effects[PBEffects::ToxicSpikes] = 0 if NEWEST_BATTLE_MECHANICS + @battle.pbDisplay(_INTL("{1} blew away poison spikes!",user.pbThis)) + end + if target.pbOwnSide.effects[PBEffects::StickyWeb] || + (NEWEST_BATTLE_MECHANICS && + target.pbOpposingSide.effects[PBEffects::StickyWeb]) + target.pbOwnSide.effects[PBEffects::StickyWeb] = false + target.pbOpposingSide.effects[PBEffects::StickyWeb] = false if NEWEST_BATTLE_MECHANICS + @battle.pbDisplay(_INTL("{1} blew away sticky webs!",user.pbThis)) + end + end +end + + + +#=============================================================================== +# Decreases the target's Attack and Defense by 1 stage each. (Tickle) +#=============================================================================== +class PokeBattle_Move_04A < PokeBattle_TargetMultiStatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::ATTACK,1,PBStats::DEFENSE,1] + end +end + + + +#=============================================================================== +# Decreases the target's Attack by 2 stages. (Charm, Feather Dance) +#=============================================================================== +class PokeBattle_Move_04B < PokeBattle_TargetStatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::ATTACK,2] + end +end + + + +#=============================================================================== +# Decreases the target's Defense by 2 stages. (Screech) +#=============================================================================== +class PokeBattle_Move_04C < PokeBattle_TargetStatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::DEFENSE,2] + end +end + + + +#=============================================================================== +# Decreases the target's Speed by 2 stages. (Cotton Spore, Scary Face, String Shot) +#=============================================================================== +class PokeBattle_Move_04D < PokeBattle_TargetStatDownMove + def initialize(battle,move) + super + inc = 2 + inc = 1 if isConst?(@id,PBMoves,:STRINGSHOT) && !NEWEST_BATTLE_MECHANICS + @statDown = [PBStats::SPEED,inc] + end +end + + + +#=============================================================================== +# Decreases the target's Special Attack by 2 stages. Only works on the opposite +# gender. (Captivate) +#=============================================================================== +class PokeBattle_Move_04E < PokeBattle_TargetStatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::SPATK,2] + end + + def pbFailsAgainstTarget?(user,target) + return true if super + return false if damagingMove? + if user.gender==2 || target.gender==2 || user.gender==target.gender + @battle.pbDisplay(_INTL("{1} is unaffected!",target.pbThis)) + return true + end + if target.hasActiveAbility?(:OBLIVIOUS) && !@battle.moldBreaker + @battle.pbShowAbilitySplash(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} is unaffected!",target.pbThis)) + else + @battle.pbDisplay(_INTL("{1}'s {2} prevents romance!",target.pbThis,target.abilityName)) + end + @battle.pbHideAbilitySplash(target) + return true + end + return false + end + + def pbAdditionalEffect(user,target) + return if user.gender==2 || target.gender==2 || user.gender==target.gender + return if target.hasActiveAbility?(:OBLIVIOUS) && !@battle.moldBreaker + super + end +end + + + +#=============================================================================== +# Decreases the target's Special Defense by 2 stages. +#=============================================================================== +class PokeBattle_Move_04F < PokeBattle_TargetStatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::SPDEF,2] + end +end + + + +#=============================================================================== +# Resets all target's stat stages to 0. (Clear Smog) +#=============================================================================== +class PokeBattle_Move_050 < PokeBattle_Move + def pbEffectAgainstTarget(user,target) + if target.damageState.calcDamage>0 && !target.damageState.substitute && + target.hasAlteredStatStages? + target.pbResetStatStages + @battle.pbDisplay(_INTL("{1}'s stat changes were removed!",target.pbThis)) + end + end +end + + + +#=============================================================================== +# Resets all stat stages for all battlers to 0. (Haze) +#=============================================================================== +class PokeBattle_Move_051 < PokeBattle_Move + def pbMoveFailed?(user,targets) + failed = true + @battle.eachBattler do |b| + failed = false if b.hasAlteredStatStages? + break if !failed + end + if failed + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + @battle.eachBattler { |b| b.pbResetStatStages } + @battle.pbDisplay(_INTL("All stat changes were eliminated!")) + end +end + + + +#=============================================================================== +# User and target swap their Attack and Special Attack stat stages. (Power Swap) +#=============================================================================== +class PokeBattle_Move_052 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbEffectAgainstTarget(user,target) + [PBStats::ATTACK,PBStats::SPATK].each do |s| + user.stages[s],target.stages[s] = target.stages[s],user.stages[s] + end + @battle.pbDisplay(_INTL("{1} switched all changes to its Attack and Sp. Atk with the target!",user.pbThis)) + end +end + + + +#=============================================================================== +# User and target swap their Defense and Special Defense stat stages. (Guard Swap) +#=============================================================================== +class PokeBattle_Move_053 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbEffectAgainstTarget(user,target) + [PBStats::DEFENSE,PBStats::SPDEF].each do |s| + user.stages[s],target.stages[s] = target.stages[s],user.stages[s] + end + @battle.pbDisplay(_INTL("{1} switched all changes to its Defense and Sp. Def with the target!",user.pbThis)) + end +end + + + +#=============================================================================== +# User and target swap all their stat stages. (Heart Swap) +#=============================================================================== +class PokeBattle_Move_054 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbEffectAgainstTarget(user,target) + PBStats.eachBattleStat do |s| + user.stages[s],target.stages[s] = target.stages[s],user.stages[s] + end + @battle.pbDisplay(_INTL("{1} switched stat changes with the target!",user.pbThis)) + end +end + + + +#=============================================================================== +# User copies the target's stat stages. (Psych Up) +#=============================================================================== +class PokeBattle_Move_055 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbEffectAgainstTarget(user,target) + PBStats.eachBattleStat { |s| user.stages[s] = target.stages[s] } + if NEWEST_BATTLE_MECHANICS + user.effects[PBEffects::FocusEnergy] = target.effects[PBEffects::FocusEnergy] + user.effects[PBEffects::LaserFocus] = target.effects[PBEffects::LaserFocus] + end + @battle.pbDisplay(_INTL("{1} copied {2}'s stat changes!",user.pbThis,target.pbThis(true))) + end +end + + + +#=============================================================================== +# For 5 rounds, user's and ally's stat stages cannot be lowered by foes. (Mist) +#=============================================================================== +class PokeBattle_Move_056 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.pbOwnSide.effects[PBEffects::Mist]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.pbOwnSide.effects[PBEffects::Mist] = 5 + @battle.pbDisplay(_INTL("{1} became shrouded in mist!",user.pbTeam)) + end +end + + + +#=============================================================================== +# Swaps the user's Attack and Defense stats. (Power Trick) +#=============================================================================== +class PokeBattle_Move_057 < PokeBattle_Move + def pbEffectGeneral(user) + user.attack,user.defense = user.defense,user.attack + user.effects[PBEffects::PowerTrick] = !user.effects[PBEffects::PowerTrick] + @battle.pbDisplay(_INTL("{1} switched its Attack and Defense!",user.pbThis)) + end +end + + + +#=============================================================================== +# Averages the user's and target's Attack. +# Averages the user's and target's Special Attack. (Power Split) +#=============================================================================== +class PokeBattle_Move_058 < PokeBattle_Move + def pbEffectAgainstTarget(user,target) + newatk = ((user.attack+target.attack)/2).floor + newspatk = ((user.spatk+target.spatk)/2).floor + user.attack = target.attack = newatk + user.spatk = target.spatk = newspatk + @battle.pbDisplay(_INTL("{1} shared its power with the target!",user.pbThis)) + end +end + + + +#=============================================================================== +# Averages the user's and target's Defense. +# Averages the user's and target's Special Defense. (Guard Split) +#=============================================================================== +class PokeBattle_Move_059 < PokeBattle_Move + def pbEffectAgainstTarget(user,target) + newdef = ((user.defense+target.defense)/2).floor + newspdef = ((user.spdef+target.spdef)/2).floor + user.defense = target.defense = newdef + user.spdef = target.spdef = newspdef + @battle.pbDisplay(_INTL("{1} shared its guard with the target!",user.pbThis)) + end +end + + + +#=============================================================================== +# Averages the user's and target's current HP. (Pain Split) +#=============================================================================== +class PokeBattle_Move_05A < PokeBattle_Move + def pbEffectAgainstTarget(user,target) + newHP = (user.hp+target.hp)/2 + if user.hp>newHP; user.pbReduceHP(user.hp-newHP,false,false) + elsif user.hpnewHP; target.pbReduceHP(target.hp-newHP,false,false) + elsif target.hp0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.pbOwnSide.effects[PBEffects::Tailwind] = 4 + @battle.pbDisplay(_INTL("The Tailwind blew from behind {1}!",user.pbTeam(true))) + end +end + + + +#=============================================================================== +# This move turns into the last move used by the target, until user switches +# out. (Mimic) +#=============================================================================== +class PokeBattle_Move_05C < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def initialize(battle,move) + super + @moveBlacklist = [ + "014", # Chatter + "0B6", # Metronome + # Struggle + "002", # Struggle + # Moves that affect the moveset + "05C", # Mimic + "05D", # Sketch + "069" # Transform + ] + end + + def pbMoveFailed?(user,targets) + if user.effects[PBEffects::Transform] || !user.pbHasMove?(@id) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + lastMoveData = pbGetMoveData(target.lastRegularMoveUsed) + if target.lastRegularMoveUsed<=0 || + user.pbHasMove?(target.lastRegularMoveUsed) || + @moveBlacklist.include?(lastMoveData[MOVE_FUNCTION_CODE]) || + isConst?(lastMoveData[MOVE_TYPE],PBTypes,:SHADOW) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + user.eachMoveWithIndex do |m,i| + next if m.id!=@id + newMove = PBMove.new(target.lastRegularMoveUsed) + user.moves[i] = PokeBattle_Move.pbFromPBMove(@battle,newMove) + @battle.pbDisplay(_INTL("{1} learned {2}!",user.pbThis, + PBMoves.getName(target.lastRegularMoveUsed))) + user.pbCheckFormOnMovesetChange + break + end + end +end + + + +#=============================================================================== +# This move permanently turns into the last move used by the target. (Sketch) +#=============================================================================== +class PokeBattle_Move_05D < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def initialize(battle,move) + super + @moveBlacklist = [ + "014", # Chatter + "05D", # Sketch (this move) + # Struggle + "002" # Struggle + ] + end + + def pbMoveFailed?(user,targets) + if user.effects[PBEffects::Transform] || !user.pbHasMove?(@id) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + lastMoveData = pbGetMoveData(target.lastRegularMoveUsed) + if target.lastRegularMoveUsed<=0 || + user.pbHasMove?(target.lastRegularMoveUsed) || + @moveBlacklist.include?(lastMoveData[MOVE_FUNCTION_CODE]) || + isConst?(lastMoveData[MOVE_TYPE],PBTypes,:SHADOW) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + user.eachMoveWithIndex do |m,i| + next if m.id!=@id + newMove = PBMove.new(target.lastRegularMoveUsed) + user.pokemon.moves[i] = newMove + user.moves[i] = PokeBattle_Move.pbFromPBMove(@battle,newMove) + @battle.pbDisplay(_INTL("{1} learned {2}!",user.pbThis, + PBMoves.getName(target.lastRegularMoveUsed))) + user.pbCheckFormOnMovesetChange + break + end + end +end + + + +#=============================================================================== +# Changes user's type to that of a random user's move, except a type the user +# already has (even partially), OR changes to the user's first move's type. +# (Conversion) +#=============================================================================== +class PokeBattle_Move_05E < PokeBattle_Move + def pbMoveFailed?(user,targets) + if !user.canChangeType? + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + userTypes = user.pbTypes(true) + @newTypes = [] + user.eachMoveWithIndex do |m,i| + break if NEWEST_BATTLE_MECHANICS && i>0 + next if PBTypes.isPseudoType?(m.type) + next if userTypes.include?(m.type) + @newTypes.push(m.type) if !@newTypes.include?(m.type) + end + if @newTypes.length==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + newType = @newTypes[@battle.pbRandom(@newTypes.length)] + user.pbChangeTypes(newType) + typeName = PBTypes.getName(newType) + @battle.pbDisplay(_INTL("{1} transformed into the {2} type!",user.pbThis,typeName)) + end +end + + + +#=============================================================================== +# Changes user's type to a random one that resists/is immune to the last move +# used by the target. (Conversion 2) +#=============================================================================== +class PokeBattle_Move_05F < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbMoveFailed?(user,targets) + if !user.canChangeType? + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + if target.lastMoveUsed<=0 || + target.lastMoveUsedType<0 || + PBTypes.isPseudoType?(pbGetMoveData(target.lastMoveUsed,MOVE_TYPE)) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + @newTypes = [] + for i in 0..PBTypes.maxValue + next if PBTypes.isPseudoType?(i) + next if user.pbHasType?(i) + next if !PBTypes.resistant?(target.lastMoveUsedType,i) + @newTypes.push(i) + end + if @newTypes.length==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + newType = @newTypes[@battle.pbRandom(@newTypes.length)] + user.pbChangeTypes(newType) + typeName = PBTypes.getName(newType) + @battle.pbDisplay(_INTL("{1} transformed into the {2} type!",user.pbThis,typeName)) + end +end + + + +#=============================================================================== +# Changes user's type depending on the environment. (Camouflage) +#=============================================================================== +class PokeBattle_Move_060 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if !user.canChangeType? + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + @newType = getID(PBTypes,:NORMAL) + checkedTerrain = false + case @battle.field.terrain + when PBBattleTerrains::Electric + if hasConst?(PBTypes,:ELECTRIC) + @newType = getID(PBTypes,:ELECTRIC); checkedTerrain = true + end + when PBBattleTerrains::Grassy + if hasConst?(PBTypes,:GRASS) + @newType = getID(PBTypes,:GRASS); checkedTerrain = true + end + when PBBattleTerrains::Misty + if hasConst?(PBTypes,:FAIRY) + @newType = getID(PBTypes,:FAIRY); checkedTerrain = true + end + when PBBattleTerrains::Psychic + if hasConst?(PBTypes,:PSYCHIC) + @newType = getID(PBTypes,:PSYCHIC); checkedTerrain = true + end + end + if !checkedTerrain + case @battle.environment + when PBEnvironment::Grass; @newType = getID(PBTypes,:GRASS) + when PBEnvironment::TallGrass; @newType = getID(PBTypes,:GRASS) + when PBEnvironment::MovingWater; @newType = getID(PBTypes,:WATER) + when PBEnvironment::StillWater; @newType = getID(PBTypes,:WATER) + when PBEnvironment::Puddle; @newType = getID(PBTypes,:WATER) + when PBEnvironment::Underwater; @newType = getID(PBTypes,:WATER) + when PBEnvironment::Cave; @newType = getID(PBTypes,:ROCK) + when PBEnvironment::Rock; @newType = getID(PBTypes,:GROUND) + when PBEnvironment::Sand; @newType = getID(PBTypes,:GROUND) + when PBEnvironment::Forest; @newType = getID(PBTypes,:BUG) + when PBEnvironment::ForestGrass; @newType = getID(PBTypes,:BUG) + when PBEnvironment::Snow; @newType = getID(PBTypes,:ICE) + when PBEnvironment::Ice; @newType = getID(PBTypes,:ICE) + when PBEnvironment::Volcano; @newType = getID(PBTypes,:FIRE) + when PBEnvironment::Graveyard; @newType = getID(PBTypes,:GHOST) + when PBEnvironment::Sky; @newType = getID(PBTypes,:FLYING) + when PBEnvironment::Space; @newType = getID(PBTypes,:DRAGON) + when PBEnvironment::UltraSpace; @newType = getID(PBTypes,:PSYCHIC) + end + end + if !user.pbHasOtherType?(@newType) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.pbChangeTypes(@newType) + typeName = PBTypes.getName(@newType) + @battle.pbDisplay(_INTL("{1} transformed into the {2} type!",user.pbThis,typeName)) + end +end + + + +#=============================================================================== +# Target becomes Water type. (Soak) +#=============================================================================== +class PokeBattle_Move_061 < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + if !target.canChangeType? || + !target.pbHasOtherType?(getConst(PBTypes,:WATER)) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + newType = getConst(PBTypes,:WATER) + user.pbChangeTypes(newType) + typeName = PBTypes.getName(newType) + @battle.pbDisplay(_INTL("{1} transformed into the {2} type!",target.pbThis,typeName)) + end +end + + + +#=============================================================================== +# User copes target's types. (Reflect Type) +#=============================================================================== +class PokeBattle_Move_062 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbMoveFailed?(user,targets) + if !user.canChangeType? + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + newTypes = target.pbTypes(true) + if newTypes.length==0 # Target has no type to copy + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if user.pbTypes==target.pbTypes && + user.effects[PBEffects::Type3]==target.effects[PBEffects::Type3] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + user.pbChangeTypes(target) + @battle.pbDisplay(_INTL("{1}'s type changed to match {2}'s!", + user.pbThis,target.pbThis(true))) + end +end + + + +#=============================================================================== +# Target's ability becomes Simple. (Simple Beam) +#=============================================================================== +class PokeBattle_Move_063 < PokeBattle_Move + def initialize(battle,move) + super + @abilityBlacklist = [ + :TRUANT, + # This ability + :SIMPLEBEAM, + # Form-changing abilities + :BATTLEBOND, + :DISGUISE, +# :FLOWERGIFT, # This can be replaced +# :FORECAST, # This can be replaced + :MULTITYPE, + :POWERCONSTRUCT, + :SCHOOLING, + :SHIELDSDOWN, + :STANCECHANGE, + :ZENMODE, + # Abilities intended to be inherent properties of a certain species + :COMATOSE, + :RKSSYSTEM + ] + end + + def pbMoveFailed?(user,targets) + if !hasConst?(PBAbilities,:SIMPLE) # Ability isn't defined + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + @abilityBlacklist.each do |abil| + next if !isConst?(target.ability,PBAbilities,abil) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + @battle.pbShowAbilitySplash(target,true,false) + oldAbil = target.ability + target.ability = getConst(PBAbilities,:SIMPLE) + @battle.pbReplaceAbilitySplash(target) + @battle.pbDisplay(_INTL("{1} acquired {2}!",target.pbThis,target.abilityName)) + @battle.pbHideAbilitySplash(target) + target.pbOnAbilityChanged(oldAbil) + end +end + + + +#=============================================================================== +# Target's ability becomes Insomnia. (Worry Seed) +#=============================================================================== +class PokeBattle_Move_064 < PokeBattle_Move + def initialize(battle,move) + super + @abilityBlacklist = [ + :TRUANT, + # This ability + :INSOMNIA, + # Form-changing abilities + :BATTLEBOND, + :DISGUISE, +# :FLOWERGIFT, # This can be replaced +# :FORECAST, # This can be replaced + :MULTITYPE, + :POWERCONSTRUCT, + :SCHOOLING, + :SHIELDSDOWN, + :STANCECHANGE, + :ZENMODE, + # Abilities intended to be inherent properties of a certain species + :COMATOSE, + :RKSSYSTEM + ] + end + + def pbMoveFailed?(user,targets) + if !hasConst?(PBAbilities,:INSOMNIA) # Ability isn't defined + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + @abilityBlacklist.each do |abil| + next if !isConst?(target.ability,PBAbilities,abil) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + @battle.pbShowAbilitySplash(target,true,false) + oldAbil = target.ability + target.ability = getConst(PBAbilities,:INSOMNIA) + @battle.pbReplaceAbilitySplash(target) + @battle.pbDisplay(_INTL("{1} acquired {2}!",target.pbThis,target.abilityName)) + @battle.pbHideAbilitySplash(target) + target.pbOnAbilityChanged(oldAbil) + end +end + + + +#=============================================================================== +# User copies target's ability. (Role Play) +#=============================================================================== +class PokeBattle_Move_065 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def initialize(battle,move) + super + @abilityBlacklistUnlosable = [ + # Form-changing abilities + :BATTLEBOND, + :DISGUISE, +# :FLOWERGIFT, # This can be lost +# :FORECAST, # This can be lost + :MULTITYPE, + :POWERCONSTRUCT, + :SCHOOLING, + :SHIELDSDOWN, + :STANCECHANGE, + :ZENMODE, + # Abilities intended to be inherent properties of a certain species + :COMATOSE, + :RKSSYSTEM + ] + @abilityBlacklistUngainable = [ + # Replaces self with another ability + :POWEROFALCHEMY, + :RECEIVER, + :TRACE, + # Form-changing abilities + :BATTLEBOND, + :DISGUISE, + :FLOWERGIFT, + :FORECAST, + :MULTITYPE, + :POWERCONSTRUCT, + :SCHOOLING, + :SHIELDSDOWN, + :STANCECHANGE, + :ZENMODE, + # Appearance-changing abilities + :ILLUSION, + :IMPOSTER, + # Abilities intended to be inherent properties of a certain species + :COMATOSE, + :RKSSYSTEM, + # Abilities that would be overpowered if allowed to be transferred + :WONDERGUARD + ] + end + + def pbMoveFailed?(user,targets) + @abilityBlacklistUnlosable.each do |abil| + next if !isConst?(user.ability,PBAbilities,abil) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + if target.ability==0 || user.ability==target.ability + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + @abilityBlacklistUngainable.each do |abil| + next if !isConst?(target.ability,PBAbilities,abil) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + @battle.pbShowAbilitySplash(user,true,false) + oldAbil = user.ability + user.ability = target.ability + @battle.pbReplaceAbilitySplash(user) + @battle.pbDisplay(_INTL("{1} copied {2}'s {3}!", + user.pbThis,target.pbThis(true),target.abilityName)) + @battle.pbHideAbilitySplash(user) + user.pbOnAbilityChanged(oldAbil) + user.pbEffectsOnSwitchIn + end +end + + + +#=============================================================================== +# Target copies user's ability. (Entrainment) +#=============================================================================== +class PokeBattle_Move_066 < PokeBattle_Move + def initialize(battle,move) + super + @abilityBlacklistUnlosable = [ + :TRUANT, + # Form-changing abilities + :BATTLEBOND, + :DISGUISE, +# :FLOWERGIFT, # This can be lost +# :FORECAST, # This can be lost + :MULTITYPE, + :POWERCONSTRUCT, + :SCHOOLING, + :SHIELDSDOWN, + :STANCECHANGE, + :ZENMODE, + # Abilities intended to be inherent properties of a certain species + :COMATOSE, + :RKSSYSTEM + ] + @abilityBlacklistUngainable = [ + # Replaces self with another ability + :POWEROFALCHEMY, + :RECEIVER, + :TRACE, + # Form-changing abilities + :BATTLEBOND, + :DISGUISE, + :FLOWERGIFT, + :FORECAST, + :MULTITYPE, + :POWERCONSTRUCT, + :SCHOOLING, + :SHIELDSDOWN, + :STANCECHANGE, + :ZENMODE, + # Appearance-changing abilities + :ILLUSION, + :IMPOSTER, + # Abilities intended to be inherent properties of a certain species + :COMATOSE, + :RKSSYSTEM + ] + end + + def pbMoveFailed?(user,targets) + if user.ability==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + @abilityBlacklistUngainable.each do |abil| + next if !isConst?(user.ability,PBAbilities,abil) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + @abilityBlacklistUnlosable.each do |abil| + next if !isConst?(target.ability,PBAbilities,abil) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + @battle.pbShowAbilitySplash(target,true,false) + oldAbil = target.ability + target.ability = user.ability + @battle.pbReplaceAbilitySplash(target) + @battle.pbDisplay(_INTL("{1} acquired {2}!",target.pbThis,target.abilityName)) + @battle.pbHideAbilitySplash(target) + target.pbOnAbilityChanged(oldAbil) + target.pbEffectsOnSwitchIn + end +end + + + +#=============================================================================== +# User and target swap abilities. (Skill Swap) +#=============================================================================== +class PokeBattle_Move_067 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def initialize(battle,move) + super + @abilityBlacklistUnlosable = [ + # Form-changing abilities + :BATTLEBOND, + :DISGUISE, +# :FLOWERGIFT, # This can be lost +# :FORECAST, # This can be lost + :MULTITYPE, + :POWERCONSTRUCT, + :SCHOOLING, + :SHIELDSDOWN, + :STANCECHANGE, + :ZENMODE, + # Abilities intended to be inherent properties of a certain species + :COMATOSE, + :RKSSYSTEM + ] + @abilityBlacklistUngainable = [ + # Form-changing abilities + :BATTLEBOND, + :DISGUISE, + :FLOWERGIFT, + :FORECAST, + :MULTITYPE, + :POWERCONSTRUCT, + :SCHOOLING, + :SHIELDSDOWN, + :STANCECHANGE, + :ZENMODE, + # Appearance-changing abilities + :ILLUSION, + :IMPOSTER, + # Abilities intended to be inherent properties of a certain species + :COMATOSE, + :RKSSYSTEM, + # Abilities that would be overpowered if allowed to be transferred + :WONDERGUARD + ] + end + + def pbMoveFailed?(user,targets) + if user.ability==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + @abilityBlacklistUnlosable.each do |abil| + next if !isConst?(user.ability,PBAbilities,abil) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + @abilityBlacklistUngainable.each do |abil| + next if !isConst?(user.ability,PBAbilities,abil) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + if target.ability==0 || + (user.ability==target.ability && !NEWEST_BATTLE_MECHANICS) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + @abilityBlacklistUnlosable.each do |abil| + next if !isConst?(target.ability,PBAbilities,abil) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + @abilityBlacklistUngainable.each do |abil| + next if !isConst?(target.ability,PBAbilities,abil) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + if user.opposes?(target) + @battle.pbShowAbilitySplash(user,false,false) + @battle.pbShowAbilitySplash(target,true,false) + end + oldUserAbil = user.ability + oldTargetAbil = target.ability + user.ability = oldTargetAbil + target.ability = oldUserAbil + if user.opposes?(target) + @battle.pbReplaceAbilitySplash(user) + @battle.pbReplaceAbilitySplash(target) + end + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} swapped Abilities with its target!",user.pbThis)) + else + @battle.pbDisplay(_INTL("{1} swapped its {2} Ability with its target's {3} Ability!", + user.pbThis,target.abilityName,user.abilityName)) + end + if user.opposes?(target) + @battle.pbHideAbilitySplash(user) + @battle.pbHideAbilitySplash(target) + end + user.pbOnAbilityChanged(oldUserAbil) + target.pbOnAbilityChanged(oldTargetAbil) + user.pbEffectsOnSwitchIn + target.pbEffectsOnSwitchIn + end +end + + + +#=============================================================================== +# Target's ability is negated. (Gastro Acid) +#=============================================================================== +class PokeBattle_Move_068 < PokeBattle_Move + def initialize(battle,move) + super + @abilityBlacklist = [ + # Form-changing abilities + :BATTLEBOND, + :DISGUISE, +# :FLOWERGIFT, # This can be negated +# :FORECAST, # This can be negated + :MULTITYPE, + :POWERCONSTRUCT, + :SCHOOLING, + :SHIELDSDOWN, + :STANCECHANGE, + :ZENMODE, + # Abilities intended to be inherent properties of a certain species + :COMATOSE, + :RKSSYSTEM + ] + end + + def pbFailsAgainstTarget?(user,target) + @abilityBlacklist.each do |abil| + next if !isConst?(target.ability,PBAbilities,abil) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::GastroAcid] = true + target.effects[PBEffects::Truant] = false + @battle.pbDisplay(_INTL("{1}'s Ability was suppressed!",target.pbThis)) + target.pbOnAbilityChanged(target.ability) + end +end + + + +#=============================================================================== +# User transforms into the target. (Transform) +#=============================================================================== +class PokeBattle_Move_069 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.effects[PBEffects::Transform] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + if target.effects[PBEffects::Transform] || + target.effects[PBEffects::Illusion] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + user.pbTransform(target) + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + super + @battle.scene.pbChangePokemon(user,targets[0].pokemon) + end +end + + + +#=============================================================================== +# Inflicts a fixed 20HP damage. (Sonic Boom) +#=============================================================================== +class PokeBattle_Move_06A < PokeBattle_FixedDamageMove + def pbFixedDamage(user,target) + return 20 + end +end + + + +#=============================================================================== +# Inflicts a fixed 40HP damage. (Dragon Rage) +#=============================================================================== +class PokeBattle_Move_06B < PokeBattle_FixedDamageMove + def pbFixedDamage(user,target) + return 40 + end +end + + + +#=============================================================================== +# Halves the target's current HP. (Nature's Madness, Super Fang) +#=============================================================================== +class PokeBattle_Move_06C < PokeBattle_FixedDamageMove + def pbFixedDamage(user,target) + return (target.hp/2.0).round + end +end + + + +#=============================================================================== +# Inflicts damage equal to the user's level. (Night Shade, Seismic Toss) +#=============================================================================== +class PokeBattle_Move_06D < PokeBattle_FixedDamageMove + def pbFixedDamage(user,target) + return user.level + end +end + + + +#=============================================================================== +# Inflicts damage to bring the target's HP down to equal the user's HP. (Endeavor) +#=============================================================================== +class PokeBattle_Move_06E < PokeBattle_FixedDamageMove + def pbFailsAgainstTarget?(user,target) + if user.hp>=target.hp + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbNumHits(user,targets); return 1; end + + def pbFixedDamage(user,target) + return target.hp-user.hp + end +end + + + +#=============================================================================== +# Inflicts damage between 0.5 and 1.5 times the user's level. (Psywave) +#=============================================================================== +class PokeBattle_Move_06F < PokeBattle_FixedDamageMove + def pbFixedDamage(user,target) + min = (user.level/2).floor + max = (user.level*3/2).floor + return min+@battle.pbRandom(max-min+1) + end +end + + + +#=============================================================================== +# OHKO. Accuracy increases by difference between levels of user and target. +#=============================================================================== +class PokeBattle_Move_070 < PokeBattle_FixedDamageMove + def hitsDiggingTargets?; return isConst?(@id,PBMoves,:FISSURE); end + + def pbFailsAgainstTarget?(user,target) + if target.level>user.level + @battle.pbDisplay(_INTL("{1} is unaffected!",target.pbThis)) + return true + end + if target.hasActiveAbility?(:STURDY) && !@battle.moldBreaker + @battle.pbShowAbilitySplash(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("But it failed to affect {1}!",target.pbThis(true))) + else + @battle.pbDisplay(_INTL("But it failed to affect {1} because of its {2}!", + target.pbThis(true),target.abilityName)) + end + @battle.pbHideAbilitySplash(target) + return true + end + if NEWEST_BATTLE_MECHANICS && + isConst?(target.damageState.typeMod,PBTypes,:ICE) && target.pbHasType?(:ICE) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbAccuracyCheck(user,target) + acc = @accuracy+user.level-target.level + acc -= 10 if NEWEST_BATTLE_MECHANICS && isConst?(@id,PBMoves,:SHEERCOLD) && + !user.pbHasType?(:ICE) + return @battle.pbRandom(100)0 + hitAlly.each do |b| + @battle.pbDisplay(_INTL("The bursting flame hit {1}!", + @battle.battlers[b[0]].pbThis(true))) + end + end + switchedAlly = [] + hitAlly.each do |b| + @battle.battlers[b[0]].pbItemHPHealCheck + if @battle.battlers[b[0]].pbAbilitiesOnDamageTaken(b[1]) + switchedAlly.push(@battle.battlers[b[0]]) + end + end + switchedAlly.each { |b| b.pbEffectsOnSwitchIn(true) } + end +end + + + +#=============================================================================== +# Power is doubled if the target is using Dive. Hits some semi-invulnerable +# targets. (Surf) +#=============================================================================== +class PokeBattle_Move_075 < PokeBattle_Move + def hitsDivingTargets?; return true; end + + def pbModifyDamage(damageMult,user,target) + damageMult *= 2 if target.inTwoTurnAttack?("0CB") # Dive + return damageMult + end +end + + + +#=============================================================================== +# Power is doubled if the target is using Dig. Power is halved if Grassy Terrain +# is in effect. Hits some semi-invulnerable targets. (Earthquake) +#=============================================================================== +class PokeBattle_Move_076 < PokeBattle_Move + def hitsDiggingTargets?; return true; end + + def pbModifyDamage(damageMult,user,target) + damageMult *= 2 if target.inTwoTurnAttack?("0CA") # Dig + damageMult = (damageMult/2.0).round if @battle.field.terrain==PBBattleTerrains::Grassy + return damageMult + end +end + + + +#=============================================================================== +# Power is doubled if the target is using Bounce, Fly or Sky Drop. Hits some +# semi-invulnerable targets. (Gust) +#=============================================================================== +class PokeBattle_Move_077 < PokeBattle_Move + def hitsFlyingTargets?; return true; end + + def pbBaseDamage(baseDmg,user,target) + baseDmg *= 2 if target.inTwoTurnAttack?("0C9","0CC","0CE") || # Fly/Bounce/Sky Drop + target.effects[PBEffects::SkyDrop]>=0 + return baseDmg + end +end + + + +#=============================================================================== +# Power is doubled if the target is using Bounce, Fly or Sky Drop. Hits some +# semi-invulnerable targets. May make the target flinch. (Twister) +#=============================================================================== +class PokeBattle_Move_078 < PokeBattle_FlinchMove + def hitsFlyingTargets?; return true; end + + def pbBaseDamage(baseDmg,user,target) + baseDmg *= 2 if target.inTwoTurnAttack?("0C9","0CC","0CE") || # Fly/Bounce/Sky Drop + target.effects[PBEffects::SkyDrop]>=0 + return baseDmg + end +end + + + +#=============================================================================== +# Power is doubled if Fusion Flare has already been used this round. (Fusion Bolt) +#=============================================================================== +class PokeBattle_Move_079 < PokeBattle_Move + def pbChangeUsageCounters(user,specialUsage) + @doublePower = @battle.field.effects[PBEffects::FusionFlare] + super + end + + def pbBaseDamageMultiplier(damageMult,user,target) + damageMult *= 2 if @doublePower + return damageMult + end + + def pbEffectGeneral(user) + @battle.field.effects[PBEffects::FusionBolt] = true + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + hitNum = 1 if (targets.length>0 && targets[0].damageState.critical) || + @doublePower # Charged anim + super + end +end + + + +#=============================================================================== +# Power is doubled if Fusion Bolt has already been used this round. (Fusion Flare) +#=============================================================================== +class PokeBattle_Move_07A < PokeBattle_Move + def pbChangeUsageCounters(user,specialUsage) + @doublePower = @battle.field.effects[PBEffects::FusionBolt] + super + end + + def pbBaseDamageMultiplier(damageMult,user,target) + damageMult *= 2 if @doublePower + return damageMult + end + + def pbEffectGeneral(user) + @battle.field.effects[PBEffects::FusionFlare] = true + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + hitNum = 1 if (targets.length>0 && targets[0].damageState.critical) || + @doublePower # Charged anim + super + end +end + + + +#=============================================================================== +# Power is doubled if the target is poisoned. (Venoshock) +#=============================================================================== +class PokeBattle_Move_07B < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + if target.poisoned? && + (target.effects[PBEffects::Substitute]==0 || ignoresSubstitute?(user)) + baseDmg *= 2 + end + return baseDmg + end +end + + + +#=============================================================================== +# Power is doubled if the target is paralyzed. Cures the target of paralysis. +# (Smelling Salts) +#=============================================================================== +class PokeBattle_Move_07C < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + if target.paralyzed? && + (target.effects[PBEffects::Substitute]==0 || ignoresSubstitute?(user)) + baseDmg *= 2 + end + return baseDmg + end + + def pbEffectAfterAllHits(user,target) + return if target.fainted? + return if target.damageState.unaffected || target.damageState.substitute + return if target.status!=PBStatuses::PARALYSIS + target.pbCureStatus + end +end + + + +#=============================================================================== +# Power is doubled if the target is asleep. Wakes the target up. (Wake-Up Slap) +#=============================================================================== +class PokeBattle_Move_07D < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + if target.asleep? && + (target.effects[PBEffects::Substitute]==0 || ignoresSubstitute?(user)) + baseDmg *= 2 + end + return baseDmg + end + + def pbEffectAfterAllHits(user,target) + return if target.fainted? + return if target.damageState.unaffected || target.damageState.substitute + return if target.status!=PBStatuses::SLEEP + target.pbCureStatus + end +end + + + +#=============================================================================== +# Power is doubled if the user is burned, poisoned or paralyzed. (Facade) +# Burn's halving of Attack is negated (new mechanics). +#=============================================================================== +class PokeBattle_Move_07E < PokeBattle_Move + def damageReducedByBurn?; return !NEWEST_BATTLE_MECHANICS; end + + def pbBaseDamage(baseDmg,user,target) + baseDmg *= 2 if user.poisoned? || user.burned? || user.paralyzed? + return baseDmg + end +end + + + +#=============================================================================== +# Power is doubled if the target has a status problem. (Hex) +#=============================================================================== +class PokeBattle_Move_07F < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + if target.pbHasAnyStatus? && + (target.effects[PBEffects::Substitute]==0 || ignoresSubstitute?(user)) + baseDmg *= 2 + end + return baseDmg + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/002_Move/006_Move_Effects_080-0FF.rb b/Data/Scripts/011_Battle/002_Move/006_Move_Effects_080-0FF.rb new file mode 100644 index 000000000..2b0a07c44 --- /dev/null +++ b/Data/Scripts/011_Battle/002_Move/006_Move_Effects_080-0FF.rb @@ -0,0 +1,3746 @@ +#=============================================================================== +# Power is doubled if the target's HP is down to 1/2 or less. (Brine) +#=============================================================================== +class PokeBattle_Move_080 < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + baseDmg *= 2 if target.hp<=target.totalhp/2 + return baseDmg + end +end + + + +#=============================================================================== +# Power is doubled if the user has lost HP due to the target's move this round. +# (Avalanche, Revenge) +#=============================================================================== +class PokeBattle_Move_081 < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + baseDmg *= 2 if user.lastAttacker.include?(target.index) + return baseDmg + end +end + + + +#=============================================================================== +# Power is doubled if the target has already lost HP this round. (Assurance) +#=============================================================================== +class PokeBattle_Move_082 < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + baseDmg *= 2 if target.tookDamage + return baseDmg + end +end + + + +#=============================================================================== +# Power is doubled if a user's ally has already used this move this round. (Round) +# If an ally is about to use the same move, make it go next, ignoring priority. +#=============================================================================== +class PokeBattle_Move_083 < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + baseDmg *= 2 if user.pbOwnSide.effects[PBEffects::Round] + return baseDmg + end + + def pbEffectGeneral(user) + user.pbOwnSide.effects[PBEffects::Round] = true + user.eachAlly do |b| + next if @battle.choices[b.index][0]!=:UseMove || b.movedThisRound? + next if @battle.choices[b.index][2].function!=@function + b.effects[PBEffects::MoveNext] = true + b.effects[PBEffects::Quash] = 0 + break + end + end +end + + + +#=============================================================================== +# Power is doubled if the target has already moved this round. (Payback) +#=============================================================================== +class PokeBattle_Move_084 < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + if @battle.choices[target.index][0]!=:None && + ((@battle.choices[target.index][0]!=:UseMove && + @battle.choices[target.index][0]!=:Shift) || target.movedThisRound?) + baseDmg *= 2 + end + return baseDmg + end +end + + + +#=============================================================================== +# Power is doubled if a user's teammate fainted last round. (Retaliate) +#=============================================================================== +class PokeBattle_Move_085 < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + lrf = user.pbOwnSide.effects[PBEffects::LastRoundFainted] + baseDmg *= 2 if lrf>=0 && lrf==@battle.turnCount-1 + return baseDmg + end +end + + + +#=============================================================================== +# Power is doubled if the user has no held item. (Acrobatics) +#=============================================================================== +class PokeBattle_Move_086 < PokeBattle_Move + def pbBaseDamageMultiplier(damageMult,user,target) + damageMult *= 2 if user.item==0 + return damageMult + end +end + + + +#=============================================================================== +# Power is doubled in weather. Type changes depending on the weather. (Weather Ball) +#=============================================================================== +class PokeBattle_Move_087 < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + baseDmg *= 2 if @battle.pbWeather!=PBWeather::None + return baseDmg + end + + def pbBaseType(user) + ret = getID(PBTypes,:NORMAL) + case @battle.pbWeather + when PBWeather::Sun, PBWeather::HarshSun + ret = getConst(PBTypes,:FIRE) || ret + when PBWeather::Rain, PBWeather::HeavyRain + ret = getConst(PBTypes,:WATER) || ret + when PBWeather::Sandstorm + ret = getConst(PBTypes,:ROCK) || ret + when PBWeather::Hail + ret = getConst(PBTypes,:ICE) || ret + end + return ret + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + t = pbBaseType(user) + hitNum = 1 if isConst?(t,PBTypes,:FIRE) # Type-specific anims + hitNum = 2 if isConst?(t,PBTypes,:WATER) + hitNum = 3 if isConst?(t,PBTypes,:ROCK) + hitNum = 4 if isConst?(t,PBTypes,:ICE) + super + end +end + + + +#=============================================================================== +# Interrupts a foe switching out or using U-turn/Volt Switch/Parting Shot. Power +# is doubled in that case. (Pursuit) +# (Handled in Battle's pbAttackPhase): Makes this attack happen before switching. +#=============================================================================== +class PokeBattle_Move_088 < PokeBattle_Move + def pbAccuracyCheck(user,target) + return true if @battle.switching + return super + end + + def pbBaseDamage(baseDmg,user,target) + baseDmg *= 2 if @battle.switching + return baseDmg + end +end + + + +#=============================================================================== +# Power increases with the user's happiness. (Return) +#=============================================================================== +class PokeBattle_Move_089 < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + return [(user.happiness*2/5).floor,1].max + end +end + + + +#=============================================================================== +# Power decreases with the user's happiness. (Frustration) +#=============================================================================== +class PokeBattle_Move_08A < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + return [((255-user.happiness)*2/5).floor,1].max + end +end + + + +#=============================================================================== +# Power increases with the user's HP. (Eruption, Water Spout) +#=============================================================================== +class PokeBattle_Move_08B < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + return [150*user.hp/user.totalhp,1].max + end +end + + + +#=============================================================================== +# Power increases with the target's HP. (Crush Grip, Wring Out) +#=============================================================================== +class PokeBattle_Move_08C < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + return [120*target.hp/target.totalhp,1].max + end +end + + + +#=============================================================================== +# Power increases the quicker the target is than the user. (Gyro Ball) +#=============================================================================== +class PokeBattle_Move_08D < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + return [[(25*target.pbSpeed/user.pbSpeed).floor,150].min,1].max + end +end + + + +#=============================================================================== +# Power increases with the user's positive stat changes (ignores negative ones). +# (Power Trip, Stored Power) +#=============================================================================== +class PokeBattle_Move_08E < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + mult = 1 + PBStats.eachBattleStat { |s| mult += user.stages[s] if user.stages[s]>0 } + return 20*mult + end +end + + + +#=============================================================================== +# Power increases with the target's positive stat changes (ignores negative ones). +# (Punishment) +#=============================================================================== +class PokeBattle_Move_08F < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + mult = 3 + PBStats.eachBattleStat { |s| mult += target.stages[s] if target.stages[s]>0 } + return [20*mult,200].min + end +end + + + +#=============================================================================== +# Power and type depends on the user's IVs. (Hidden Power) +#=============================================================================== +class PokeBattle_Move_090 < PokeBattle_Move + def pbBaseType(user) + hp = pbHiddenPower(user) + return hp[0] + end + + def pbBaseDamage(baseDmg,user,target) + return super if NEWEST_BATTLE_MECHANICS + hp = pbHiddenPower(user) + return hp[1] + end +end + + + +def pbHiddenPower(pkmn) + # NOTE: This allows Hidden Power to be Fairy-type (if you have that type in + # your game). I don't care that the official games don't work like that. + iv = pkmn.iv + idxType = 0; power = 60 + types = [] + for i in 0..PBTypes.maxValue + next if PBTypes.isPseudoType?(i) + next if isConst?(i,PBTypes,:NORMAL) || isConst?(i,PBTypes,:SHADOW) + types.push(i) + end + idxType |= (iv[PBStats::HP]&1) + idxType |= (iv[PBStats::ATTACK]&1)<<1 + idxType |= (iv[PBStats::DEFENSE]&1)<<2 + idxType |= (iv[PBStats::SPEED]&1)<<3 + idxType |= (iv[PBStats::SPATK]&1)<<4 + idxType |= (iv[PBStats::SPDEF]&1)<<5 + idxType = (types.length-1)*idxType/63 + type = types[idxType] + if !NEWEST_BATTLE_MECHANICS + powerMin = 30 + powerMax = 70 + power |= (iv[PBStats::HP]&2)>>1 + power |= (iv[PBStats::ATTACK]&2) + power |= (iv[PBStats::DEFENSE]&2)<<1 + power |= (iv[PBStats::SPEED]&2)<<2 + power |= (iv[PBStats::SPATK]&2)<<3 + power |= (iv[PBStats::SPDEF]&2)<<4 + power = powerMin+(powerMax-powerMin)*power/63 + end + return [type,power] +end + + + +#=============================================================================== +# Power doubles for each consecutive use. (Fury Cutter) +#=============================================================================== +class PokeBattle_Move_091 < PokeBattle_Move + def pbChangeUsageCounters(user,specialUsage) + oldVal = user.effects[PBEffects::FuryCutter] + super + maxMult = 1 + while (@baseDamage<<(maxMult-1))<160 + maxMult += 1 # 1-4 for base damage of 20, 1-3 for base damage of 40 + end + user.effects[PBEffects::FuryCutter] = (oldVal>=maxMult) ? maxMult : oldVal+1 + end + + def pbBaseDamage(baseDmg,user,target) + return baseDmg<<(user.effects[PBEffects::FuryCutter]-1) + end +end + + + +#=============================================================================== +# Power is multiplied by the number of consecutive rounds in which this move was +# used by any Pokémon on the user's side. (Echoed Voice) +#=============================================================================== +class PokeBattle_Move_092 < PokeBattle_Move + def pbChangeUsageCounters(user,specialUsage) + oldVal = user.pbOwnSide.effects[PBEffects::EchoedVoiceCounter] + super + if !user.pbOwnSide.effects[PBEffects::EchoedVoiceUsed] + user.pbOwnSide.effects[PBEffects::EchoedVoiceCounter] = (oldVal>=5) ? 5 : oldVal+1 + end + user.pbOwnSide.effects[PBEffects::EchoedVoiceUsed] = true + end + + def pbBaseDamage(baseDmg,user,target) + return baseDmg*user.pbOwnSide.effects[PBEffects::EchoedVoiceCounter] # 1-5 + end +end + + + +#=============================================================================== +# User rages until the start of a round in which they don't use this move. (Rage) +# (Handled in Battler's pbProcessMoveAgainstTarget): Ups rager's Attack by 1 +# stage each time it loses HP due to a move. +#=============================================================================== +class PokeBattle_Move_093 < PokeBattle_Move + def pbEffectGeneral(user) + user.effects[PBEffects::Rage] = true + end +end + + + +#=============================================================================== +# Randomly damages or heals the target. (Present) +# NOTE: Apparently a Normal Gem should be consumed even if this move will heal, +# but I think that's silly so I've omitted that effect. +#=============================================================================== +class PokeBattle_Move_094 < PokeBattle_Move + def pbOnStartUse(user,targets) + @presentDmg = 0 # 0 = heal, >0 = damage + r = @battle.pbRandom(100) + if r<40; @presentDmg = 40 + elsif r<70; @presentDmg = 80 + elsif r<80; @presentDmg = 120 + end + end + + def pbFailsAgainstTarget?(user,target) + return false if @presentDmg>0 + if !target.canHeal? + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbDamagingMove? + return false if @presentDmg==0 + return super + end + + def pbBaseDamage(baseDmg,user,target) + return @presentDmg + end + + def pbEffectAgainstTarget(user,target) + return if @presentDmg>0 + target.pbRecoverHP(target.totalhp/4) + @battle.pbDisplay(_INTL("{1}'s HP was restored.",target.pbThis)) + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + hitNum = 1 if @presentDmg==0 # Healing anim + super + end +end + + + +#=============================================================================== +# Power is chosen at random. Power is doubled if the target is using Dig. Hits +# some semi-invulnerable targets. (Magnitude) +#=============================================================================== +class PokeBattle_Move_095 < PokeBattle_Move + def hitsDiggingTargets?; return true; end + + def pbOnStartUse(user,targets) + baseDmg = [10,30,50,70,90,110,150] + magnitudes = [ + 4, + 5,5, + 6,6,6,6, + 7,7,7,7,7,7, + 8,8,8,8, + 9,9, + 10 + ] + magni = magnitudes[@battle.pbRandom(magnitudes.length)] + @magnitudeDmg = baseDmg[magni-4] + @battle.pbDisplay(_INTL("Magnitude {1}!",magni)) + end + + def pbBaseDamage(baseDmg,user,target) + return @magnitudeDmg + end + + def pbModifyDamage(damageMult,user,target) + damageMult *= 2 if target.inTwoTurnAttack?("0CA") # Dig + damageMult = (damageMult/2.0).round if @battle.field.terrain==PBBattleTerrains::Grassy + return damageMult + end +end + + + +#=============================================================================== +# Power and type depend on the user's held berry. Destroys the berry. +# (Natural Gift) +#=============================================================================== +class PokeBattle_Move_096 < PokeBattle_Move + def initialize(battle,move) + super + @typeArray = { + :NORMAL => [:CHILANBERRY], + :FIRE => [:CHERIBERRY, :BLUKBERRY, :WATMELBERRY, :OCCABERRY], + :WATER => [:CHESTOBERRY, :NANABBERRY, :DURINBERRY, :PASSHOBERRY], + :ELECTRIC => [:PECHABERRY, :WEPEARBERRY, :BELUEBERRY, :WACANBERRY], + :GRASS => [:RAWSTBERRY, :PINAPBERRY, :RINDOBERRY, :LIECHIBERRY], + :ICE => [:ASPEARBERRY, :POMEGBERRY, :YACHEBERRY, :GANLONBERRY], + :FIGHTING => [:LEPPABERRY, :KELPSYBERRY, :CHOPLEBERRY, :SALACBERRY], + :POISON => [:ORANBERRY, :QUALOTBERRY, :KEBIABERRY, :PETAYABERRY], + :GROUND => [:PERSIMBERRY, :HONDEWBERRY, :SHUCABERRY, :APICOTBERRY], + :FLYING => [:LUMBERRY, :GREPABERRY, :COBABERRY, :LANSATBERRY], + :PSYCHIC => [:SITRUSBERRY, :TAMATOBERRY, :PAYAPABERRY, :STARFBERRY], + :BUG => [:FIGYBERRY, :CORNNBERRY, :TANGABERRY, :ENIGMABERRY], + :ROCK => [:WIKIBERRY, :MAGOSTBERRY, :CHARTIBERRY, :MICLEBERRY], + :GHOST => [:MAGOBERRY, :RABUTABERRY, :KASIBBERRY, :CUSTAPBERRY], + :DRAGON => [:AGUAVBERRY, :NOMELBERRY, :HABANBERRY, :JABOCABERRY], + :DARK => [:IAPAPABERRY, :SPELONBERRY, :COLBURBERRY, :ROWAPBERRY, :MARANGABERRY], + :STEEL => [:RAZZBERRY, :PAMTREBERRY, :BABIRIBERRY], + :FAIRY => [:ROSELIBERRY, :KEEBERRY] + } + @damageArray = { + 60 => [:CHERIBERRY, :CHESTOBERRY, :PECHABERRY, :RAWSTBERRY, :ASPEARBERRY, + :LEPPABERRY, :ORANBERRY, :PERSIMBERRY, :LUMBERRY, :SITRUSBERRY, + :FIGYBERRY, :WIKIBERRY, :MAGOBERRY, :AGUAVBERRY, :IAPAPABERRY, + :RAZZBERRY, :OCCABERRY, :PASSHOBERRY, :WACANBERRY, :RINDOBERRY, + :YACHEBERRY, :CHOPLEBERRY, :KEBIABERRY, :SHUCABERRY, :COBABERRY, + :PAYAPABERRY, :TANGABERRY, :CHARTIBERRY, :KASIBBERRY, :HABANBERRY, + :COLBURBERRY, :BABIRIBERRY, :CHILANBERRY, :ROSELIBERRY], + 70 => [:BLUKBERRY, :NANABBERRY, :WEPEARBERRY, :PINAPBERRY, :POMEGBERRY, + :KELPSYBERRY, :QUALOTBERRY, :HONDEWBERRY, :GREPABERRY, :TAMATOBERRY, + :CORNNBERRY, :MAGOSTBERRY, :RABUTABERRY, :NOMELBERRY, :SPELONBERRY, + :PAMTREBERRY], + 80 => [:WATMELBERRY, :DURINBERRY, :BELUEBERRY, :LIECHIBERRY, :GANLONBERRY, + :SALACBERRY, :PETAYABERRY, :APICOTBERRY, :LANSATBERRY, :STARFBERRY, + :ENIGMABERRY, :MICLEBERRY, :CUSTAPBERRY, :JABOCABERRY, :ROWAPBERRY, + :KEEBERRY, :MARANGABERRY] + } + @berry = 0 + end + + def pbMoveFailed?(user,targets) + # NOTE: Unnerve does not stop a Pokémon using this move. + @berry = user.item + if !pbIsBerry?(@berry) || !user.itemActive? + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + # NOTE: The AI calls this method via pbCalcType, but it involves @berry which + # won't always be accurate (although it will always be defined). Since + # the AI won't want to use it if the user has no item anyway, and + # complex item movement is unlikely, perhaps this is good enough. + def pbBaseType(user) + ret = getID(PBTypes,:NORMAL) + found = false + @typeArray.each do |type, items| + items.each do |i| + next if !isConst?(@berry,PBItems,i) + ret = getConst(PBTypes,type) || ret + found = true; break + end + break if found + end + return ret + end + + # This is a separate method so that the AI can use it as well + def pbNaturalGiftBaseDamage(heldItem) + ret = 1 + found = false + @damageArray.each do |dmg, items| + items.each do |i| + next if !isConst?(heldItem,PBItems,i) + ret = dmg + ret += 20 if NEWEST_BATTLE_MECHANICS + found = true; break + end + break if found + end + return ret + end + + def pbBaseDamage(baseDmg,user,target) + return pbNaturalGiftBaseDamage(@berry) + end + + def pbEndOfMoveUsageEffect(user,targets,numHits,switchedBattlers) + # NOTE: The item is consumed even if this move was Protected against or it + # missed. The item is not consumed if the target was switched out by + # an effect like a target's Red Card. + # NOTE: There is no item consumption animation. + user.pbConsumeItem(true,true,false) if user.item>0 + @berry = 0 + end +end + + + +#=============================================================================== +# Power increases the less PP this move has. (Trump Card) +#=============================================================================== +class PokeBattle_Move_097 < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + dmgs = [200,80,60,50,40] + ppLeft = [@pp,dmgs.length-1].min # PP is reduced before the move is used + baseDmg = dmgs[ppLeft] + return baseDmg + end +end + + + +#=============================================================================== +# Power increases the less HP the user has. (Flail, Reversal) +#=============================================================================== +class PokeBattle_Move_098 < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + ret = 20 + n = 48*user.hp/user.totalhp + if n<2; ret = 200 + elsif n<5; ret = 150 + elsif n<10; ret = 100 + elsif n<17; ret = 80 + elsif n<33; ret = 40 + end + return ret + end +end + + + +#=============================================================================== +# Power increases the quicker the user is than the target. (Electro Ball) +#=============================================================================== +class PokeBattle_Move_099 < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + ret = 40 + n = user.pbSpeed/target.pbSpeed + if n>=4; ret = 150 + elsif n>=3; ret = 120 + elsif n>=2; ret = 80 + elsif n>=1; ret = 60 + end + return ret + end +end + + + +#=============================================================================== +# Power increases the heavier the target is. (Grass Knot, Low Kick) +#=============================================================================== +class PokeBattle_Move_09A < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + ret = 20 + weight = target.pbWeight + if weight>=2000; ret = 120 + elsif weight>=1000; ret = 100 + elsif weight>=500; ret = 80 + elsif weight>=250; ret = 60 + elsif weight>=100; ret = 40 + end + return ret + end +end + + + +#=============================================================================== +# Power increases the heavier the user is than the target. (Heat Crash, Heavy Slam) +# Does double damage and has perfect accuracy if the target is Minimized. +#=============================================================================== +class PokeBattle_Move_09B < PokeBattle_Move + def tramplesMinimize?(param=1) + return true if NEWEST_BATTLE_MECHANICS # Perfect accuracy and double damage + return super + end + + def pbBaseDamage(baseDmg,user,target) + ret = 40 + n = (user.pbWeight/target.pbWeight).floor + if n>=5; ret = 120 + elsif n>=4; ret = 100 + elsif n>=3; ret = 80 + elsif n>=2; ret = 60 + end + return ret + end +end + + + +#=============================================================================== +# Powers up the ally's attack this round by 1.5. (Helping Hand) +#=============================================================================== +class PokeBattle_Move_09C < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbFailsAgainstTarget?(user,target) + if target.fainted? || target.effects[PBEffects::HelpingHand] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return true if pbMoveFailedTargetAlreadyMoved?(target) + return false + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::HelpingHand] = true + @battle.pbDisplay(_INTL("{1} is ready to help {2}!",user.pbThis,target.pbThis(true))) + end +end + + + +#=============================================================================== +# Weakens Electric attacks. (Mud Sport) +#=============================================================================== +class PokeBattle_Move_09D < PokeBattle_Move + def pbMoveFailed?(user,targets) + if NEWEST_BATTLE_MECHANICS + if @battle.field.effects[PBEffects::MudSportField]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + else + @battle.eachBattler do |b| + next if !b.effects[PBEffects::MudSport] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + end + return false + end + + def pbEffectGeneral(user) + if NEWEST_BATTLE_MECHANICS + @battle.field.effects[PBEffects::MudSportField] = 5 + else + user.effects[PBEffects::MudSport] = true + end + @battle.pbDisplay(_INTL("Electricity's power was weakened!")) + end +end + + + +#=============================================================================== +# Weakens Fire attacks. (Water Sport) +#=============================================================================== +class PokeBattle_Move_09E < PokeBattle_Move + def pbMoveFailed?(user,targets) + if NEWEST_BATTLE_MECHANICS + if @battle.field.effects[PBEffects::WaterSportField]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + else + @battle.eachBattler do |b| + next if !b.effects[PBEffects::WaterSport] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + end + return false + end + + def pbEffectGeneral(user) + if NEWEST_BATTLE_MECHANICS + @battle.field.effects[PBEffects::WaterSportField] = 5 + else + user.effects[PBEffects::WaterSport] = true + end + @battle.pbDisplay(_INTL("Fire's power was weakened!")) + end +end + + + +#=============================================================================== +# Type depends on the user's held item. (Judgment, Multi-Attack, Techno Blast) +#=============================================================================== +class PokeBattle_Move_09F < PokeBattle_Move + def initialize(battle,move) + super + if isConst?(@id,PBMoves,:JUDGMENT) + @itemTypes = { + :FISTPLATE => :FIGHTING, + :SKYPLATE => :FLYING, + :TOXICPLATE => :POISON, + :EARTHPLATE => :GROUND, + :STONEPLATE => :ROCK, + :INSECTPLATE => :BUG, + :SPOOKYPLATE => :GHOST, + :IRONPLATE => :STEEL, + :FLAMEPLATE => :FIRE, + :SPLASHPLATE => :WATER, + :MEADOWPLATE => :GRASS, + :ZAPPLATE => :ELECTRIC, + :MINDPLATE => :PSYCHIC, + :ICICLEPLATE => :ICE, + :DRACOPLATE => :DRAGON, + :DREADPLATE => :DARK, + :PIXIEPLATE => :FAIRY + } + elsif isConst?(@id,PBMoves,:TECHNOBLAST) + @itemTypes = { + :SHOCKDRIVE => :ELECTRIC, + :BURNDRIVE => :FIRE, + :CHILLDRIVE => :ICE, + :DOUSEDRIVE => :WATER + } + elsif isConst?(@id,PBMoves,:MULTIATTACK) + @itemTypes = { + :FIGHTINGMEMORY => :FIGHTING, + :SLYINGMEMORY => :FLYING, + :POISONMEMORY => :POISON, + :GROUNDMEMORY => :GROUND, + :ROCKMEMORY => :ROCK, + :BUGMEMORY => :BUG, + :GHOSTMEMORY => :GHOST, + :STEELMEMORY => :STEEL, + :FIREMEMORY => :FIRE, + :WATERMEMORY => :WATER, + :GRASSMEMORY => :GRASS, + :ELECTRICMEMORY => :ELECTRIC, + :PSYCHICMEMORY => :PSYCHIC, + :ICEMEMORY => :ICE, + :DRAGONMEMORY => :DRAGON, + :DARKMEMORY => :DARK, + :FAIRYMEMORY => :FAIRY + } + end + end + + def pbBaseType(user) + ret = getID(PBTypes,:NORMAL) + if user.itemActive? + @itemTypes.each do |item, itemType| + next if !isConst?(user.item,PBItems,item) + t = hasConst?(PBTypes,itemType) + ret = t || ret + break + end + end + return ret + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + if isConst?(@id,PBMoves,:TECHNOBLAST) # Type-specific anim + t = pbBaseType(user) + hitNum = 0 + hitNum = 1 if isConst?(t,PBTypes,:ELECTRIC) + hitNum = 2 if isConst?(t,PBTypes,:FIRE) + hitNum = 3 if isConst?(t,PBTypes,:ICE) + hitNum = 4 if isConst?(t,PBTypes,:WATER) + end + super + end +end + + + +#=============================================================================== +# This attack is always a critical hit. (Frost Breath, Storm Throw) +#=============================================================================== +class PokeBattle_Move_0A0 < PokeBattle_Move + def pbCritialOverride(user,target); return 1; end +end + + + +#=============================================================================== +# For 5 rounds, foes' attacks cannot become critical hits. (Lucky Chant) +#=============================================================================== +class PokeBattle_Move_0A1 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.pbOwnSide.effects[PBEffects::LuckyChant]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.pbOwnSide.effects[PBEffects::LuckyChant] = 5 + @battle.pbDisplay(_INTL("The Lucky Chant shielded {1} from critical hits!",user.pbTeam(true))) + end +end + + + +#=============================================================================== +# For 5 rounds, lowers power of physical attacks against the user's side. +# (Reflect) +#=============================================================================== +class PokeBattle_Move_0A2 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.pbOwnSide.effects[PBEffects::Reflect]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.pbOwnSide.effects[PBEffects::Reflect] = 5 + user.pbOwnSide.effects[PBEffects::Reflect] = 8 if user.hasActiveItem?(:LIGHTCLAY) + @battle.pbDisplay(_INTL("{1} raised {2}'s Defense!",@name,user.pbTeam(true))) + end +end + + + +#=============================================================================== +# For 5 rounds, lowers power of special attacks against the user's side. (Light Screen) +#=============================================================================== +class PokeBattle_Move_0A3 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.pbOwnSide.effects[PBEffects::LightScreen]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.pbOwnSide.effects[PBEffects::LightScreen] = 5 + user.pbOwnSide.effects[PBEffects::LightScreen] = 8 if user.hasActiveItem?(:LIGHTCLAY) + @battle.pbDisplay(_INTL("{1} raised {2}'s Special Defense!",@name,user.pbTeam(true))) + end +end + + + +#=============================================================================== +# Effect depends on the environment. (Secret Power) +#=============================================================================== +class PokeBattle_Move_0A4 < PokeBattle_Move + def flinchingMove?; return [6,10,12].include?(@secretPower); end + + def pbOnStartUse(user,targets) + # NOTE: This is Gen 7's list plus some of Gen 6 plus a bit of my own. + @secretPower = 0 # Body Slam, paralysis + case @battle.field.terrain + when PBBattleTerrains::Electric + @secretPower = 1 # Thunder Shock, paralysis + when PBBattleTerrains::Grassy + @secretPower = 2 # Vine Whip, sleep + when PBBattleTerrains::Misty + @secretPower = 3 # Fairy Wind, lower Sp. Atk by 1 + when PBBattleTerrains::Psychic + @secretPower = 4 # Confusion, lower Speed by 1 + else + case @battle.environment + when PBEnvironment::Grass, PBEnvironment::TallGrass, + PBEnvironment::Forest, PBEnvironment::ForestGrass + @secretPower = 2 # (Same as Grassy Terrain) + when PBEnvironment::MovingWater, PBEnvironment::StillWater, + PBEnvironment::Puddle, PBEnvironment::Underwater + @secretPower = 5 # Water Pulse, lower Attack by 1 + when PBEnvironment::Puddle + @secretPower = 6 # Mud Shot, lower Speed by 1 + when PBEnvironment::Cave + @secretPower = 7 # Rock Throw, flinch + when PBEnvironment::Rock, PBEnvironment::Sand + @secretPower = 8 # Mud-Slap, lower Acc by 1 + when PBEnvironment::Snow, PBEnvironment::Ice + @secretPower = 9 # Ice Shard, freeze + when PBEnvironment::Volcano + @secretPower = 10 # Incinerate, burn + when PBEnvironment::Graveyard + @secretPower = 11 # Shadow Sneak, flinch + when PBEnvironment::Sky + @secretPower = 12 # Gust, lower Speed by 1 + when PBEnvironment::Space + @secretPower = 13 # Swift, flinch + when PBEnvironment::UltraSpace + @secretPower = 14 # Psywave, lower Defense by 1 + end + end + end + + # NOTE: This intentionally doesn't use def pbAdditionalEffect, because that + # method is called per hit and this move's additional effect only occurs + # once per use, after all the hits have happened (two hits are possible + # via Parental Bond). + def pbEffectAfterAllHits(user,target) + return if target.fainted? + return if target.damageState.unaffected || target.damageState.substitute + chance = pbAdditionalEffectChance(user,target) + return if @battle.pbRandom(100)>=chance + case @secretPower + when 2 + target.pbSleep if target.pbCanSleep?(user,false,self) + when 10 + target.pbBurn(user) if target.pbCanBurn?(user,false,self) + when 0, 1 + target.pbParalyze(user) if target.pbCanParalyze?(user,false,self) + when 9 + target.pbFreeze if target.pbCanFreeze?(user,false,self) + when 5 + if target.pbCanLowerStatStage?(PBStats::ATTACK,user,self) + target.pbLowerStatStage(PBStats::ATTACK,1,user) + end + when 14 + if target.pbCanLowerStatStage?(PBStats::DEFENSE,user,self) + target.pbLowerStatStage(PBStats::DEFENSE,1,user) + end + when 3 + if target.pbCanLowerStatStage?(PBStats::SPATK,user,self) + target.pbLowerStatStage(PBStats::SPATK,1,user) + end + when 4, 6, 12 + if target.pbCanLowerStatStage?(PBStats::SPEED,user,self) + target.pbLowerStatStage(PBStats::SPEED,1,user) + end + when 8 + if target.pbCanLowerStatStage?(PBStats::ACCURACY,user,self) + target.pbLowerStatStage(PBStats::ACCURACY,1,user) + end + when 7, 11, 13 + target.pbFlinch(user) + end + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + id = getConst(PBMoves,:BODYSLAM) # Environment-specific anim + case @secretPower + when 1; id = getConst(PBMoves,:THUNDERSHOCK) || id + when 2; id = getConst(PBMoves,:VINEWHIP) || id + when 3; id = getConst(PBMoves,:FAIRYWIND) || id + when 4; id = getConst(PBMoves,:CONFUSION) || id + when 5; id = getConst(PBMoves,:WATERPULSE) || id + when 6; id = getConst(PBMoves,:MUDSHOT) || id + when 7; id = getConst(PBMoves,:ROCKTHROW) || id + when 8; id = getConst(PBMoves,:MUDSLAP) || id + when 9; id = getConst(PBMoves,:ICESHARD) || id + when 10; id = getConst(PBMoves,:INCINERATE) || id + when 11; id = getConst(PBMoves,:SHADOWSNEAK) || id + when 12; id = getConst(PBMoves,:GUST) || id + when 13; id = getConst(PBMoves,:SWIFT) || id + when 14; id = getConst(PBMoves,:PSYWAVE) || id + end + super + end +end + + + +#=============================================================================== +# Always hits. +#=============================================================================== +class PokeBattle_Move_0A5 < PokeBattle_Move + def pbAccuracyCheck(user,target); return true; end +end + + + +#=============================================================================== +# User's attack next round against the target will definitely hit. +# (Lock-On, Mind Reader) +#=============================================================================== +class PokeBattle_Move_0A6 < PokeBattle_Move + def pbEffectAgainstTarget(user,target) + user.effects[PBEffects::LockOn] = 2 + user.effects[PBEffects::LockOnPos] = target.index + @battle.pbDisplay(_INTL("{1} took aim at {2}!",user.pbThis,target.pbThis(true))) + end +end + + + +#=============================================================================== +# Target's evasion stat changes are ignored from now on. (Foresight, Odor Sleuth) +# Normal and Fighting moves have normal effectiveness against the Ghost-type target. +#=============================================================================== +class PokeBattle_Move_0A7 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::Foresight] = true + @battle.pbDisplay(_INTL("{1} was identified!",target.pbThis)) + end +end + + + +#=============================================================================== +# Target's evasion stat changes are ignored from now on. (Miracle Eye) +# Psychic moves have normal effectiveness against the Dark-type target. +#=============================================================================== +class PokeBattle_Move_0A8 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::MiracleEye] = true + @battle.pbDisplay(_INTL("{1} was identified!",target.pbThis)) + end +end + + + +#=============================================================================== +# This move ignores target's Defense, Special Defense and evasion stat changes. +# (Chip Away, Darkest Lariat, Sacred Sword) +#=============================================================================== +class PokeBattle_Move_0A9 < PokeBattle_Move + def pbCalcAccuracyMultipliers(user,target,multipliers) + super + modifiers[EVA_STAGE] = 0 # Accuracy stat stage + end + + def pbGetDefenseStats(user,target) + ret1, ret2 = super + return ret1, 6 # Def/SpDef stat stage + end +end + + + +#=============================================================================== +# User is protected against moves with the "B" flag this round. (Detect, Protect) +#=============================================================================== +class PokeBattle_Move_0AA < PokeBattle_ProtectMove + def initialize(battle,move) + super + @effect = PBEffects::Protect + end +end + + + +#=============================================================================== +# User's side is protected against moves with priority greater than 0 this round. +# (Quick Guard) +#=============================================================================== +class PokeBattle_Move_0AB < PokeBattle_ProtectMove + def initialize(battle,move) + super + @effect = PBEffects::QuickGuard + @sidedEffect = true + end +end + + + +#=============================================================================== +# User's side is protected against moves that target multiple battlers this round. +# (Wide Guard) +#=============================================================================== +class PokeBattle_Move_0AC < PokeBattle_ProtectMove + def initialize(battle,move) + super + @effect = PBEffects::WideGuard + @sidedEffect = true + end +end + + + +#=============================================================================== +# Ends target's protections immediately. (Feint) +#=============================================================================== +class PokeBattle_Move_0AD < PokeBattle_Move + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::BanefulBunker] = false + target.effects[PBEffects::KingsShield] = false + target.effects[PBEffects::Protect] = false + target.effects[PBEffects::SpikyShield] = false + target.pbOwnSide.effects[PBEffects::CraftyShield] = false + target.pbOwnSide.effects[PBEffects::MatBlock] = false + target.pbOwnSide.effects[PBEffects::QuickGuard] = false + target.pbOwnSide.effects[PBEffects::WideGuard] = false + end +end + + + +#=============================================================================== +# Uses the last move that the target used. (Mirror Move) +#=============================================================================== +class PokeBattle_Move_0AE < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + def callsAnotherMove?; return true; end + + def pbFailsAgainstTarget?(user,target) + if target.lastRegularMoveUsed<=0 || + !pbGetMoveData(target.lastRegularMoveUsed,MOVE_FLAGS)[/e/] # Not copyable by Mirror Move + @battle.pbDisplay(_INTL("The mirror move failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.pbUseMoveSimple(target.lastRegularMoveUsed,target.index) + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + # No animation + end +end + + + +#=============================================================================== +# Uses the last move that was used. (Copycat) +#=============================================================================== +class PokeBattle_Move_0AF < PokeBattle_Move + def callsAnotherMove?; return true; end + + def initialize(battle,move) + super + @moveBlacklist = [ + # Struggle, Chatter, Belch + "002", # Struggle + "014", # Chatter + "158", # Belch # Not listed on Bulbapedia + # Moves that affect the moveset + "05C", # Mimic + "05D", # Sketch + "069", # Transform + # Counter moves + "071", # Counter + "072", # Mirror Coat + "073", # Metal Burst # Not listed on Bulbapedia + # Helping Hand, Feint (always blacklisted together, don't know why) + "09C", # Helping Hand + "0AD", # Feint + # Protection moves + "0AA", # Detect, Protect + "0AB", # Quick Guard # Not listed on Bulbapedia + "0AC", # Wide Guard # Not listed on Bulbapedia + "0E8", # Endure + "149", # Mat Block + "14A", # Crafty Shield # Not listed on Bulbapedia + "14B", # King's Shield + "14C", # Spiky Shield + "168", # Baneful Bunker + # Moves that call other moves + "0AE", # Mirror Move + "0AF", # Copycat (this move) + "0B0", # Me First + "0B3", # Nature Power # Not listed on Bulbapedia + "0B4", # Sleep Talk + "0B5", # Assist + "0B6", # Metronome + # Move-redirecting and stealing moves + "0B1", # Magic Coat # Not listed on Bulbapedia + "0B2", # Snatch + "117", # Follow Me, Rage Powder + "16A", # Spotlight + # Set up effects that trigger upon KO + "0E6", # Grudge # Not listed on Bulbapedia + "0E7", # Destiny Bond + # Held item-moving moves + "0F1", # Covet, Thief + "0F2", # Switcheroo, Trick + "0F3", # Bestow + # Moves that start focussing at the start of the round + "115", # Focus Punch + "171", # Shell Trap + "172", # Beak Blast + # Event moves that do nothing + "133", # Hold Hands + "134" # Celebrate + ] + if NEWEST_BATTLE_MECHANICS + @moveBlacklist += [ + # Target-switching moves + "0EB", # Roar, Whirlwind + "0EC" # Circle Throw, Dragon Tail + ] + end + end + + def pbMoveFailed?(user,targets) + if @battle.lastMoveUsed<=0 || + @moveBlacklist.include?(pbGetMoveData(@battle.lastMoveUsed,MOVE_FUNCTION_CODE)) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.pbUseMoveSimple(@battle.lastMoveUsed) + end +end + + + +#=============================================================================== +# Uses the move the target was about to use this round, with 1.5x power. +# (Me First) +#=============================================================================== +class PokeBattle_Move_0B0 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + def callsAnotherMove?; return true; end + + def initialize(battle,move) + super + @moveBlacklist = [ + "0F1", # Covet, Thief + # Struggle, Chatter, Belch + "002", # Struggle + "014", # Chatter + "158", # Belch + # Counter moves + "071", # Counter + "072", # Mirror Coat + "073", # Metal Burst + # Moves that start focussing at the start of the round + "115", # Focus Punch + "171", # Shell Trap + "172" # Beak Blast + ] + end + + def pbFailsAgainstTarget?(user,target) + return true if pbMoveFailedTargetAlreadyMoved?(target) + oppMove = @battle.choices[target.index][2] + if !oppMove || oppMove.id<=0 || + oppMove.statusMove? || @moveBlacklist.include?(oppMove.function) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + user.effects[PBEffects::MeFirst] = true + user.pbUseMoveSimple(@battle.choices[target.index][2].id) + user.effects[PBEffects::MeFirst] = false + end +end + + + +#=============================================================================== +# This round, reflects all moves with the "C" flag targeting the user back at +# their origin. (Magic Coat) +#=============================================================================== +class PokeBattle_Move_0B1 < PokeBattle_Move + def pbEffectGeneral(user) + user.effects[PBEffects::MagicCoat] = true + @battle.pbDisplay(_INTL("{1} shrouded itself with Magic Coat!",user.pbThis)) + end +end + + + +#=============================================================================== +# This round, snatches all used moves with the "D" flag. (Snatch) +#=============================================================================== +class PokeBattle_Move_0B2 < PokeBattle_Move + def pbEffectGeneral(user) + user.effects[PBEffects::Snatch] = 1 + @battle.eachBattler do |b| + next if b.effects[PBEffects::Snatch]0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return true if pbMoveFailedAromaVeil?(user,target) + canDisable = false + target.eachMove do |m| + next if m.id!=target.lastRegularMoveUsed + next if m.pp==0 && m.totalpp>0 + canDisable = true + break + end + if !canDisable + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::Disable] = 5 + target.effects[PBEffects::DisableMove] = target.lastRegularMoveUsed + @battle.pbDisplay(_INTL("{1}'s {2} was disabled!",target.pbThis, + PBMoves.getName(target.lastRegularMoveUsed))) + target.pbItemStatusCureCheck + end +end + + + +#=============================================================================== +# For 4 rounds, disables the target's non-damaging moves. (Taunt) +#=============================================================================== +class PokeBattle_Move_0BA < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbFailsAgainstTarget?(user,target) + if target.effects[PBEffects::Taunt]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return true if pbMoveFailedAromaVeil?(user,target) + if NEWEST_BATTLE_MECHANICS && target.hasActiveAbility?(:OBLIVIOUS) && + !@battle.moldBreaker + @battle.pbShowAbilitySplash(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("But it failed!")) + else + @battle.pbDisplay(_INTL("But it failed because of {1}'s {2}!", + target.pbThis(true),target.abilityName)) + end + @battle.pbHideAbilitySplash(target) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::Taunt] = 4 + @battle.pbDisplay(_INTL("{1} fell for the taunt!",target.pbThis)) + target.pbItemStatusCureCheck + end +end + + + +#=============================================================================== +# For 5 rounds, disables the target's healing moves. (Heal Block) +#=============================================================================== +class PokeBattle_Move_0BB < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + if target.effects[PBEffects::HealBlock]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return true if pbMoveFailedAromaVeil?(user,target) + return false + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::HealBlock] = 5 + @battle.pbDisplay(_INTL("{1} was prevented from healing!",target.pbThis)) + target.pbItemStatusCureCheck + end +end + + + +#=============================================================================== +# For 4 rounds, the target must use the same move each round. (Encore) +#=============================================================================== +class PokeBattle_Move_0BC < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def initialize(battle,move) + super + @moveBlacklist = [ + "0BC", # Encore + # Struggle + "002", # Struggle + # Moves that affect the moveset + "05C", # Mimic + "05D", # Sketch + "069", # Transform + # Moves that call other moves (see also below) + "0AE" # Mirror Move + ] + if NEWEST_BATTLE_MECHANICS + @moveBlacklist += [ + # Moves that call other moves +# "0AE", # Mirror Move # See above + "0AF", # Copycat + "0B0", # Me First + "0B3", # Nature Power + "0B4", # Sleep Talk + "0B5", # Assist + "0B6" # Metronome + ] + end + end + + def pbFailsAgainstTarget?(user,target) + if target.effects[PBEffects::Encore]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if target.lastRegularMoveUsed<=0 || + @moveBlacklist.include?(pbGetMoveData(target.lastRegularMoveUsed,MOVE_FUNCTION_CODE)) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if target.effects[PBEffects::ShellTrap] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return true if pbMoveFailedAromaVeil?(user,target) + canEncore = false + target.eachMove do |m| + next if m.id!=target.lastRegularMoveUsed + next if m.pp==0 && m.totalpp>0 + canEncore = true + break + end + if !canEncore + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::Encore] = 4 + target.effects[PBEffects::EncoreMove] = target.lastRegularMoveUsed + @battle.pbDisplay(_INTL("{1} received an encore!",target.pbThis)) + target.pbItemStatusCureCheck + end +end + + + +#=============================================================================== +# Hits twice. +#=============================================================================== +class PokeBattle_Move_0BD < PokeBattle_Move + def multiHitMove?; return true; end + def pbNumHits(user,targets); return 2; end +end + + + +#=============================================================================== +# Hits twice. May poison the target on each hit. (Twineedle) +#=============================================================================== +class PokeBattle_Move_0BE < PokeBattle_PoisonMove + def multiHitMove?; return true; end + def pbNumHits(user,targets); return 2; end +end + + + +#=============================================================================== +# Hits 3 times. Power is multiplied by the hit number. (Triple Kick) +# An accuracy check is performed for each hit. +#=============================================================================== +class PokeBattle_Move_0BF < PokeBattle_Move + def multiHitMove?; return true; end + def pbNumHits(user,targets); return 3; end + + def successCheckPerHit? + return @accCheckPerHit + end + + def pbOnStartUse(user,targets) + @calcBaseDmg = 0 + @accCheckPerHit = !user.hasActiveAbility?(:SKILLLINK) + end + + def pbBaseDamage(baseDmg,user,target) + @calcBaseDmg += baseDmg + return @calcBaseDmg + end +end + + + +#=============================================================================== +# Hits 2-5 times. +#=============================================================================== +class PokeBattle_Move_0C0 < PokeBattle_Move + def multiHitMove?; return true; end + + def pbNumHits(user,targets) + if isConst?(@id,PBMoves,:WATERSHURIKEN) && + isConst?(user.species,PBSpecies,:GRENINJA) && user.form==1 + return 3 + end + hitChances = [2,2,3,3,4,5] + r = @battle.pbRandom(hitChances.length) + r = hitChances.length-1 if user.hasActiveAbility?(:SKILLLINK) + return hitChances[r] + end + + def pbBaseDamage(baseDmg,user,target) + if isConst?(@id,PBMoves,:WATERSHURIKEN) && + isConst?(user.species,PBSpecies,:GRENINJA) && user.form==1 + return 20 + end + return super + end +end + + + +#=============================================================================== +# Hits X times, where X is the number of non-user unfainted status-free Pokémon +# in the user's party (not including partner trainers). Fails if X is 0. +# Base power of each hit depends on the base Attack stat for the species of that +# hit's participant. (Beat Up) +#=============================================================================== +class PokeBattle_Move_0C1 < PokeBattle_Move + def multiHitMove?; return true; end + + def pbMoveFailed?(user,targets) + @beatUpList = [] + @battle.eachInTeamFromBattlerIndex(user.index) do |pkmn,i| + next if !pkmn.able? || pkmn.status!=PBStatuses::NONE + @beatUpList.push(i) + end + if @beatUpList.length==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbNumHits(user,targets) + return @beatUpList.length + end + + def pbBaseDamage(baseDmg,user,target) + i = @beatUpList.shift # First element in array, and removes it from array + atk = @battle.pbParty(user.index)[i].baseStats[PBStats::ATTACK] + return 5+(atk/10) + end +end + + + +#=============================================================================== +# Two turn attack. Attacks first turn, skips second turn (if successful). +#=============================================================================== +class PokeBattle_Move_0C2 < PokeBattle_Move + def pbEffectGeneral(user) + user.effects[PBEffects::HyperBeam] = 2 + user.currentMove = @id + end +end + + + +#=============================================================================== +# Two turn attack. Skips first turn, attacks second turn. (Razor Wind) +#=============================================================================== +class PokeBattle_Move_0C3 < PokeBattle_TwoTurnMove + def pbChargingTurnMessage(user,targets) + @battle.pbDisplay(_INTL("{1} whipped up a whirlwind!",user.pbThis)) + end +end + + + +#=============================================================================== +# Two turn attack. Skips first turn, attacks second turn. (Solar Beam, Solar Blade) +# Power halved in all weather except sunshine. In sunshine, takes 1 turn instead. +#=============================================================================== +class PokeBattle_Move_0C4 < PokeBattle_TwoTurnMove + def pbIsChargingTurn?(user) + ret = super + if user.effects[PBEffects::TwoTurnAttack]==0 + w = @battle.pbWeather + if w==PBWeather::Sun || w==PBWeather::HarshSun + @powerHerb = false + @chargingTurn = true + @damagingTurn = true + return false + end + end + return ret + end + + def pbChargingTurnMessage(user,targets) + @battle.pbDisplay(_INTL("{1} took in sunlight!",user.pbThis)) + end + + def pbBaseDamageMultiplier(damageMult,user,target) + w = @battle.pbWeather + if w>0 && w!=PBWeather::Sun && w!=PBWeather::HarshSun + damageMult = (damageMult/2.0).round + end + return damageMult + end +end + + + +#=============================================================================== +# Two turn attack. Skips first turn, attacks second turn. (Freeze Shock) +# May paralyze the target. +#=============================================================================== +class PokeBattle_Move_0C5 < PokeBattle_TwoTurnMove + def pbChargingTurnMessage(user,targets) + @battle.pbDisplay(_INTL("{1} became cloaked in a freezing light!",user.pbThis)) + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + target.pbParalyze(user) if target.pbCanParalyze?(user,false,self) + end +end + + + +#=============================================================================== +# Two turn attack. Skips first turn, attacks second turn. (Ice Burn) +# May burn the target. +#=============================================================================== +class PokeBattle_Move_0C6 < PokeBattle_TwoTurnMove + def pbChargingTurnMessage(user,targets) + @battle.pbDisplay(_INTL("{1} became cloaked in freezing air!",user.pbThis)) + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + target.pbBurn(user) if target.pbCanBurn?(user,false,self) + end +end + + + +#=============================================================================== +# Two turn attack. Skips first turn, attacks second turn. (Sky Attack) +# May make the target flinch. +#=============================================================================== +class PokeBattle_Move_0C7 < PokeBattle_TwoTurnMove + def flinchingMove?; return true; end + + def pbChargingTurnMessage(user,targets) + @battle.pbDisplay(_INTL("{1} became cloaked in a harsh light!",user.pbThis)) + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + target.pbFlinch(user) + end +end + + + +#=============================================================================== +# Two turn attack. Ups user's Defense by 1 stage first turn, attacks second turn. +# (Skull Bash) +#=============================================================================== +class PokeBattle_Move_0C8 < PokeBattle_TwoTurnMove + def pbChargingTurnMessage(user,targets) + @battle.pbDisplay(_INTL("{1} tucked in its head!",user.pbThis)) + end + + def pbChargingTurnEffect(user,target) + if user.pbCanRaiseStatStage?(PBStats::DEFENSE,user,self) + user.pbRaiseStatStage(PBStats::DEFENSE,1,user) + end + end +end + + + +#=============================================================================== +# Two turn attack. Skips first turn, attacks second turn. (Fly) +# (Handled in Battler's pbSuccessCheckPerHit): Is semi-invulnerable during use. +#=============================================================================== +class PokeBattle_Move_0C9 < PokeBattle_TwoTurnMove + def unusableInGravity?; return true; end + + def pbChargingTurnMessage(user,targets) + @battle.pbDisplay(_INTL("{1} flew up high!",user.pbThis)) + end +end + + + +#=============================================================================== +# Two turn attack. Skips first turn, attacks second turn. (Dig) +# (Handled in Battler's pbSuccessCheckPerHit): Is semi-invulnerable during use. +#=============================================================================== +class PokeBattle_Move_0CA < PokeBattle_TwoTurnMove + def pbChargingTurnMessage(user,targets) + @battle.pbDisplay(_INTL("{1} burrowed its way under the ground!",user.pbThis)) + end +end + + + +#=============================================================================== +# Two turn attack. Skips first turn, attacks second turn. (Dive) +# (Handled in Battler's pbSuccessCheckPerHit): Is semi-invulnerable during use. +#=============================================================================== +class PokeBattle_Move_0CB < PokeBattle_TwoTurnMove + def pbChargingTurnMessage(user,targets) + @battle.pbDisplay(_INTL("{1} hid underwater!",user.pbThis)) + end +end + + + +#=============================================================================== +# Two turn attack. Skips first turn, attacks second turn. (Bounce) +# May paralyze the target. +# (Handled in Battler's pbSuccessCheckPerHit): Is semi-invulnerable during use. +#=============================================================================== +class PokeBattle_Move_0CC < PokeBattle_TwoTurnMove + def unusableInGravity?; return true; end + + def pbChargingTurnMessage(user,targets) + @battle.pbDisplay(_INTL("{1} sprang up!",user.pbThis)) + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + target.pbParalyze(user) if target.pbCanParalyze?(user,false,self) + end +end + + + +#=============================================================================== +# Two turn attack. Skips first turn, attacks second turn. (Shadow Force) +# Is invulnerable during use. Ends target's protections upon hit. +#=============================================================================== +class PokeBattle_Move_0CD < PokeBattle_TwoTurnMove + def pbChargingTurnMessage(user,targets) + @battle.pbDisplay(_INTL("{1} vanished instantly!",user.pbThis)) + end + + def pbAttackingTurnEffect(user,target) + target.effects[PBEffects::BanefulBunker] = false + target.effects[PBEffects::KingsShield] = false + target.effects[PBEffects::Protect] = false + target.effects[PBEffects::SpikyShield] = false + target.pbOwnSide.effects[PBEffects::CraftyShield] = false + target.pbOwnSide.effects[PBEffects::MatBlock] = false + target.pbOwnSide.effects[PBEffects::QuickGuard] = false + target.pbOwnSide.effects[PBEffects::WideGuard] = false + end +end + + + +#=============================================================================== +# Two turn attack. Skips first turn, attacks second turn. (Sky Drop) +# (Handled in Battler's pbSuccessCheckPerHit): Is semi-invulnerable during use. +# Target is also semi-invulnerable during use, and can't take any action. +# Doesn't damage airborne Pokémon (but still makes them unable to move during). +#=============================================================================== +class PokeBattle_Move_0CE < PokeBattle_TwoTurnMove + def unusableInGravity?; return true; end + + def pbIsChargingTurn?(user) + # NOTE: Sky Drop doesn't benefit from Power Herb, probably because it works + # differently (i.e. immobilises the target during use too). + @powerHerb = false + @chargingTurn = (user.effects[PBEffects::TwoTurnAttack]==0) + @damagingTurn = (user.effects[PBEffects::TwoTurnAttack]!=0) + return !@damagingTurn + end + + def pbFailsAgainstTarget?(user,target) + if !target.opposes?(user) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if target.effects[PBEffects::Substitute]>0 && !ignoresSubstitute?(user) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if NEWEST_BATTLE_MECHANICS && target.pbWeight>=2000 # 200.0kg + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if target.semiInvulnerable? || + (target.effects[PBEffects::SkyDrop]>=0 && @chargingTurn) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if target.effects[PBEffects::SkyDrop]!=user.index && @damagingTurn + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbCalcTypeMod(movetype,user,target) + return PBTypeEffectiveness::INEFFECTIVE if target.pbHasType?(:FLYING) + return super + end + + def pbChargingTurnMessage(user,targets) + @battle.pbDisplay(_INTL("{1} took {2} into the sky!",user.pbThis,targets[0].pbThis(true))) + end + + def pbAttackingTurnMessage(user,targets) + @battle.pbDisplay(_INTL("{1} was freed from the Sky Drop!",targets[0].pbThis)) + end + + def pbChargingTurnEffect(user,target) + target.effects[PBEffects::SkyDrop] = user.index + end + + def pbAttackingTurnEffect(user,target) + target.effects[PBEffects::SkyDrop] = -1 + end +end + + + +#=============================================================================== +# Trapping move. Traps for 5 or 6 rounds. Trapped Pokémon lose 1/16 of max HP +# at end of each round. +#=============================================================================== +class PokeBattle_Move_0CF < PokeBattle_Move + def pbEffectAgainstTarget(user,target) + return if target.fainted? || target.damageState.substitute + return if target.effects[PBEffects::Trapping]>0 + # Set trapping effect duration and info + if user.hasActiveItem?(:GRIPCLAW) + target.effects[PBEffects::Trapping] = (NEWEST_BATTLE_MECHANICS) ? 8 : 6 + else + target.effects[PBEffects::Trapping] = 5+@battle.pbRandom(2) + end + target.effects[PBEffects::TrappingMove] = @id + target.effects[PBEffects::TrappingUser] = user.index + # Message + msg = _INTL("{1} was trapped in the vortex!",target.pbThis) + if isConst?(@id,PBMoves,:BIND) + msg = _INTL("{1} was squeezed by {2}!",target.pbThis,user.pbThis(true)) + elsif isConst?(@id,PBMoves,:CLAMP) + msg = _INTL("{1} clamped {2}!",user.pbThis,target.pbThis(true)) + elsif isConst?(@id,PBMoves,:FIRESPIN) + msg = _INTL("{1} was trapped in the fiery vortex!",target.pbThis) + elsif isConst?(@id,PBMoves,:INFESTATION) + msg = _INTL("{1} has been afflicted with an infestation by {2}!",target.pbThis,user.pbThis(true)) + elsif isConst?(@id,PBMoves,:MAGMASTORM) + msg = _INTL("{1} became trapped by Magma Storm!",target.pbThis) + elsif isConst?(@id,PBMoves,:SANDTOMB) + msg = _INTL("{1} became trapped by Sand Tomb!",target.pbThis) + elsif isConst?(@id,PBMoves,:WHIRLPOOL) + msg = _INTL("{1} became trapped in the vortex!",target.pbThis) + elsif isConst?(@id,PBMoves,:WRAP) + msg = _INTL("{1} was wrapped by {2}!",target.pbThis,user.pbThis(true)) + end + @battle.pbDisplay(msg) + end +end + + + +#=============================================================================== +# Trapping move. Traps for 5 or 6 rounds. Trapped Pokémon lose 1/16 of max HP +# at end of each round. (Whirlpool) +# Power is doubled if target is using Dive. Hits some semi-invulnerable targets. +#=============================================================================== +class PokeBattle_Move_0D0 < PokeBattle_Move_0CF + def hitsDivingTargets?; return true; end + + def pbModifyDamage(damageMult,user,target) + damageMult *= 2 if target.inTwoTurnAttack?("0CB") # Dive + return damageMult + end +end + + + +#=============================================================================== +# User must use this move for 2 more rounds. No battlers can sleep. (Uproar) +# NOTE: Bulbapedia claims that an uproar will wake up Pokémon even if they have +# Soundproof, and will not allow Pokémon to fall asleep even if they have +# Soundproof. I think this is an oversight, so I've let Soundproof Pokémon +# be unaffected by Uproar waking/non-sleeping effects. +#=============================================================================== +class PokeBattle_Move_0D1 < PokeBattle_Move + def pbEffectGeneral(user) + return if user.effects[PBEffects::Uproar]>0 + user.effects[PBEffects::Uproar] = 3 + user.currentMove = @id + @battle.pbDisplay(_INTL("{1} caused an uproar!",user.pbThis)) + @battle.pbPriority(true).each do |b| + next if b.fainted? || b.status!=PBStatuses::SLEEP + next if b.hasActiveAbility?(:SOUNDPROOF) + b.pbCureStatus + end + end +end + + + +#=============================================================================== +# User must use this move for 1 or 2 more rounds. At end, user becomes confused. +# (Outrage, Petal Dange, Thrash) +#=============================================================================== +class PokeBattle_Move_0D2 < PokeBattle_Move + def pbEffectAfterAllHits(user,target) + if !target.damageState.unaffected && user.effects[PBEffects::Outrage]==0 + user.effects[PBEffects::Outrage] = 2+@battle.pbRandom(2) + user.currentMove = @id + end + if user.effects[PBEffects::Outrage]>0 + user.effects[PBEffects::Outrage] -= 1 + if user.effects[PBEffects::Outrage]==0 && user.pbCanConfuseSelf?(false) + user.pbConfuse(_INTL("{1} became confused due to fatigue!",user.pbThis)) + end + end + end +end + + + +#=============================================================================== +# User must use this move for 4 more rounds. Power doubles each round. +# Power is also doubled if user has curled up. (Ice Ball, Rollout) +#=============================================================================== +class PokeBattle_Move_0D3 < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + shift = (4-user.effects[PBEffects::Rollout]) # 0-4, where 0 is most powerful + shift += 1 if user.effects[PBEffects::DefenseCurl] + baseDmg = baseDmg<0 + end +end + + + +#=============================================================================== +# User bides its time this round and next round. The round after, deals 2x the +# total direct damage it took while biding to the last battler that damaged it. +# (Bide) +#=============================================================================== +class PokeBattle_Move_0D4 < PokeBattle_FixedDamageMove + def pbAddTarget(targets,user) + return if user.effects[PBEffects::Bide]!=1 # Not the attack turn + idxTarget = user.effects[PBEffects::BideTarget] + t = (idxTarget>=0) ? @battle.battlers[idxTarget] : nil + if !user.pbAddTarget(targets,user,t,self,false) + user.pbAddTargetRandomFoe(targets,user,self,false) + end + end + + def pbMoveFailed?(user,targets) + return false if user.effects[PBEffects::Bide]!=1 # Not the attack turn + if user.effects[PBEffects::BideDamage]==0 + @battle.pbDisplay(_INTL("But it failed!")) + user.effects[PBEffects::Bide] = 0 # No need to reset other Bide variables + return true + end + if targets.length==0 + @battle.pbDisplay(_INTL("But there was no target...")) + user.effects[PBEffects::Bide] = 0 # No need to reset other Bide variables + return true + end + return false + end + + def pbOnStartUse(user,targets) + @damagingTurn = (user.effects[PBEffects::Bide]==1) # If attack turn + end + + def pbDisplayUseMessage(user) + if @damagingTurn # Attack turn + @battle.pbDisplayBrief(_INTL("{1} unleashed energy!",user.pbThis)) + elsif user.effects[PBEffects::Bide]>1 # Charging turns + @battle.pbDisplayBrief(_INTL("{1} is storing energy!",user.pbThis)) + else + super # Start using Bide + end + end + + def pbDamagingMove? # Stops damage being dealt in the charging turns + return false if !@damagingTurn + return super + end + + def pbFixedDamage(user,target) + return user.effects[PBEffects::BideDamage]*2 + end + + def pbEffectGeneral(user) + if user.effects[PBEffects::Bide]==0 # Starting using Bide + user.effects[PBEffects::Bide] = 3 + user.effects[PBEffects::BideDamage] = 0 + user.effects[PBEffects::BideTarget] = -1 + user.currentMove = @id + end + user.effects[PBEffects::Bide] -= 1 + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + hitNum = 1 if !@damagingTurn # Charging anim + super + end +end + + + +#=============================================================================== +# Heals user by 1/2 of its max HP. +#=============================================================================== +class PokeBattle_Move_0D5 < PokeBattle_HealingMove + def pbHealAmount(user) + return (user.totalhp/2.0).round + end +end + + + +#=============================================================================== +# Heals user by 1/2 of its max HP. (Roost) +# User roosts, and its Flying type is ignored for attacks used against it. +#=============================================================================== +class PokeBattle_Move_0D6 < PokeBattle_HealingMove + def pbHealAmount(user) + return (user.totalhp/2.0).round + end + + def pbEffectAfterAllHits(user,target) + user.effects[PBEffects::Roost] = true + end +end + + + +#=============================================================================== +# Battler in user's position is healed by 1/2 of its max HP, at the end of the +# next round. (Wish) +#=============================================================================== +class PokeBattle_Move_0D7 < PokeBattle_Move + def healingMove?; return true; end + + def pbMoveFailed?(user,targets) + if @battle.positions[user.index].effects[PBEffects::Wish]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + @battle.positions[user.index].effects[PBEffects::Wish] = 2 + @battle.positions[user.index].effects[PBEffects::WishAmount] = (user.totalhp/2.0).round + @battle.positions[user.index].effects[PBEffects::WishMaker] = user.pokemonIndex + end +end + + + +#=============================================================================== +# Heals user by an amount depending on the weather. (Moonlight, Morning Sun, +# Synthesis) +#=============================================================================== +class PokeBattle_Move_0D8 < PokeBattle_HealingMove + def pbOnStartUse(user,targets) + case @battle.pbWeather + when PBWeather::Sun, PBWeather::HarshSun + @healAmount = (user.totalhp*2/3.0).round + when PBWeather::None, PBWeather::StrongWinds + @healAmount = (user.totalhp/2.0).round + else + @healAmount = (user.totalhp/4.0).round + end + end + + def pbHealAmount(user) + return @healAmount + end +end + + + +#=============================================================================== +# Heals user to full HP. User falls asleep for 2 more rounds. (Rest) +#=============================================================================== +class PokeBattle_Move_0D9 < PokeBattle_HealingMove + def pbMoveFailed?(user,targets) + if user.asleep? + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return true if !user.pbCanSleep?(user,true,self,true) + return true if super + return false + end + + def pbHealAmount(user) + return user.totalhp-user.hp + end + + def pbEffectGeneral(user) + user.pbSleepSelf(_INTL("{1} slept and became healthy!",user.pbThis),3) + super + end +end + + + +#=============================================================================== +# Rings the user. Ringed Pokémon gain 1/16 of max HP at the end of each round. +# (Aqua Ring) +#=============================================================================== +class PokeBattle_Move_0DA < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.effects[PBEffects::AquaRing] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.effects[PBEffects::AquaRing] = true + @battle.pbDisplay(_INTL("{1} surrounded itself with a veil of water!",user.pbThis)) + end +end + + + +#=============================================================================== +# Ingrains the user. Ingrained Pokémon gain 1/16 of max HP at the end of each +# round, and cannot flee or switch out. (Ingrain) +#=============================================================================== +class PokeBattle_Move_0DB < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.effects[PBEffects::Ingrain] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.effects[PBEffects::Ingrain] = true + @battle.pbDisplay(_INTL("{1} planted its roots!",user.pbThis)) + end +end + + + +#=============================================================================== +# Seeds the target. Seeded Pokémon lose 1/8 of max HP at the end of each round, +# and the Pokémon in the user's position gains the same amount. (Leech Seed) +#=============================================================================== +class PokeBattle_Move_0DC < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + if target.effects[PBEffects::LeechSeed]>=0 + @battle.pbDisplay(_INTL("{1} evaded the attack!",target.pbThis)) + return true + end + if target.pbHasType?(:GRASS) + @battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true))) + return true + end + return false + end + + def pbMissMessage(user,target) + @battle.pbDisplay(_INTL("{1} evaded the attack!",target.pbThis)) + return true + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::LeechSeed] = user.index + @battle.pbDisplay(_INTL("{1} was seeded!",target.pbThis)) + end +end + + + +#=============================================================================== +# User gains half the HP it inflicts as damage. +#=============================================================================== +class PokeBattle_Move_0DD < PokeBattle_Move + def healingMove?; return NEWEST_BATTLE_MECHANICS; end + + def pbEffectAgainstTarget(user,target) + return if target.damageState.hpLost<=0 + hpGain = (target.damageState.hpLost/2.0).round + user.pbRecoverHPFromDrain(hpGain,target) + end +end + + + +#=============================================================================== +# User gains half the HP it inflicts as damage. Fails if target is not asleep. +# (Dream Eater) +#=============================================================================== +class PokeBattle_Move_0DE < PokeBattle_Move + def healingMove?; return NEWEST_BATTLE_MECHANICS; end + + def pbFailsAgainstTarget?(user,target) + if !target.asleep? + @battle.pbDisplay(_INTL("{1} wasn't affected!",target.pbThis)) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + return if target.damageState.hpLost<=0 + hpGain = (target.damageState.hpLost/2.0).round + user.pbRecoverHPFromDrain(hpGain,target) + end +end + + + +#=============================================================================== +# Heals target by 1/2 of its max HP. (Heal Pulse) +#=============================================================================== +class PokeBattle_Move_0DF < PokeBattle_Move + def healingMove?; return true; end + + def pbFailsAgainstTarget?(user,target) + if target.hp==target.totalhp + @battle.pbDisplay(_INTL("{1}'s HP is full!",target.pbThis)) + return true + elsif !target.canHeal? + @battle.pbDisplay(_INTL("{1} is unaffected!",target.pbThis)) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + hpGain = (target.totalhp/2.0).round + if pulseMove? && user.hasActiveAbility?(:MEGALAUNCHER) + hpGain = (target.totalhp*3/4.0).round + end + target.pbRecoverHP(hpGain) + @battle.pbDisplay(_INTL("{1}'s HP was restored.",target.pbThis)) + end +end + + + +#=============================================================================== +# User faints, even if the move does nothing else. (Explosion, Self-Destruct) +#=============================================================================== +class PokeBattle_Move_0E0 < PokeBattle_Move + def worksWithNoTargets?; return true; end + def pbNumHits(user,targets); return 1; end + + def pbMoveFailed?(user,targets) + if !@battle.moldBreaker + bearer = @battle.pbCheckGlobalAbility(:DAMP) + if bearer!=nil + @battle.pbShowAbilitySplash(bearer) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} cannot use {2}!",user.pbThis,@name)) + else + @battle.pbDisplay(_INTL("{1} cannot use {2} because of {3}'s {4}!", + user.pbThis,@name,bearer.pbThis(true),bearer.abilityName)) + end + @battle.pbHideAbilitySplash(bearer) + return true + end + end + return false + end + + def pbSelfKO(user) + return if user.fainted? + user.pbReduceHP(user.hp,false) + user.pbItemHPHealCheck + end +end + + + +#=============================================================================== +# Inflicts fixed damage equal to user's current HP. (Final Gambit) +# User faints (if successful). +#=============================================================================== +class PokeBattle_Move_0E1 < PokeBattle_FixedDamageMove + def pbNumHits(user,targets); return 1; end + + def pbOnStartUse(user,targets) + @finalGambitDamage = user.hp + end + + def pbFixedDamage(user,target) + return @finalGambitDamage + end + + def pbSelfKO(user) + return if user.fainted? + user.pbReduceHP(user.hp,false) + user.pbItemHPHealCheck + end +end + + + +#=============================================================================== +# Decreases the target's Attack and Special Attack by 2 stages each. (Memento) +# User faints (if successful). +#=============================================================================== +class PokeBattle_Move_0E2 < PokeBattle_TargetMultiStatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::ATTACK,2,PBStats::SPATK,2] + end + + # NOTE: The user faints even if the target's stats cannot be changed, so this + # method must always return false to allow the move's usage to continue. + def pbFailsAgainstTarget?(user,target) + return false + end + + def pbSelfKO(user) + return if user.fainted? + user.pbReduceHP(user.hp,false) + user.pbItemHPHealCheck + end +end + + + +#=============================================================================== +# User faints. The Pokémon that replaces the user is fully healed (HP and +# status). Fails if user won't be replaced. (Healing Wish) +#=============================================================================== +class PokeBattle_Move_0E3 < PokeBattle_Move + def healingMove?; return true; end + + def pbMoveFailed?(user,targets) + if !@battle.pbCanChooseNonActive?(user.index) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbSelfKO(user) + return if user.fainted? + user.pbReduceHP(user.hp,false) + user.pbItemHPHealCheck + @battle.positions[user.index].effects[PBEffects::HealingWish] = true + end +end + + + +#=============================================================================== +# User faints. The Pokémon that replaces the user is fully healed (HP, PP and +# status). Fails if user won't be replaced. (Lunar Dance) +#=============================================================================== +class PokeBattle_Move_0E4 < PokeBattle_Move + def healingMove?; return true; end + + def pbMoveFailed?(user,targets) + if !@battle.pbCanChooseNonActive?(user.index) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbSelfKO(user) + return if user.fainted? + user.pbReduceHP(user.hp,false) + user.pbItemHPHealCheck + @battle.positions[user.index].effects[PBEffects::LunarDance] = true + end +end + + + +#=============================================================================== +# All current battlers will perish after 3 more rounds. (Perish Song) +#=============================================================================== +class PokeBattle_Move_0E5 < PokeBattle_Move + def pbMoveFailed?(user,targets) + failed = true + targets.each do |b| + next if b.effects[PBEffects::PerishSong]>0 # Heard it before + failed = false + break + end + if failed + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + return target.effects[PBEffects::PerishSong]>0 # Heard it before + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::PerishSong] = 4 + target.effects[PBEffects::PerishSongUser] = user.index + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + super + @battle.pbDisplay(_INTL("All Pokémon that hear the song will faint in three turns!")) + end +end + + + +#=============================================================================== +# If user is KO'd before it next moves, the attack that caused it loses all PP. +# (Grudge) +#=============================================================================== +class PokeBattle_Move_0E6 < PokeBattle_Move + def pbEffectGeneral(user) + user.effects[PBEffects::Grudge] = true + @battle.pbDisplay(_INTL("{1} wants its target to bear a grudge!",user.pbThis)) + end +end + + + +#=============================================================================== +# If user is KO'd before it next moves, the battler that caused it also faints. +# (Destiny Bond) +#=============================================================================== +class PokeBattle_Move_0E7 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if NEWEST_BATTLE_MECHANICS && user.effects[PBEffects::DestinyBondPrevious] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.effects[PBEffects::DestinyBond] = true + @battle.pbDisplay(_INTL("{1} is hoping to take its attacker down with it!",user.pbThis)) + end +end + + + +#=============================================================================== +# If user would be KO'd this round, it survives with 1HP instead. (Endure) +#=============================================================================== +class PokeBattle_Move_0E8 < PokeBattle_ProtectMove + def initialize(battle,move) + super + @effect = PBEffects::Endure + end + + def pbProtectMessage(user) + @battle.pbDisplay(_INTL("{1} braced itself!",user.pbThis)) + end +end + + + +#=============================================================================== +# If target would be KO'd by this attack, it survives with 1HP instead. +# (False Swipe, Hold Back) +#=============================================================================== +class PokeBattle_Move_0E9 < PokeBattle_Move + def nonLethal?(user,target); return true; end +end + + + +#=============================================================================== +# User flees from battle. Fails in trainer battles. (Teleport) +#=============================================================================== +class PokeBattle_Move_0EA < PokeBattle_Move + def pbMoveFailed?(user,targets) + if !@battle.pbCanRun?(user.index) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + @battle.pbDisplay(_INTL("{1} fled from battle!",user.pbThis)) + @battle.decision = 3 # Escaped + end +end + + + +#=============================================================================== +# In wild battles, makes target flee. Fails if target is a higher level than the +# user. +# In trainer battles, target switches out. +# For status moves. (Roar, Whirlwind) +#=============================================================================== +class PokeBattle_Move_0EB < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbFailsAgainstTarget?(user,target) + if target.hasActiveAbility?(:SUCTIONCUPS) && !@battle.moldBreaker + @battle.pbShowAbilitySplash(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} anchors itself!",target.pbThis)) + else + @battle.pbDisplay(_INTL("{1} anchors itself with {2}!",target.pbThis,target.abilityName)) + end + @battle.pbHideAbilitySplash(target) + return true + end + if target.effects[PBEffects::Ingrain] + @battle.pbDisplay(_INTL("{1} anchored itself with its roots!",target.pbThis)) + return true + end + if @battle.wildBattle? && target.level>user.level + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if @battle.trainerBattle? + canSwitch = false + @battle.eachInTeamFromBattlerIndex(target.index) do |pkmn,i| + next if !@battle.pbCanSwitchLax?(target.index,i) + canSwitch = true + break + end + if !canSwitch + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + end + return false + end + + def pbEffectGeneral(user) + @battle.decision = 3 if @battle.wildBattle? # Escaped from battle + end + + def pbSwitchOutTargetsEffect(user,targets,numHits,switchedBattlers) + return if @battle.wildBattle? + return if user.fainted? || numHits==0 + roarSwitched = [] + targets.each do |b| + next if b.fainted? || b.damageState.unaffected || switchedBattlers.include?(b.index) + newPkmn = @battle.pbGetReplacementPokemonIndex(b.index,true) # Random + next if newPkmn<0 + @battle.pbRecallAndReplace(b.index,newPkmn) + @battle.pbDisplay(_INTL("{1} was dragged out!",b.pbThis)) + @battle.pbClearChoice(b.index) # Replacement Pokémon does nothing this round + switchedBattlers.push(b.index) + roarSwitched.push(b.index) + end + if roarSwitched.length>0 + @battle.moldBreaker = false if roarSwitched.include?(user.index) + @battle.pbPriority(true).each do |b| + b.pbEffectsOnSwitchIn(true) if roarSwitched.include?(b.index) + end + end + end +end + + + +#=============================================================================== +# In wild battles, makes target flee. Fails if target is a higher level than the +# user. +# In trainer battles, target switches out. +# For damaging moves. (Circle Throw, Dragon Tail) +#=============================================================================== +class PokeBattle_Move_0EC < PokeBattle_Move + def pbEffectAgainstTarget(user,target) + if @battle.wildBattle? && target.level<=user.level && + (target.effects[PBEffects::Substitute]==0 || ignoresSubstitute?(user)) + @battle.decision = 3 + end + end + + def pbSwitchOutTargetsEffect(user,targets,numHits,switchedBattlers) + return if @battle.wildBattle? + return if user.fainted? || numHits==0 + roarSwitched = [] + targets.each do |b| + next if b.fainted? || b.damageState.unaffected || b.damageState.substitute + next if switchedBattlers.include?(b.index) + next if b.effects[PBEffects::Ingrain] + next if b.hasActiveAbility?(:SUCTIONCUPS) && !@battle.moldBreaker + newPkmn = @battle.pbGetReplacementPokemonIndex(b.index,true) # Random + next if newPkmn<0 + @battle.pbRecallAndReplace(b.index,newPkmn) + @battle.pbDisplay(_INTL("{1} was dragged out!",b.pbThis)) + @battle.pbClearChoice(b.index) # Replacement Pokémon does nothing this round + switchedBattlers.push(b.index) + roarSwitched.push(b.index) + end + if roarSwitched>0 + @battle.moldBreaker = false if roarSwitched.include?(user.index) + @battle.pbPriority(true).each do |b| + b.pbEffectsOnSwitchIn(true) if roarSwitched.include?(b.index) + end + end + end +end + + + +#=============================================================================== +# User switches out. Various effects affecting the user are passed to the +# replacement. (Baton Pass) +#=============================================================================== +class PokeBattle_Move_0ED < PokeBattle_Move + def pbMoveFailed?(user,targets) + if !@battle.pbCanChooseNonActive?(user.index) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEndOfMoveUsageEffect(user,targets,numHits,switchedBattlers) + return if user.fainted? || numHits==0 + return if !@battle.pbCanChooseNonActive?(user.index) + @battle.pbPursuit(user.index) + return if user.fainted? + newPkmn = @battle.pbGetReplacementPokemonIndex(user.index) # Owner chooses + return if newPkmn<0 + @battle.pbRecallAndReplace(user.index,newPkmn,true) + @battle.pbClearChoice(user.index) # Replacement Pokémon does nothing this round + @battle.moldBreaker = false + switchedBattlers.push(user.index) + user.pbEffectsOnSwitchIn(true) + end +end + + + +#=============================================================================== +# After inflicting damage, user switches out. Ignores trapping moves. +# (U-turn, Volt Switch) +#=============================================================================== +class PokeBattle_Move_0EE < PokeBattle_Move + def pbEndOfMoveUsageEffect(user,targets,numHits,switchedBattlers) + return if user.fainted? || numHits==0 + targetSwitched = true + targets.each do |b| + targetSwitched = false if !switchedBattlers.include?(b.index) + end + return if targetSwitched + return if !@battle.pbCanChooseNonActive?(user.index) + @battle.pbDisplay(_INTL("{1} went back to {2}!",user.pbThis, + @battle.pbGetOwnerName(user.index))) + @battle.pbPursuit(user.index) + return if user.fainted? + newPkmn = @battle.pbGetReplacementPokemonIndex(user.index) # Owner chooses + return if newPkmn<0 + @battle.pbRecallAndReplace(user.index,newPkmn) + @battle.pbClearChoice(user.index) # Replacement Pokémon does nothing this round + @battle.moldBreaker = false + switchedBattlers.push(user.index) + user.pbEffectsOnSwitchIn(true) + end +end + + + +#=============================================================================== +# Target can no longer switch out or flee, as long as the user remains active. +# (Anchor Shot, Block, Mean Look, Spider Web, Spirit Shackle, Thousand Waves) +#=============================================================================== +class PokeBattle_Move_0EF < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + return false if damagingMove? + if target.effects[PBEffects::MeanLook]>=0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if NEWEST_BATTLE_MECHANICS && target.pbHasType?(:GHOST) + @battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true))) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + return if damagingMove? + target.effects[PBEffects::MeanLook] = user.index + @battle.pbDisplay(_INTL("{1} can no longer escape!",target.pbThis)) + end + + def pbAdditionalEffect(user,target) + return if target.fainted? || target.damageState.substitute + return if target.effects[PBEffects::MeanLook]>=0 + return if NEWEST_BATTLE_MECHANICS && target.pbHasType?(:GHOST) + target.effects[PBEffects::MeanLook] = user.index + @battle.pbDisplay(_INTL("{1} can no longer escape!",target.pbThis)) + end +end + + + +#=============================================================================== +# Target drops its item. It regains the item at the end of the battle. (Knock Off) +# If target has a losable item, damage is multiplied by 1.5. +#=============================================================================== +class PokeBattle_Move_0F0 < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + if NEWEST_BATTLE_MECHANICS && + target.item!=0 && !target.unlosableItem?(target.item) + # NOTE: Damage is still boosted even if target has Sticky Hold or a + # substitute. + baseDmg = (baseDmg*1.5).round + end + return baseDmg + end + + def pbEffectAfterAllHits(user,target) + return if @battle.wildBattle? && user.opposes? # Wild Pokémon can't knock off + return if user.fainted? + return if target.damageState.unaffected || target.damageState.substitute + return if target.item==0 || target.unlosableItem?(target.item) + return if target.hasActiveAbility?(:STICKYHOLD) && !@battle.moldBreaker + itemName = target.itemName + target.pbRemoveItem(false) + @battle.pbDisplay(_INTL("{1} dropped its {2}!",target.pbThis,itemName)) + end +end + + + +#=============================================================================== +# User steals the target's item, if the user has none itself. (Covet, Thief) +# Items stolen from wild Pokémon are kept after the battle. +#=============================================================================== +class PokeBattle_Move_0F1 < PokeBattle_Move + def pbEffectAfterAllHits(user,target) + return if @battle.wildBattle? && user.opposes? # Wild Pokémon can't thieve + return if user.fainted? + return if target.damageState.unaffected || target.damageState.substitute + return if target.item==0 || user.item!=0 + return if target.unlosableItem?(target.item) + return if user.unlosableItem?(target.item) + return if target.hasActiveAbility?(:STICKYHOLD) && !@battle.moldBreaker + itemName = target.itemName + user.item = target.item + # Permanently steal the item from wild Pokémon + if @battle.wildBattle? && target.opposes? && + target.initialItem==target.item && user.initialItem==0 + user.setInitialItem(target.item) + target.pbRemoveItem + else + target.pbRemoveItem(false) + end + @battle.pbDisplay(_INTL("{1} stole {2}'s {3}!",user.pbThis,target.pbThis(true),itemName)) + user.pbHeldItemTriggerCheck + end +end + + + +#=============================================================================== +# User and target swap items. They remain swapped after wild battles. +# (Switcheroo, Trick) +#=============================================================================== +class PokeBattle_Move_0F2 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if @battle.wildBattle? && user.opposes? + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + if user.item==0 && target.item==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if target.unlosableItem?(target.item) || + target.unlosableItem?(user.item) || + user.unlosableItem?(user.item) || + user.unlosableItem?(target.item) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if target.hasActiveAbility?(:STICKYHOLD) && !@battle.moldBreaker + @battle.pbShowAbilitySplash(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("But it failed to affect {1}!",target.pbThis(true))) + else + @battle.pbDisplay(_INTL("But it failed to affect {1} because of its {2}!", + target.pbThis(true),target.abilityName)) + end + @battle.pbHideAbilitySplash(target) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + oldUserItem = user.item; oldUserItemName = user.itemName + oldTargetItem = target.item; oldTargetItemName = target.itemName + user.item = oldTargetItem + user.effects[PBEffects::ChoiceBand] = -1 + user.effects[PBEffects::Unburden] = (user.item==0 && oldUserItem>0) + target.item = oldUserItem + target.effects[PBEffects::ChoiceBand] = -1 + target.effects[PBEffects::Unburden] = (target.item==0 && oldTargetItem>0) + # Permanently steal the item from wild Pokémon + if @battle.wildBattle? && target.opposes? && + target.initialItem==oldTargetItem && user.initialItem==0 + user.setInitialItem(oldTargetItem) + end + @battle.pbDisplay(_INTL("{1} switched items with its opponent!",user.pbThis)) + if oldUserItem>0 && oldTargetItem>0 + @battle.pbDisplay(_INTL("{1} obtained {2}.",user.pbThis,oldTargetItemName)) + elsif oldTargetItem>0 + @battle.pbDisplay(_INTL("{1} obtained {2}.",user.pbThis,oldTargetItemName)) + end + @battle.pbDisplay(_INTL("{1} obtained {2}.",target.pbThis,oldUserItemName)) if oldUserItem>0 + user.pbHeldItemTriggerCheck + target.pbHeldItemTriggerCheck + end +end + + + +#=============================================================================== +# User gives its item to the target. The item remains given after wild battles. +# (Bestow) +#=============================================================================== +class PokeBattle_Move_0F3 < PokeBattle_Move + def ignoresSubstitute?(user) + return true if NEWEST_BATTLE_MECHANICS + return super + end + + def pbMoveFailed?(user,targets) + if user.item==0 || user.unlosableItem?(user.item) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + if target.item!=0 || target.unlosableItem?(user.item) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + itemName = user.itemName + target.item = user.item + # Permanently steal the item from wild Pokémon + if @battle.wildBattle? && user.opposes? && + user.initialItem==user.item && target.initialItem==0 + target.setInitialItem(user.item) + user.pbRemoveItem + else + user.pbRemoveItem(false) + end + @battle.pbDisplay(_INTL("{1} received {2} from {3}!",target.pbThis,itemName,user.pbThis(true))) + target.pbHeldItemTriggerCheck + end +end + + + +#=============================================================================== +# User consumes target's berry and gains its effect. (Bug Bite, Pluck) +#=============================================================================== +class PokeBattle_Move_0F4 < PokeBattle_Move + def pbEffectAfterAllHits(user,target) + return if user.fainted? || target.fainted? + return if target.damageState.unaffected || target.damageState.substitute + return if target.item==0 || !pbIsBerry?(target.item) + return if target.hasActiveAbility?(:STICKYHOLD) && !@battle.moldBreaker + item = target.item + itemName = target.itemName + target.pbRemoveItem + @battle.pbDisplay(_INTL("{1} stole and ate its target's {2}!",user.pbThis,itemName)) + user.pbHeldItemTriggerCheck(item,false) + end +end + + + +#=============================================================================== +# Target's berry/Gem is destroyed. (Incinerate) +#=============================================================================== +class PokeBattle_Move_0F5 < PokeBattle_Move + def pbEffectWhenDealingDamage(user,target) + return if target.damageState.substitute || target.damageState.berryWeakened + return if !pbIsBerry?(target.item) && + !(NEWEST_BATTLE_MECHANICS && pbIsGem?(target.item)) + target.pbRemoveItem + @battle.pbDisplay(_INTL("{1}'s {2} was incinerated!",target.pbThis,target.itemName)) + end +end + + + +#=============================================================================== +# User recovers the last item it held and consumed. (Recycle) +#=============================================================================== +class PokeBattle_Move_0F6 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.recycleItem==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + item = user.recycleItem + user.item = item + user.setInitialItem(item) if @battle.wildBattle? && user.initialItem==0 + user.setRecycleItem(0) + user.effects[PBEffects::PickupItem] = 0 + user.effects[PBEffects::PickupUse] = 0 + itemName = PBItems.getName(item) + if itemName.starts_with_vowel? + @battle.pbDisplay(_INTL("{1} found an {2}!",user.pbThis,itemName)) + else + @battle.pbDisplay(_INTL("{1} found a {2}!",user.pbThis,itemName)) + end + user.pbHeldItemTriggerCheck + end +end + + + +#=============================================================================== +# User flings its item at the target. Power/effect depend on the item. (Fling) +#=============================================================================== +class PokeBattle_Move_0F7 < PokeBattle_Move + def initialize(battle,move) + super + # 80 => all Mega Stones + # 10 => all Berries + @flingPowers = { + 130 => [:IRONBALL + ], + 100 => [:HARDSTONE,:RAREBONE, + # Fossils + :ARMORFOSSIL,:CLAWFOSSIL,:COVERFOSSIL,:DOMEFOSSIL,:HELIXFOSSIL, + :JAWFOSSIL,:OLDAMBER,:PLUMEFOSSIL,:ROOTFOSSIL,:SAILFOSSIL, + :SKULLFOSSIL + ], + 90 => [:DEEPSEATOOTH,:GRIPCLAW,:THICKCLUB, + # Plates + :DRACOPLATE,:DREADPLATE,:EARTHPLATE,:FISTPLATE,:FLAMEPLATE, + :ICICLEPLATE,:INSECTPLATE,:IRONPLATE,:MEADOWPLATE,:MINDPLATE, + :PIXIEPLATE,:SKYPLATE,:SPLASHPLATE,:SPOOKYPLATE,:STONEPLATE, + :TOXICPLATE,:ZAPPLATE + ], + 80 => [:ASSAULTVEST,:DAWNSTONE,:DUSKSTONE,:ELECTIRIZER,:MAGMARIZER, + :ODDKEYSTONE,:OVALSTONE,:PROTECTOR,:QUICKCLAW,:RAZORCLAW,:SACHET, + :SAFETYGOGGLES,:SHINYSTONE,:STICKYBARB,:WEAKNESSPOLICY, + :WHIPPEDDREAM + ], + 70 => [:DRAGONFANG,:POISONBARB, + # EV-training items (Macho Brace is 60) + :POWERANKLET,:POWERBAND,:POWERBELT,:POWERBRACER,:POWERLENS, + :POWERWEIGHT, + # Drives + :BURNDRIVE,:CHILLDRIVE,:DOUSEDRIVE,:SHOCKDRIVE + ], + 60 => [:ADAMANTORB,:DAMPROCK,:GRISEOUSORB,:HEATROCK,:LUSTROUSORB, + :MACHOBRACE,:ROCKYHELMET,:STICK,:TERRAINEXTENDER + ], + 50 => [:DUBIOUSDISC,:SHARPBEAK, + # Memories + :BUGMEMORY,:DARKMEMORY,:DRAGONMEMORY,:ELECTRICMEMORY,:FAIRYMEMORY, + :FIGHTINGMEMORY,:FIREMEMORY,:FLYINGMEMORY,:GHOSTMEMORY, + :GRASSMEMORY,:GROUNDMEMORY,:ICEMEMORY,:POISONMEMORY, + :PSYCHICMEMORY,:ROCKMEMORY,:STEELMEMORY,:WATERMEMORY + ], + 40 => [:EVIOLITE,:ICYROCK,:LUCKYPUNCH + ], + 30 => [:ABSORBBULB,:ADRENALINEORB,:AMULETCOIN,:BINDINGBAND,:BLACKBELT, + :BLACKGLASSES,:BLACKSLUDGE,:BOTTLECAP,:CELLBATTERY,:CHARCOAL, + :CLEANSETAG,:DEEPSEASCALE,:DRAGONSCALE,:EJECTBUTTON,:ESCAPEROPE, + :EXPSHARE,:FLAMEORB,:FLOATSTONE,:FLUFFYTAIL,:GOLDBOTTLECAP, + :HEARTSCALE,:HONEY,:KINGSROCK,:LIFEORB,:LIGHTBALL,:LIGHTCLAY, + :LUCKYEGG,:LUMINOUSMOSS,:MAGNET,:METALCOAT,:METRONOME, + :MIRACLESEED,:MYSTICWATER,:NEVERMELTICE,:PASSORB,:POKEDOLL, + :POKETOY,:PRISMSCALE,:PROTECTIVEPADS,:RAZORFANG,:SACREDASH, + :SCOPELENS,:SHELLBELL,:SHOALSALT,:SHOALSHELL,:SMOKEBALL,:SNOWBALL, + :SOULDEW,:SPELLTAG,:TOXICORB,:TWISTEDSPOON,:UPGRADE, + # Healing items + :ANTIDOTE,:AWAKENING,:BERRYJUICE,:BIGMALASADA,:BLUEFLUTE, + :BURNHEAL,:CASTELIACONE,:ELIXIR,:ENERGYPOWDER,:ENERGYROOT,:ETHER, + :FRESHWATER,:FULLHEAL,:FULLRESTORE,:HEALPOWDER,:HYPERPOTION, + :ICEHEAL,:LAVACOOKIE,:LEMONADE,:LUMIOSEGALETTE,:MAXELIXIR, + :MAXETHER,:MAXPOTION,:MAXREVIVE,:MOOMOOMILK,:OLDGATEAU, + :PARALYZEHEAL,:PARLYZHEAL,:PEWTERCRUNCHIES,:POTION,:RAGECANDYBAR, + :REDFLUTE,:REVIVALHERB,:REVIVE,:SHALOURSABLE,:SODAPOP, + :SUPERPOTION,:SWEETHEART,:YELLOWFLUTE, + # Battle items + :XACCURACY,:XACCURACY2,:XACCURACY3,:XACCURACY6, + :XATTACK,:XATTACK2,:XATTACK3,:XATTACK6, + :XDEFEND,:XDEFEND2,:XDEFEND3,:XDEFEND6, + :XDEFENSE,:XDEFENSE2,:XDEFENSE3,:XDEFENSE6, + :XSPATK,:XSPATK2,:XSPATK3,:XSPATK6, + :XSPECIAL,:XSPECIAL2,:XSPECIAL3,:XSPECIAL6, + :XSPDEF,:XSPDEF2,:XSPDEF3,:XSPDEF6, + :XSPEED,:XSPEED2,:XSPEED3,:XSPEED6, + :DIREHIT,:DIREHIT2,:DIREHIT3, + :ABILITYURGE,:GUARDSPEC,:ITEMDROP,:ITEMURGE,:RESETURGE, + # Vitamins + :CALCIUM,:CARBOS,:HPUP,:IRON,:PPUP,:PPMAX,:PROTEIN,:ZINC, + :RARECANDY, + # Most evolution stones (see also 80) + :EVERSTONE,:FIRESTONE,:ICESTONE,:LEAFSTONE,:MOONSTONE,:SUNSTONE, + :THUNDERSTONE,:WATERSTONE, + # Repels + :MAXREPEL,:REPEL,:SUPERREPEL, + # Mulches + :AMAZEMULCH,:BOOSTMULCH,:DAMPMULCH,:GOOEYMULCH,:GROWTHMULCH, + :RICHMULCH,:STABLEMULCH,:SURPRISEMULCH, + # Shards + :BLUESHARD,:GREENSHARD,:REDSHARD,:YELLOWSHARD, + # Valuables + :BALMMUSHROOM,:BIGMUSHROOM,:BIGNUGGET,:BIGPEARL,:COMETSHARD, + :NUGGET,:PEARL,:PEARLSTRING,:RELICBAND,:RELICCOPPER,:RELICCROWN, + :RELICGOLD,:RELICSILVER,:RELICSTATUE,:RELICVASE,:STARDUST, + :STARPIECE,:STRANGESOUVENIR,:TINYMUSHROOM + ], + 20 => [# Wings + :CLEVERWING,:GENIUSWING,:HEALTHWING,:MUSCLEWING,:PRETTYWING, + :RESISTWING,:SWIFTWING + ], + 10 => [:AIRBALLOON,:BIGROOT,:BRIGHTPOWDER,:CHOICEBAND,:CHOICESCARF, + :CHOICESPECS,:DESTINYKNOT,:DISCOUNTCOUPON,:EXPERTBELT,:FOCUSBAND, + :FOCUSSASH,:LAGGINGTAIL,:LEFTOVERS,:MENTALHERB,:METALPOWDER, + :MUSCLEBAND,:POWERHERB,:QUICKPOWDER,:REAPERCLOTH,:REDCARD, + :RINGTARGET,:SHEDSHELL,:SILKSCARF,:SILVERPOWDER,:SMOOTHROCK, + :SOFTSAND,:SOOTHEBELL,:WHITEHERB,:WIDELENS,:WISEGLASSES,:ZOOMLENS, + # Terrain seeds + :ELECTRICSEED,:GRASSYSEED,:MISTYSEED,:PSYCHICSEED, + # Nectar + :PINKNECTAR,:PURPLENECTAR,:REDNECTAR,:YELLOWNECTAR, + # Incenses + :FULLINCENSE,:LAXINCENSE,:LUCKINCENSE,:ODDINCENSE,:PUREINCENSE, + :ROCKINCENSE,:ROSEINCENSE,:SEAINCENSE,:WAVEINCENSE, + # Scarves + :BLUESCARF,:GREENSCARF,:PINKSCARF,:REDSCARF,:YELLOWSCARF + ] + } + end + + def pbCheckFlingSuccess + @willFail = false + @willFail = true if user.item==0 || !user.itemActive? || user.unlosableItem?(user.item) + if pbIsBerry?(user.item) + @willFail = true if @battle.pbCheckOpposingAbility(:UNNERVE,user.index) + return + end + return if pbIsMegaStone?(user.item) + flingableItem = false + @flingPowers.each do |power,items| + items.each do |i| + next if !isConst?(user.item,PBItems,i) + flingableItem = true + break + end + break if flingableItem + end + @willFail = true if !flingableItem + end + + def pbMoveFailed?(user,targets) + if @willFail + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbDisplayUseMessage(user) + super + pbCheckFlingSuccess + if !@willFail + @battle.pbDisplay(_INTL("{1} flung its {2}!",user.pbThis,user.itemName)) + end + end + + def pbNumHits(user,targets); return 1; end + + def pbBaseDamage(baseDmg,user,target) + return 10 if pbIsBerry?(user.item) + return 80 if pbIsMegaStone?(user.item) + @flingPowers.each do |power,items| + items.each { |i| return power if isConst?(user.item,PBItems,i) } + end + return 10 + end + + def pbEffectAgainstTarget(user,target) + return if target.damageState.substitute + return if target.hasActiveAbility?(:SHIELDDUST) && !@battle.moldBreaker + if isConst?(user.item,PBItems,:POISONBARB) + target.pbPoison(user) if target.pbCanPoison?(user,false,self) + elsif isConst?(user.item,PBItems,:TOXICORB) + target.pbPoison(user,nil,true) if target.pbCanPoison?(user,false,self) + elsif isConst?(user.item,PBItems,:FLAMEORB) + target.pbBurn(user) if target.pbCanBurn?(user,false,self) + elsif isConst?(user.item,PBItems,:LIGHTBALL) + target.pbParalyze(user) if target.pbCanParalyze?(user,false,self) + elsif isConst?(user.item,PBItems,:KINGSROCK) || + isConst?(user.item,PBItems,:RAZORFANG) + target.pbFlinch(user) + else + target.pbHeldItemTriggerCheck(user.item,true) + end + end + + def pbEndOfMoveUsageEffect(user,targets,numHits,switchedBattlers) + # NOTE: The item is consumed even if this move was Protected against or it + # missed. The item is not consumed if the target was switched out by + # an effect like a target's Red Card. + # NOTE: There is no item consumption animation. + user.pbConsumeItem(true,true,false) if user.item>0 + end +end + + + +#=============================================================================== +# For 5 rounds, the target cannnot use its held item, its held item has no +# effect, and no items can be used on it. (Embargo) +#=============================================================================== +class PokeBattle_Move_0F8 < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + if target.effects[PBEffects::Embargo]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::Embargo] = 5 + @battle.pbDisplay(_INTL("{1} can't use items anymore!",target.pbThis)) + end +end + + + +#=============================================================================== +# For 5 rounds, all held items cannot be used in any way and have no effect. +# Held items can still change hands, but can't be thrown. (Magic Room) +#=============================================================================== +class PokeBattle_Move_0F9 < PokeBattle_Move + def pbEffectGeneral(user) + if @battle.field.effects[PBEffects::MagicRoom]>0 + @battle.field.effects[PBEffects::MagicRoom] = 0 + @battle.pbDisplay(_INTL("The area returned to normal!")) + else + @battle.field.effects[PBEffects::MagicRoom] = 5 + @battle.pbDisplay(_INTL("It created a bizarre area in which Pokémon's held items lose their effects!")) + end + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + return if @battle.field.effects[PBEffects::MagicRoom]>0 # No animation + super + end +end + + + +#=============================================================================== +# User takes recoil damage equal to 1/4 of the damage this move dealt. +#=============================================================================== +class PokeBattle_Move_0FA < PokeBattle_RecoilMove + def pbRecoilDamage(user,target) + return (target.damageState.totalHPLost/4.0).round + end +end + + + +#=============================================================================== +# User takes recoil damage equal to 1/3 of the damage this move dealt. +#=============================================================================== +class PokeBattle_Move_0FB < PokeBattle_RecoilMove + def pbRecoilDamage(user,target) + return (target.damageState.totalHPLost/3.0).round + end +end + + + +#=============================================================================== +# User takes recoil damage equal to 1/2 of the damage this move dealt. +# (Head Smash, Light of Ruin) +#=============================================================================== +class PokeBattle_Move_0FC < PokeBattle_RecoilMove + def pbRecoilDamage(user,target) + return (target.damageState.totalHPLost/2.0).round + end +end + + + +#=============================================================================== +# User takes recoil damage equal to 1/3 of the damage this move dealt. +# May paralyze the target. (Volt Tackle) +#=============================================================================== +class PokeBattle_Move_0FD < PokeBattle_RecoilMove + def pbRecoilDamage(user,target) + return (target.damageState.totalHPLost/3.0).round + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + target.pbParalyze(user) if target.pbCanParalyze?(user,false,self) + end +end + + + +#=============================================================================== +# User takes recoil damage equal to 1/3 of the damage this move dealt. +# May burn the target. (Flare Blitz) +#=============================================================================== +class PokeBattle_Move_0FE < PokeBattle_RecoilMove + def pbRecoilDamage(user,target) + return (target.damageState.totalHPLost/3.0).round + end + + def pbAdditionalEffect(user,target) + return if target.damageState.substitute + target.pbBurn(user) if target.pbCanBurn?(user,false,self) + end +end + + + +#=============================================================================== +# Starts sunny weather. (Sunny Day) +#=============================================================================== +class PokeBattle_Move_0FF < PokeBattle_WeatherMove + def initialize(battle,move) + super + @weatherType = PBWeather::Sun + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/002_Move/007_Move_Effects_100-17F.rb b/Data/Scripts/011_Battle/002_Move/007_Move_Effects_100-17F.rb new file mode 100644 index 000000000..32b32c07e --- /dev/null +++ b/Data/Scripts/011_Battle/002_Move/007_Move_Effects_100-17F.rb @@ -0,0 +1,2626 @@ +#=============================================================================== +# Starts rainy weather. (Rain Dance) +#=============================================================================== +class PokeBattle_Move_100 < PokeBattle_WeatherMove + def initialize(battle,move) + super + @weatherType = PBWeather::Rain + end +end + + + +#=============================================================================== +# Starts sandstorm weather. (Sandstorm) +#=============================================================================== +class PokeBattle_Move_101 < PokeBattle_WeatherMove + def initialize(battle,move) + super + @weatherType = PBWeather::Sandstorm + end +end + + + +#=============================================================================== +# Starts hail weather. (Hail) +#=============================================================================== +class PokeBattle_Move_102 < PokeBattle_WeatherMove + def initialize(battle,move) + super + @weatherType = PBWeather::Hail + end +end + + + +#=============================================================================== +# Entry hazard. Lays spikes on the opposing side (max. 3 layers). (Spikes) +#=============================================================================== +class PokeBattle_Move_103 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.pbOpposingSide.effects[PBEffects::Spikes]>=3 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.pbOpposingSide.effects[PBEffects::Spikes] += 1 + @battle.pbDisplay(_INTL("Spikes were scattered all around {1}'s feet!", + user.pbOpposingTeam(true))) + end +end + + + +#=============================================================================== +# Entry hazard. Lays poison spikes on the opposing side (max. 2 layers). +# (Toxic Spikes) +#=============================================================================== +class PokeBattle_Move_104 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.pbOpposingSide.effects[PBEffects::ToxicSpikes]>=2 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.pbOpposingSide.effects[PBEffects::ToxicSpikes] += 1 + @battle.pbDisplay(_INTL("Poison spikes were scattered all around {1}'s feet!", + user.pbOpposingTeam(true))) + end +end + + + +#=============================================================================== +# Entry hazard. Lays stealth rocks on the opposing side. (Stealth Rock) +#=============================================================================== +class PokeBattle_Move_105 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.pbOpposingSide.effects[PBEffects::StealthRock] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.pbOpposingSide.effects[PBEffects::StealthRock] = true + @battle.pbDisplay(_INTL("Pointed stones float in the air around {1}!", + user.pbOpposingTeam(true))) + end +end + + + +#=============================================================================== +# Combos with another Pledge move used by the ally. (Grass Pledge) +# If the move is a combo, power is doubled and causes either a sea of fire or a +# swamp on the opposing side. +#=============================================================================== +class PokeBattle_Move_106 < PokeBattle_PledgeMove + def initialize(battle,move) + super + # [Function code to combo with, effect, override type, override animation] + @combos = [["107",:SeaOfFire,getConst(PBTypes,:FIRE),getConst(PBMoves,:FIREPLEDGE)], + ["108",:Swamp,nil,nil]] + end +end + + + +#=============================================================================== +# Combos with another Pledge move used by the ally. (Fire Pledge) +# If the move is a combo, power is doubled and causes either a rainbow on the +# user's side or a sea of fire on the opposing side. +#=============================================================================== +class PokeBattle_Move_107 < PokeBattle_PledgeMove + def initialize(battle,move) + super + # [Function code to combo with, effect, override type, override animation] + @combos = [["108",:Rainbow,getConst(PBTypes,:WATER),getConst(PBMoves,:WATERPLEDGE)], + ["106",:SeaOfFire,nil,nil]] + end +end + + + +#=============================================================================== +# Combos with another Pledge move used by the ally. (Water Pledge) +# If the move is a combo, power is doubled and causes either a swamp on the +# opposing side or a rainbow on the user's side. +#=============================================================================== +class PokeBattle_Move_108 < PokeBattle_PledgeMove + def initialize(battle,move) + super + # [Function code to combo with, effect, override type, override animation] + @combos = [["106",:Swamp,getConst(PBTypes,:GRASS),getConst(PBMoves,:GRASSPLEDGE)], + ["107",:Rainbow,nil,nil]] + end +end + + + +#=============================================================================== +# Scatters coins that the player picks up after winning the battle. (Pay Day) +# NOTE: In Gen 6+, if the user levels up after this move is used, the amount of +# money picked up depends on the user's new level rather than its level +# when it used the move. I think this is silly, so I haven't coded this +# effect. +#=============================================================================== +class PokeBattle_Move_109 < PokeBattle_Move + def pbEffectGeneral(user) + if user.pbOwnedByPlayer? + @battle.field.effects[PBEffects::PayDay] += 5*user.level + end + @battle.pbDisplay(_INTL("Coins were scattered everywhere!")) + end +end + + + +#=============================================================================== +# Ends the opposing side's Light Screen, Reflect and Aurora Break. (Brick Break, +# Psychic Fangs) +#=============================================================================== +class PokeBattle_Move_10A < PokeBattle_Move + def ignoresReflect?; return true; end + + def pbEffectGeneral(user) + if user.pbOpposingSide.effects[PBEffects::LightScreen]>0 + user.pbOpposingSide.effects[PBEffects::LightScreen] = 0 + @battle.pbDisplayP(_INTL("{1}'s Light Screen wore off!",user.pbOpposingTeam)) + end + if user.pbOpposingSide.effects[PBEffects::Reflect]>0 + user.pbOpposingSide.effects[PBEffects::Reflect] = 0 + @battle.pbDisplay(_INTL("{1}'s Reflect wore off!",user.pbOpposingTeam)) + end + if user.pbOpposingSide.effects[PBEffects::AuroraVeil]>0 + user.pbOpposingSide.effects[PBEffects::AuroraVeil] = 0 + @battle.pbDisplay(_INTL("{1}'s Aurora Veil wore off!",user.pbOpposingTeam)) + end + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + if user.pbOpposingSide.effects[PBEffects::LightScreen]>0 || + user.pbOpposingSide.effects[PBEffects::Reflect]>0 || + user.pbOpposingSide.effects[PBEffects::AuroraVeil]>0 + hitNum = 1 # Wall-breaking anim + end + super + end +end + + + +#=============================================================================== +# If attack misses, user takes crash damage of 1/2 of max HP. +# (High Jump Kick, Jump Kick) +#=============================================================================== +class PokeBattle_Move_10B < PokeBattle_Move + def recoilMove?; return true; end + def unusableInGravity?; return true; end + + def pbCrashDamage(user) + return if !user.takesIndirectDamage? + @battle.pbDisplay(_INTL("{1} kept going and crashed!",user.pbThis)) + @battle.scene.pbDamageAnimation(user) + user.pbReduceHP(user.totalhp/2,false) + user.pbItemHPHealCheck + user.pbFaint if user.fainted? + end +end + + + +#=============================================================================== +# User turns 1/4 of max HP into a substitute. (Substitute) +#=============================================================================== +class PokeBattle_Move_10C < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.effects[PBEffects::Substitute]>0 + @battle.pbDisplay(_INTL("{1} already has a substitute!",user.pbThis)) + return true + end + @subLife = user.totalhp/4 + @subLife = 1 if @subLife<1 + if user.hp<=@subLife + @battle.pbDisplay(_INTL("But it does not have enough HP left to make a substitute!")) + return true + end + return false + end + + def pbOnStartUse(user,targets) + user.pbReduceHP(@subLife,false,false) + user.pbItemHPHealCheck + end + + def pbEffectGeneral(user) + user.effects[PBEffects::Trapping] = 0 + user.effects[PBEffects::TrappingMove] = 0 + user.effects[PBEffects::Substitute] = @subLife + @battle.pbDisplay(_INTL("{1} put in a substitute!",user.pbThis)) + end +end + + + +#=============================================================================== +# User is Ghost: User loses 1/2 of max HP, and curses the target. +# Cursed Pokémon lose 1/4 of their max HP at the end of each round. +# User is not Ghost: Decreases the user's Speed by 1 stage, and increases the +# user's Attack and Defense by 1 stage each. (Curse) +#=============================================================================== +class PokeBattle_Move_10D < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbTarget(user) + return PBTargets::NearFoe if user.pbHasType?(:GHOST) + super + end + + def pbMoveFailed?(user,targets) + return false if user.pbHasType?(:GHOST) + if !user.pbCanLowerStatStage?(PBStats::SPEED,user,self) && + !user.pbCanRaiseStatStage?(PBStats::ATTACK,user,self) && + !user.pbCanRaiseStatStage?(PBStats::DEFENSE,user,self) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + if user.pbHasType?(:GHOST) && target.effects[PBEffects::Curse] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + return if user.pbHasType?(:GHOST) + # Non-Ghost effect + if user.pbCanLowerStatStage?(PBStats::SPEED,user,self) + user.pbLowerStatStage(PBStats::SPEED,1,user) + end + showAnim = true + if user.pbCanRaiseStatStage?(PBStats::ATTACK,user,self) + if user.pbRaiseStatStage(PBStats::ATTACK,1,user,showAnim) + showAnim = false + end + end + if user.pbCanRaiseStatStage?(PBStats::DEFENSE,user,self) + user.pbRaiseStatStage(PBStats::DEFENSE,1,user,showAnim) + end + end + + def pbEffectAgainstTarget(user,target) + return if !user.pbHasType?(:GHOST) + # Ghost effect + @battle.pbDisplay(_INTL("{1} cut its own HP and laid a curse on {2}!",user.pbThis,target.pbThis(true))) + target.effects[PBEffects::Curse] = true + user.pbReduceHP(user.totalhp/2,false) + user.pbItemHPHealCheck + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + hitNum = 1 if !user.pbHasType?(:GHOST) # Non-Ghost anim + super + end +end + + + +#=============================================================================== +# Target's last move used loses 4 PP. (Spite) +#=============================================================================== +class PokeBattle_Move_10E < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbFailsAgainstTarget?(user,target) + failed = true + target.eachMove do |m| + next if m.id!=target.lastRegularMoveUsed || m.pp==0 || m.totalpp<=0 + failed = false; break + end + if failed + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + target.eachMove do |m| + next if m.id!=target.lastRegularMoveUsed + reduction = [4,m.pp].min + target.pbSetPP(m,m.pp-reduction) + @battle.pbDisplay(_INTL("It reduced the PP of {1}'s {2} by {3}!", + target.pbThis(true),m.name,reduction)) + break + end + end +end + + + +#=============================================================================== +# Target will lose 1/4 of max HP at end of each round, while asleep. (Nightmare) +#=============================================================================== +class PokeBattle_Move_10F < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + if !target.asleep? || target.effects[PBEffects::Nightmare] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::Nightmare] = true + @battle.pbDisplay(_INTL("{1} began having a nightmare!",target.pbThis)) + end +end + + + +#=============================================================================== +# Removes trapping moves, entry hazards and Leech Seed on user/user's side. +# (Rapid Spin) +#=============================================================================== +class PokeBattle_Move_110 < PokeBattle_Move + def pbEffectAfterAllHits(user,target) + return if user.fainted? || target.damageState.unaffected + if user.effects[PBEffects::Trapping]>0 + trapMove = PBMoves.getName(user.effects[PBEffects::TrappingMove]) + trapUser = @battle.battlers[user.effects[PBEffects::TrappingUser]] + @battle.pbDisplay(_INTL("{1} got free of {2}'s {3}!",user.pbThis,trapUser.pbThis(true),trapMove)) + user.effects[PBEffects::Trapping] = 0 + user.effects[PBEffects::TrappingMove] = 0 + user.effects[PBEffects::TrappingUser] = -1 + end + if user.effects[PBEffects::LeechSeed]>=0 + user.effects[PBEffects::LeechSeed] = -1 + @battle.pbDisplay(_INTL("{1} shed Leech Seed!",user.pbThis)) + end + if user.pbOwnSide.effects[PBEffects::StealthRock] + user.pbOwnSide.effects[PBEffects::StealthRock] = false + @battle.pbDisplay(_INTL("{1} blew away stealth rocks!",user.pbThis)) + end + if user.pbOwnSide.effects[PBEffects::Spikes]>0 + user.pbOwnSide.effects[PBEffects::Spikes] = 0 + @battle.pbDisplay(_INTL("{1} blew away spikes!",user.pbThis)) + end + if user.pbOwnSide.effects[PBEffects::ToxicSpikes]>0 + user.pbOwnSide.effects[PBEffects::ToxicSpikes] = 0 + @battle.pbDisplay(_INTL("{1} blew away poison spikes!",user.pbThis)) + end + if user.pbOwnSide.effects[PBEffects::StickyWeb] + user.pbOwnSide.effects[PBEffects::StickyWeb] = false + @battle.pbDisplay(_INTL("{1} blew away sticky webs!",user.pbThis)) + end + end +end + + + +#=============================================================================== +# Attacks 2 rounds in the future. (Doom Desire, Future Sight) +#=============================================================================== +class PokeBattle_Move_111 < PokeBattle_Move + def cannotRedirect?; return true; end + + def pbDamagingMove? # Stops damage being dealt in the setting-up turn + return false if !@battle.futureSight + return super + end + + def pbAccuracyCheck(user,target) + return true if !@battle.futureSight + return super + end + + def pbDisplayUseMessage(user) + super if !@battle.futureSight + end + + def pbFailsAgainstTarget?(user,target) + if !@battle.futureSight && + @battle.positions[target.index].effects[PBEffects::FutureSightCounter]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + return if @battle.futureSight # Attack is hitting + effects = @battle.positions[target.index].effects + effects[PBEffects::FutureSightCounter] = 3 + effects[PBEffects::FutureSightMove] = @id + effects[PBEffects::FutureSightUserIndex] = user.index + effects[PBEffects::FutureSightUserPartyIndex] = user.pokemonIndex + if isConst?(@id,PBMoves,:DOOMDESIRE) + @battle.pbDisplay(_INTL("{1} chose Doom Desire as its destiny!",user.pbThis)) + else + @battle.pbDisplay(_INTL("{1} foresaw an attack!",user.pbThis)) + end + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + hitNum = 1 if !@battle.futureSight # Charging anim + super + end +end + + + +#=============================================================================== +# Increases the user's Defense and Special Defense by 1 stage each. Ups the +# user's stockpile by 1 (max. 3). (Stockpile) +#=============================================================================== +class PokeBattle_Move_112 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.effects[PBEffects::Stockpile]>=3 + @battle.pbDisplay(_INTL("{1} can't stockpile any more!",user.pbThis)) + return true + end + return false + end + + def pbEffectGeneral(user) + user.effects[PBEffects::Stockpile] += 1 + @battle.pbDisplay(_INTL("{1} stockpiled {2}!", + user.pbThis,user.effects[PBEffects::Stockpile])) + showAnim = true + if user.pbCanRaiseStatStage?(PBStats::DEFENSE,user,self) + if user.pbRaiseStatStage(PBStats::DEFENSE,1,user,showAnim) + user.effects[PBEffects::StockpileDef] += 1 + showAnim = false + end + end + if user.pbCanRaiseStatStage?(PBStats::SPDEF,user,self) + if user.pbRaiseStatStage(PBStats::SPDEF,1,user,showAnim) + user.effects[PBEffects::StockpileSpDef] += 1 + end + end + end +end + + + +#=============================================================================== +# Power is 100 multiplied by the user's stockpile (X). Resets the stockpile to +# 0. Decreases the user's Defense and Special Defense by X stages each. (Spit Up) +#=============================================================================== +class PokeBattle_Move_113 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.effects[PBEffects::Stockpile]==0 + @battle.pbDisplay(_INTL("But it failed to spit up a thing!")) + return true + end + return false + end + + def pbBaseDamage(baseDmg,user,target) + return 100*user.effects[PBEffects::Stockpile] + end + + def pbEffectAfterAllHits(user,target) + return if user.fainted? || user.effects[PBEffects::Stockpile]==0 + return if target.damageState.unaffected + @battle.pbDisplay(_INTL("{1}'s stockpiled effect wore off!",user.pbThis)) + return if @battle.pbAllFainted?(target.idxOwnSide) + showAnim = true + if user.effects[PBEffects::StockpileDef]>0 && + user.pbCanLowerStatStage?(PBStats::DEFENSE,user,self) + if user.pbLowerStatStage(PBStats::DEFENSE,user.effects[PBEffects::StockpileDef],user,showAnim) + showAnim = false + end + end + if user.effects[PBEffects::StockpileSpDef]>0 && + user.pbCanLowerStatStage?(PBStats::SPDEF,user,self) + user.pbLowerStatStage(PBStats::SPDEF,user.effects[PBEffects::StockpileSpDef],user,showAnim) + end + user.effects[PBEffects::Stockpile] = 0 + user.effects[PBEffects::StockpileDef] = 0 + user.effects[PBEffects::StockpileSpDef] = 0 + end +end + + + +#=============================================================================== +# Heals user depending on the user's stockpile (X). Resets the stockpile to 0. +# Decreases the user's Defense and Special Defense by X stages each. (Swallow) +#=============================================================================== +class PokeBattle_Move_114 < PokeBattle_Move + def healingMove?; return true; end + + def pbMoveFailed?(user,targets) + if user.effects[PBEffects::Stockpile]==0 + @battle.pbDisplay(_INTL("But it failed to swallow a thing!")) + return true + end + if !user.canHeal? && + user.effects[PBEffects::StockpileDef]==0 && + user.effects[PBEffects::StockpileSpDef]==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + hpGain = 0 + case [user.effects[PBEffects::Stockpile],1].max + when 1; hpGain = user.totalhp/4 + when 2; hpGain = user.totalhp/2 + when 3; hpGain = user.totalhp + end + if user.pbRecoverHP(hpGain)>0 + @battle.pbDisplay(_INTL("{1}'s HP was restored.",user.pbThis)) + end + @battle.pbDisplay(_INTL("{1}'s stockpiled effect wore off!",user.pbThis)) + showAnim = true + if user.effects[PBEffects::StockpileDef]>0 && + user.pbCanLowerStatStage?(PBStats::DEFENSE,user,self) + if user.pbLowerStatStage(PBStats::DEFENSE,user.effects[PBEffects::StockpileDef],user,showAnim) + showAnim = false + end + end + if user.effects[PBEffects::StockpileSpDef]>0 && + user.pbCanLowerStatStage?(PBStats::SPDEF,user,self) + user.pbLowerStatStage(PBStats::SPDEF,user.effects[PBEffects::StockpileSpDef],user,showAnim) + end + user.effects[PBEffects::Stockpile] = 0 + user.effects[PBEffects::StockpileDef] = 0 + user.effects[PBEffects::StockpileSpDef] = 0 + end +end + + + +#=============================================================================== +# Fails if user was hit by a damaging move this round. (Focus Punch) +#=============================================================================== +class PokeBattle_Move_115 < PokeBattle_Move + def pbDisplayChargeMessage(user) + user.effects[PBEffects::FocusPunch] = true + @battle.pbCommonAnimation("FocusPunch",user) + @battle.pbDisplay(_INTL("{1} is tightening its focus!",user.pbThis)) + end + + def pbDisplayUseMessage(user) + super if !user.effects[PBEffects::FocusPunch] || user.lastHPLost==0 + end + + def pbMoveFailed?(user,targets) + if user.effects[PBEffects::FocusPunch] && user.lastHPLost>0 + @battle.pbDisplay(_INTL("{1} lost its focus and couldn't move!",user.pbThis)) + return true + end + return false + end +end + + + +#=============================================================================== +# Fails if the target didn't chose a damaging move to use this round, or has +# already moved. (Sucker Punch) +#=============================================================================== +class PokeBattle_Move_116 < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + if @battle.choices[target.index][0]!=:UseMove + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + oppMove = @battle.choices[target.index][2] + if !oppMove || oppMove.id<=0 || + (oppMove.function!="0B0" && # Me First + (target.movedThisRound? || oppMove.statusMove?)) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end +end + + + +#=============================================================================== +# This round, user becomes the target of attacks that have single targets. +# (Follow Me, Rage Powder) +#=============================================================================== +class PokeBattle_Move_117 < PokeBattle_Move + def pbEffectGeneral(user) + user.effects[PBEffects::FollowMe] = 1 + user.eachAlly do |b| + next if b.effects[PBEffects::FollowMe]0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + @battle.field.effects[PBEffects::Gravity] = 5 + @battle.pbDisplay(_INTL("Gravity intensified!")) + @battle.eachBattler do |b| + showMessage = false + if b.inTwoTurnAttack?("0C9","0CC","0CE") # Fly/Bounce/Sky Drop + b.effects[PBEffects::TwoTurnAttack] = 0 + @battle.pbClearChoice(b.index) if !b.movedThisRound? + showMessage = true + end + if b.effects[PBEffects::MagnetRise]>0 || + b.effects[PBEffects::Telekinesis]>0 || + b.effects[PBEffects::SkyDrop]>=0 + b.effects[PBEffects::MagnetRise] = 0 + b.effects[PBEffects::Telekinesis] = 0 + b.effects[PBEffects::SkyDrop] = -1 + showMessage = true + end + @battle.pbDisplay(_INTL("{1} couldn't stay airborne because of gravity!", + b.pbThis)) if showMessage + end + end +end + + + +#=============================================================================== +# For 5 rounds, user becomes airborne. (Magnet Rise) +#=============================================================================== +class PokeBattle_Move_119 < PokeBattle_Move + def unusableInGravity?; return true; end + + def pbMoveFailed?(user,targets) + if user.effects[PBEffects::Ingrain] || + user.effects[PBEffects::SmackDown] || + user.effects[PBEffects::MagnetRise]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.effects[PBEffects::MagnetRise] = 5 + @battle.pbDisplay(_INTL("{1} levitated with electromagnetism!",user.pbThis)) + end +end + + + +#=============================================================================== +# For 3 rounds, target becomes airborne and can always be hit. (Telekinesis) +#=============================================================================== +class PokeBattle_Move_11A < PokeBattle_Move + def unusableInGravity?; return true; end + + def pbFailsAgainstTarget?(user,target) + if target.effects[PBEffects::Ingrain] || + target.effects[PBEffects::SmackDown] || + target.effects[PBEffects::Telekinesis]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if isConst?(target.species,PBSpecies,:DIGLETT) || + isConst?(target.species,PBSpecies,:DUGTRIO) || + isConst?(target.species,PBSpecies,:SANDYGAST) || + isConst?(target.species,PBSpecies,:PALOSSAND) || + (isConst?(target.species,PBSpecies,:GENGAR) && target.mega?) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::Telekinesis] = 3 + @battle.pbDisplay(_INTL("{1} was hurled into the air!",target.pbThis)) + end +end + + + +#=============================================================================== +# Hits airborne semi-invulnerable targets. (Sky Uppercut) +#=============================================================================== +class PokeBattle_Move_11B < PokeBattle_Move + def hitsFlyingTargets?; return true; end +end + + + +#=============================================================================== +# Grounds the target while it remains active. Hits some semi-invulnerable +# targets. (Smack Down, Thousand Arrows) +#=============================================================================== +class PokeBattle_Move_11C < PokeBattle_Move + def hitsFlyingTargets?; return true; end + + def pbCalcTypeModSingle(moveType,defType,user,target) + return PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE if isConst?(moveType,PBTypes,:GROUND) && + isConst?(defType,PBTypes,:FLYING) + return super + end + + def pbEffectAfterAllHits(user,target) + return if target.fainted? + return if target.damageState.unaffected || target.damageState.substitute + return if target.inTwoTurnAttack?("0CE") || target.effects[PBEffects::SkyDrop]>=0 # Sky Drop + return if !target.airborne? && !target.inTwoTurnAttack?("0C9","0CC") # Fly/Bounce + target.effects[PBEffects::SmackDown] = true + if target.inTwoTurnAttack?("0C9","0CC") # Fly/Bounce. NOTE: Not Sky Drop. + target.effects[PBEffects::TwoTurnAttack] = 0 + @battle.pbClearChoice(target.index) if !target.movedThisRound? + end + target.effects[PBEffects::MagnetRise] = 0 + target.effects[PBEffects::Telekinesis] = 0 + @battle.pbDisplay(_INTL("{1} fell straight down!",target.pbThis)) + end +end + + + +#=============================================================================== +# Target moves immediately after the user, ignoring priority/speed. (After You) +#=============================================================================== +class PokeBattle_Move_11D < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbFailsAgainstTarget?(user,target) + # Target has already moved this round + return true if pbMoveFailedTargetAlreadyMoved?(target) + # Target was going to move next anyway (somehow) + if target.effects[PBEffects::MoveNext] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + # Target didn't choose to use a move this round + oppMove = @battle.choices[target.index][2] + if !oppMove || oppMove.id<=0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::MoveNext] = true + target.effects[PBEffects::Quash] = 0 + @battle.pbDisplay(_INTL("{1} took the kind offer!",target.pbThis)) + end +end + + + +#=============================================================================== +# Target moves last this round, ignoring priority/speed. (Quash) +#=============================================================================== +class PokeBattle_Move_11E < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + return true if pbMoveFailedTargetAlreadyMoved?(target) + # Target isn't going to use a move + oppMove = @battle.choices[target.index][2] + if !oppMove || oppMove.id<=0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + # Target is already maximally Quashed and will move last anyway + highestQuash = 0 + @battle.battlers.each do |b| + next if b.effects[PBEffects::Quash]<=highestQuash + highestQuash = b.effects[PBEffects::Quash] + end + if highestQuash>0 && target.effects[PBEffects::Quash]==highestQuash + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + # Target was already going to move last + if highestQuash==0 && @battle.pbPriority.last.index==target.index + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + highestQuash = 0 + @battle.battlers.each do |b| + next if b.effects[PBEffects::Quash]<=highestQuash + highestQuash = b.effects[PBEffects::Quash] + end + target.effects[PBEffects::Quash] = highestQuash+1 + target.effects[PBEffects::MoveNext] = false + @battle.pbDisplay(_INTL("{1}'s move was postponed!",target.pbThis)) + end +end + + + +#=============================================================================== +# For 5 rounds, for each priority bracket, slow Pokémon move before fast ones. +# (Trick Room) +#=============================================================================== +class PokeBattle_Move_11F < PokeBattle_Move + def pbEffectGeneral(user) + if @battle.field.effects[PBEffects::TrickRoom]>0 + @battle.field.effects[PBEffects::TrickRoom] = 0 + @battle.pbDisplay(_INTL("{1} reverted the dimensions!",user.pbThis)) + else + @battle.field.effects[PBEffects::TrickRoom] = 5 + @battle.pbDisplay(_INTL("{1} twisted the dimensions!",user.pbThis)) + end + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + return if @battle.field.effects[PBEffects::TrickRoom]>0 # No animation + super + end +end + + + +#=============================================================================== +# User switches places with its ally. (Ally Switch) +#=============================================================================== +class PokeBattle_Move_120 < PokeBattle_Move + def pbMoveFailed?(user,targets) + numTargets = 0 + @idxAlly = -1 + idxUserOwner = @battle.pbGetOwnerIndexFromBattlerIndex(user.index) + user.eachAlly do |b| + next if @battle.pbGetOwnerIndexFromBattlerIndex(b.index)!=idxUserOwner + next if !b.near?(user) + numTargets += 1 + @idxAlly = b.index + end + if numTargets!=1 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + idxA = user.index + idxB = @idxAlly + if @battle.pbSwapBattlers(idxA,idxB) + @battle.pbDisplay(_INTL("{1} and {2} switched places!", + @battle.battlers[idxB].pbThis,@battle.battlers[idxA].pbThis(true))) + end + end +end + + + +#=============================================================================== +# Target's Attack is used instead of user's Attack for this move's calculations. +# (Foul Play) +#=============================================================================== +class PokeBattle_Move_121 < PokeBattle_Move + def pbGetAttackStats(user,target) + if specialMove? + return target.spatk, target.stages[PBStats::SPATK]+6 + end + return target.attack, target.stages[PBStats::ATTACK]+6 + end +end + + + +#=============================================================================== +# Target's Defense is used instead of its Special Defense for this move's +# calculations. (Psyshock, Psystrike, Secret Sword) +#=============================================================================== +class PokeBattle_Move_122 < PokeBattle_Move + def pbGetDefenseStats(user,target) + return target.defense, target.stages[PBStats::DEFENSE]+6 + end +end + + + +#=============================================================================== +# Only damages Pokémon that share a type with the user. (Synchronoise) +#=============================================================================== +class PokeBattle_Move_123 < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + userTypes = user.pbTypes(true) + targetTypes = target.pbTypes(true) + sharesType = false + userTypes.each do |t| + next if !targetTypes.include?(t) + sharesType = true + break + end + if !sharesType + @battle.pbDisplay(_INTL("{1} is unaffected!",target.pbThis)) + return true + end + return false + end +end + + + +#=============================================================================== +# For 5 rounds, swaps all battlers' base Defense with base Special Defense. +# (Wonder Room) +#=============================================================================== +class PokeBattle_Move_124 < PokeBattle_Move + def pbEffectGeneral(user) + if @battle.field.effects[PBEffects::WonderRoom]>0 + @battle.field.effects[PBEffects::WonderRoom] = 0 + @battle.pbDisplay(_INTL("Wonder Room wore off, and the Defense and Sp. Def stats returned to normal!")) + else + @battle.field.effects[PBEffects::WonderRoom] = 5 + @battle.pbDisplay(_INTL("It created a bizarre area in which the Defense and Sp. Def stats are swapped!")) + end + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + return if @battle.field.effects[PBEffects::WonderRoom]>0 # No animation + super + end +end + + + +#=============================================================================== +# Fails unless user has already used all other moves it knows. (Last Resort) +#=============================================================================== +class PokeBattle_Move_125 < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + hasThisMove = false; hasOtherMoves = false; hasUnusedMoves = false + user.eachMove do |m| + hasThisMove = true if m.id==@id + hasOtherMoves = true if m.id!=@id + hasUnusedMoves = true if m.id!=@id && !user.movesUsed.include?(m.id) + end + if !hasThisMove || !hasOtherMoves || hasUnusedMoves + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end +end + + + +#=============================================================================== +# NOTE: Shadow moves use function codes 126-132 inclusive. +#=============================================================================== + + + +#=============================================================================== +# Does absolutely nothing. (Hold Hands) +#=============================================================================== +class PokeBattle_Move_133 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbMoveFailed?(user,targets) + hasAlly = false + user.eachAlly do |b| + hasAlly = true + break + end + if !hasAlly + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end +end + + + +#=============================================================================== +# Does absolutely nothing. Shows a special message. (Celebrate) +#=============================================================================== +class PokeBattle_Move_134 < PokeBattle_Move + def pbEffectGeneral(user) + if @battle.wildBattle? && user.opposes? + @battle.pbDisplay(_INTL("Congratulations from {1}!",user.pbThis(true))) + else + @battle.pbDisplay(_INTL("Congratulations, {1}!",@battle.pbGetOwnerName(user.index))) + end + end +end + + + +#=============================================================================== +# Freezes the target. Effectiveness against Water-type is 2x. (Freeze-Dry) +#=============================================================================== +class PokeBattle_Move_135 < PokeBattle_FreezeMove + def pbCalcTypeModSingle(moveType,defType,user,target) + return PBTypeEffectiveness::SUPER_EFFECTIVE_ONE if isConst?(defType,PBTypes,:WATER) + return super + end +end + + + +#=============================================================================== +# Increases the user's Defense by 2 stages. (Diamond Storm) +#=============================================================================== +class PokeBattle_Move_136 < PokeBattle_Move_02F + # NOTE: In Gen 6, this move increased the user's Defense by 1 stage for each + # target it hit. This effect changed in Gen 7 and is now identical to + # function code 02F. +end + + + +#=============================================================================== +# Increases the user's and its ally's Defense and Special Defense by 1 stage +# each, if they have Plus or Minus. (Magnetic Flux) +#=============================================================================== +# NOTE: In Gen 5, this move should have a target of UserSide, while in Gen 6+ it +# should have a target of UserAndAllies. This is because, in Gen 5, this +# move shouldn't call def pbSuccessCheckAgainstTarget for each Pokémon +# currently in battle that will be affected by this move (i.e. allies +# aren't protected by their substitute/ability/etc., but they are in Gen +# 6+). We achieve this by not targeting any battlers in Gen 5, since +# pbSuccessCheckAgainstTarget is only called for targeted battlers. +class PokeBattle_Move_137 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbMoveFailed?(user,targets) + @validTargets = [] + @battle.eachSameSideBattler(user) do |b| + next if !b.hasActiveAbility?([:MINUS,:PLUS]) + next if !b.pbCanRaiseStatStage?(PBStats::DEFENSE,user,self) && + !b.pbCanRaiseStatStage?(PBStats::SPDEF,user,self) + @validTargets.push(b) + end + if @validTargets.length==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + return false if @validTargets.any? { |b| b.index==target.index } + return true if !target.hasActiveAbility?([:MINUS,:PLUS]) + @battle.pbDisplay(_INTL("{1}'s stats can't be raised further!",target.pbThis)) + return true + end + + + def pbEffectAgainstTarget(user,target) + showAnim = true + if target.pbCanRaiseStatStage?(PBStats::DEFENSE,user,self) + if target.pbRaiseStatStage(PBStats::DEFENSE,1,user,showAnim) + showAnim = false + end + end + if target.pbCanRaiseStatStage?(PBStats::SPDEF,user,self) + target.pbRaiseStatStage(PBStats::SPDEF,1,user,showAnim) + end + end + + def pbEffectGeneral(user) + return if pbTarget(user)==PBTargets::UserAndAllies + @validTargets.each { |b| pbEffectAgainstTarget(user,b) } + end +end + + + +#=============================================================================== +# Increases target's Special Defense by 1 stage. (Aromatic Mist) +#=============================================================================== +class PokeBattle_Move_138 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbFailsAgainstTarget?(user,target) + return true if !target.pbCanRaiseStatStage?(PBStats::SPDEF,user,self,true) + return false + end + + def pbEffectAgainstTarget(user,target) + target.pbRaiseStatStage(PBStats::SPDEF,1,user) + end +end + + + +#=============================================================================== +# Decreases the target's Attack by 1 stage. Always hits. (Play Nice) +#=============================================================================== +class PokeBattle_Move_139 < PokeBattle_TargetStatDownMove + def ignoresSubstitute?(user); return true; end + + def initialize(battle,move) + super + @statDown = [PBStats::ATTACK,1] + end + + def pbAccuracyCheck(user,target); return true; end +end + + + +#=============================================================================== +# Decreases the target's Attack and Special Attack by 1 stage each. Always hits. +# (Noble Roar) +#=============================================================================== +class PokeBattle_Move_13A < PokeBattle_TargetMultiStatDownMove + def ignoresSubstitute?(user); return true; end + + def initialize(battle,move) + super + @statDown = [PBStats::ATTACK,1,PBStats::SPATK,1] + end + + def pbAccuracyCheck(user,target); return true; end +end + + + +#=============================================================================== +# Decreases the user's Defense by 1 stage. Always hits. Ends target's +# protections immediately. (Hyperspace Fury) +#=============================================================================== +class PokeBattle_Move_13B < PokeBattle_StatDownMove + def ignoresSubstitute?(user); return true; end + + def initialize(battle,move) + super + @statDown = [PBStats::DEFENSE,1] + end + + def pbMoveFailed?(user,targets) + if !isConst?(user.species,PBSpecies,:HOOPA) + @battle.pbDisplay(_INTL("But {1} can't use the move!",user.pbThis(true))) + return true + elsif user.form!=1 + @battle.pbDisplay(_INTL("But {1} can't use it the way it is now!",user.pbThis(true))) + return true + end + return false + end + + def pbAccuracyCheck(user,target); return true; end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::BanefulBunker] = false + target.effects[PBEffects::KingsShield] = false + target.effects[PBEffects::Protect] = false + target.effects[PBEffects::SpikyShield] = false + target.pbOwnSide.effects[PBEffects::CraftyShield] = false + target.pbOwnSide.effects[PBEffects::MatBlock] = false + target.pbOwnSide.effects[PBEffects::QuickGuard] = false + target.pbOwnSide.effects[PBEffects::WideGuard] = false + end +end + + + +#=============================================================================== +# Decreases the target's Special Attack by 1 stage. Always hits. (Confide) +#=============================================================================== +class PokeBattle_Move_13C < PokeBattle_TargetStatDownMove + def ignoresSubstitute?(user); return true; end + + def initialize(battle,move) + super + @statDown = [PBStats::SPATK,1] + end + + def pbAccuracyCheck(user,target); return true; end +end + + + +#=============================================================================== +# Decreases the target's Special Attack by 2 stages. (Eerie Impulse) +#=============================================================================== +class PokeBattle_Move_13D < PokeBattle_TargetStatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::SPATK,2] + end +end + + + +#=============================================================================== +# Increases the Attack and Special Attack of all Grass-type Pokémon in battle by +# 1 stage each. Doesn't affect airborne Pokémon. (Rototiller) +#=============================================================================== +class PokeBattle_Move_13E < PokeBattle_Move + def pbMoveFailed?(user,targets) + @validTargets = [] + @battle.eachBattler do |b| + next if !b.pbHasType?(:GRASS) + next if b.airborne? || b.semiInvulnerable? + next if !b.pbCanRaiseStatStage?(PBStats::ATTACK,user,self) && + !b.pbCanRaiseStatStage?(PBStats::SPATK,user,self) + @validTargets.push(b.index) + end + if @validTargets.length==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + return false if @validTargets.include?(target.index) + return true if !target.pbHasType?(:GRASS) + return true if target.airborne? || target.semiInvulnerable? + @battle.pbDisplay(_INTL("{1}'s stats can't be raised further!",target.pbThis)) + return true + end + + def pbEffectAgainstTarget(user,target) + showAnim = true + if target.pbCanRaiseStatStage?(PBStats::ATTACK,user,self) + if target.pbRaiseStatStage(PBStats::ATTACK,1,user,showAnim) + showAnim = false + end + end + if target.pbCanRaiseStatStage?(PBStats::SPATK,user,self) + target.pbRaiseStatStage(PBStats::SPATK,1,user,showAnim) + end + end +end + + + +#=============================================================================== +# Increases the Defense of all Grass-type Pokémon on the field by 1 stage each. +# (Flower Shield) +#=============================================================================== +class PokeBattle_Move_13F < PokeBattle_Move + def pbMoveFailed?(user,targets) + @validTargets = [] + @battle.eachBattler do |b| + next if !b.pbHasType?(:GRASS) + next if b.semiInvulnerable? + next if !b.pbCanRaiseStatStage?(PBStats::DEFENSE,user,self) + @validTargets.push(b.index) + end + if @validTargets.length==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + return false if @validTargets.include?(target.index) + return true if !target.pbHasType?(:GRASS) || target.semiInvulnerable? + return !target.pbCanRaiseStatStage?(PBStats::DEFENSE,user,self,true) + end + + def pbEffectAgainstTarget(user,target) + target.pbRaiseStatStage(PBStats::DEFENSE,1,user) + end +end + + + +#=============================================================================== +# Decreases the Attack, Special Attack and Speed of all poisoned targets by 1 +# stage each. (Venom Drench) +#=============================================================================== +class PokeBattle_Move_140 < PokeBattle_Move + def pbMoveFailed?(user,targets) + @validTargets = [] + targets.each do |b| + next if !b || b.fainted? + next if !b.poisoned? + next if !b.pbCanLowerStatStage?(PBStats::ATTACK,user,self) && + !b.pbCanLowerStatStage?(PBStats::SPATK,user,self) && + !b.pbCanLowerStatStage?(PBStats::SPEED,user,self) + @validTargets.push(b.index) + end + if @validTargets.length==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + return if !@validTargets.include?(target.index) + showAnim = true + [PBStats::ATTACK,PBStats::SPATK,PBStats::SPEED].each do |s| + next if !target.pbCanLowerStatStage?(s,user,self) + if target.pbLowerStatStage(s,1,user,showAnim) + showAnim = false + end + end + end +end + + + +#=============================================================================== +# Reverses all stat changes of the target. (Topsy-Turvy) +#=============================================================================== +class PokeBattle_Move_141 < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + failed = true + PBStats.eachBattleStat do |s| + next if target.stages[s]==0 + failed = false + break + end + if failed + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + PBStats.eachBattleStat { |s| target.stages[s] *= -1 } + @battle.pbDisplay(_INTL("{1}'s stats were reversed!",target.pbThis)) + end +end + + + +#=============================================================================== +# Gives target the Ghost type. (Trick-or-Treat) +#=============================================================================== +class PokeBattle_Move_142 < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + if !hasConst?(PBTypes,:GHOST) || target.pbHasType?(:GHOST) || !target.canChangeType? + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + ghostType = getConst(PBTypes,:GHOST) + target.effects[PBEffects::Type3] = ghostType + typeName = PBTypes.getName(ghostType) + @battle.pbDisplay(_INTL("{1} transformed into the {2} type!",target.pbThis,typeName)) + end +end + + + +#=============================================================================== +# Gives target the Grass type. (Forest's Curse) +#=============================================================================== +class PokeBattle_Move_143 < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + if !hasConst?(PBTypes,:GRASS) || target.pbHasType?(:GRASS) || !target.canChangeType? + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + grassType = getConst(PBTypes,:GRASS) + target.effects[PBEffects::Type3] = grassType + typeName = PBTypes.getName(grassType) + @battle.pbDisplay(_INTL("{1} transformed into the {2} type!",target.pbThis,typeName)) + end +end + + + +#=============================================================================== +# Type effectiveness is multiplied by the Flying-type's effectiveness against +# the target. Does double damage and has perfect accuracy if the target is +# Minimized. (Flying Press) +#=============================================================================== +class PokeBattle_Move_144 < PokeBattle_Move + def tramplesMinimize?(param=1) + return true if param==1 && NEWEST_BATTLE_MECHANICS # Perfect accuracy + return true if param==2 # Double damage + return super + end + + def pbCalcTypeModSingle(moveType,defType,user,target) + ret = super + if hasConst?(PBTypes,:FLYING) + flyingEff = PBTypes.getEffectiveness(getConst(PBTypes,:FLYING),defType) + ret *= flyingEff.to_f/PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE + end + return ret + end +end + + + +#=============================================================================== +# Target's moves become Electric-type for the rest of the round. (Electrify) +#=============================================================================== +class PokeBattle_Move_145 < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + if target.effects[PBEffects::Electrify] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return true if pbMoveFailedTargetAlreadyMoved?(target) + return false + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::Electrify] = true + @battle.pbDisplay(_INTL("{1}'s moves have been electrified!",target.pbThis)) + end +end + + + +#=============================================================================== +# All Normal-type moves become Electric-type for the rest of the round. +# (Ion Deluge, Plasma Fists) +#=============================================================================== +class PokeBattle_Move_146 < PokeBattle_Move + def pbMoveFailed?(user,targets) + return false if damagingMove? + if @battle.field.effects[PBEffects::IonDeluge] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return true if pbMoveFailedLastInRound?(user) + return false + end + + def pbEffectGeneral(user) + return if @battle.field.effects[PBEffects::IonDeluge] + @battle.field.effects[PBEffects::IonDeluge] = true + @battle.pbDisplay(_INTL("A deluge of ions showers the battlefield!")) + end +end + + + +#=============================================================================== +# Always hits. Ends target's protections immediately. (Hyperspace Hole) +#=============================================================================== +class PokeBattle_Move_147 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + def pbAccuracyCheck(user,target); return true; end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::BanefulBunker] = false + target.effects[PBEffects::KingsShield] = false + target.effects[PBEffects::Protect] = false + target.effects[PBEffects::SpikyShield] = false + target.pbOwnSide.effects[PBEffects::CraftyShield] = false + target.pbOwnSide.effects[PBEffects::MatBlock] = false + target.pbOwnSide.effects[PBEffects::QuickGuard] = false + target.pbOwnSide.effects[PBEffects::WideGuard] = false + end +end + + + +#=============================================================================== +# Powders the foe. This round, if it uses a Fire move, it loses 1/4 of its max +# HP instead. (Powder) +#=============================================================================== +class PokeBattle_Move_148 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbFailsAgainstTarget?(user,target) + if target.effects[PBEffects::Powder] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::Powder] = true + @battle.pbDisplay(_INTL("{1} is covered in powder!",user.pbThis)) + end +end + + + +#=============================================================================== +# This round, the user's side is unaffected by damaging moves. (Mat Block) +#=============================================================================== +class PokeBattle_Move_149 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.turnCount>1 || user.lastRoundMoved>=0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return true if pbMoveFailedLastInRound?(user) + return false + end + + def pbEffectGeneral(user) + user.pbOwnSide.effects[PBEffects::MatBlock] = true + @battle.pbDisplay(_INTL("{1} intends to flip up a mat and block incoming attacks!",user.pbThis)) + end +end + + + +#=============================================================================== +# User's side is protected against status moves this round. (Crafty Shield) +#=============================================================================== +class PokeBattle_Move_14A < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.pbOwnSide.effects[PBEffects::CraftyShield] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return true if pbMoveFailedLastInRound?(user) + return false + end + + def pbEffectGeneral(user) + user.pbOwnSide.effects[PBEffects::CraftyShield] = true + @battle.pbDisplay(_INTL("Crafty Shield protected {1}!",user.pbTeam(true))) + end +end + + + +#=============================================================================== +# User is protected against damaging moves this round. Decreases the Attack of +# the user of a stopped contact move by 2 stages. (King's Shield) +#=============================================================================== +class PokeBattle_Move_14B < PokeBattle_ProtectMove + def initialize(battle,move) + super + @effect = PBEffects::KingsShield + end +end + + + +#=============================================================================== +# User is protected against moves that target it this round. Damages the user of +# a stopped contact move by 1/8 of its max HP. (Spiky Shield) +#=============================================================================== +class PokeBattle_Move_14C < PokeBattle_ProtectMove + def initialize(battle,move) + super + @effect = PBEffects::SpikyShield + end +end + + + +#=============================================================================== +# Two turn attack. Skips first turn, attacks second turn. (Phantom Force) +# Is invulnerable during use. Ends target's protections upon hit. +#=============================================================================== +class PokeBattle_Move_14D < PokeBattle_Move_0CD + # NOTE: This move is identical to function code 0CD (Shadow Force). +end + + + +#=============================================================================== +# Two turn attack. Skips first turn, and increases the user's Special Attack, +# Special Defense and Speed by 2 stages each in the second turn. (Geomancy) +#=============================================================================== +class PokeBattle_Move_14E < PokeBattle_TwoTurnMove + def pbMoveFailed?(user,targets) + return false if user.effects[PBEffects::TwoTurnAttack]>0 # Charging turn + if !user.pbCanRaiseStatStage?(PBStats::SPATK,user,self) && + !user.pbCanRaiseStatStage?(PBStats::SPDEF,user,self) && + !user.pbCanRaiseStatStage?(PBStats::SPEED,user,self) + @battle.pbDisplay(_INTL("{1}'s stats won't go any higher!",user.pbThis)) + return true + end + return false + end + + def pbChargingTurnMessage(user,targets) + @battle.pbDisplay(_INTL("{1} is absorbing power!",user.pbThis)) + end + + def pbAttackingTurnEffect(user,target) + showAnim = true + [PBStats::SPATK,PBStats::SPDEF,PBStats::SPEED].each do |s| + next if !user.pbCanRaiseStatStage?(s,user,self) + if user.pbRaiseStatStage(s,2,user,showAnim) + showAnim = false + end + end + end +end + + + +#=============================================================================== +# User gains 3/4 the HP it inflicts as damage. (Draining Kiss, Oblivion Wing) +#=============================================================================== +class PokeBattle_Move_14F < PokeBattle_Move + def healingMove?; return NEWEST_BATTLE_MECHANICS; end + + def pbEffectAgainstTarget(user,target) + return if target.damageState.hpLost<=0 + hpGain = (target.damageState.hpLost*0.75).round + user.pbRecoverHPFromDrain(hpGain,target) + end +end + + + +#=============================================================================== +# If this move KO's the target, increases the user's Attack by 3 stages. +# (Fell Stinger) +#=============================================================================== +class PokeBattle_Move_150 < PokeBattle_Move + def pbEffectAfterAllHits(user,target) + return if !target.damageState.fainted + return if !user.pbCanRaiseStatStage?(PBStats::ATTACK,user,self) + user.pbRaiseStatStage(PBStats::ATTACK,3,user) + end +end + + + +#=============================================================================== +# Decreases the target's Attack and Special Attack by 1 stage each. Then, user +# switches out. Ignores trapping moves. (Parting Shot) +#=============================================================================== +class PokeBattle_Move_151 < PokeBattle_TargetMultiStatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::ATTACK,1,PBStats::SPATK,1] + end + + def pbEndOfMoveUsageEffect(user,targets,numHits,switchedBattlers) + switcher = user + targets.each do |b| + next if switchedBattlers.include?(b.index) + switcher = b if b.effects[PBEffects::MagicCoat] || b.effects[PBEffects::MagicBounce] + end + return if switcher.fainted? || numHits==0 + return if !@battle.pbCanChooseNonActive?(switcher.index) + @battle.pbDisplay(_INTL("{1} went back to {2}!",switcher.pbThis, + @battle.pbGetOwnerName(switcher.index))) + @battle.pbPursuit(switcher.index) + return if switcher.fainted? + newPkmn = @battle.pbGetReplacementPokemonIndex(switcher.index) # Owner chooses + return if newPkmn<0 + @battle.pbRecallAndReplace(switcher.index,newPkmn) + @battle.pbClearChoice(switcher.index) # Replacement Pokémon does nothing this round + @battle.moldBreaker = false if switcher.index==user.index + switchedBattlers.push(switcher.index) + switcher.pbEffectsOnSwitchIn(true) + end +end + + + +#=============================================================================== +# No Pokémon can switch out or flee until the end of the next round, as long as +# the user remains active. (Fairy Lock) +#=============================================================================== +class PokeBattle_Move_152 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if @battle.field.effects[PBEffects::FairyLock]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + @battle.field.effects[PBEffects::FairyLock] = 2 + @battle.pbDisplay(_INTL("No one will be able to run away during the next turn!")) + end +end + + + +#=============================================================================== +# Entry hazard. Lays stealth rocks on the opposing side. (Sticky Web) +#=============================================================================== +class PokeBattle_Move_153 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.pbOpposingSide.effects[PBEffects::StickyWeb] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.pbOpposingSide.effects[PBEffects::StickyWeb] = true + @battle.pbDisplay(_INTL("A sticky web has been laid out beneath {1}'s feet!", + user.pbOpposingTeam(true))) + end +end + + + +#=============================================================================== +# For 5 rounds, creates an electric terrain which boosts Electric-type moves and +# prevents Pokémon from falling asleep. Affects non-airborne Pokémon only. +# (Electric Terrain) +#=============================================================================== +class PokeBattle_Move_154 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if @battle.field.terrain==PBBattleTerrains::Electric + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + @battle.pbStartTerrain(user,PBBattleTerrains::Electric) + end +end + + + +#=============================================================================== +# For 5 rounds, creates a grassy terrain which boosts Grass-type moves and heals +# Pokémon at the end of each round. Affects non-airborne Pokémon only. +# (Grassy Terrain) +#=============================================================================== +class PokeBattle_Move_155 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if @battle.field.terrain==PBBattleTerrains::Grassy + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + @battle.pbStartTerrain(user,PBBattleTerrains::Grassy) + end +end + + + +#=============================================================================== +# For 5 rounds, creates a misty terrain which weakens Dragon-type moves and +# protects Pokémon from status problems. Affects non-airborne Pokémon only. +# (Misty Terrain) +#=============================================================================== +class PokeBattle_Move_156 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if @battle.field.terrain==PBBattleTerrains::Misty + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + @battle.pbStartTerrain(user,PBBattleTerrains::Misty) + end +end + + + +#=============================================================================== +# Doubles the prize money the player gets after winning the battle. (Happy Hour) +#=============================================================================== +class PokeBattle_Move_157 < PokeBattle_Move + def pbEffectGeneral(user) + @battle.field.effects[PBEffects::HappyHour] = true if !user.opposes? + @battle.pbDisplay(_INTL("Everyone is caught up in the happy atmosphere!")) + end +end + + + +#=============================================================================== +# Fails unless user has consumed a berry at some point. (Belch) +#=============================================================================== +class PokeBattle_Move_158 < PokeBattle_Move + def pbCanChooseMove?(user,commandPhase,showMessages) + if !user.belched? + if showMessages + msg = _INTL("{1} hasn't eaten any held berry, so it can't possibly belch!",user.pbThis) + (commandPhase) ? @battle.pbDisplayPaused(msg) : @battle.pbDisplay(msg) + end + return false + end + return true + end + + def pbMoveFailed?(user,targets) + if !user.belched? + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end +end + + + +#=============================================================================== +# Poisons the target and decreases its Speed by 1 stage. (Toxic Thread) +#=============================================================================== +class PokeBattle_Move_159 < PokeBattle_Move + def pbFailsAgainstTarget?(user,target) + if !target.pbCanPoison?(user,false,self) && + !target.pbCanLowerStatStage?(PBStats::SPEED,user,self) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + target.pbPoison(user) if target.pbCanPoison?(user,false,self) + if target.pbCanLowerStatStage?(PBStats::SPEED,user,self) + target.pbLowerStatStage(PBStats::SPEED,1,user) + end + end +end + + + +#=============================================================================== +# Cures the target's burn. (Sparkling Aria) +#=============================================================================== +class PokeBattle_Move_15A < PokeBattle_Move + def pbAdditionalEffect(user,target) + return if target.fainted? || target.damageState.substitute + return if target.status!=PBStatuses::BURN + target.pbCureStatus + end +end + + + +#=============================================================================== +# Cures the target's permanent status problems. Heals user by 1/2 of its max HP. +# (Purify) +#=============================================================================== +class PokeBattle_Move_15B < PokeBattle_HealingMove + def pbFailsAgainstTarget?(user,target) + if target.status==PBStatuses::NONE + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbHealAmount(user) + return (user.totalhp/2.0).round + end + + def pbEffectAgainstTarget(user,target) + target.pbCureStatus + super + end +end + + + +#=============================================================================== +# Increases the user's and its ally's Attack and Special Attack by 1 stage each, +# if they have Plus or Minus. (Gear Up) +#=============================================================================== +# NOTE: In Gen 5, this move should have a target of UserSide, while in Gen 6+ it +# should have a target of UserAndAllies. This is because, in Gen 5, this +# move shouldn't call def pbSuccessCheckAgainstTarget for each Pokémon +# currently in battle that will be affected by this move (i.e. allies +# aren't protected by their substitute/ability/etc., but they are in Gen +# 6+). We achieve this by not targeting any battlers in Gen 5, since +# pbSuccessCheckAgainstTarget is only called for targeted battlers. +class PokeBattle_Move_15C < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbMoveFailed?(user,targets) + @validTargets = [] + @battle.eachSameSideBattler(user) do |b| + next if !b.hasActiveAbility?([:MINUS,:PLUS]) + next if !b.pbCanRaiseStatStage?(PBStats::ATTACK,user,self) && + !b.pbCanRaiseStatStage?(PBStats::SPATK,user,self) + @validTargets.push(b) + end + if @validTargets.length==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbFailsAgainstTarget?(user,target) + return false if @validTargets.any? { |b| b.index==target.index } + return true if !target.hasActiveAbility?([:MINUS,:PLUS]) + @battle.pbDisplay(_INTL("{1}'s stats can't be raised further!",target.pbThis)) + return true + end + + def pbEffectAgainstTarget(user,target) + showAnim = true + if target.pbCanRaiseStatStage?(PBStats::ATTACK,user,self) + if target.pbRaiseStatStage(PBStats::ATTACK,1,user,showAnim) + showAnim = false + end + end + if target.pbCanRaiseStatStage?(PBStats::SPATK,user,self) + target.pbRaiseStatStage(PBStats::SPATK,1,user,showAnim) + end + end + + def pbEffectGeneral(user) + return if pbTarget(user)==PBTargets::UserAndAllies + @validTargets.each { |b| pbEffectAgainstTarget(user,b) } + end +end + + + +#=============================================================================== +# User gains stat stages equal to each of the target's positive stat stages, +# and target's positive stat stages become 0, before damage calculation. +# (Spectral Thief) +#=============================================================================== +class PokeBattle_Move_15D < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbCalcDamage(user,target,numTargets=1) + if target.hasRaisedStatStages? + pbShowAnimation(@id,user,target,1) # Stat stage-draining animation + @battle.pbDisplay(_INTL("{1} stole the target's boosted stats!",user.pbThis)) + showAnim = true + PBStats.eachBattleStat do |s| + next if target.stages[s]<=0 + if user.pbCanRaiseStatStage?(s,user,self) + if user.pbRaiseStatStage(s,target.stages[s],user,showAnim) + showAnim = false + end + end + target.stages[s] = 0 + end + end + super + end +end + + + +#=============================================================================== +# Until the end of the next round, the user's moves will always be critical hits. +# (Laser Focus) +#=============================================================================== +class PokeBattle_Move_15E < PokeBattle_Move + def pbEffectGeneral(user) + user.effects[PBEffects::LaserFocus] = 2 + @battle.pbDisplay(_INTL("{1} concentrated intensely!",user.pbThis)) + end +end + + + +#=============================================================================== +# Decreases the user's Defense by 1 stage. (Clanging Scales) +#=============================================================================== +class PokeBattle_Move_15F < PokeBattle_StatDownMove + def initialize(battle,move) + super + @statDown = [PBStats::DEFENSE,1] + end +end + + + +#=============================================================================== +# Decreases the target's Attack by 1 stage. Heals user by an amount equal to the +# target's Attack stat (after applying stat stages, before this move decreases +# it). (Strength Sap) +#=============================================================================== +class PokeBattle_Move_160 < PokeBattle_Move + def healingMove?; return true; end + + def pbFailsAgainstTarget?(user,target) + # NOTE: The official games appear to just check whether the target's Attack + # stat stage is -6 and fail if so, but I've added the "fail if target + # has Contrary and is at +6" check too for symmetry. This move still + # works even if the stat stage cannot be changed due to an ability or + # other effect. + if !@battle.moldBreaker && target.hasActiveAbility?(:CONTRARY) && + target.statStageAtMax?(PBStats::ATTACK) + @battle.pbDisplay(_INTL("But it failed!")) + return true + elsif target.statStageAtMin?(PBStats::ATTACK) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + # Calculate target's effective attack value + stageMul = [2,2,2,2,2,2, 2, 3,4,5,6,7,8] + stageDiv = [8,7,6,5,4,3, 2, 2,2,2,2,2,2] + atk = target.attack + atkStage = target.stages[PBStats::ATTACK]+6 + healAmt = (atk.to_f*stageMul[atkStage]/stageDiv[atkStage]).floor + # Reduce target's Attack stat + if target.pbCanLowerStatStage?(PBStats::ATTACK,user,self) + target.pbLowerStatStage(PBStats::ATTACK,1,user) + end + # Heal user + if target.hasActiveAbility?(:LIQUIDOOZE) + @battle.pbShowAbilitySplash(target) + user.pbReduceHP(healAmt) + @battle.pbDisplay(_INTL("{1} sucked up the liquid ooze!",user.pbThis)) + @battle.pbHideAbilitySplash(target) + user.pbItemHPHealCheck + elsif user.canHeal? + healAmt = (healAmt*1.3).floor if user.hasActiveItem?(:BIGROOT) + user.pbRecoverHP(healAmt) + @battle.pbDisplay(_INTL("{1}'s HP was restored.",user.pbThis)) + end + end +end + + + +#=============================================================================== +# User and target swap their Speed stats (not their stat stages). (Speed Swap) +#=============================================================================== +class PokeBattle_Move_161 < PokeBattle_Move + def ignoresSubstitute?(user); return true; end + + def pbEffectAgainstTarget(user,target) + user.speed, target.speed = target.speed, user.speed + @battle.pbDisplay(_INTL("{1} switched Speed with its target!",user.pbThis)) + end +end + + + +#=============================================================================== +# User loses their Fire type. Fails if user is not Fire-type. (Burn Up) +#=============================================================================== +class PokeBattle_Move_162 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if !user.pbHasType?(:FIRE) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAfterAllHits(user,target) + if !user.effects[PBEffects::BurnUp] + user.effects[PBEffects::BurnUp] = true + @battle.pbDisplay(_INTL("{1} burned itself out!",user.pbThis)) + end + end +end + + + +#=============================================================================== +# Ignores all abilities that alter this move's success or damage. +# (Moongeist Beam, Sunsteel Strike) +#=============================================================================== +class PokeBattle_Move_163 < PokeBattle_Move + def pbChangeUsageCounters(user,specialUsage) + super + @battle.moldBreaker = true if !specialUsage + end +end + + + +#=============================================================================== +# Ignores all abilities that alter this move's success or damage. This move is +# physical if user's Attack is higher than its Special Attack (after applying +# stat stages), and special otherwise. (Photon Geyser) +#=============================================================================== +class PokeBattle_Move_164 < PokeBattle_Move_163 + def initialize(battle,move) + super + @calcCategory = 1 + end + + def physicalMove?(thisType=nil); return (@calcCategory==0); end + def specialMove?(thisType=nil); return (@calcCategory==1); end + + def pbOnStartUse(user,targets) + # Calculate user's effective attacking value + stageMul = [2,2,2,2,2,2, 2, 3,4,5,6,7,8] + stageDiv = [8,7,6,5,4,3, 2, 2,2,2,2,2,2] + atk = user.attack + atkStage = user.stages[PBStats::ATTACK]+6 + realAtk = (atk.to_f*stageMul[atkStage]/stageDiv[atkStage]).floor + spAtk = user.spatk + spAtkStage = user.stages[PBStats::SPATK]+6 + realSpAtk = (spAtk.to_f*stageMul[spAtkStage]/stageDiv[spAtkStage]).floor + # Determine move's category + @calcCategory = (realAtk>realSpAtk) ? 0 : 1 + end +end + + + +#=============================================================================== +# Negates the target's ability while it remains on the field, if it has already +# performed its action this round. (Core Enforcer) +#=============================================================================== +class PokeBattle_Move_165 < PokeBattle_Move + def initialize(battle,move) + super + @abilityBlacklist = [ + # Form-changing abilities + :BATTLEBOND, + :DISGUISE, +# :FLOWERGIFT, # This can be negated +# :FORECAST, # This can be negated + :MULTITYPE, + :POWERCONSTRUCT, + :SCHOOLING, + :SHIELDSDOWN, + :STANCECHANGE, + :ZENMODE, + # Abilities intended to be inherent properties of a certain species + :COMATOSE, + :RKSSYSTEM + ] + end + + def pbEffectAgainstTarget(user,target) + return if target.damageState.substitute || target.effects[PBEffects::GastroAcid] + @abilityBlacklist.each do |abil| + return if isConst?(target.ability,PBAbilities,abil) + end + return if @battle.choices[target.index][0]!=:UseItem && + !((@battle.choices[target.index][0]==:UseMove || + @battle.choices[target.index][0]==:Shift) && target.movedThisRound?) + target.effects[PBEffects::GastroAcid] = true + target.effects[PBEffects::Truant] = false + @battle.pbDisplay(_INTL("{1}'s Ability was suppressed!",target.pbThis)) + target.pbOnAbilityChanged(target.ability) + end +end + + + +#=============================================================================== +# Power is doubled if the user's last move failed. (Stomping Tantrum) +#=============================================================================== +class PokeBattle_Move_166 < PokeBattle_Move + def pbBaseDamage(baseDmg,user,target) + baseDmg *= 2 if user.lastRoundMoveFailed + return baseDmg + end +end + + + +#=============================================================================== +# For 5 rounds, lowers power of attacks against the user's side. Fails if +# weather is not hail. (Aurora Veil) +#=============================================================================== +class PokeBattle_Move_167 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if @battle.pbWeather!=PBWeather::Hail + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if user.pbOwnSide.effects[PBEffects::AuroraVeil]>0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + user.pbOwnSide.effects[PBEffects::AuroraVeil] = 5 + user.pbOwnSide.effects[PBEffects::AuroraVeil] = 8 if user.hasActiveItem?(:LIGHTCLAY) + @battle.pbDisplay(_INTL("{1} made {2} stronger against physical and special moves!", + @name,user.pbTeam(true))) + end +end + + + +#=============================================================================== +# User is protected against moves with the "B" flag this round. If a Pokémon +# makes contact with the user while this effect applies, that Pokémon is +# poisoned. (Baneful Bunker) +#=============================================================================== +class PokeBattle_Move_168 < PokeBattle_ProtectMove + def initialize(battle,move) + super + @effect = PBEffects::BanefulBunker + end +end + + + +#=============================================================================== +# This move's type is the same as the user's first type. (Revelation Dance) +#=============================================================================== +class PokeBattle_Move_169 < PokeBattle_Move + def pbBaseType(user) + userTypes = user.pbTypes(true) + return (userTypes.length==0) ? -1 : userTypes[0] + end +end + + + +#=============================================================================== +# This round, target becomes the target of attacks that have single targets. +# (Spotlight) +#=============================================================================== +class PokeBattle_Move_16A < PokeBattle_Move + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::Spotlight] = 1 + target.eachAlly do |b| + next if b.effects[PBEffects::Spotlight]0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + target.effects[PBEffects::Instruct] = true + end +end + + + +#=============================================================================== +# Target cannot use sound-based moves for 2 more rounds. (Throat Chop) +#=============================================================================== +class PokeBattle_Move_16C < PokeBattle_Move + def pbAdditionalEffect(user,target) + return if target.fainted? || target.damageState.substitute + @battle.pbDisplay(_INTL("The effects of {1} prevent {2} from using certain moves!", + @name,target.pbThis(true))) if target.effects[PBEffects::ThroatChop]==0 + target.effects[PBEffects::ThroatChop] = 3 + end +end + + + +#=============================================================================== +# Heals user by 1/2 of its max HP, or 2/3 of its max HP in a sandstorm. (Shore Up) +#=============================================================================== +class PokeBattle_Move_16D < PokeBattle_HealingMove + def pbHealAmount(user) + return (user.totalhp*2/3.0).round if @battle.pbWeather==PBWeather::Sandstorm + return (user.totalhp/2.0).round + end +end + + + +#=============================================================================== +# Heals target by 1/2 of its max HP, or 2/3 of its max HP in Grassy Terrain. +# (Floral Healing) +#=============================================================================== +class PokeBattle_Move_16E < PokeBattle_Move + def healingMove?; return true; end + + def pbFailsAgainstTarget?(user,target) + if target.hp==target.totalhp + @battle.pbDisplay(_INTL("{1}'s HP is full!",target.pbThis)) + return true + elsif !target.canHeal? + @battle.pbDisplay(_INTL("{1} is unaffected!",target.pbThis)) + return true + end + return false + end + + def pbEffectAgainstTarget(user,target) + hpGain = (target.totalhp/2.0).round + hpGain = (target.totalhp*2/3.0).round if @battle.field.terrain==PBBattleTerrains::Grassy + target.pbRecoverHP(hpGain) + @battle.pbDisplay(_INTL("{1}'s HP was restored.",target.pbThis)) + end +end + + + +#=============================================================================== +# Damages target if target is a foe, or heals target by 1/2 of its max HP if +# target is an ally. (Pollen Puff) +#=============================================================================== +class PokeBattle_Move_16F < PokeBattle_Move + def pbTarget(user) + return PBTargets::NearFoe if user.effects[PBEffects::HealBlock]>0 + super + end + + def pbOnStartUse(user,targets) + @healing = false + @healing = !user.opposes?(targets[0]) if targets.length>0 + end + + def pbFailsAgainstTarget?(user,target) + return false if !@healing + if target.effects[PBEffects::Substitute]>0 && !ignoresSubstitute?(user) + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if !target.canHeal? + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbDamagingMove? + return false if @healing + return super + end + + def pbEffectAgainstTarget(user,target) + return if !@healing + target.pbRecoverHP(target.totalhp/2) + @battle.pbDisplay(_INTL("{1}'s HP was restored.",target.pbThis)) + end + + def pbShowAnimation(id,user,targets,hitNum=0,showAnimation=true) + hitNum = 1 if @healing # Healing anim + super + end +end + + + +#=============================================================================== +# Damages user by 1/2 of its max HP, even if this move misses. (Mind Blown) +#=============================================================================== +class PokeBattle_Move_170 < PokeBattle_Move + def worksWithNoTargets?; return true; end + + def pbMoveFailed?(user,targets) + if !@battle.moldBreaker + bearer = @battle.pbCheckGlobalAbility(:DAMP) + if bearer!=nil + @battle.pbShowAbilitySplash(bearer) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @battle.pbDisplay(_INTL("{1} cannot use {2}!",user.pbThis,@name)) + else + @battle.pbDisplay(_INTL("{1} cannot use {2} because of {3}'s {4}!", + user.pbThis,@name,bearer.pbThis(true),bearer.abilityName)) + end + @battle.pbHideAbilitySplash(bearer) + return true + end + end + return false + end + + def pbSelfKO(user) + return if !user.takesIndirectDamage? + user.pbReduceHP((user.totalhp/2.0).round,false) + user.pbItemHPHealCheck + end +end + + + +#=============================================================================== +# Fails if user has not been hit by an opponent's physical move this round. +# (Shell Trap) +#=============================================================================== +class PokeBattle_Move_171 < PokeBattle_Move + def pbDisplayChargeMessage(user) + user.effects[PBEffects::ShellTrap] = true + @battle.pbCommonAnimation("ShellTrap",user) + @battle.pbDisplay(_INTL("{1} set a shell trap!",user.pbThis)) + end + + def pbDisplayUseMessage(user) + super if user.tookPhysicalHit + end + + def pbMoveFailed?(user,targets) + if !user.effects[PBEffects::ShellTrap] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + if !user.tookPhysicalHit + @battle.pbDisplay(_INTL("{1}'s shell trap didn't work!",user.pbThis)) + return true + end + return false + end +end + + + +#=============================================================================== +# If a Pokémon makes contact with the user before it uses this move, the +# attacker is burned. (Beak Blast) +#=============================================================================== +class PokeBattle_Move_172 < PokeBattle_Move + def pbDisplayChargeMessage(user) + user.effects[PBEffects::BeakBlast] = true + @battle.pbCommonAnimation("BeakBlast",user) + @battle.pbDisplay(_INTL("{1} started heating up its beak!",user.pbThis)) + end + + def pbMoveFailed?(user,targets) + if !user.effects[PBEffects::BeakBlast] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end +end + + + +#=============================================================================== +# For 5 rounds, creates a psychic terrain which boosts Psychic-type moves and +# prevents Pokémon from being hit by >0 priority moves. Affects non-airborne +# Pokémon only. (Psychic Terrain) +#=============================================================================== +class PokeBattle_Move_173 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if @battle.field.terrain==PBBattleTerrains::Psychic + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end + + def pbEffectGeneral(user) + @battle.pbStartTerrain(user,PBBattleTerrains::Psychic) + end +end + + + +#=============================================================================== +# Fails if this isn't the user's first turn. (First Impression) +#=============================================================================== +class PokeBattle_Move_174 < PokeBattle_Move + def pbMoveFailed?(user,targets) + if user.turnCount>1 || user.lastRoundMoved>=0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return false + end +end + + + +#=============================================================================== +# Hits twice. Causes the target to flinch. Does double damage and has perfect +# accuracy if the target is Minimized. (Double Iron Bash) +#=============================================================================== +class PokeBattle_Move_175 < PokeBattle_FlinchMove + def multiHitMove?; return true; end + def pbNumHits(user,targets); return 2; end + def tramplesMinimize?(param=1); return true; end +end + + + +# NOTE: If you're inventing new move effects, use function code 176 and onwards. +# Actually, you might as well use high numbers like 500+ (up to FFFF), +# just to make sure later additions to Essentials don't clash with your +# new effects. \ No newline at end of file diff --git a/Data/Scripts/011_Battle/002_PBWeather.rb b/Data/Scripts/011_Battle/002_PBWeather.rb new file mode 100644 index 000000000..d5482b784 --- /dev/null +++ b/Data/Scripts/011_Battle/002_PBWeather.rb @@ -0,0 +1,32 @@ +begin + module PBWeather + None = 0 + Sun = 1 + Rain = 2 + Sandstorm = 3 + Hail = 4 + HarshSun = 5 + HeavyRain = 6 + StrongWinds = 7 + ShadowSky = 8 + + def self.animationName(weather) + case weather + when Sun; return "Sun" + when Rain; return "Rain" + when Sandstorm; return "Sandstorm" + when Hail; return "Hail" + when HarshSun; return "HarshSun" + when HeavyRain; return "HeavyRain" + when StrongWinds; return "StrongWinds" + when ShadowSky; return "ShadowSky" + end + return nil + end + end + +rescue Exception + if $!.is_a?(SystemExit) || "#{$!.class}"=="Reset" + raise $! + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/003_Battle/001_PokeBattle_BattleCommon.rb b/Data/Scripts/011_Battle/003_Battle/001_PokeBattle_BattleCommon.rb new file mode 100644 index 000000000..bd9b59227 --- /dev/null +++ b/Data/Scripts/011_Battle/003_Battle/001_PokeBattle_BattleCommon.rb @@ -0,0 +1,230 @@ +module PokeBattle_BattleCommon + #============================================================================= + # Store caught Pokémon + #============================================================================= + def pbStorePokemon(pkmn) + # Nickname the Pokémon (unless it's a Shadow Pokémon) + if !pkmn.shadowPokemon? + if pbDisplayConfirm(_INTL("Would you like to give a nickname to {1}?",pkmn.name)) + nickname = @scene.pbNameEntry(_INTL("{1}'s nickname?",pkmn.speciesName),pkmn) + pkmn.name = nickname if nickname!="" + end + end + # Store the Pokémon + currentBox = @peer.pbCurrentBox + storedBox = @peer.pbStorePokemon(pbPlayer,pkmn) + if storedBox<0 + pbDisplayPaused(_INTL("{1} has been added to your party.",pkmn.name)) + @initialItems[0][pbPlayer.party.length-1] = pkmn.item if @initialItems + return + end + # Messages saying the Pokémon was stored in a PC box + creator = @peer.pbGetStorageCreatorName + curBoxName = @peer.pbBoxName(currentBox) + boxName = @peer.pbBoxName(storedBox) + if storedBox!=currentBox + if creator + pbDisplayPaused(_INTL("Box \"{1}\" on {2}'s PC was full.",curBoxName,creator)) + else + pbDisplayPaused(_INTL("Box \"{1}\" on someone's PC was full.",curBoxName)) + end + pbDisplayPaused(_INTL("{1} was transferred to box \"{2}\".",pkmn.name,boxName)) + else + if creator + pbDisplayPaused(_INTL("{1} was transferred to {2}'s PC.",pkmn.name,creator)) + else + pbDisplayPaused(_INTL("{1} was transferred to someone's PC.",pkmn.name)) + end + pbDisplayPaused(_INTL("It was stored in box \"{1}\".",boxName)) + end + end + + # Register all caught Pokémon in the Pokédex, and store them. + def pbRecordAndStoreCaughtPokemon + @caughtPokemon.each do |pkmn| + pbSeenForm(pkmn) # In case the form changed upon leaving battle + # Record the Pokémon's species as owned in the Pokédex + if !pbPlayer.hasOwned?(pkmn.species) + pbPlayer.setOwned(pkmn.species) + if $Trainer.pokedex + pbDisplayPaused(_INTL("{1}'s data was added to the Pokédex.",pkmn.name)) + @scene.pbShowPokedex(pkmn.species) + end + end + # Record a Shadow Pokémon's species as having been caught + if pkmn.shadowPokemon? + pbPlayer.shadowcaught = [] if !pbPlayer.shadowcaught + pbPlayer.shadowcaught[pkmn.species] = true + end + # Store caught Pokémon + pbStorePokemon(pkmn) + end + @caughtPokemon.clear + end + + #============================================================================= + # Throw a Poké Ball + #============================================================================= + def pbThrowPokeBall(idxBattler,ball,rareness=nil,showPlayer=false) + # Determine which Pokémon you're throwing the Poké Ball at + battler = nil + if opposes?(idxBattler) + battler = @battlers[idxBattler] + else + battler = @battlers[idxBattler].pbDirectOpposing(true) + end + if battler.fainted? + battler.eachAlly do |b| + battler = b + break + end + end + # Messages + itemName = PBItems.getName(ball) + if battler.fainted? + if itemName.starts_with_vowel? + pbDisplay(_INTL("{1} threw an {2}!",pbPlayer.name,itemName)) + else + pbDisplay(_INTL("{1} threw a {2}!",pbPlayer.name,itemName)) + end + pbDisplay(_INTL("But there was no target...")) + return + end + if itemName.starts_with_vowel? + pbDisplayBrief(_INTL("{1} threw an {2}!",pbPlayer.name,itemName)) + else + pbDisplayBrief(_INTL("{1} threw a {2}!",pbPlayer.name,itemName)) + end + # Animation of opposing trainer blocking Poké Balls (unless it's a Snag Ball + # at a Shadow Pokémon) + if trainerBattle? && !(pbIsSnagBall?(ball) && battler.shadowPokemon?) + @scene.pbThrowAndDeflect(ball,1) + pbDisplay(_INTL("The Trainer blocked your Poké Ball! Don't be a thief!")) + return + end + # Calculate the number of shakes (4=capture) + pkmn = battler.pokemon + @criticalCapture = false + numShakes = pbCaptureCalc(pkmn,battler,rareness,ball) + PBDebug.log("[Threw Poké Ball] #{itemName}, #{numShakes} shakes (4=capture)") + # Animation of Ball throw, absorb, shake and capture/burst out + @scene.pbThrow(ball,numShakes,@criticalCapture,battler.index,showPlayer) + # Outcome message + case numShakes + when 0 + pbDisplay(_INTL("Oh no! The Pokémon broke free!")) + BallHandlers.onFailCatch(ball,self,battler) + when 1 + pbDisplay(_INTL("Aww! It appeared to be caught!")) + BallHandlers.onFailCatch(ball,self,battler) + when 2 + pbDisplay(_INTL("Aargh! Almost had it!")) + BallHandlers.onFailCatch(ball,self,battler) + when 3 + pbDisplay(_INTL("Gah! It was so close, too!")) + BallHandlers.onFailCatch(ball,self,battler) + when 4 + pbDisplayBrief(_INTL("Gotcha! {1} was caught!",pkmn.name)) + @scene.pbThrowSuccess # Play capture success jingle + pbRemoveFromParty(battler.index,battler.pokemonIndex) + # Gain Exp + if GAIN_EXP_FOR_CAPTURE + battler.captured = true + pbGainExp + battler.captured = false + end + battler.pbReset + if trainerBattle? + @decision = 1 if pbAllFainted?(battler.index) + else + @decision = 4 if pbAllFainted?(battler.index) # Battle ended by capture + end + # Modify the Pokémon's properties because of the capture + if pbIsSnagBall?(ball) + pkmn.ot = pbPlayer.name + pkmn.trainerID = pbPlayer.id + end + BallHandlers.onCatch(ball,self,pkmn) + pkmn.ballused = pbGetBallType(ball) + pkmn.makeUnmega if pkmn.mega? + pkmn.makeUnprimal + pkmn.pbUpdateShadowMoves if pkmn.shadowPokemon? + pkmn.pbRecordFirstMoves + # Reset form + pkmn.forcedForm = nil if MultipleForms.hasFunction?(pkmn.species,"getForm") + @peer.pbOnLeavingBattle(self,pkmn,true,true) + # Make the Poké Ball and data box disappear + @scene.pbHideCaptureBall(idxBattler) + # Save the Pokémon for storage at the end of battle + @caughtPokemon.push(pkmn) + end + end + + #============================================================================= + # Calculate how many shakes a thrown Poké Ball will make (4 = capture) + #============================================================================= + def pbCaptureCalc(pkmn,battler,rareness,ball) + return 4 if $DEBUG && Input.press?(Input::CTRL) + # Get a rareness if one wasn't provided + if !rareness + rareness = pbGetSpeciesData(pkmn.species,pkmn.form,SpeciesRareness) + end + # Modify rareness depending on the Poké Ball's effect + ultraBeast = (isConst?(battler.species,PBSpecies,:NIHILEGO) || + isConst?(battler.species,PBSpecies,:BUZZWOLE) || + isConst?(battler.species,PBSpecies,:PHEROMOSA) || + isConst?(battler.species,PBSpecies,:XURKITREE) || + isConst?(battler.species,PBSpecies,:CELESTEELA) || + isConst?(battler.species,PBSpecies,:KARTANA) || + isConst?(battler.species,PBSpecies,:GUZZLORD) || + isConst?(battler.species,PBSpecies,:POIPOLE) || + isConst?(battler.species,PBSpecies,:NAGANADEL) || + isConst?(battler.species,PBSpecies,:STAKATAKA) || + isConst?(battler.species,PBSpecies,:BLACEPHALON)) + if !ultraBeast || isConst?(ball,PBItems,:BEASTBALL) + rareness = BallHandlers.modifyCatchRate(ball,rareness,self,battler,ultraBeast) + else + rareness /= 10 + end + # First half of the shakes calculation + a = battler.totalhp + b = battler.hp + x = ((3*a-2*b)*rareness.to_f)/(3*a) + # Calculation modifiers + if battler.status==PBStatuses::SLEEP || battler.status==PBStatuses::FROZEN + x *= 2.5 + elsif battler.status!=PBStatuses::NONE + x *= 1.5 + end + x = x.floor + x = 1 if x<1 + # Definite capture, no need to perform randomness checks + return 4 if x>=255 || BallHandlers.isUnconditional?(ball,self,battler) + # Second half of the shakes calculation + y = ( 65536 / ((255.0/x)**0.1875) ).floor + # Critical capture check + if ENABLE_CRITICAL_CAPTURES + c = 0 + numOwned = $Trainer.pokedexOwned + if numOwned>600; c = x*5/12 + elsif numOwned>450; c = x*4/12 + elsif numOwned>300; c = x*3/12 + elsif numOwned>150; c = x*2/12 + elsif numOwned>30; c = x/12 + end + # Calculate the number of shakes + if c>0 && pbRandom(256)pbSideSize(1)) ? (pbSideSize(0)-1)*2 : pbSideSize(1)*2-1 + end + + #============================================================================= + # Trainers and owner-related methods + #============================================================================= + def pbPlayer; return @player[0]; end + + # Given a battler index, returns the index within @player/@opponent of the + # trainer that controls that battler index. + # NOTE: You shouldn't ever have more trainers on a side than there are battler + # positions on that side. This method doesn't account for if you do. + def pbGetOwnerIndexFromBattlerIndex(idxBattler) + trainer = (opposes?(idxBattler)) ? @opponent : @player + return 0 if !trainer + case trainer.length + when 2 + n = pbSideSize(idxBattler%2) + return [0,0,1][idxBattler/2] if n==3 + return idxBattler/2 # Same as [0,1][idxBattler/2], i.e. 2 battler slots + when 3; return idxBattler/2 + end + return 0 + end + + def pbGetOwnerFromBattlerIndex(idxBattler) + idxTrainer = pbGetOwnerIndexFromBattlerIndex(idxBattler) + return (opposes?(idxBattler)) ? @opponent[idxTrainer] : @player[idxTrainer] + end + + def pbGetOwnerIndexFromPartyIndex(idxBattler,idxParty) + ret = -1 + pbPartyStarts(idxBattler).each_with_index do |start,i| + break if start>idxParty + ret = i + end + return ret + end + + # Only used for the purpose of an error message when one trainer tries to + # switch another trainer's Pokémon. + def pbGetOwnerFromPartyIndex(idxBattler,idxParty) + idxTrainer = pbGetOwnerIndexFromPartyIndex(idxBattler,idxParty) + return (opposes?(idxBattler)) ? @opponent[idxTrainer] : @player[idxTrainer] + end + + def pbGetOwnerName(idxBattler) + idxTrainer = pbGetOwnerIndexFromBattlerIndex(idxBattler) + return @opponent[idxTrainer].fullname if opposes?(idxBattler) # Opponent + return @player[idxTrainer].fullname if idxTrainer>0 # Ally trainer + return @player[idxTrainer].name # Player + end + + def pbGetOwnerItems(idxBattler) + return [] if !@items || !opposes?(idxBattler) + return @items[pbGetOwnerIndexFromBattlerIndex(idxBattler)] + end + + # Returns whether the battler in position idxBattler is owned by the same + # trainer that owns the Pokémon in party slot idxParty. This assumes that + # both the battler position and the party slot are from the same side. + def pbIsOwner?(idxBattler,idxParty) + idxTrainer1 = pbGetOwnerIndexFromBattlerIndex(idxBattler) + idxTrainer2 = pbGetOwnerIndexFromPartyIndex(idxBattler,idxParty) + return idxTrainer1==idxTrainer2 + end + + def pbOwnedByPlayer?(idxBattler) + return false if opposes?(idxBattler) + return pbGetOwnerIndexFromBattlerIndex(idxBattler)==0 + end + + # Returns the number of Pokémon positions controlled by the given trainerIndex + # on the given side of battle. + def pbNumPositions(side,idxTrainer) + ret = 0 + for i in 0...pbSideSize(side) + t = pbGetOwnerIndexFromBattlerIndex(i*2+side) + next if t!=idxTrainer + ret += 1 + end + return ret + end + + #============================================================================= + # Get party information (counts all teams on the same side) + #============================================================================= + def pbParty(idxBattler) + return (opposes?(idxBattler)) ? @party2 : @party1 + end + + def pbOpposingParty(idxBattler) + return (opposes?(idxBattler)) ? @party1 : @party2 + end + + def pbPartyOrder(idxBattler) + return (opposes?(idxBattler)) ? @party2order : @party1order + end + + def pbPartyStarts(idxBattler) + return (opposes?(idxBattler)) ? @party2starts : @party1starts + end + + # Returns the player's team in its display order. Used when showing the party + # screen. + def pbPlayerDisplayParty(idxBattler=0) + partyOrders = pbPartyOrder(idxBattler) + idxStart, idxEnd = pbTeamIndexRangeFromBattlerIndex(idxBattler) + ret = [] + eachInTeamFromBattlerIndex(idxBattler) { |pkmn,i| ret[partyOrders[i]-idxStart] = pkmn } + return ret + end + + def pbAbleCount(idxBattler=0) + party = pbParty(idxBattler) + count = 0 + party.each { |pkmn| count += 1 if pkmn && pkmn.able? } + return count + end + + def pbAbleNonActiveCount(idxBattler=0) + party = pbParty(idxBattler) + inBattleIndices = [] + eachSameSideBattler(idxBattler) { |b| inBattleIndices.push(b.pokemonIndex) } + count = 0 + party.each_with_index do |pkmn,idxParty| + next if !pkmn || !pkmn.able? + next if inBattleIndices.include?(idxParty) + count += 1 + end + return count + end + + def pbAllFainted?(idxBattler=0) + return pbAbleCount(idxBattler)==0 + end + + # For the given side of the field (0=player's, 1=opponent's), returns an array + # containing the number of able Pokémon in each team. + def pbAbleTeamCounts(side) + party = pbParty(side) + partyStarts = pbPartyStarts(side) + ret = [] + idxTeam = -1 + nextStart = 0 + party.each_with_index do |pkmn,i| + if i>=nextStart + idxTeam += 1 + nextStart = (idxTeam=idxPartyStart && i=idxPartyStart && i=idxPartyEnd # Check the team only + next if !pkmn || !pkmn.able? # Can't copy a non-fainted Pokémon or egg + ret = i if partyOrders[i]>partyOrders[ret] + end + return ret + end + + # Used to calculate money gained/lost after winning/losing a battle. + def pbMaxLevelInTeam(side,idxTrainer) + ret = 1 + eachInTeam(side,idxTrainer) do |pkmn,i| + ret = pkmn.level if pkmn.level>ret + end + return ret + end + + #============================================================================= + # Iterate through battlers + #============================================================================= + def eachBattler + @battlers.each { |b| yield b if b && !b.fainted? } + end + + def eachSameSideBattler(idxBattler=0) + idxBattler = idxBattler.index if idxBattler.respond_to?("index") + @battlers.each { |b| yield b if b && !b.fainted? && !b.opposes?(idxBattler) } + end + + def eachOtherSideBattler(idxBattler=0) + idxBattler = idxBattler.index if idxBattler.respond_to?("index") + @battlers.each { |b| yield b if b && !b.fainted? && b.opposes?(idxBattler) } + end + + def pbSideBattlerCount(idxBattler=0) + ret = 0 + eachSameSideBattler(idxBattler) { |b| ret += 1 } + return ret + end + + def pbOpposingBattlerCount(idxBattler=0) + ret = 0 + eachOtherSideBattler(idxBattler) { |b| ret += 1 } + return ret + end + + # This method only counts the player's Pokémon, not a partner trainer's. + def pbPlayerBattlerCount + ret = 0 + eachSameSideBattler { |b| ret += 1 if b.pbOwnedByPlayer? } + return ret + end + + def pbCheckGlobalAbility(abil) + eachBattler { |b| return b if b.hasActiveAbility?(abil) } + return nil + end + + def pbCheckOpposingAbility(abil,idxBattler=0,nearOnly=false) + eachOtherSideBattler(idxBattler) do |b| + next if nearOnly && !b.near?(idxBattler) + return b if b.hasActiveAbility?(abil) + end + return nil + end + + # Given a battler index, and using battle side sizes, returns an array of + # battler indices from the opposing side that are in order of most "opposite". + # Used when choosing a target and pressing up/down to move the cursor to the + # opposite side, and also when deciding which target to select first for some + # moves. + def pbGetOpposingIndicesInOrder(idxBattler) + case pbSideSize(0) + when 1 + case pbSideSize(1) + when 1 # 1v1 single + return [0] if opposes?(idxBattler) + return [1] + when 2 # 1v2 + return [0] if opposes?(idxBattler) + return [3,1] + when 3 # 1v3 + return [0] if opposes?(idxBattler) + return [3,5,1] + end + when 2 + case pbSideSize(1) + when 1 # 2v1 + return [0,2] if opposes?(idxBattler) + return [1] + when 2 # 2v2 double + return [[3,1],[2,0],[1,3],[0,2]][idxBattler] + when 3 # 2v3 + return [[5,3,1],[2,0],[3,1,5]][idxBattler] if idxBattler<3 + return [0,2] + end + when 3 + case pbSideSize(1) + when 1 # 3v1 + return [2,0,4] if opposes?(idxBattler) + return [1] + when 2 # 3v2 + return [[3,1],[2,4,0],[3,1],[2,0,4],[1,3]][idxBattler] + when 3 # 3v3 triple + return [[5,3,1],[4,2,0],[3,5,1],[2,0,4],[1,3,5],[0,2,4]][idxBattler] + end + end + return [idxBattler] + end + + #============================================================================= + # Comparing the positions of two battlers + #============================================================================= + def opposes?(idxBattler1,idxBattler2=0) + idxBattler1 = idxBattler1.index if idxBattler1.respond_to?("index") + idxBattler2 = idxBattler2.index if idxBattler2.respond_to?("index") + return (idxBattler1&1)!=(idxBattler2&1) + end + + def nearBattlers?(idxBattler1,idxBattler2) + return false if idxBattler1==idxBattler2 + return true if pbSideSize(0)<=2 && pbSideSize(1)<=2 + # Get all pairs of battler positions that are not close to each other + pairsArray = [[0,4],[1,5]] # Covers 3v1 and 1v3 + case pbSideSize(0) + when 3 + case pbSideSize(1) + when 3 # 3v3 (triple) + pairsArray.push([0,1]) + pairsArray.push([4,5]) + when 2 # 3v2 + pairsArray.push([0,1]) + pairsArray.push([3,4]) + end + when 2 # 2v3 + pairsArray.push([0,1]) + pairsArray.push([2,5]) + end + # See if any pair matches the two battlers being assessed + pairsArray.each do |pair| + return false if pair.include?(idxBattler1) && pair.include?(idxBattler2) + end + return true + end + + #============================================================================= + # Altering a party or rearranging battlers + #============================================================================= + def pbRemoveFromParty(idxBattler,idxParty) + party = pbParty(idxBattler) + # Erase the Pokémon from the party + party[idxParty] = nil + # Rearrange the display order of the team to place the erased Pokémon last + # in it (to avoid gaps) + partyOrders = pbPartyOrder(idxBattler) + partyStarts = pbPartyStarts(idxBattler) + idxTrainer = pbGetOwnerIndexFromPartyIndex(idxBattler,idxParty) + idxPartyStart = partyStarts[idxTrainer] + idxPartyEnd = (idxTrainer=idxPartyEnd # Only check the team + next if partyOrders[i]0 && user && user.itemActive? + duration = BattleHandlers.triggerWeatherExtenderItem(user.item, + @field.weather,duration,user,self) + end + @field.weatherDuration = duration + pbCommonAnimation(PBWeather.animationName(@field.weather)) if showAnim + pbHideAbilitySplash(user) if user + case @field.weather + when PBWeather::Sun; pbDisplay(_INTL("The sunlight turned harsh!")) + when PBWeather::Rain; pbDisplay(_INTL("It started to rain!")) + when PBWeather::Sandstorm; pbDisplay(_INTL("A sandstorm brewed!")) + when PBWeather::Hail; pbDisplay(_INTL("It started to hail!")) + when PBWeather::HarshSun; pbDisplay(_INTL("The sunlight turned extremely harsh!")) + when PBWeather::HeavyRain; pbDisplay(_INTL("A heavy rain began to fall!")) + when PBWeather::StrongWinds; pbDisplay(_INTL("Mysterious strong winds are protecting Flying-type Pokémon!")) + when PBWeather::ShadowSky; pbDisplay(_INTL("A shadow sky appeared!")) + end + # Check for end of primordial weather, and weather-triggered form changes + eachBattler { |b| b.pbCheckFormOnWeatherChange } + pbEndPrimordialWeather + end + + def pbEndPrimordialWeather + oldWeather = @field.weather + # End Primordial Sea, Desolate Land, Delta Stream + case @field.weather + when PBWeather::HarshSun + if !pbCheckGlobalAbility(:DESOLATELAND) + @field.weather = PBWeather::None + pbDisplay("The harsh sunlight faded!") + end + when PBWeather::HeavyRain + if !pbCheckGlobalAbility(:PRIMORDIALSEA) + @field.weather = PBWeather::None + pbDisplay("The heavy rain has lifted!") + end + when PBWeather::StrongWinds + if !pbCheckGlobalAbility(:DELTASTREAM) + @field.weather = PBWeather::None + pbDisplay("The mysterious air current has dissipated!") + end + end + # Check for form changes caused by the weather changing + if @field.weather!=oldWeather + eachBattler { |b| b.pbCheckFormOnWeatherChange } + end + end + + def defaultTerrain=(value) + @field.defaultTerrain = value + @field.terrain = value + @field.terrainDuration = -1 + end + + def pbStartTerrain(user,newTerrain,fixedDuration=true) + return if @field.terrain==newTerrain + @field.terrain = newTerrain + duration = (fixedDuration) ? 5 : -1 + if duration>0 && user && user.itemActive? + duration = BattleHandlers.triggerTerrainExtenderItem(user.item, + newTerrain,duration,user,self) + end + @field.terrainDuration = duration + pbCommonAnimation(PBBattleTerrains.animationName(@field.terrain)) + pbHideAbilitySplash(user) if user + case @field.terrain + when PBBattleTerrains::Electric + pbDisplay(_INTL("An electric current runs across the battlefield!")) + when PBBattleTerrains::Grassy + pbDisplay(_INTL("Grass grew to cover the battlefield!")) + when PBBattleTerrains::Misty + pbDisplay(_INTL("Mist swirled about the battlefield!")) + when PBBattleTerrains::Psychic + pbDisplay(_INTL("The battlefield got weird!")) + end + # Check for terrain seeds that boost stats in a terrain + eachBattler { |b| b.pbItemTerrainStatBoostCheck } + end + + #============================================================================= + # Messages and animations + #============================================================================= + def pbDisplay(msg,&block) + @scene.pbDisplayMessage(msg,&block) + end + + def pbDisplayBrief(msg) + @scene.pbDisplayMessage(msg,true) + end + + def pbDisplayPaused(msg,&block) + @scene.pbDisplayPausedMessage(msg,&block) + end + + def pbDisplayConfirm(msg) + return @scene.pbDisplayConfirmMessage(msg) + end + + def pbShowCommands(msg,commands,canCancel=true) + @scene.pbShowCommands(msg,commands,canCancel) + end + + def pbAnimation(move,user,targets,hitNum=0) + @scene.pbAnimation(move,user,targets,hitNum) if @showAnims + end + + def pbCommonAnimation(name,user=nil,targets=nil,hitNum=0) + @scene.pbCommonAnimation(name,user,targets,hitNum) if @showAnims + end + + def pbShowAbilitySplash(battler,delay=false,logTrigger=true) + PBDebug.log("[Ability triggered] #{battler.pbThis}'s #{battler.abilityName}") if logTrigger + return if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @scene.pbShowAbilitySplash(battler) + if delay + Graphics.frame_rate.times { @scene.pbUpdate } # 1 second + end + end + + def pbHideAbilitySplash(battler) + return if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @scene.pbHideAbilitySplash(battler) + end + + def pbReplaceAbilitySplash(battler) + return if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @scene.pbReplaceAbilitySplash(battler) + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/003_Battle/003_Battle_StartAndEnd.rb b/Data/Scripts/011_Battle/003_Battle/003_Battle_StartAndEnd.rb new file mode 100644 index 000000000..2149baf8f --- /dev/null +++ b/Data/Scripts/011_Battle/003_Battle/003_Battle_StartAndEnd.rb @@ -0,0 +1,537 @@ +class PokeBattle_Battle + class BattleAbortedException < Exception; end + + def pbAbort + raise BattleAbortedException.new("Battle aborted") + end + + #============================================================================= + # Makes sure all Pokémon exist that need to. Alter the type of battle if + # necessary. Will never try to create battler positions, only delete them + # (except for wild Pokémon whose number of positions are fixed). Reduces the + # size of each side by 1 and tries again. If the side sizes are uneven, only + # the larger side's size will be reduced by 1 each time, until both sides are + # an equal size (then both sides will be reduced equally). + #============================================================================= + def pbEnsureParticipants + # Prevent battles larger than 2v2 if both sides have multiple trainers + # NOTE: This is necessary to ensure that battlers can never become unable to + # hit each other due to being too far away. In such situations, + # battlers will move to the centre position at the end of a round, but + # because they cannot move into a position owned by a different + # trainer, it's possible that battlers will be unable to move close + # enough to hit each other if there are multiple trainers on each + # side. + if trainerBattle? && (@sideSizes[0]>2 || @sideSizes[1]>2) && + @player.length>1 && @opponent.length>1 + raise _INTL("Can't have battles larger than 2v2 where both sides have multiple trainers") + end + # Find out how many Pokémon each trainer has + side1counts = pbAbleTeamCounts(0) + side2counts = pbAbleTeamCounts(1) + # Change the size of the battle depending on how many wild Pokémon there are + if wildBattle? && side2counts[0]!=@sideSizes[1] + if @sideSizes[0]==@sideSizes[1] + # Even number of battlers per side, change both equally + @sideSizes = [side2counts[0],side2counts[0]] + else + # Uneven number of battlers per side, just change wild side's size + @sideSizes[1] = side2counts[0] + end + end + # Check if battle is possible, including changing the number of battlers per + # side if necessary + loop do + needsChanging = false + for side in 0...2 # Each side in turn + next if side==1 && wildBattle? # Wild side's size already checked above + sideCounts = (side==0) ? side1counts : side2counts + requireds = [] + # Find out how many Pokémon each trainer on side needs to have + for i in 0...@sideSizes[side] + idxTrainer = pbGetOwnerIndexFromBattlerIndex(i*2+side) + requireds[idxTrainer] = 0 if requireds[idxTrainer].nil? + requireds[idxTrainer] += 1 + end + # Compare the have values with the need values + if requireds.length>sideCounts.length + raise _INTL("Error: def pbGetOwnerIndexFromBattlerIndex gives invalid owner index ({1} for battle type {2}v{3}, trainers {4}v{5})", + requireds.length-1,@sideSizes[0],@sideSizes[1],side1counts.length,side2counts.length) + end + sideCounts.each_with_index do |count,i| + if !requireds[i] || requireds[i]==0 + raise _INTL("Player-side trainer {1} has no battler position for their Pokémon to go (trying {2}v{3} battle)", + i+1,@sideSizes[0],@sideSizes[1]) if side==0 + raise _INTL("Opposing trainer {1} has no battler position for their Pokémon to go (trying {2}v{3} battle)", + i+1,@sideSizes[0],@sideSizes[1]) if side==1 + end + next if requireds[i]<=sideCounts[i] # Trainer has enough Pokémon to fill their positions + if requireds[i]==1 + raise _INTL("Player-side trainer {1} has no able Pokémon",i+1) if side==0 + raise _INTL("Opposing trainer {1} has no able Pokémon",i+1) if side==1 + end + # Not enough Pokémon, try lowering the number of battler positions + needsChanging = true + break + end + break if needsChanging + end + break if !needsChanging + # Reduce one or both side's sizes by 1 and try again + if wildBattle? + PBDebug.log("#{@sideSizes[0]}v#{@sideSizes[1]} battle isn't possible " + + "(#{side1counts} player-side teams versus #{side2counts[0]} wild Pokémon)") + newSize = @sideSizes[0]-1 + else + PBDebug.log("#{@sideSizes[0]}v#{@sideSizes[1]} battle isn't possible " + + "(#{side1counts} player-side teams versus #{side2counts} opposing teams)") + newSize = @sideSizes.max-1 + end + if newSize==0 + raise _INTL("Couldn't lower either side's size any further, battle isn't possible") + end + for side in 0...2 + next if side==1 && wildBattle? # Wild Pokémon's side size is fixed + next if @sideSizes[side]==1 || newSize>@sideSizes[side] + @sideSizes[side] = newSize + end + PBDebug.log("Trying #{@sideSizes[0]}v#{@sideSizes[1]} battle instead") + end + end + + #============================================================================= + # Set up all battlers + #============================================================================= + def pbCreateBattler(idxBattler,pkmn,idxParty) + if !@battlers[idxBattler].nil? + raise _INTL("Battler index {1} already exists",idxBattler) + end + @battlers[idxBattler] = PokeBattle_Battler.new(self,idxBattler) + @positions[idxBattler] = PokeBattle_ActivePosition.new + pbClearChoice(idxBattler) + @successStates[idxBattler] = PokeBattle_SuccessState.new + @battlers[idxBattler].pbInitialize(pkmn,idxParty) + end + + def pbSetUpSides + ret = [[],[]] + for side in 0...2 + # Set up wild Pokémon + if side==1 && wildBattle? + pbParty(1).each_with_index do |pkmn,idxPkmn| + pbCreateBattler(2*idxPkmn+side,pkmn,idxPkmn) + # Changes the Pokémon's form upon entering battle (if it should) + @peer.pbOnEnteringBattle(self,pkmn,true) + pbSetSeen(@battlers[2*idxPkmn+side]) + @usedInBattle[side][idxPkmn] = true + end + next + end + # Set up player's Pokémon and trainers' Pokémon + trainer = (side==0) ? @player : @opponent + requireds = [] + # Find out how many Pokémon each trainer on side needs to have + for i in 0...@sideSizes[side] + idxTrainer = pbGetOwnerIndexFromBattlerIndex(i*2+side) + requireds[idxTrainer] = 0 if requireds[idxTrainer].nil? + requireds[idxTrainer] += 1 + end + # For each trainer in turn, find the needed number of Pokémon for them to + # send out, and initialize them + battlerNumber = 0 + trainer.each_with_index do |t,idxTrainer| + ret[side][idxTrainer] = [] + eachInTeam(side,idxTrainer) do |pkmn,idxPkmn| + next if !pkmn.able? + idxBattler = 2*battlerNumber+side + pbCreateBattler(idxBattler,pkmn,idxPkmn) + ret[side][idxTrainer].push(idxBattler) + battlerNumber += 1 + break if ret[side][idxTrainer].length>=requireds[idxTrainer] + end + end + end + return ret + end + + #============================================================================= + # Send out all battlers at the start of battle + #============================================================================= + def pbStartBattleSendOut(sendOuts) + # "Want to battle" messages + if wildBattle? + foeParty = pbParty(1) + case foeParty.length + when 1 + pbDisplayPaused(_INTL("Oh! A wild {1} appeared!",foeParty[0].name)) + when 2 + pbDisplayPaused(_INTL("Oh! A wild {1} and {2} appeared!",foeParty[0].name, + foeParty[1].name)) + when 3 + pbDisplayPaused(_INTL("Oh! A wild {1}, {2} and {3} appeared!",foeParty[0].name, + foeParty[1].name,foeParty[2].name)) + end + else # Trainer battle + case @opponent.length + when 1 + pbDisplayPaused(_INTL("You are challenged by {1}!",@opponent[0].fullname)) + when 2 + pbDisplayPaused(_INTL("You are challenged by {1} and {2}!",@opponent[0].fullname, + @opponent[1].fullname)) + when 3 + pbDisplayPaused(_INTL("You are challenged by {1}, {2} and {3}!", + @opponent[0].fullname,@opponent[1].fullname,@opponent[2].fullname)) + end + end + # Send out Pokémon (opposing trainers first) + for side in [1,0] + next if side==1 && wildBattle? + msg = "" + toSendOut = [] + trainers = (side==0) ? @player : @opponent + # Opposing trainers and partner trainers's messages about sending out Pokémon + trainers.each_with_index do |t,i| + next if side==0 && i==0 # The player's message is shown last + msg += "\r\n" if msg.length>0 + sent = sendOuts[side][i] + case sent.length + when 1 + msg += _INTL("{1} sent out {2}!",t.fullname,@battlers[sent[0]].name) + when 2 + msg += _INTL("{1} sent out {2} and {3}!",t.fullname, + @battlers[sent[0]].name,@battlers[sent[1]].name) + when 3 + msg += _INTL("{1} sent out {2}, {3} and {4}!",t.fullname, + @battlers[sent[0]].name,@battlers[sent[1]].name,@battlers[sent[2]].name) + end + toSendOut.concat(sent) + end + # The player's message about sending out Pokémon + if side==0 + msg += "\r\n" if msg.length>0 + sent = sendOuts[side][0] + case sent.length + when 1 + msg += _INTL("Go! {1}!",@battlers[sent[0]].name) + when 2 + msg += _INTL("Go! {1} and {2}!",@battlers[sent[0]].name,@battlers[sent[1]].name) + when 3 + msg += _INTL("Go! {1}, {2} and {3}!",@battlers[sent[0]].name, + @battlers[sent[1]].name,@battlers[sent[2]].name) + end + toSendOut.concat(sent) + end + pbDisplayBrief(msg) if msg.length>0 + # The actual sending out of Pokémon + animSendOuts = [] + toSendOut.each do |idxBattler| + animSendOuts.push([idxBattler,@battlers[idxBattler].pokemon]) + end + pbSendOut(animSendOuts,true) + end + end + + #============================================================================= + # Start a battle + #============================================================================= + def pbStartBattle + PBDebug.log("") + PBDebug.log("******************************************") + logMsg = "[Started battle] " + if @sideSizes[0]==1 && @sideSizes[1]==1 + logMsg += "Single " + elsif @sideSizes[0]==2 && @sideSizes[1]==2 + logMsg += "Double " + elsif @sideSizes[0]==3 && @sideSizes[1]==3 + logMsg += "Triple " + else + logMsg += "#{@sideSizes[0]}v#{@sideSizes[1]} " + end + logMsg += "wild " if wildBattle? + logMsg += "trainer " if trainerBattle? + logMsg += "battle (#{@player.length} trainer(s) vs. " + logMsg += "#{pbParty(1).length} wild Pokémon)" if wildBattle? + logMsg += "#{@opponent.length} trainer(s))" if trainerBattle? + PBDebug.log(logMsg) + pbEnsureParticipants + begin + pbStartBattleCore + rescue BattleAbortedException + @decision = 0 + @scene.pbEndBattle(@decision) + end + return @decision + end + + def pbStartBattleCore + # Set up the battlers on each side + sendOuts = pbSetUpSides + # Create all the sprites and play the battle intro animation + @scene.pbStartBattle(self) + # Show trainers on both sides sending out Pokémon + pbStartBattleSendOut(sendOuts) + # Weather announcement + pbCommonAnimation(PBWeather.animationName(@field.weather)) + case @field.weather + when PBWeather::Sun; pbDisplay(_INTL("The sunlight is strong.")) + when PBWeather::Rain; pbDisplay(_INTL("It is raining.")) + when PBWeather::Sandstorm; pbDisplay(_INTL("A sandstorm is raging.")) + when PBWeather::Hail; pbDisplay(_INTL("Hail is falling.")) + when PBWeather::HarshSun; pbDisplay(_INTL("The sunlight is extremely harsh.")) + when PBWeather::HeavyRain; pbDisplay(_INTL("It is raining heavily.")) + when PBWeather::StrongWinds; pbDisplay(_INTL("The wind is strong.")) + when PBWeather::ShadowSky; pbDisplay(_INTL("The sky is shadowy.")) + end + # Terrain announcement + pbCommonAnimation(PBBattleTerrains.animationName(@field.terrain)) + case @field.terrain + when PBBattleTerrains::Electric + pbDisplay(_INTL("An electric current runs across the battlefield!")) + when PBBattleTerrains::Grassy + pbDisplay(_INTL("Grass is covering the battlefield!")) + when PBBattleTerrains::Misty + pbDisplay(_INTL("Mist swirls about the battlefield!")) + when PBBattleTerrains::Psychic + pbDisplay(_INTL("The battlefield is weird!")) + end + # Abilities upon entering battle + pbOnActiveAll + # Main battle loop + pbBattleLoop + end + + #============================================================================= + # Main battle loop + #============================================================================= + def pbBattleLoop + @turnCount = 0 + loop do # Now begin the battle loop + PBDebug.log("") + PBDebug.log("***Round #{@turnCount+1}***") + if @debug && @turnCount>=100 + @decision = pbDecisionOnTime + PBDebug.log("") + PBDebug.log("***Undecided after 100 rounds, aborting***") + pbAbort + break + end + PBDebug.log("") + # Command phase + PBDebug.logonerr { pbCommandPhase } + break if @decision>0 + # Attack phase + PBDebug.logonerr { pbAttackPhase } + break if @decision>0 + # End of round phase + PBDebug.logonerr { pbEndOfRoundPhase } + break if @decision>0 + @turnCount += 1 + end + pbEndOfBattle + end + + #============================================================================= + # End of battle + #============================================================================= + def pbGainMoney + return if !@internalBattle || !@moneyGain + # Money rewarded from opposing trainers + if trainerBattle? + tMoney = 0 + @opponent.each_with_index do |t,i| + tMoney += pbMaxLevelInTeam(1,i)*t.moneyEarned + end + tMoney *= 2 if @field.effects[PBEffects::AmuletCoin] + tMoney *= 2 if @field.effects[PBEffects::HappyHour] + oldMoney = pbPlayer.money + pbPlayer.money += tMoney + moneyGained = pbPlayer.money-oldMoney + if moneyGained>0 + pbDisplayPaused(_INTL("You got ${1} for winning!",moneyGained.to_s_formatted)) + end + end + # Pick up money scattered by Pay Day + if @field.effects[PBEffects::PayDay]>0 + @field.effects[PBEffects::PayDay] *= 2 if @field.effects[PBEffects::AmuletCoin] + @field.effects[PBEffects::PayDay] *= 2 if @field.effects[PBEffects::HappyHour] + oldMoney = pbPlayer.money + pbPlayer.money += @field.effects[PBEffects::PayDay] + moneyGained = pbPlayer.money-oldMoney + if moneyGained>0 + pbDisplayPaused(_INTL("You picked up ${1}!",moneyGained.to_s_formatted)) + end + end + end + + def pbLoseMoney + return if !@internalBattle || !@moneyGain + return if $game_switches[NO_MONEY_LOSS] + maxLevel = pbMaxLevelInTeam(0,0) # Player's Pokémon only, not partner's + multiplier = [8,16,24,36,48,64,80,100,120] + idxMultiplier = [pbPlayer.numbadges,multiplier.length-1].min + tMoney = maxLevel*multiplier[idxMultiplier] + tMoney = pbPlayer.money if tMoney>pbPlayer.money + oldMoney = pbPlayer.money + pbPlayer.money -= tMoney + moneyLost = oldMoney-pbPlayer.money + if moneyLost>0 + if trainerBattle? + pbDisplayPaused(_INTL("You gave ${1} to the winner...",moneyLost.to_s_formatted)) + else + pbDisplayPaused(_INTL("You panicked and dropped ${1}...",moneyLost.to_s_formatted)) + end + end + end + + def pbEndOfBattle + oldDecision = @decision + @decision = 4 if @decision==1 && wildBattle? && @caughtPokemon.length>0 + case oldDecision + ##### WIN ##### + when 1 + PBDebug.log("") + PBDebug.log("***Player won***") + if trainerBattle? + @scene.pbTrainerBattleSuccess + case @opponent.length + when 1 + pbDisplayPaused(_INTL("You defeated {1}!",@opponent[0].fullname)) + when 2 + pbDisplayPaused(_INTL("You defeated {1} and {2}!",@opponent[0].fullname, + @opponent[1].fullname)) + when 3 + pbDisplayPaused(_INTL("You defeated {1}, {2} and {3}!",@opponent[0].fullname, + @opponent[1].fullname,@opponent[2].fullname)) + end + @opponent.each_with_index do |t,i| + @scene.pbShowOpponent(i) + msg = (@endSpeeches[i] && @endSpeeches[i]!="") ? @endSpeeches[i] : "..." + pbDisplayPaused(msg.gsub(/\\[Pp][Nn]/,pbPlayer.name)) + end + end + # Gain money from winning a trainer battle, and from Pay Day + pbGainMoney if @decision!=4 + # Hide remaining trainer + @scene.pbShowOpponent(@opponent.length) if trainerBattle? && @caughtPokemon.length>0 + ##### LOSE, DRAW ##### + when 2, 5 + PBDebug.log("") + PBDebug.log("***Player lost***") if @decision==2 + PBDebug.log("***Player drew with opponent***") if @decision==5 + if @internalBattle + pbDisplayPaused(_INTL("You have no more Pokémon that can fight!")) + if trainerBattle? + case @opponent.length + when 1 + pbDisplayPaused(_INTL("You lost against {1}!",@opponent[0].fullname)) + when 2 + pbDisplayPaused(_INTL("You lost against {1} and {2}!", + @opponent[0].fullname,@opponent[1].fullname)) + when 3 + pbDisplayPaused(_INTL("You lost against {1}, {2} and {3}!", + @opponent[0].fullname,@opponent[1].fullname,@opponent[2].fullname)) + end + end + # Lose money from losing a battle + pbLoseMoney + pbDisplayPaused(_INTL("You blacked out!")) if !@canLose + elsif @decision==2 + if @opponent + @opponent.each_with_index do |t,i| + @scene.pbShowOpponent(i) + msg = (@endSpeechesWin[i] && @endSpeechesWin[i]!="") ? @endSpeechesWin[i] : "..." + pbDisplayPaused(msg.gsub(/\\[Pp][Nn]/,pbPlayer.name)) + end + end + end + ##### CAUGHT WILD POKÉMON ##### + when 4 + @scene.pbWildBattleSuccess if !GAIN_EXP_FOR_CAPTURE + end + # Register captured Pokémon in the Pokédex, and store them + pbRecordAndStoreCaughtPokemon + # Collect Pay Day money in a wild battle that ended in a capture + pbGainMoney if @decision==4 + # Pass on Pokérus within the party + if @internalBattle + infected = [] + $Trainer.party.each_with_index do |pkmn,i| + infected.push(i) if pkmn.pokerusStage==1 + end + infected.each do |idxParty| + strain = $Trainer.party[idxParty].pokerusStrain + if idxParty>0 && $Trainer.party[idxParty-1].pokerusStage==0 + $Trainer.party[idxParty-1].givePokerus(strain) if rand(3)==0 # 33% + end + if idxParty<$Trainer.party.length-1 && $Trainer.party[idxParty+1].pokerusStage==0 + $Trainer.party[idxParty+1].givePokerus(strain) if rand(3)==0 # 33% + end + end + end + # Clean up battle stuff + @scene.pbEndBattle(@decision) + @battlers.each do |b| + next if !b + pbCancelChoice(b.index) # Restore unused items to Bag + BattleHandlers.triggerAbilityOnSwitchOut(b.ability,b,true) if b.abilityActive? + end + pbParty(0).each_with_index do |pkmn,i| + next if !pkmn + @peer.pbOnLeavingBattle(self,pkmn,@usedInBattle[0][i],true) # Reset form + pkmn.setItem(@initialItems[0][i] || 0) + end + return @decision + end + + #============================================================================= + # Judging + #============================================================================= + def pbJudgeCheckpoint(user,move=nil); end + + def pbDecisionOnTime + counts = [0,0] + hpTotals = [0,0] + for side in 0...2 + pbParty(side).each do |pkmn| + next if !pkmn || !pkmn.able? + counts[side] += 1 + hpTotals[side] += pkmn.hp + end + end + return 1 if counts[0]>counts[1] # Win (player has more able Pokémon) + return 2 if counts[0]hpTotals[1] # Win (player has more HP in total) + return 2 if hpTotals[0]1 + end + return 1 if counts[0]>counts[1] # Win (player has more able Pokémon) + return 2 if counts[0]hpTotals[1] # Win (player has a bigger average HP %) + return 2 if hpTotals[0]0 || expShare.length>0 || expAll + # Gain EVs and Exp for participants + eachInTeam(0,0) do |pkmn,i| + next if !pkmn.able? + next unless b.participants.include?(i) || expShare.include?(i) + pbGainEVsOne(i,b) + pbGainExpOne(i,b,numPartic,expShare,expAll) + end + # Gain EVs and Exp for all other Pokémon because of Exp All + if expAll + showMessage = true + eachInTeam(0,0) do |pkmn,i| + next if !pkmn.able? + next if b.participants.include?(i) || expShare.include?(i) + pbDisplayPaused(_INTL("Your party Pokémon in waiting also got Exp. Points!")) if showMessage + showMessage = false + pbGainEVsOne(i,b) + pbGainExpOne(i,b,numPartic,expShare,expAll,false) + end + end + end + # Clear the participants array + b.participants = [] + end + end + + def pbGainEVsOne(idxParty,defeatedBattler) + pkmn = pbParty(0)[idxParty] # The Pokémon gaining EVs from defeatedBattler + evYield = defeatedBattler.pokemon.evYield + # Num of effort points pkmn already has + evTotal = 0 + PBStats.eachStat { |s| evTotal += pkmn.ev[s] } + # Modify EV yield based on pkmn's held item + if !BattleHandlers.triggerEVGainModifierItem(pkmn.item,pkmn,evYield) + BattleHandlers.triggerEVGainModifierItem(@initialItems[0][idxParty],pkmn,evYield) + end + # Double EV gain because of Pokérus + if pkmn.pokerusStage>=1 # Infected or cured + evYield.collect! { |a| a*2 } + end + # Gain EVs for each stat in turn + PBStats.eachStat do |s| + evGain = evYield[s] + # Can't exceed overall limit + if evTotal+evGain>PokeBattle_Pokemon::EV_LIMIT + evGain = PokeBattle_Pokemon::EV_LIMIT-evTotal + end + # Can't exceed individual stat limit + if pkmn.ev[s]+evGain>PokeBattle_Pokemon::EV_STAT_LIMIT + evGain = PokeBattle_Pokemon::EV_STAT_LIMIT-pkmn.ev[s] + end + # Add EV gain + pkmn.ev[s] += evGain + evTotal += evGain + end + end + + def pbGainExpOne(idxParty,defeatedBattler,numPartic,expShare,expAll,showMessages=true) + pkmn = pbParty(0)[idxParty] # The Pokémon gaining EVs from defeatedBattler + growthRate = pkmn.growthrate + # Don't bother calculating if gainer is already at max Exp + if pkmn.exp>=PBExperience.pbGetMaxExperience(growthRate) + pkmn.calcStats # To ensure new EVs still have an effect + return + end + isPartic = defeatedBattler.participants.include?(idxParty) + hasExpShare = expShare.include?(idxParty) + level = defeatedBattler.level + # Main Exp calculation + exp = 0 + a = level*defeatedBattler.pokemon.baseExp + if expShare.length>0 && (isPartic || hasExpShare) + if numPartic==0 # No participants, all Exp goes to Exp Share holders + exp = a/(SPLIT_EXP_BETWEEN_GAINERS ? expShare.length : 1) + elsif SPLIT_EXP_BETWEEN_GAINERS # Gain from participating and/or Exp Share + exp = a/(2*numPartic) if isPartic + exp += a/(2*expShare.length) if hasExpShare + else # Gain from participating and/or Exp Share (Exp not split) + exp = (isPartic) ? a : a/2 + end + elsif isPartic # Participated in battle, no Exp Shares held by anyone + exp = a/(SPLIT_EXP_BETWEEN_GAINERS ? numPartic : 1) + elsif expAll # Didn't participate in battle, gaining Exp due to Exp All + # NOTE: Exp All works like the Exp Share from Gen 6+, not like the Exp All + # from Gen 1, i.e. Exp isn't split between all Pokémon gaining it. + exp = a/2 + end + return if exp<=0 + # Pokémon gain more Exp from trainer battles + exp = (exp*1.5).floor if trainerBattle? + # Scale the gained Exp based on the gainer's level (or not) + if SCALED_EXP_FORMULA + exp /= 5 + levelAdjust = (2*level+10.0)/(pkmn.level+level+10.0) + levelAdjust = levelAdjust**5 + levelAdjust = Math.sqrt(levelAdjust) + exp *= levelAdjust + exp = exp.floor + exp += 1 if isPartic || hasExpShare + else + exp /= 7 + end + # Foreign Pokémon gain more Exp + isOutsider = (pkmn.trainerID!=pbPlayer.id || + (pkmn.language!=0 && pkmn.language!=pbPlayer.language)) + if isOutsider + if pkmn.language!=0 && pkmn.language!=pbPlayer.language + exp = (exp*1.7).floor + else + exp = (exp*1.5).floor + end + end + # Modify Exp gain based on pkmn's held item + i = BattleHandlers.triggerExpGainModifierItem(pkmn.item,pkmn,exp) + if i<0 + i = BattleHandlers.triggerExpGainModifierItem(@initialItems[0][idxParty],pkmn,exp) + end + exp = i if i>=0 + # Make sure Exp doesn't exceed the maximum + expFinal = PBExperience.pbAddExperience(pkmn.exp,exp,growthRate) + expGained = expFinal-pkmn.exp + return if expGained<=0 + # "Exp gained" message + if showMessages + if isOutsider + pbDisplayPaused(_INTL("{1} got a boosted {2} Exp. Points!",pkmn.name,expGained)) + else + pbDisplayPaused(_INTL("{1} got {2} Exp. Points!",pkmn.name,expGained)) + end + end + curLevel = pkmn.level + newLevel = PBExperience.pbGetLevelFromExperience(expFinal,growthRate) + if newLevelnewLevel + # Gained all the Exp now, end the animation + pkmn.calcStats + battler.pbUpdate(false) if battler + @scene.pbRefreshOne(battler.index) if battler + break + end + # Levelled up + pbCommonAnimation("LevelUp",battler) if battler + oldTotalHP = pkmn.totalhp + oldAttack = pkmn.attack + oldDefense = pkmn.defense + oldSpAtk = pkmn.spatk + oldSpDef = pkmn.spdef + oldSpeed = pkmn.speed + if battler && battler.pokemon + battler.pokemon.changeHappiness("levelup") + end + pkmn.calcStats + battler.pbUpdate(false) if battler + @scene.pbRefreshOne(battler.index) if battler + pbDisplayPaused(_INTL("{1} grew to Lv. {2}!",pkmn.name,curLevel)) + @scene.pbLevelUp(pkmn,battler,oldTotalHP,oldAttack,oldDefense, + oldSpAtk,oldSpDef,oldSpeed) + # Learn all moves learned at this level + moveList = pkmn.getMoveList + moveList.each { |m| pbLearnMove(idxParty,m[1]) if m[0]==curLevel } + end + end + + #============================================================================= + # Learning a move + #============================================================================= + def pbLearnMove(idxParty,newMove) + pkmn = pbParty(0)[idxParty] + return if !pkmn + pkmnName = pkmn.name + battler = pbFindBattler(idxParty) + moveName = PBMoves.getName(newMove) + # Find a space for the new move in pkmn's moveset and learn it + pkmn.moves.each_with_index do |m,i| + return if m.id==newMove # Already knows the new move + next if m.id!=0 # Not a blank move slot + pkmn.moves[i] = PBMove.new(newMove) + battler.moves[i] = PokeBattle_Move.pbFromPBMove(self,pkmn.moves[i]) if battler + pbDisplay(_INTL("{1} learned {2}!",pkmnName,moveName)) { pbSEPlay("Pkmn move learnt") } + battler.pbCheckFormOnMovesetChange if battler + return + end + # pkmn already knows four moves, need to forget one to learn newMove + loop do + pbDisplayPaused(_INTL("{1} wants to learn {2}, but it already knows four moves.",pkmnName,moveName)) + if pbDisplayConfirm(_INTL("Forget a move to learn {1}?",moveName)) + pbDisplayPaused(_INTL("Which move should be forgotten?")) + forgetMove = @scene.pbForgetMove(pkmn,newMove) + if forgetMove>=0 + oldMoveName = PBMoves.getName(pkmn.moves[forgetMove].id) + pkmn.moves[forgetMove] = PBMove.new(newMove) # Replaces current/total PP + battler.moves[forgetMove] = PokeBattle_Move.pbFromPBMove(self,pkmn.moves[forgetMove]) if battler + pbDisplayPaused(_INTL("1, 2, and... ... ... Ta-da!")) + pbDisplayPaused(_INTL("{1} forgot how to use {2}. And...",pkmnName,oldMoveName)) + pbDisplay(_INTL("{1} learned {2}!",pkmnName,moveName)) { pbSEPlay("Pkmn move learnt") } + battler.pbCheckFormOnMovesetChange if battler + break + elsif pbDisplayConfirm(_INTL("Give up on learning {1}?",moveName)) + pbDisplay(_INTL("{1} did not learn {2}.",pkmnName,moveName)) + break + end + elsif pbDisplayConfirm(_INTL("Give up on learning {1}?",moveName)) + pbDisplay(_INTL("{1} did not learn {2}.",pkmnName,moveName)) + break + end + end + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/003_Battle/005_Battle_Action_AttacksPriority.rb b/Data/Scripts/011_Battle/003_Battle/005_Battle_Action_AttacksPriority.rb new file mode 100644 index 000000000..c4b7f5b5b --- /dev/null +++ b/Data/Scripts/011_Battle/003_Battle/005_Battle_Action_AttacksPriority.rb @@ -0,0 +1,254 @@ +class PokeBattle_Battle + #============================================================================= + # Choosing a move/target + #============================================================================= + def pbCanChooseMove?(idxBattler,idxMove,showMessages,sleepTalk=false) + battler = @battlers[idxBattler] + move = battler.moves[idxMove] + return false unless move && move.id>0 + if move.pp==0 && move.totalpp>0 && !sleepTalk + pbDisplayPaused(_INTL("There's no PP left for this move!")) if showMessages + return false + end + if battler.effects[PBEffects::Encore]>0 + idxEncoredMove = battler.pbEncoredMoveIndex + return false if idxEncoredMove>=0 && idxMove!=idxEncoredMove + end + return battler.pbCanChooseMove?(move,true,showMessages,sleepTalk) + end + + def pbCanChooseAnyMove?(idxBattler,sleepTalk=false) + battler = @battlers[idxBattler] + battler.eachMoveWithIndex do |m,i| + next if m.pp==0 && m.totalpp>0 && !sleepTalk + if battler.effects[PBEffects::Encore]>0 + idxEncoredMove = battler.pbEncoredMoveIndex + next if idxEncoredMove>=0 && i!=idxEncoredMove + end + next if !battler.pbCanChooseMove?(m,true,false,sleepTalk) + return true + end + return false + end + + # Called when the Pokémon is Encored, or if it can't use any of its moves. + # Makes the Pokémon use the Encored move (if Encored), or Struggle. + def pbAutoChooseMove(idxBattler,showMessages=true) + battler = @battlers[idxBattler] + if battler.fainted? + pbClearChoice(idxBattler) + return true + end + # Encore + idxEncoredMove = battler.pbEncoredMoveIndex + if idxEncoredMove>=0 && pbCanChooseMove?(idxBattler,idxEncoredMove,false) + encoreMove = battler.moves[idxEncoredMove] + @choices[idxBattler][0] = :UseMove # "Use move" + @choices[idxBattler][1] = idxEncoredMove # Index of move to be used + @choices[idxBattler][2] = encoreMove # PokeBattle_Move object + @choices[idxBattler][3] = -1 # No target chosen yet + return true if singleBattle? + if pbOwnedByPlayer?(idxBattler) + if showMessages + pbDisplayPaused(_INTL("{1} has to use {2}!",battler.name,encoreMove.name)) + end + return pbChooseTarget(battler,encoreMove) + end + return true + end + # Struggle + if pbOwnedByPlayer?(idxBattler) && showMessages + pbDisplayPaused(_INTL("{1} has no moves left!",battler.name)) + end + @choices[idxBattler][0] = :UseMove # "Use move" + @choices[idxBattler][1] = -1 # Index of move to be used + @choices[idxBattler][2] = @struggle # Struggle PokeBattle_Move object + @choices[idxBattler][3] = -1 # No target chosen yet + return true + end + + def pbRegisterMove(idxBattler,idxMove,showMessages=true) + battler = @battlers[idxBattler] + move = battler.moves[idxMove] + return false if !pbCanChooseMove?(idxBattler,idxMove,showMessages) + @choices[idxBattler][0] = :UseMove # "Use move" + @choices[idxBattler][1] = idxMove # Index of move to be used + @choices[idxBattler][2] = move # PokeBattle_Move object + @choices[idxBattler][3] = -1 # No target chosen yet + return true + end + + def pbChoseMove?(idxBattler,moveID) + return false if !@battlers[idxBattler] || @battlers[idxBattler].fainted? + idxMove = @choices[idxBattler][1] + if @choices[idxBattler][0]==:UseMove && idxMove>=0 + chosenMoveID = @battlers[idxBattler].moves[idxMove].id + return isConst?(chosenMoveID,PBMoves,moveID) + end + return false + end + + def pbChoseMoveFunctionCode?(idxBattler,code) + return false if @battlers[idxBattler].fainted? + idxMove = @choices[idxBattler][1] + if @choices[idxBattler][0]==:UseMove && idxMove>=0 + return @battlers[idxBattler].moves[idxMove].function==code + end + return false + end + + def pbRegisterTarget(idxBattler,idxTarget) + @choices[idxBattler][3] = idxTarget # Set target of move + end + + # Returns whether the idxTarget will be targeted by a move with targetType + # used by a battler in idxUser. + def pbMoveCanTarget?(idxUser,idxTarget,targetType) + return false if PBTargets.noTargets?(targetType) + case targetType + when PBTargets::NearAlly + return false if opposes?(idxUser,idxTarget) + return false if !nearBattlers?(idxUser,idxTarget) + when PBTargets::UserOrNearAlly + return true if idxUser==idxTarget + return false if opposes?(idxUser,idxTarget) + return false if !nearBattlers?(idxUser,idxTarget) + when PBTargets::NearFoe, PBTargets::AllNearFoes, PBTargets::RandomNearFoe + return false if !opposes?(idxUser,idxTarget) + return false if !nearBattlers?(idxUser,idxTarget) + when PBTargets::Foe + return false if !opposes?(idxUser,idxTarget) + when PBTargets::NearOther, PBTargets::AllNearOthers + return false if !nearBattlers?(idxUser,idxTarget) + when PBTargets::Other + return false if idxUser==idxTarget + when PBTargets::UserAndAllies + return false if opposes?(idxUser,idxTarget) + when PBTargets::AllFoes + return false if !opposes?(idxUser,idxTarget) + end + return true + end + + #============================================================================= + # Turn order calculation (priority) + #============================================================================= + def pbCalculatePriority(fullCalc=false,indexArray=nil) + needRearranging = false + if fullCalc + @priorityTrickRoom = (@field.effects[PBEffects::TrickRoom]>0) + # Recalculate everything from scratch + randomOrder = Array.new(maxBattlerIndex+1) { |i| i } + (randomOrder.length-1).times do |i| # Can't use shuffle! here + r = i+pbRandom(randomOrder.length-i) + randomOrder[i], randomOrder[r] = randomOrder[r], randomOrder[i] + end + @priority.clear + for i in 0..maxBattlerIndex + b = @battlers[i] + next if !b + # [battler, speed, sub-priority, priority, tie-breaker order] + bArray = [b,b.pbSpeed,0,0,randomOrder[i]] + if @choices[b.index][0]==:UseMove || @choices[b.index][0]==:Shift + # Calculate move's priority + if @choices[b.index][0]==:UseMove + move = @choices[b.index][2] + pri = move.priority + if b.abilityActive? + pri = BattleHandlers.triggerPriorityChangeAbility(b.ability,b,move,pri) + end + bArray[3] = pri + @choices[b.index][4] = pri + end + # Calculate sub-priority (first/last within priority bracket) + # NOTE: Going fast beats going slow. A Pokémon with Stall and Quick + # Claw will go first in its priority bracket if Quick Claw + # triggers, regardless of Stall. + subPri = 0 + # Abilities (Stall) + if b.abilityActive? + newSubPri = BattleHandlers.triggerPriorityBracketChangeAbility(b.ability, + b,subPri,self) + if subPri!=newSubPri + subPri = newSubPri + b.effects[PBEffects::PriorityAbility] = true + b.effects[PBEffects::PriorityItem] = false + end + end + # Items (Quick Claw, Custap Berry, Lagging Tail, Full Incense) + if b.itemActive? + newSubPri = BattleHandlers.triggerPriorityBracketChangeItem(b.item, + b,subPri,self) + if subPri!=newSubPri + subPri = newSubPri + b.effects[PBEffects::PriorityAbility] = false + b.effects[PBEffects::PriorityItem] = true + end + end + bArray[2] = subPri + end + @priority.push(bArray) + end + needRearranging = true + else + if (@field.effects[PBEffects::TrickRoom]>0)!=@priorityTrickRoom + needRearranging = true + @priorityTrickRoom = (@field.effects[PBEffects::TrickRoom]>0) + end + # Just recheck all battler speeds + @priority.each do |orderArray| + next if !orderArray + next if indexArray && !indexArray.include?(orderArray[0].index) + oldSpeed = orderArray[1] + orderArray[1] = orderArray[0].pbSpeed + needRearranging = true if orderArray[1]!=oldSpeed + end + end + # Reorder the priority array + if needRearranging + @priority.sort! { |a,b| + if a[3]!=b[3] + # Sort by priority (highest value first) + b[3]<=>a[3] + elsif a[2]!=b[2] + # Sort by sub-priority (highest value first) + b[2]<=>a[2] + elsif @priorityTrickRoom + # Sort by speed (lowest first), and use tie-breaker if necessary + (a[1]==b[1]) ? b[4]<=>a[4] : a[1]<=>b[1] + else + # Sort by speed (highest first), and use tie-breaker if necessary + (a[1]==b[1]) ? b[4]<=>a[4] : b[1]<=>a[1] + end + } + # Write the priority order to the debug log + logMsg = (fullCalc) ? "[Round order] " : "[Round order recalculated] " + comma = false + @priority.each do |orderArray| + logMsg += ", " if comma + logMsg += "#{orderArray[0].pbThis(comma)} (#{orderArray[0].index})" + comma = true + end + PBDebug.log(logMsg) + end + end + + # NOTE: In Gen 8, battler speeds stored in priority are recalculated far more + # frequently than they used to be. There are several quoted-out calls to + # pbCalculatePriority in these scripts which do this; just unquote them. + def pbPriority(onlySpeedSort=false) + ret = [] + if onlySpeedSort + # Sort battlers by their speed stats and tie-breaker order only. + tempArray = [] + @priority.each { |pArray| tempArray.push([pArray[0],pArray[1],pArray[4]]) } + tempArray.sort! { |a,b| (a[1]==b[1]) ? b[2]<=>a[2] : b[1]<=>a[1] } + tempArray.each { |tArray| ret.push(tArray[0]) } + else + # Sort battlers by priority, sub-priority and their speed. Ties are + # resolved in the same way each time this method is called in a round. + @priority.each { |pArray| ret.push(pArray[0]) if !pArray[0].fainted? } + end + return ret + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/003_Battle/006_Battle_Action_Switching.rb b/Data/Scripts/011_Battle/003_Battle/006_Battle_Action_Switching.rb new file mode 100644 index 000000000..3ef5dc5b4 --- /dev/null +++ b/Data/Scripts/011_Battle/003_Battle/006_Battle_Action_Switching.rb @@ -0,0 +1,411 @@ +class PokeBattle_Battle + #============================================================================= + # Choosing Pokémon to switch + #============================================================================= + # Checks whether the replacement Pokémon (at party index idxParty) can enter + # battle. + # NOTE: Messages are only shown while in the party screen when choosing a + # command for the next round. + def pbCanSwitchLax?(idxBattler,idxParty,partyScene=nil) + return true if idxParty<0 + party = pbParty(idxBattler) + return false if idxParty>=party.length + return false if !party[idxParty] + if party[idxParty].egg? + partyScene.pbDisplay(_INTL("An Egg can't battle!")) if partyScene + return false + end + if !pbIsOwner?(idxBattler,idxParty) + owner = pbGetOwnerFromPartyIndex(idxBattler,idxParty) + partyScene.pbDisplay(_INTL("You can't switch {1}'s Pokémon with one of yours!", + owner.name)) if partyScene + return false + end + if party[idxParty].fainted? + partyScene.pbDisplay(_INTL("{1} has no energy left to battle!", + party[idxParty].name)) if partyScene + return false + end + if pbFindBattler(idxParty,idxBattler) + partyScene.pbDisplay(_INTL("{1} is already in battle!", + party[idxParty].name)) if partyScene + return false + end + return true + end + + # Check whether the currently active Pokémon (at battler index idxBattler) can + # switch out (and that its replacement at party index idxParty can switch in). + # NOTE: Messages are only shown while in the party screen when choosing a + # command for the next round. + def pbCanSwitch?(idxBattler,idxParty=-1,partyScene=nil) + # Check whether party Pokémon can switch in + return false if !pbCanSwitchLax?(idxBattler,idxParty,partyScene) + # Make sure another battler isn't already choosing to switch to the party + # Pokémon + eachSameSideBattler(idxBattler) do |b| + next if choices[b.index][0]!=:SwitchOut || choices[b.index][1]!=idxParty + partyScene.pbDisplay(_INTL("{1} has already been selected.", + pbParty(idxBattler)[idxParty].name)) if partyScene + return false + end + # Check whether battler can switch out + battler = @battlers[idxBattler] + return true if battler.fainted? + # Ability/item effects that allow switching no matter what + if battler.abilityActive? + if BattleHandlers.triggerCertainSwitchingUserAbility(battler.ability,battler,self) + return true + end + end + if battler.itemActive? + if BattleHandlers.triggerCertainSwitchingUserItem(battler.item,battler,self) + return true + end + end + # Other certain switching effects + return true if NEWEST_BATTLE_MECHANICS && battler.pbHasType?(:GHOST) + # Other certain trapping effects + if battler.effects[PBEffects::Trapping]>0 || + battler.effects[PBEffects::MeanLook]>=0 || + battler.effects[PBEffects::Ingrain] || + @field.effects[PBEffects::FairyLock]>0 + partyScene.pbDisplay(_INTL("{1} can't be switched out!",battler.pbThis)) if partyScene + return false + end + # Trapping abilities/items + eachOtherSideBattler(idxBattler) do |b| + next if !b.abilityActive? + if BattleHandlers.triggerTrappingTargetAbility(b.ability,battler,b,self) + partyScene.pbDisplay(_INTL("{1}'s {2} prevents switching!", + b.pbThis,b.abilityName)) if partyScene + return false + end + end + eachOtherSideBattler(idxBattler) do |b| + next if !b.itemActive? + if BattleHandlers.triggerTrappingTargetItem(b.item,battler,b,self) + partyScene.pbDisplay(_INTL("{1}'s {2} prevents switching!", + b.pbThis,b.itemName)) if partyScene + return false + end + end + return true + end + + def pbCanChooseNonActive?(idxBattler) + pbParty(idxBattler).each_with_index do |pkmn,i| + return true if pbCanSwitchLax?(idxBattler,i) + end + return false + end + + def pbRegisterSwitch(idxBattler,idxParty) + return false if !pbCanSwitch?(idxBattler,idxParty) + @choices[idxBattler][0] = :SwitchOut + @choices[idxBattler][1] = idxParty # Party index of Pokémon to switch in + @choices[idxBattler][2] = nil + return true + end + + #============================================================================= + # Open the party screen and potentially pick a replacement Pokémon (or AI + # chooses replacement) + #============================================================================= + # Open party screen and potentially choose a Pokémon to switch with. Used in + # all instances where the party screen is opened. + def pbPartyScreen(idxBattler,checkLaxOnly=false,canCancel=false,shouldRegister=false) + ret = -1 + @scene.pbPartyScreen(idxBattler,canCancel) { |idxParty,partyScene| + if checkLaxOnly + next false if !pbCanSwitchLax?(idxBattler,idxParty,partyScene) + else + next false if !pbCanSwitch?(idxBattler,idxParty,partyScene) + end + if shouldRegister + next false if idxParty<0 || !pbRegisterSwitch(idxBattler,idxParty) + end + ret = idxParty + next true + } + return ret + end + + # For choosing a replacement Pokémon when prompted in the middle of other + # things happening (U-turn, Baton Pass, in def pbSwitch). + def pbSwitchInBetween(idxBattler,checkLaxOnly=false,canCancel=false) + return pbPartyScreen(idxBattler,checkLaxOnly,canCancel) if pbOwnedByPlayer?(idxBattler) + return @battleAI.pbDefaultChooseNewEnemy(idxBattler,pbParty(idxBattler)) + end + + #============================================================================= + # Switching Pokémon + #============================================================================= + # General switching method that checks if any Pokémon need to be sent out and, + # if so, does. Called at the end of each round. + def pbEORSwitch(favorDraws=false) + return if @decision>0 && !favorDraws + return if @decision==5 && favorDraws + pbJudge + return if @decision>0 + # Check through each fainted battler to see if that spot can be filled. + switched = [] + loop do + switched.clear + @battlers.each do |b| + next if !b || !b.fainted? + idxBattler = b.index + next if !pbCanChooseNonActive?(idxBattler) + if !pbOwnedByPlayer?(idxBattler) # Opponent/ally is switching in + next if wildBattle? && opposes?(idxBattler) # Wild Pokémon can't switch + idxPartyNew = pbSwitchInBetween(idxBattler) + opponent = pbGetOwnerFromBattlerIndex(idxBattler) + # NOTE: The player is only offered the chance to switch their own + # Pokémon when an opponent replaces a fainted Pokémon in single + # battles. In double battles, etc. there is no such offer. + if @internalBattle && @switchStyle && trainerBattle? && pbSideSize(0)==1 && + opposes?(idxBattler) && !@battlers[0].fainted? && pbCanChooseNonActive?(0) && + @battlers[0].effects[PBEffects::Outrage]==0 + idxPartyForName = idxPartyNew + enemyParty = pbParty(idxBattler) + if isConst?(enemyParty[idxPartyNew].ability,PBAbilities,:ILLUSION) + idxPartyForName = pbGetLastPokeInTeam(idxBattler) + end + if pbDisplayConfirm(_INTL("{1} is about to send in {2}. Will you switch your Pokémon?", + opponent.fullname,enemyParty[idxPartyForName].name)) + idxPlayerPartyNew = pbSwitchInBetween(0,false,true) + if idxPlayerPartyNew>=0 + pbMessageOnRecall(@battlers[0]) + pbRecallAndReplace(0,idxPlayerPartyNew) + switched.push(0) + end + end + end + pbRecallAndReplace(idxBattler,idxPartyNew) + switched.push(idxBattler) + elsif trainerBattle? # Player switches in in a trainer battle + idxPlayerPartyNew = pbGetReplacementPokemonIndex(idxBattler) # Owner chooses + pbRecallAndReplace(idxBattler,idxPlayerPartyNew) + switched.push(idxBattler) + else # Player's Pokémon has fainted in a wild battle + switch = false + if !pbDisplayConfirm(_INTL("Use next Pokémon?")) + switch = (pbRun(idxBattler,true)<=0) + else + switch = true + end + if switch + idxPlayerPartyNew = pbGetReplacementPokemonIndex(idxBattler) # Owner chooses + pbRecallAndReplace(idxBattler,idxPlayerPartyNew) + switched.push(idxBattler) + end + end + end + break if switched.length==0 + pbPriority(true).each do |b| + b.pbEffectsOnSwitchIn(true) if switched.include?(b.index) + end + end + end + + def pbGetReplacementPokemonIndex(idxBattler,random=false) + if random + return -1 if !pbCanSwitch?(idxBattler) # Can battler switch out? + choices = [] # Find all Pokémon that can switch in + eachInTeamFromBattlerIndex(idxBattler) do |pkmn,i| + choices.push(i) if pbCanSwitchLax?(idxBattler,i) + end + return -1 if choices.length==0 + return choices[pbRandom(choices.length)] + else + return pbSwitchInBetween(idxBattler,true) + end + end + + # Actually performs the recalling and sending out in all situations. + def pbRecallAndReplace(idxBattler,idxParty,batonPass=false) + @scene.pbRecall(idxBattler) if !@battlers[idxBattler].fainted? + @battlers[idxBattler].pbAbilitiesOnSwitchOut # Inc. primordial weather check + @scene.pbShowPartyLineup(idxBattler&1) if pbSideSize(idxBattler)==1 + pbMessagesOnReplace(idxBattler,idxParty) + pbReplace(idxBattler,idxParty,batonPass) + end + + def pbMessageOnRecall(battler) + if battler.pbOwnedByPlayer? + if battler.hp<=battler.totalhp/4 + pbDisplayBrief(_INTL("Good job, {1}! Come back!",battler.name)) + elsif battler.hp<=battler.totalhp/2 + pbDisplayBrief(_INTL("OK, {1}! Come back!",battler.name)) + elsif battler.turnCount>=5 + pbDisplayBrief(_INTL("{1}, that’s enough! Come back!",battler.name)) + elsif battler.turnCount>=2 + pbDisplayBrief(_INTL("{1}, come back!",battler.name)) + else + pbDisplayBrief(_INTL("{1}, switch out! Come back!",battler.name)) + end + else + owner = pbGetOwnerName(b.index) + pbDisplayBrief(_INTL("{1} withdrew {2}!",owner,battler.name)) + end + end + + # Only called from def pbRecallAndReplace and Battle Arena's def pbSwitch. + def pbMessagesOnReplace(idxBattler,idxParty) + party = pbParty(idxBattler) + newPkmnName = party[idxParty].name + if isConst?(party[idxParty].ability,PBAbilities,:ILLUSION) + newPkmnName = party[pbLastInTeam(idxBattler)].name + end + if pbOwnedByPlayer?(idxBattler) + opposing = @battlers[idxBattler].pbDirectOpposing + if opposing.fainted? || opposing.hp==opposing.totalhp + pbDisplayBrief(_INTL("You're in charge, {1}!",newPkmnName)) + elsif opposing.hp>=opposing.totalhp/2 + pbDisplayBrief(_INTL("Go for it, {1}!",newPkmnName)) + elsif opposing.hp>=opposing.totalhp/4 + pbDisplayBrief(_INTL("Just a little more! Hang in there, {1}!",newPkmnName)) + else + pbDisplayBrief(_INTL("Your opponent's weak! Get 'em, {1}!",newPkmnName)) + end + else + owner = pbGetOwnerFromBattlerIndex(idxBattler) + pbDisplayBrief(_INTL("{1} sent out {2}!",owner.fullname,newPkmnName)) + end + end + + # Only called from def pbRecallAndReplace above and Battle Arena's def + # pbSwitch. + def pbReplace(idxBattler,idxParty,batonPass=false) + party = pbParty(idxBattler) + idxPartyOld = @battlers[idxBattler].pokemonIndex + # Initialise the new Pokémon + @battlers[idxBattler].pbInitialize(party[idxParty],idxParty,batonPass) + # Reorder the party for this battle + partyOrder = pbPartyOrder(idxBattler) + partyOrder[idxParty],partyOrder[idxPartyOld] = partyOrder[idxPartyOld],partyOrder[idxParty] + # Send out the new Pokémon + pbSendOut([[idxBattler,party[idxParty]]]) +# pbCalculatePriority(false,[idxBattler]) if NEWEST_BATTLE_MECHANICS + end + + # Called from def pbReplace above and at the start of battle. + # sendOuts is an array; each element is itself an array: [idxBattler,pkmn] + def pbSendOut(sendOuts,startBattle=false) + sendOuts.each { |b| @peer.pbOnEnteringBattle(self,b[1]) } + @scene.pbSendOutBattlers(sendOuts,startBattle) + sendOuts.each do |b| + @scene.pbResetMoveIndex(b[0]) + pbSetSeen(@battlers[b[0]]) + @usedInBattle[b[0]&1][b[0]/2] = true + end + end + + #============================================================================= + # Effects upon a Pokémon entering battle + #============================================================================= + # Called at the start of battle only. + def pbOnActiveAll + # Weather-inducing abilities, Trace, Imposter, etc. + pbCalculatePriority(true) + pbPriority(true).each { |b| b.pbEffectsOnSwitchIn(true) } + pbCalculatePriority + # Check forms are correct + eachBattler { |b| b.pbCheckForm } + end + + # Called when a Pokémon switches in (entry effects, entry hazards). + def pbOnActiveOne(battler) + return false if battler.fainted? + # Introduce Shadow Pokémon + if battler.opposes? && battler.shadowPokemon? + pbCommonAnimation("Shadow",battler) + pbDisplay(_INTL("Oh!\nA Shadow Pokémon!")) + end + # Record money-doubling effect of Amulet Coin/Luck Incense + if !battler.opposes? && (isConst?(battler.item,PBItems,:AMULETCOIN) || + isConst?(battler.item,PBItems,:LUCKINCENSE)) + @field.effects[PBEffects::AmuletCoin] = true + end + # Update battlers' participants (who will gain Exp/EVs when a battler faints) + eachBattler { |b| b.pbUpdateParticipants } + # Healing Wish + if @positions[battler.index].effects[PBEffects::HealingWish] + pbCommonAnimation("HealingWish",battler) + pbDisplay(_INTL("The healing wish came true for {1}!",battler.pbThis(true))) + battler.pbRecoverHP(battler.totalhp) + battler.pbCureStatus(false) + @positions[battler.index].effects[PBEffects::HealingWish] = false + end + # Lunar Dance + if @positions[battler.index].effects[PBEffects::LunarDance] + pbCommonAnimation("LunarDance",battler) + pbDisplay(_INTL("{1} became cloaked in mystical moonlight!",battler.pbThis)) + battler.pbRecoverHP(battler.totalhp) + battler.pbCureStatus(false) + battler.eachMove { |m| m.pp = m.totalpp } + @positions[battler.index].effects[PBEffects::LunarDance] = false + end + # Entry hazards + # Stealth Rock + if battler.pbOwnSide.effects[PBEffects::StealthRock] && battler.takesIndirectDamage? + aType = getConst(PBTypes,:ROCK) || 0 + bTypes = battler.pbTypes(true) + eff = PBTypes.getCombinedEffectiveness(aType,bTypes[0],bTypes[1],bTypes[2]) + if !PBTypes.ineffective?(eff) + eff = eff.to_f/PBTypeEffectiveness::NORMAL_EFFECTIVE + oldHP = battler.hp + battler.pbReduceHP(battler.totalhp*eff/8,false) + pbDisplay(_INTL("Pointed stones dug into {1}!",battler.pbThis)) + battler.pbItemHPHealCheck + if battler.pbAbilitiesOnDamageTaken(oldHP) # Switched out + return pbOnActiveOne(battler) # For replacement battler + end + end + end + # Spikes + if battler.pbOwnSide.effects[PBEffects::Spikes]>0 && battler.takesIndirectDamage? && + !battler.airborne? + spikesDiv = [8,6,4][battler.pbOwnSide.effects[PBEffects::Spikes]-1] + oldHP = battler.hp + battler.pbReduceHP(battler.totalhp/spikesDiv,false) + pbDisplay(_INTL("{1} is hurt by the spikes!",battler.pbThis)) + battler.pbItemHPHealCheck + if battler.pbAbilitiesOnDamageTaken(oldHP) # Switched out + return pbOnActiveOne(battler) # For replacement battler + end + end + # Toxic Spikes + if battler.pbOwnSide.effects[PBEffects::ToxicSpikes]>0 && !battler.fainted? && + !battler.airborne? + if battler.pbHasType?(:POISON) + battler.pbOwnSide.effects[PBEffects::ToxicSpikes] = 0 + pbDisplay(_INTL("{1} absorbed the poison spikes!",battler.pbThis)) + elsif battler.pbCanPoison?(nil,false) + if battler.pbOwnSide.effects[PBEffects::ToxicSpikes]==2 + battler.pbPoison(nil,_INTL("{1} was badly poisoned by the poison spikes!",battler.pbThis),true) + else + battler.pbPoison(nil,_INTL("{1} was poisoned by the poison spikes!",battler.pbThis)) + end + end + end + # Sticky Web + if battler.pbOwnSide.effects[PBEffects::StickyWeb] && !battler.fainted? && + !battler.airborne? + pbDisplay(_INTL("{1} was caught in a sticky web!",battler.pbThis)) + if battler.pbCanLowerStatStage?(PBStats::SPEED) + battler.pbLowerStatStage(PBStats::SPEED,1,nil) + battler.pbItemStatRestoreCheck + end + end + # Battler faints if it is knocked out because of an entry hazard above + if battler.fainted? + battler.pbFaint + pbGainExp + pbJudge + return false + end + battler.pbCheckForm + return true + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/003_Battle/007_Battle_Action_UseItem.rb b/Data/Scripts/011_Battle/003_Battle/007_Battle_Action_UseItem.rb new file mode 100644 index 000000000..29ee51f57 --- /dev/null +++ b/Data/Scripts/011_Battle/003_Battle/007_Battle_Action_UseItem.rb @@ -0,0 +1,144 @@ +class PokeBattle_Battle + #============================================================================= + # Choosing to use an item + #============================================================================= + def pbCanUseItemOnPokemon?(item,pkmn,battler,scene,showMessages=true) + if pkmn.egg? + scene.pbDisplay(_INTL("It won't have any effect.")) if showMessages + return false + end + # Embargo + if battler && battler.effects[PBEffects::Embargo]>0 + scene.pbDisplay(_INTL("Embargo's effect prevents the item's use on {1}!", + battler.pbThis(true))) if showMessages + return false + end + return true + end + + # NOTE: Using a Poké Ball consumes all your actions for the round. The method + # below is one half of making this happen; the other half is in the + # ItemHandlers::CanUseInBattle for Poké Balls. + def pbItemUsesAllActions?(item) + return true if pbIsPokeBall?(item) + return false + end + + def pbRegisterItem(idxBattler,item,idxTarget=nil,idxMove=nil) + # Register for use of item on a Pokémon in the party + @choices[idxBattler][0] = :UseItem + @choices[idxBattler][1] = item # ID of item to be used + @choices[idxBattler][2] = idxTarget # Party index of Pokémon to use item on + @choices[idxBattler][3] = idxMove # Index of move to recharge (Ethers) + # Delete the item from the Bag. If it turns out it will have no effect, it + # will be re-added to the Bag later. + pbConsumeItemInBag(item,idxBattler) + return true + end + + #============================================================================= + # Using an item + #============================================================================= + def pbConsumeItemInBag(item,idxBattler) + return if item==0 + useType = pbGetItemData(item,ITEM_BATTLE_USE) + return if !useType || useType==0 || (useType>=6 && useType<=10) # Not consumed upon use + if pbOwnedByPlayer?(idxBattler) + if !$PokemonBag.pbDeleteItem(item) + raise _INTL("Tried to consume item that wasn't in the Bag somehow.") + end + else + items = pbGetOwnerItems(idxBattler) + items.each_with_index do |thisItem,i| + next if thisItem!=item + items[i] = nil + break + end + items.compact! + end + end + + def pbReturnUnusedItemToBag(item,idxBattler) + return if item==0 + useType = pbGetItemData(item,ITEM_BATTLE_USE) + return if !useType || useType==0 || (useType>=6 && useType<=10) # Not consumed upon use + if pbOwnedByPlayer?(idxBattler) + if $PokemonBag && $PokemonBag.pbCanStore?(item) + $PokemonBag.pbStoreItem(item) + else + raise _INTL("Couldn't return unused item to Bag somehow.") + end + else + items = pbGetOwnerItems(idxBattler) + items.push(item) if items + end + end + + def pbUseItemMessage(item,trainerName) + itemName = PBItems.getName(item) + if itemName.starts_with_vowel? + pbDisplayBrief(_INTL("{1} used an {2}.",trainerName,itemName)) + else + pbDisplayBrief(_INTL("{1} used a {2}.",trainerName,itemName)) + end + end + + # Uses an item on a Pokémon in the player's party. + def pbUseItemOnPokemon(item,idxParty,userBattler) + trainerName = pbGetOwnerName(userBattler.index) + pbUseItemMessage(item,trainerName) + pkmn = pbParty(userBattler.index)[idxParty] + battler = pbFindBattler(idxParty,userBattler.index) + ch = @choices[userBattler.index] + if ItemHandlers.triggerCanUseInBattle(item,pkmn,battler,ch[3],true,self,@scene,false) + ItemHandlers.triggerBattleUseOnPokemon(item,pkmn,battler,ch,@scene) + ch[1] = 0 # Delete item from choice + return + end + pbDisplay(_INTL("But it had no effect!")) + # Return unused item to Bag + pbReturnUnusedItemToBag(item,userBattler.index) + end + + # Uses an item on a Pokémon in battle that belongs to the player. + def pbUseItemOnBattler(item,idxParty,userBattler) + trainerName = pbGetOwnerName(userBattler.index) + pbUseItemMessage(item,trainerName) + pkmn = pbParty(userBattler.index)[idxParty] + battler = pbFindBattler(idxParty,userBattler.index) + ch = @choices[userBattler.index] + if ItemHandlers.triggerCanUseInBattle(item,pkmn,battler,ch[3],true,self,@scene,false) + ItemHandlers.triggerBattleUseOnBattler(item,battler,@scene) + ch[1] = 0 # Delete item from choice + return + end + pbDisplay(_INTL("But it's not where this item can be used!")) + # Return unused item to Bag + pbReturnUnusedItemToBag(item,userBattler.index) + end + + # Uses a Poké Ball in battle directly. + def pbUsePokeBallInBattle(item,idxBattler,userBattler) + idxBattler = userBattler.index if idxBattler<0 + battler = @battlers[idxBattler] + ItemHandlers.triggerUseInBattle(item,battler,self) + @choices[userBattler.index][1] = 0 # Delete item from choice + end + + # Uses an item in battle directly. + def pbUseItemInBattle(item,idxBattler,userBattler) + trainerName = pbGetOwnerName(userBattler.index) + pbUseItemMessage(item,trainerName) + battler = (idxBattler<0) ? userBattler : @battlers[idxBattler] + pkmn = battler.pokemon + ch = @choices[userBattler.index] + if ItemHandlers.triggerCanUseInBattle(item,pkmn,battler,ch[3],true,self,@scene,false) + ItemHandlers.triggerUseInBattle(item,battler,self) + ch[1] = 0 # Delete item from choice + return + end + pbDisplay(_INTL("But it had no effect!")) + # Return unused item to Bag + pbReturnUnusedItemToBag(item,userBattler.index) + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/003_Battle/008_Battle_Action_Running.rb b/Data/Scripts/011_Battle/003_Battle/008_Battle_Action_Running.rb new file mode 100644 index 000000000..01ed9f073 --- /dev/null +++ b/Data/Scripts/011_Battle/003_Battle/008_Battle_Action_Running.rb @@ -0,0 +1,145 @@ +class PokeBattle_Battle + #============================================================================= + # Running from battle + #============================================================================= + def pbCanRun?(idxBattler) + return false if trainerBattle? + battler = @battlers[idxBattler] + return false if !@canRun && !battler.opposes? + return true if battler.pbHasType?(:GHOST) && NEWEST_BATTLE_MECHANICS + return true if battler.abilityActive? && + BattleHandlers.triggerRunFromBattleAbility(battler.ability,battler) + return true if battler.itemActive? && + BattleHandlers.triggerRunFromBattleItem(battler.item,battler) + return false if battler.effects[PBEffects::Trapping]>0 || + battler.effects[PBEffects::MeanLook]>=0 || + battler.effects[PBEffects::Ingrain] || + @field.effects[PBEffects::FairyLock]>0 + eachOtherSideBattler(idxBattler) do |b| + return false if b.abilityActive? && + BattleHandlers.triggerTrappingTargetAbility(b.ability,battler,b,self) + return false if b.itemActive? && + BattleHandlers.triggerTrappingTargetItem(b.item,battler,b,self) + end + return true + end + + # Return values: + # -1: Failed fleeing + # 0: Wasn't possible to attempt fleeing, continue choosing action for the round + # 1: Succeeded at fleeing, battle will end + # duringBattle is true for replacing a fainted Pokémon during the End Of Round + # phase, and false for choosing the Run command. + def pbRun(idxBattler,duringBattle=false) + battler = @battlers[idxBattler] + if battler.opposes? + return 0 if trainerBattle? + @choices[idxBattler][0] = :Run + @choices[idxBattler][1] = 0 + @choices[idxBattler][2] = nil + return -1 + end + # Fleeing 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 + pbDisplayPaused(_INTL("No! There's no running from a Trainer battle!")) + elsif pbDisplayConfirm(_INTL("Would you like to forfeit the match and quit now?")) + pbDisplay(_INTL("{1} forfeited the match!",self.pbPlayer.name)) { pbSEPlay("Battle flee") } + @decision = 3 + return 1 + end + return 0 + end + # Fleeing from wild battles + if $DEBUG && Input.press?(Input::CTRL) + pbDisplayPaused(_INTL("You got away safely!")) { pbSEPlay("Battle flee") } + @decision = 3 + return 1 + end + if !@canRun + pbDisplayPaused(_INTL("You can't escape!")) + return 0 + end + if !duringBattle + if battler.pbHasType?(:GHOST) && NEWEST_BATTLE_MECHANICS + pbDisplayPaused(_INTL("You got away safely!")) { pbSEPlay("Battle flee") } + @decision = 3 + return 1 + end + # Abilities that guarantee escape + if battler.abilityActive? + if BattleHandlers.triggerRunFromBattleAbility(battler.ability,battler) + pbShowAbilitySplash(battler,true) + pbHideAbilitySplash(battler) + pbDisplayPaused(_INTL("You got away safely!")) { pbSEPlay("Battle flee") } + @decision = 3 + return 1 + end + end + # Held items that guarantee escape + if battler.itemActive? + if BattleHandlers.triggerRunFromBattleItem(battler.item,battler) + pbDisplayPaused(_INTL("{1} fled using its {2}!", + battler.pbThis,battler.itemName)) { pbSEPlay("Battle flee") } + @decision = 3 + return 1 + end + end + # Other certain trapping effects + if battler.effects[PBEffects::Trapping]>0 || + battler.effects[PBEffects::MeanLook]>=0 || + battler.effects[PBEffects::Ingrain] || + @field.effects[PBEffects::FairyLock]>0 + pbDisplayPaused(_INTL("You can't escape!")) + return 0 + end + # Trapping abilities/items + eachOtherSideBattler(idxBattler) do |b| + next if !b.abilityActive? + if BattleHandlers.triggerTrappingTargetAbility(b.ability,battler,b,self) + pbDisplayPaused(_INTL("{1} prevents escape with {2}!",b.pbThis,b.abilityName)) + return 0 + end + end + eachOtherSideBattler(idxBattler) do |b| + next if !b.itemActive? + if BattleHandlers.triggerTrappingTargetItem(b.item,battler,b,self) + pbDisplayPaused(_INTL("{1} prevents escape with {2}!",b.pbThis,b.itemName)) + return 0 + end + end + end + # Fleeing calculation + # Get the speeds of the Pokémon fleeing and the fastest opponent + # NOTE: Not pbSpeed, because using unmodified Speed. + @runCommand += 1 if !duringBattle # Make it easier to flee next time + speedPlayer = @battlers[idxBattler].speed + speedEnemy = 1 + eachOtherSideBattler(idxBattler) do |b| + speed = b.speed + speedEnemy = speed if speedEnemyspeedEnemy + rate = 256 + else + rate = (speedPlayer*128)/speedEnemy + rate += @runCommand*30 + end + if rate>=256 || @battleAI.pbAIRandom(256)=0 + return false if !pbHasMegaRing?(idxBattler) + side = @battlers[idxBattler].idxOwnSide + owner = pbGetOwnerIndexFromBattlerIndex(idxBattler) + return @megaEvolution[side][owner]==-1 + end + + def pbRegisterMegaEvolution(idxBattler) + side = @battlers[idxBattler].idxOwnSide + owner = pbGetOwnerIndexFromBattlerIndex(idxBattler) + @megaEvolution[side][owner] = idxBattler + end + + def pbUnregisterMegaEvolution(idxBattler) + side = @battlers[idxBattler].idxOwnSide + owner = pbGetOwnerIndexFromBattlerIndex(idxBattler) + @megaEvolution[side][owner] = -1 if @megaEvolution[side][owner]==idxBattler + end + + def pbToggleRegisteredMegaEvolution(idxBattler) + side = @battlers[idxBattler].idxOwnSide + owner = pbGetOwnerIndexFromBattlerIndex(idxBattler) + if @megaEvolution[side][owner]==idxBattler + @megaEvolution[side][owner] = -1 + else + @megaEvolution[side][owner] = idxBattler + end + end + + def pbRegisteredMegaEvolution?(idxBattler) + side = @battlers[idxBattler].idxOwnSide + owner = pbGetOwnerIndexFromBattlerIndex(idxBattler) + return @megaEvolution[side][owner]==idxBattler + end + + #============================================================================= + # Mega Evolving a battler + #============================================================================= + def pbMegaEvolve(idxBattler) + battler = @battlers[idxBattler] + return if !battler || !battler.pokemon + return if !battler.hasMega? || battler.mega? + trainerName = pbGetOwnerName(idxBattler) + # Break Illusion + if battler.hasActiveAbility?(:ILLUSION) + BattleHandlers.triggerTargetAbilityOnHit(battler.ability,nil,battler,nil,self) + end + # Mega Evolve + case battler.pokemon.megaMessage + when 1 # Rayquaza + pbDisplay(_INTL("{1}'s fervent wish has reached {2}!",trainerName,battler.pbThis)) + else + pbDisplay(_INTL("{1}'s {2} is reacting to {3}'s {4}!", + battler.pbThis,battler.itemName,trainerName,pbGetMegaRingName(idxBattler))) + end + pbCommonAnimation("MegaEvolution",battler) + battler.pokemon.makeMega + battler.form = battler.pokemon.form + battler.pbUpdate(true) + @scene.pbChangePokemon(battler,battler.pokemon) + @scene.pbRefreshOne(idxBattler) + pbCommonAnimation("MegaEvolution2",battler) + megaName = battler.pokemon.megaName + if !megaName || megaName=="" + megaName = _INTL("Mega {1}",PBSpecies.getName(battler.pokemon.species)) + end + pbDisplay(_INTL("{1} has Mega Evolved into {2}!",battler.pbThis,megaName)) + side = battler.idxOwnSide + owner = pbGetOwnerIndexFromBattlerIndex(idxBattler) + @megaEvolution[side][owner] = -2 + if isConst?(battler.species,PBSpecies,:GENGAR) && battler.mega? + battler.effects[PBEffects::Telekinesis] = 0 + end + pbCalculatePriority(false,[idxBattler]) if NEWEST_BATTLE_MECHANICS + # Trigger ability + battler.pbEffectsOnSwitchIn + end + + #============================================================================= + # Primal Reverting a battler + #============================================================================= + def pbPrimalReversion(idxBattler) + battler = @battlers[idxBattler] + return if !battler || !battler.pokemon + return if !battler.hasPrimal? || battler.primal? + if isConst?(battler.pokemon.species,PBSpecies,:KYOGRE) + pbCommonAnimation("PrimalKyogre",battler) + elsif isConst?(battler.pokemon.species,PBSpecies,:GROUDON) + pbCommonAnimation("PrimalGroudon",battler) + end + battler.pokemon.makePrimal + battler.form = battler.pokemon.form + battler.pbUpdate(true) + @scene.pbChangePokemon(battler,battler.pokemon) + @scene.pbRefreshOne(idxBattler) + if isConst?(battler.pokemon.species,PBSpecies,:KYOGRE) + pbCommonAnimation("PrimalKyogre2",battler) + elsif isConst?(battler.pokemon.species,PBSpecies,:GROUDON) + pbCommonAnimation("PrimalGroudon2",battler) + end + pbDisplay(_INTL("{1}'s Primal Reversion!\nIt reverted to its primal form!",battler.pbThis)) + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/003_Battle/010_Battle_Phase_Command.rb b/Data/Scripts/011_Battle/003_Battle/010_Battle_Phase_Command.rb new file mode 100644 index 000000000..cebeff5fa --- /dev/null +++ b/Data/Scripts/011_Battle/003_Battle/010_Battle_Phase_Command.rb @@ -0,0 +1,250 @@ +class PokeBattle_Battle + #============================================================================= + # Clear commands + #============================================================================= + def pbClearChoice(idxBattler) + @choices[idxBattler] = [] if !@choices[idxBattler] + @choices[idxBattler][0] = :None + @choices[idxBattler][1] = 0 + @choices[idxBattler][2] = nil + @choices[idxBattler][3] = -1 + end + + def pbCancelChoice(idxBattler) + # If idxBattler's choice was to use an item, return that item to the Bag + if @choices[idxBattler][0]==:UseItem + item = @choices[idxBattler][1] + pbReturnUnusedItemToBag(item,idxBattler) if item && item>0 + end + # If idxBattler chose to Mega Evolve, cancel it + pbUnregisterMegaEvolution(idxBattler) + # Clear idxBattler's choice + pbClearChoice(idxBattler) + end + + #============================================================================= + # Use main command menu (Fight/Pokémon/Bag/Run) + #============================================================================= + def pbCommandMenu(idxBattler,firstAction) + return @scene.pbCommandMenu(idxBattler,firstAction) + end + + #============================================================================= + # Check whether actions can be taken + #============================================================================= + def pbCanShowCommands?(idxBattler) + battler = @battlers[idxBattler] + return false if !battler || battler.fainted? + return false if battler.usingMultiTurnAttack? + return true + end + + def pbCanShowFightMenu?(idxBattler) + battler = @battlers[idxBattler] + # Encore + return false if battler.effects[PBEffects::Encore]>0 + # No moves that can be chosen (will Struggle instead) + usable = false + battler.eachMoveWithIndex do |m,i| + next if !pbCanChooseMove?(idxBattler,i,false) + usable = true + break + end + return usable + end + + #============================================================================= + # Use sub-menus to choose an action, and register it if is allowed + #============================================================================= + # Returns true if a choice was made, false if cancelled. + def pbFightMenu(idxBattler) + # Auto-use Encored move or no moves choosable, so auto-use Struggle + return pbAutoChooseMove(idxBattler) if !pbCanShowFightMenu?(idxBattler) + # Battle Palace only + return true if pbAutoFightMenu(idxBattler) + # Regular move selection + ret = false + @scene.pbFightMenu(idxBattler,pbCanMegaEvolve?(idxBattler)) { |cmd| + case cmd + when -1 # Cancel + when -2 # Toggle Mega Evolution + pbToggleRegisteredMegaEvolution(idxBattler) + next false + when -3 # Shift + pbUnregisterMegaEvolution(idxBattler) + pbRegisterShift(idxBattler) + ret = true + else # Chose a move to use + next false if cmd<0 || !@battlers[idxBattler].moves[cmd] || + @battlers[idxBattler].moves[cmd].id<=0 + next false if !pbRegisterMove(idxBattler,cmd) + next false if !singleBattle? && + !pbChooseTarget(@battlers[idxBattler],@battlers[idxBattler].moves[cmd]) + ret = true + end + next true + } + return ret + end + + def pbAutoFightMenu(idxBattler); return false; end + + def pbChooseTarget(battler,move) + targetType = move.pbTarget(battler) + idxTarget = @scene.pbChooseTarget(battler.index,targetType) + return false if idxTarget<0 + pbRegisterTarget(battler.index,idxTarget) + return true + end + + def pbItemMenu(idxBattler,firstAction) + if !@internalBattle + pbDisplay(_INTL("Items can't be used here.")) + return false + end + ret = false + @scene.pbItemMenu(idxBattler,firstAction) { |item,useType,idxPkmn,idxMove,itemScene| + next false if item<0 + battler = pkmn = nil + case useType + when 1, 2, 6, 7 # Use on Pokémon/Pokémon's move + next false if !ItemHandlers.hasBattleUseOnPokemon(item) + battler = pbFindBattler(idxPkmn,idxBattler) + pkmn = pbParty(idxBattler)[idxPkmn] + next false if !pbCanUseItemOnPokemon?(item,pkmn,battler,itemScene) + when 3, 8 # Use on battler + next false if !ItemHandlers.hasBattleUseOnBattler(item) + battler = pbFindBattler(idxPkmn,idxBattler) + pkmn = battler.pokemon if battler + next false if !pbCanUseItemOnPokemon?(item,pkmn,battler,itemScene) + when 4, 9 # Poké Balls + next false if idxPkmn<0 + battler = @battlers[idxPkmn] + pkmn = battler.pokemon if battler + when 5, 10 # No target (Poké Doll, Guard Spec., Launcher items) + battler = @battlers[idxBattler] + pkmn = battler.pokemon if battler + else + next false + end + next false if !pkmn + next false if !ItemHandlers.triggerCanUseInBattle(item, + pkmn,battler,idxMove,firstAction,self,itemScene) + next false if !pbRegisterItem(idxBattler,item,idxPkmn,idxMove) + ret = true + next true + } + return ret + end + + def pbPartyMenu(idxBattler) + ret = -1 + if @debug + ret = @battleAI.pbDefaultChooseNewEnemy(idxBattler,pbParty(idxBattler)) + else + ret = pbPartyScreen(idxBattler,false,true,true) + end + return ret>=0 + end + + def pbRunMenu(idxBattler) + # Regardless of succeeding or failing to run, stop choosing actions + return pbRun(idxBattler)!=0 + end + + def pbCallMenu(idxBattler) + return pbRegisterCall(idxBattler) + end + + def pbDebugMenu + # NOTE: This doesn't do anything yet. Maybe you can write your own debugging + # options! + end + + #============================================================================= + # Command phase + #============================================================================= + def pbCommandPhase + @scene.pbBeginCommandPhase + # Reset choices if commands can be shown + @battlers.each_with_index do |b,i| + next if !b + pbClearChoice(i) if pbCanShowCommands?(i) + end + # Reset choices to perform Mega Evolution if it wasn't done somehow + for side in 0...2 + @megaEvolution[side].each_with_index do |megaEvo,i| + @megaEvolution[side][i] = -1 if megaEvo>=0 + end + end + # Choose actions for the round (player first, then AI) + pbCommandPhaseLoop(true) # Player chooses their actions + return if @decision!=0 # Battle ended, stop choosing actions + pbCommandPhaseLoop(false) # AI chooses their actions + end + + def pbCommandPhaseLoop(isPlayer) + # NOTE: Doing some things (e.g. running, throwing a Poké Ball) takes up all + # your actions in a round. + actioned = [] + idxBattler = -1 + loop do + break if @decision!=0 # Battle ended, stop choosing actions + idxBattler += 1 + break if idxBattler>=@battlers.length + next if !@battlers[idxBattler] || pbOwnedByPlayer?(idxBattler)!=isPlayer + next if @choices[idxBattler][0]!=:None # Action is forced, can't choose one + next if !pbCanShowCommands?(idxBattler) # Action is forced, can't choose one + # AI controls this battler + if @controlPlayer || !pbOwnedByPlayer?(idxBattler) + @battleAI.pbDefaultChooseEnemyCommand(idxBattler) + next + end + # Player chooses an action + actioned.push(idxBattler) + commandsEnd = false # Whether to cancel choosing all other actions this round + loop do + cmd = pbCommandMenu(idxBattler,actioned.length==1) + # If being Sky Dropped, can't do anything except use a move + if cmd>0 && @battlers[idxBattler].effects[PBEffects::SkyDrop]>=0 + pbDisplay(_INTL("Sky Drop won't let {1} go!",@battlers[idxBattler].pbThis(true))) + next + end + case cmd + when 0 # Fight + break if pbFightMenu(idxBattler) + when 1 # Bag + if pbItemMenu(idxBattler,actioned.length==1) + commandsEnd = true if pbItemUsesAllActions?(@choices[idxBattler][1]) + break + end + when 2 # Pokémon + break if pbPartyMenu(idxBattler) + when 3 # Run + # NOTE: "Run" is only an available option for the first battler the + # player chooses an action for in a round. Attempting to run + # from battle prevents you from choosing any other actions in + # that round. + if pbRunMenu(idxBattler) + commandsEnd = true + break + end + when 4 # Call + break if pbCallMenu(idxBattler) + when -2 # Debug + pbDebugMenu + next + when -1 # Go back to previous battler's action choice + next if actioned.length<=1 + actioned.pop # Forget this battler was done + idxBattler = actioned.last-1 + pbCancelChoice(idxBattler+1) # Clear the previous battler's choice + actioned.pop # Forget the previous battler was done + break + end + pbCancelChoice(idxBattler) + end + break if commandsEnd + end + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/003_Battle/011_Battle_Phase_Attack.rb b/Data/Scripts/011_Battle/003_Battle/011_Battle_Phase_Attack.rb new file mode 100644 index 000000000..d1b7e89fd --- /dev/null +++ b/Data/Scripts/011_Battle/003_Battle/011_Battle_Phase_Attack.rb @@ -0,0 +1,190 @@ +class PokeBattle_Battle + #============================================================================= + # Attack phase actions + #============================================================================= + # Quick Claw, Custap Berry's "X let it move first!" message. + def pbAttackPhasePriorityChangeMessages + pbPriority.each do |b| + if b.effects[PBEffects::PriorityAbility] && b.abilityActive? + BattleHandlers.triggerPriorityBracketUseAbility(b.ability,b,self) + elsif b.effects[PBEffects::PriorityItem] && b.itemActive? + BattleHandlers.triggerPriorityBracketUseItem(b.item,b,self) + end + end + end + + def pbAttackPhaseCall + pbPriority.each do |b| + next unless @choices[b.index][0]==:Call && !b.fainted? + b.lastMoveFailed = false # Counts as a successful move for Stomping Tantrum + pbCall(b.index) + end + end + + def pbPursuit(idxSwitcher) + @switching = true + pbPriority.each do |b| + next if b.fainted? || !b.opposes?(idxSwitcher) # Shouldn't hit an ally + next if b.movedThisRound? || !pbChoseMoveFunctionCode?(b.index,"088") # Pursuit + # Check whether Pursuit can be used + next unless pbMoveCanTarget?(b.index,idxSwitcher,@choices[b.index][2].target) + next unless pbCanChooseMove?(b.index,@choices[b.index][1],false) + next if b.status==PBStatuses::SLEEP || b.status==PBStatuses::FROZEN + next if b.effects[PBEffects::SkyDrop]>=0 + next if b.hasActiveAbility?(:TRUANT) && b.effects[PBEffects::Truant] + # Mega Evolve + if !wildBattle? || !b.opposes? + owner = pbGetOwnerIndexFromBattlerIndex(b.index) + pbMegaEvolve(b.index) if @megaEvolution[b.idxOwnSide][owner]==b.index + end + # Use Pursuit + @choices[b.index][3] = idxSwitcher # Change Pursuit's target + if b.pbProcessTurn(@choices[b.index],false) + b.effects[PBEffects::Pursuit] = true + end + break if @decision>0 || @battlers[idxSwitcher].fainted? + end + @switching = false + end + + def pbAttackPhaseSwitch + pbPriority.each do |b| + next unless @choices[b.index][0]==:SwitchOut && !b.fainted? + idxNewPkmn = @choices[b.index][1] # Party index of Pokémon to switch to + b.lastMoveFailed = false # Counts as a successful move for Stomping Tantrum + @lastMoveUser = b.index + # Switching message + pbMessageOnRecall(b) + # Pursuit interrupts switching + pbPursuit(b.index) + return if @decision>0 + # Switch Pokémon + pbRecallAndReplace(b.index,idxNewPkmn) + b.pbEffectsOnSwitchIn(true) + end + end + + def pbAttackPhaseItems + pbPriority.each do |b| + next unless @choices[b.index][0]==:UseItem && !b.fainted? + b.lastMoveFailed = false # Counts as a successful move for Stomping Tantrum + item = @choices[b.index][1] + next if !item || item<=0 + useType = pbGetItemData(item,ITEM_BATTLE_USE) + next if !useType + case useType + when 1, 2, 6, 7 # Use on Pokémon/Pokémon's move + pbUseItemOnPokemon(item,@choices[b.index][2],b) if @choices[b.index][2]>=0 + when 3, 8 # Use on battler + pbUseItemOnBattler(item,@choices[b.index][2],b) + when 4, 9 # Use Poké Ball + pbUsePokeBallInBattle(item,@choices[b.index][2],b) + when 5, 10 # Use directly + pbUseItemInBattle(item,@choices[b.index][2],b) + end + return if @decision>0 + end +# pbCalculatePriority if NEWEST_BATTLE_MECHANICS + end + + def pbAttackPhaseMegaEvolution + pbPriority.each do |b| + next if wildBattle? && b.opposes? + next unless @choices[b.index][0]==:UseMove && !b.fainted? + owner = pbGetOwnerIndexFromBattlerIndex(b.index) + next if @megaEvolution[b.idxOwnSide][owner]!=b.index + pbMegaEvolve(b.index) + end + end + + def pbAttackPhaseMoves + # Show charging messages (Focus Punch) + pbPriority.each do |b| + next unless @choices[b.index][0]==:UseMove && !b.fainted? + next if b.movedThisRound? + @choices[b.index][2].pbDisplayChargeMessage(b) + end + # Main move processing loop + loop do + priority = pbPriority + # Forced to go next + advance = false + priority.each do |b| + next unless b.effects[PBEffects::MoveNext] && !b.fainted? + next unless @choices[b.index][0]==:UseMove || @choices[b.index][0]==:Shift + next if b.movedThisRound? + advance = b.pbProcessTurn(@choices[b.index]) + break if advance + end + return if @decision>0 + next if advance + # Regular priority order + priority.each do |b| + next if b.effects[PBEffects::Quash]>0 || b.fainted? + next unless @choices[b.index][0]==:UseMove || @choices[b.index][0]==:Shift + next if b.movedThisRound? + advance = b.pbProcessTurn(@choices[b.index]) + break if advance + end + return if @decision>0 + next if advance + # Quashed + quashLevel = 0 + loop do + quashLevel += 1 + moreQuash = false + priority.each do |b| + moreQuash = true if b.effects[PBEffects::Quash]>quashLevel + next unless b.effects[PBEffects::Quash]==quashLevel && !b.fainted? + next unless @choices[b.index][0]==:UseMove || @choices[b.index][0]==:Shift + next if b.movedThisRound? + advance = b.pbProcessTurn(@choices[b.index]) + break + end + break if advance || !moreQuash + end + return if @decision>0 + next if advance + # Check for all done + priority.each do |b| + if !b.fainted? && !b.movedThisRound? + advance = true if @choices[b.index][0]==:UseMove || @choices[b.index][0]==:Shift + end + break if advance + end + next if advance + # All Pokémon have moved; end the loop + break + end + end + + #============================================================================= + # Attack phase + #============================================================================= + def pbAttackPhase + @scene.pbBeginAttackPhase + # Reset certain effects + @battlers.each_with_index do |b,i| + next if !b + b.turnCount += 1 if !b.fainted? + @successStates[i].clear + if @choices[i][0]!=:UseMove && @choices[i][0]!=:Shift && @choices[i][0]!=:SwitchOut + b.effects[PBEffects::DestinyBond] = false + b.effects[PBEffects::Grudge] = false + end + b.effects[PBEffects::Rage] = false if !pbChoseMoveFunctionCode?(i,"093") # Rage + end + PBDebug.log("") + # Calculate move order for this round + pbCalculatePriority(true) + # Perform actions + pbAttackPhasePriorityChangeMessages + pbAttackPhaseCall + pbAttackPhaseSwitch + return if @decision>0 + pbAttackPhaseItems + return if @decision>0 + pbAttackPhaseMegaEvolution + pbAttackPhaseMoves + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/003_Battle/012_Battle_Phase_EndOfRound.rb b/Data/Scripts/011_Battle/003_Battle/012_Battle_Phase_EndOfRound.rb new file mode 100644 index 000000000..04fb7e061 --- /dev/null +++ b/Data/Scripts/011_Battle/003_Battle/012_Battle_Phase_EndOfRound.rb @@ -0,0 +1,667 @@ +class PokeBattle_Battle + #============================================================================= + # Decrement effect counters + #============================================================================= + def pbEORCountDownBattlerEffect(priority,effect) + priority.each do |b| + next if b.fainted? || b.effects[effect]==0 + b.effects[effect] -= 1 + yield b if block_given? && b.effects[effect]==0 + end + end + + def pbEORCountDownSideEffect(side,effect,msg) + if @sides[side].effects[effect]>0 + @sides[side].effects[effect] -= 1 + pbDisplay(msg) if @sides[side].effects[effect]==0 + end + end + + def pbEORCountDownFieldEffect(effect,msg) + if @field.effects[effect]>0 + @field.effects[effect] -= 1 + if @field.effects[effect]==0 + pbDisplay(msg) + if effect==PBEffects::MagicRoom + pbPriority(true).each { |b| b.pbItemTerrainStatBoostCheck } + end + end + end + end + + #============================================================================= + # End Of Round weather + #============================================================================= + def pbEORWeather(priority) + # NOTE: Primordial weather doesn't need to be checked here, because if it + # could wear off here, it will have worn off already. + # Count down weather duration + @field.weatherDuration -= 1 if @field.weatherDuration>0 + # Weather wears off + if @field.weatherDuration==0 + case @field.weather + when PBWeather::Sun + pbDisplay(_INTL("The sunlight faded.")) + when PBWeather::Rain + pbDisplay(_INTL("The rain stopped.")) + when PBWeather::Sandstorm + pbDisplay(_INTL("The sandstorm subsided.")) + when PBWeather::Hail + pbDisplay(_INTL("The hail stopped.")) + when PBWeather::ShadowSky + pbDisplay(_INTL("The shadow sky faded.")) + end + @field.weather = PBWeather::None + # Check for form changes caused by the weather changing + eachBattler { |b| b.pbCheckFormOnWeatherChange } + # Start up the default weather + pbStartWeather(nil,@field.defaultWeather) if @field.defaultWeather!=PBWeather::None + return if @field.weather==PBWeather::None + end + # Weather continues + pbCommonAnimation(PBWeather.animationName(@field.weather)) + case @field.weather +# when PBWeather::Sun; pbDisplay(_INTL("The sunlight is strong.")) +# when PBWeather::Rain; pbDisplay(_INTL("Rain continues to fall.")) + when PBWeather::Sandstorm; pbDisplay(_INTL("The sandstorm is raging.")) + when PBWeather::Hail; pbDisplay(_INTL("The hail is crashing down.")) +# when PBWeather::HarshSun; pbDisplay(_INTL("The sunlight is extremely harsh.")) +# when PBWeather::HeavyRain; pbDisplay(_INTL("It is raining heavily.")) +# when PBWeather::StrongWinds; pbDisplay(_INTL("The wind is strong.")) + when PBWeather::ShadowSky; pbDisplay(_INTL("The shadow sky continues.")); + end + # Effects due to weather + curWeather = pbWeather + priority.each do |b| + # Weather-related abilities + if b.abilityActive? + BattleHandlers.triggerEORWeatherAbility(b.ability,curWeather,b,self) + b.pbFaint if b.fainted? + end + # Weather damage + # NOTE: + case curWeather + when PBWeather::Sandstorm + next if !b.takesSandstormDamage? + pbDisplay(_INTL("{1} is buffeted by the sandstorm!",b.pbThis)) + @scene.pbDamageAnimation(b) + b.pbReduceHP(b.totalhp/16,false) + b.pbItemHPHealCheck + b.pbFaint if b.fainted? + when PBWeather::Hail + next if !b.takesHailDamage? + pbDisplay(_INTL("{1} is buffeted by the hail!",b.pbThis)) + @scene.pbDamageAnimation(b) + b.pbReduceHP(b.totalhp/16,false) + b.pbItemHPHealCheck + b.pbFaint if b.fainted? + when PBWeather::ShadowSky + next if !b.takesShadowSkyDamage? + pbDisplay(_INTL("{1} is hurt by the shadow sky!",b.pbThis)) + @scene.pbDamageAnimation(b) + b.pbReduceHP(b.totalhp/16,false) + b.pbItemHPHealCheck + b.pbFaint if b.fainted? + end + end + end + + #============================================================================= + # End Of Round terrain + #============================================================================= + def pbEORTerrain + # Count down terrain duration + @field.terrainDuration -= 1 if @field.terrainDuration>0 + # Terrain wears off + if @field.terrain!=PBBattleTerrains::None && @field.terrainDuration==0 + case @field.terrain + when PBBattleTerrains::Electric + pbDisplay(_INTL("The electric current disappeared from the battlefield!")) + when PBBattleTerrains::Grassy + pbDisplay(_INTL("The grass disappeared from the battlefield!")) + when PBBattleTerrains::Misty + pbDisplay(_INTL("The mist disappeared from the battlefield!")) + when PBBattleTerrains::Psychic + pbDisplay(_INTL("The weirdness disappeared from the battlefield!")) + end + @field.terrain = PBBattleTerrains::None + # Start up the default terrain + pbStartTerrain(nil,@field.defaultTerrain,false) if @field.defaultTerrain!=PBBattleTerrains::None + return if @field.terrain==PBBattleTerrains::None + end + # Terrain continues + pbCommonAnimation(PBBattleTerrains.animationName(@field.terrain)) + case @field.terrain + when PBBattleTerrains::Electric; pbDisplay(_INTL("An electric current is running across the battlefield.")) + when PBBattleTerrains::Grassy; pbDisplay(_INTL("Grass is covering the battlefield.")) + when PBBattleTerrains::Misty; pbDisplay(_INTL("Mist is swirling about the battlefield.")) + when PBBattleTerrains::Psychic; pbDisplay(_INTL("The battlefield is weird.")) + end + end + + #============================================================================= + # End Of Round shift distant battlers to middle positions + #============================================================================= + def pbEORShiftDistantBattlers + # Move battlers around if none are near to each other + # NOTE: This code assumes each side has a maximum of 3 battlers on it, and + # is not generalised to larger side sizes. + if !singleBattle? + swaps = [] # Each element is an array of two battler indices to swap + for side in 0...2 + next if pbSideSize(side)==1 # Only battlers on sides of size 2+ need to move + # Check if any battler on this side is near any battler on the other side + anyNear = false + eachSameSideBattler(side) do |b| + eachOtherSideBattler(b) do |otherB| + next if !nearBattlers?(otherB.index,b.index) + anyNear = true + break + end + break if anyNear + end + break if anyNear + # No battlers on this side are near any battlers on the other side; try + # to move them + # NOTE: If we get to here (assuming both sides are of size 3 or less), + # there is definitely only 1 able battler on this side, so we + # don't need to worry about multiple battlers trying to move into + # the same position. If you add support for a side of size 4+, + # this code will need revising to account for that, as well as to + # add more complex code to ensure battlers will end up near each + # other. + eachSameSideBattler(side) do |b| + # Get the position to move to + pos = -1 + case pbSideSize(side) + when 2; pos = [2,3,0,1][b.index] # The unoccupied position + when 3; pos = (side==0) ? 2 : 3 # The centre position + end + next if pos<0 + # Can't move if the same trainer doesn't control both positions + idxOwner = pbGetOwnerIndexFromBattlerIndex(b.index) + next if pbGetOwnerIndexFromBattlerIndex(pos)!=idxOwner + swaps.push([b.index,pos]) + end + end + # Move battlers around + swaps.each do |pair| + next if pbSideSize(pair[0])==2 && swaps.length>1 + next if !pbSwapBattlers(pair[0],pair[1]) + case pbSideSize(side) + when 2 + pbDisplay(_INTL("{1} moved across!",@battlers[pair[1]].pbThis)) + when 3 + pbDisplay(_INTL("{1} moved to the center!",@battlers[pair[1]].pbThis)) + end + end + end + end + + #============================================================================= + # End Of Round phase + #============================================================================= + def pbEndOfRoundPhase + PBDebug.log("") + PBDebug.log("[End of round]") + @endOfRound = true + @scene.pbBeginEndOfRoundPhase + pbCalculatePriority # recalculate speeds + priority = pbPriority(true) # in order of fastest -> slowest speeds only + # Weather + pbEORWeather(priority) + # Future Sight/Doom Desire + @positions.each_with_index do |pos,idxPos| + next if !pos || pos.effects[PBEffects::FutureSightCounter]==0 + pos.effects[PBEffects::FutureSightCounter] -= 1 + next if pos.effects[PBEffects::FutureSightCounter]>0 + next if !@battlers[idxPos] || @battlers[idxPos].fainted? # No target + moveUser = nil + eachBattler do |b| + next if b.opposes?(pos.effects[PBEffects::FutureSightUserIndex]) + next if b.pokemonIndex!=pos.effects[PBEffects::FutureSightUserPartyIndex] + moveUser = b + break + end + next if moveUser && moveUser.index==idxPos # Target is the user + if !moveUser # User isn't in battle, get it from the party + party = pbParty(pos.effects[PBEffects::FutureSightUserIndex]) + pkmn = party[pos.effects[PBEffects::FutureSightUserPartyIndex]] + if pkmn && pkmn.able? + moveUser = PokeBattle_Battler.new(self,pos.effects[PBEffects::FutureSightUserIndex]) + moveUser.pbInitDummyPokemon(pkmn,pos.effects[PBEffects::FutureSightUserPartyIndex]) + end + end + next if !moveUser # User is fainted + move = pos.effects[PBEffects::FutureSightMove] + pbDisplay(_INTL("{1} took the {2} attack!",@battlers[idxPos].pbThis,PBMoves.getName(move))) + # NOTE: Future Sight failing against the target here doesn't count towards + # Stomping Tantrum. + userLastMoveFailed = moveUser.lastMoveFailed + @futureSight = true + moveUser.pbUseMoveSimple(move,idxPos) + @futureSight = false + moveUser.lastMoveFailed = userLastMoveFailed + @battlers[idxPos].pbFaint if @battlers[idxPos].fainted? + pos.effects[PBEffects::FutureSightCounter] = 0 + pos.effects[PBEffects::FutureSightMove] = 0 + pos.effects[PBEffects::FutureSightUserIndex] = -1 + pos.effects[PBEffects::FutureSightUserPartyIndex] = -1 + end + # Wish + @positions.each_with_index do |pos,idxPos| + next if !pos || pos.effects[PBEffects::Wish]==0 + pos.effects[PBEffects::Wish] -= 1 + next if pos.effects[PBEffects::Wish]>0 + next if !@battlers[idxPos] || !@battlers[idxPos].canHeal? + wishMaker = pbThisEx(idxPos,pos.effects[PBEffects::WishMaker]) + @battlers[idxPos].pbRecoverHP(pos.effects[PBEffects::WishAmount]) + pbDisplay(_INTL("{1}'s wish came true!",wishMaker)) + end + # Sea of Fire damage (Fire Pledge + Grass Pledge combination) + curWeather = pbWeather + for side in 0...2 + next if sides[side].effects[PBEffects::SeaOfFire]==0 + next if curWeather==PBWeather::Rain || curWeather==PBWeather::HeavyRain + @battle.pbCommonAnimation("SeaOfFire") if side==0 + @battle.pbCommonAnimation("SeaOfFireOpp") if side==1 + priority.each do |b| + next if b.opposes?(side) + next if !b.takesIndirectDamage? || b.pbHasType?(:FIRE) + oldHP = b.hp + @scene.pbDamageAnimation(b) + b.pbReduceHP(b.totalhp/8,false) + pbDisplay(_INTL("{1} is hurt by the sea of fire!",b.pbThis)) + b.pbItemHPHealCheck + b.pbAbilitiesOnDamageTaken(oldHP) + b.pbFaint if b.fainted? + end + end + # Status-curing effects/abilities and HP-healing items + priority.each do |b| + next if b.fainted? + # Grassy Terrain (healing) + if @field.terrain==PBBattleTerrains::Grassy && b.affectedByTerrain? && b.canHeal? + PBDebug.log("[Lingering effect] Grassy Terrain heals #{b.pbThis(true)}") + b.pbRecoverHP(b.totalhp/16) + pbDisplay(_INTL("{1}'s HP was restored.",b.pbThis)) + end + # Healer, Hydration, Shed Skin + BattleHandlers.triggerEORHealingAbility(b.ability,b,self) if b.abilityActive? + # Black Sludge, Leftovers + BattleHandlers.triggerEORHealingItem(b.item,b,self) if b.itemActive? + end + # Aqua Ring + priority.each do |b| + next if !b.effects[PBEffects::AquaRing] + next if !b.canHeal? + hpGain = b.totalhp/16 + hpGain = (hpGain*1.3).floor if b.hasActiveItem?(:BIGROOT) + hpGain = b.pbRecoverHP(hpGain) + pbDisplay(_INTL("Aqua Ring restored {1}'s HP!",b.pbThis(true))) + end + # Ingrain + priority.each do |b| + next if !b.effects[PBEffects::Ingrain] + next if !b.canHeal? + hpGain = b.totalhp/16 + hpGain = (hpGain*1.3).floor if b.hasActiveItem?(:BIGROOT) + hpGain = b.pbRecoverHP(hpGain) + pbDisplay(_INTL("{1} absorbed nutrients with its roots!",b.pbThis)) + end + # Leech Seed + priority.each do |b| + next if b.effects[PBEffects::LeechSeed]<0 + next if !b.takesIndirectDamage? + recipient = @battlers[b.effects[PBEffects::LeechSeed]] + next if !recipient || recipient.fainted? + oldHP = b.hp + oldHPRecipient = recipient.hp + pbCommonAnimation("LeechSeed",recipient,b) + hpLoss = b.pbReduceHP(b.totalhp/8) + recipient.pbRecoverHPFromDrain(hpLoss,b, + _INTL("{1}'s health is sapped by Leech Seed!",b.pbThis)) + recipient.pbAbilitiesOnDamageTaken(oldHPRecipient) if recipient.hp0 + b.effects[PBEffects::Toxic] += 1 + b.effects[PBEffects::Toxic] = 15 if b.effects[PBEffects::Toxic]>15 + end + if b.hasActiveAbility?(:POISONHEAL) + if b.canHeal? + pbCommonAnimation("Poison",b) + pbShowAbilitySplash(b) + b.pbRecoverHP(b.totalhp/8) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + pbDisplay(_INTL("{1}'s HP was restored.",b.pbThis)) + else + pbDisplay(_INTL("{1}'s {2} restored its HP.",b.pbThis,b.abilityName)) + end + pbHideAbilitySplash(b) + end + elsif b.takesIndirectDamage? + oldHP = b.hp + dmg = (b.statusCount==0) ? b.totalhp/8 : b.totalhp*b.effects[PBEffects::Toxic]/16 + b.pbContinueStatus { b.pbReduceHP(dmg,false) } + b.pbItemHPHealCheck + b.pbAbilitiesOnDamageTaken(oldHP) + b.pbFaint if b.fainted? + end + end + # Damage from burn + priority.each do |b| + next if b.status!=PBStatuses::BURN || !b.takesIndirectDamage? + oldHP = b.hp + dmg = (NEWEST_BATTLE_MECHANICS) ? b.totalhp/16 : b.totalhp/8 + dmg = (dmg/2.0).round if b.hasActiveAbility?(:HEATPROOF) + b.pbContinueStatus { b.pbReduceHP(dmg,false) } + b.pbItemHPHealCheck + b.pbAbilitiesOnDamageTaken(oldHP) + b.pbFaint if b.fainted? + end + # Damage from sleep (Nightmare) + priority.each do |b| + b.effects[PBEffects::Nightmare] = false if !b.asleep? + next if !b.effects[PBEffects::Nightmare] || !b.takesIndirectDamage? + oldHP = b.hp + b.pbReduceHP(b.totalhp/4) + pbDisplay(_INTL("{1} is locked in a nightmare!",b.pbThis)) + b.pbItemHPHealCheck + b.pbAbilitiesOnDamageTaken(oldHP) + b.pbFaint if b.fainted? + end + # Curse + priority.each do |b| + next if !b.effects[PBEffects::Curse] || !b.takesIndirectDamage? + oldHP = b.hp + b.pbReduceHP(b.totalhp/4) + pbDisplay(_INTL("{1} is afflicted by the curse!",b.pbThis)) + b.pbItemHPHealCheck + b.pbAbilitiesOnDamageTaken(oldHP) + b.pbFaint if b.fainted? + end + # Trapping attacks (Bind/Clamp/Fire Spin/Magma Storm/Sand Tomb/Whirlpool/Wrap) + priority.each do |b| + next if b.fainted? || b.effects[PBEffects::Trapping]==0 + b.effects[PBEffects::Trapping] -= 1 + moveName = PBMoves.getName(b.effects[PBEffects::TrappingMove]) + if b.effects[PBEffects::Trapping]==0 + pbDisplay(_INTL("{1} was freed from {2}!",b.pbThis,moveName)) + else + trappingMove = b.effects[PBEffects::TrappingMove] + if isConst?(trappingMove,PBMoves,:BIND); pbCommonAnimation("Bind",b) + elsif isConst?(trappingMove,PBMoves,:CLAMP); pbCommonAnimation("Clamp",b) + elsif isConst?(trappingMove,PBMoves,:FIRESPIN); pbCommonAnimation("FireSpin",b) + elsif isConst?(trappingMove,PBMoves,:MAGMASTORM); pbCommonAnimation("MagmaStorm",b) + elsif isConst?(trappingMove,PBMoves,:SANDTOMB); pbCommonAnimation("SandTomb",b) + elsif isConst?(trappingMove,PBMoves,:WRAP); pbCommonAnimation("Wrap",b) + elsif isConst?(trappingMove,PBMoves,:INFESTATION); pbCommonAnimation("Infestation",b) + else; pbCommonAnimation("Wrap",b) + end + if b.takesIndirectDamage? + hpLoss = (NEWEST_BATTLE_MECHANICS) ? b.totalhp/8 : b.totalhp/16 + if @battlers[b.effects[PBEffects::TrappingUser]].hasActiveItem?(:BINDINGBAND) + hpLoss = (NEWEST_BATTLE_MECHANICS) ? b.totalhp/6 : b.totalhp/8 + end + @scene.pbDamageAnimation(b) + b.pbReduceHP(hpLoss,false) + pbDisplay(_INTL("{1} is hurt by {2}!",b.pbThis,moveName)) + b.pbItemHPHealCheck + # NOTE: No need to call pbAbilitiesOnDamageTaken as b can't switch out. + b.pbFaint if b.fainted? + end + end + end + # Taunt + pbEORCountDownBattlerEffect(priority,PBEffects::Taunt) { |battler| + pbDisplay(_INTL("{1}'s taunt wore off!",battler.pbThis)) + } + # Encore + priority.each do |b| + next if b.fainted? || b.effects[PBEffects::Encore]==0 + idxEncoreMove = b.pbEncoredMoveIndex + if idxEncoreMove>=0 + b.effects[PBEffects::Encore] -= 1 + if b.effects[PBEffects::Encore]==0 || b.moves[idxEncoreMove].pp==0 + b.effects[PBEffects::Encore] = 0 + pbDisplay(_INTL("{1}'s encore ended!",b.pbThis)) + end + else + PBDebug.log("[End of effect] #{b.pbThis}'s encore ended (encored move no longer known)") + b.effects[PBEffects::Encore] = 0 + b.effects[PBEffects::EncoreMove] = 0 + end + end + # Disable/Cursed Body + pbEORCountDownBattlerEffect(priority,PBEffects::Disable) { |battler| + battler.effects[PBEffects::DisableMove] = 0 + pbDisplay(_INTL("{1} is no longer disabled!",battler.pbThis)) + } + # Magnet Rise + pbEORCountDownBattlerEffect(priority,PBEffects::MagnetRise) { |battler| + pbDisplay(_INTL("{1}'s electromagnetism wore off!",battler.pbThis)) + } + # Telekinesis + pbEORCountDownBattlerEffect(priority,PBEffects::Telekinesis) { |battler| + pbDisplay(_INTL("{1} was freed from the telekinesis!",battler.pbThis)) + } + # Heal Block + pbEORCountDownBattlerEffect(priority,PBEffects::HealBlock) { |battler| + pbDisplay(_INTL("{1}'s Heal Block wore off!",battler.pbThis)) + } + # Embargo + pbEORCountDownBattlerEffect(priority,PBEffects::Embargo) { |battler| + pbDisplay(_INTL("{1} can use items again!",battler.pbThis)) + battler.pbItemTerrainStatBoostCheck + } + # Yawn + pbEORCountDownBattlerEffect(priority,PBEffects::Yawn) { |battler| + if battler.pbCanSleepYawn? + PBDebug.log("[Lingering effect] #{battler.pbThis} fell asleep because of Yawn") + battler.pbSleep + end + } + # Perish Song + perishSongUsers = [] + priority.each do |b| + next if b.fainted? || b.effects[PBEffects::PerishSong]==0 + b.effects[PBEffects::PerishSong] -= 1 + pbDisplay(_INTL("{1}'s perish count fell to {2}!",b.pbThis,b.effects[PBEffects::PerishSong])) + if b.effects[PBEffects::PerishSong]==0 + perishSongUsers.push(b.effects[PBEffects::PerishSongUser]) + b.pbReduceHP(b.hp) + end + b.pbItemHPHealCheck + b.pbFaint if b.fainted? + end + if perishSongUsers.length>0 + # If all remaining Pokemon fainted by a Perish Song triggered by a single side + if (perishSongUsers.find_all { |idxBattler| opposes?(idxBattler) }.length==perishSongUsers.length) || + (perishSongUsers.find_all { |idxBattler| !opposes?(idxBattler) }.length==perishSongUsers.length) + pbJudgeCheckpoint(@battlers[perishSongUsers[0]]) + end + end + # Check for end of battle + if @decision>0 + pbGainExp + return + end + for side in 0...2 + # Reflect + pbEORCountDownSideEffect(side,PBEffects::Reflect, + _INTL("{1}'s Reflect wore off!",@battlers[side].pbTeam)) + # Light Screen + pbEORCountDownSideEffect(side,PBEffects::LightScreen, + _INTL("{1}'s Light Screen wore off!",@battlers[side].pbTeam)) + # Safeguard + pbEORCountDownSideEffect(side,PBEffects::Safeguard, + _INTL("{1} is no longer protected by Safeguard!",@battlers[side].pbTeam)) + # Mist + pbEORCountDownSideEffect(side,PBEffects::Mist, + _INTL("{1} is no longer protected by mist!",@battlers[side].pbTeam)) + # Tailwind + pbEORCountDownSideEffect(side,PBEffects::Tailwind, + _INTL("{1}'s Tailwind petered out!",@battlers[side].pbTeam)) + # Lucky Chant + pbEORCountDownSideEffect(side,PBEffects::LuckyChant, + _INTL("{1}'s Lucky Chant wore off!",@battlers[side].pbTeam)) + # Pledge Rainbow + pbEORCountDownSideEffect(side,PBEffects::Rainbow, + _INTL("The rainbow on {1}'s side disappeared!",@battlers[side].pbTeam(true))) + # Pledge Sea of Fire + pbEORCountDownSideEffect(side,PBEffects::SeaOfFire, + _INTL("The sea of fire around {1} disappeared!",@battlers[side].pbTeam(true))) + # Pledge Swamp + pbEORCountDownSideEffect(side,PBEffects::Swamp, + _INTL("The swamp around {1} disappeared!",@battlers[side].pbTeam(true))) + # Aurora Veil + pbEORCountDownSideEffect(side,PBEffects::AuroraVeil, + _INTL("{1}'s Aurora Veil wore off!",@battlers[side].pbTeam(true))) + end + # Trick Room + pbEORCountDownFieldEffect(PBEffects::TrickRoom, + _INTL("The twisted dimensions returned to normal!")) + # Gravity + pbEORCountDownFieldEffect(PBEffects::Gravity, + _INTL("Gravity returned to normal!")) + # Water Sport + pbEORCountDownFieldEffect(PBEffects::WaterSportField, + _INTL("The effects of Water Sport have faded.")) + # Mud Sport + pbEORCountDownFieldEffect(PBEffects::MudSportField, + _INTL("The effects of Mud Sport have faded.")) + # Wonder Room + pbEORCountDownFieldEffect(PBEffects::WonderRoom, + _INTL("Wonder Room wore off, and Defense and Sp. Def stats returned to normal!")) + # Magic Room + pbEORCountDownFieldEffect(PBEffects::MagicRoom, + _INTL("Magic Room wore off, and held items' effects returned to normal!")) + # End of terrains + pbEORTerrain + priority.each do |b| + next if b.fainted? + # Hyper Mode (Shadow Pokémon) + if b.inHyperMode? + if pbRandom(100)<10 + b.pokemon.hypermode = false + b.pokemon.adjustHeart(-50) + pbDisplay(_INTL("{1} came to its senses!",b.pbThis)) + else + pbDisplay(_INTL("{1} is in Hyper Mode!",b.pbThis)) + end + end + # Uproar + if b.effects[PBEffects::Uproar]>0 + b.effects[PBEffects::Uproar] -= 1 + if b.effects[PBEffects::Uproar]==0 + pbDisplay(_INTL("{1} calmed down.",b.pbThis)) + else + pbDisplay(_INTL("{1} is making an uproar!",b.pbThis)) + end + end + # Slow Start's end message + if b.effects[PBEffects::SlowStart]>0 + b.effects[PBEffects::SlowStart] -= 1 + if b.effects[PBEffects::SlowStart]==0 + pbDisplay(_INTL("{1} finally got its act together!",b.pbThis)) + end + end + # Bad Dreams, Moody, Speed Boost + BattleHandlers.triggerEOREffectAbility(b.ability,b,self) if b.abilityActive? + # Flame Orb, Sticky Barb, Toxic Orb + BattleHandlers.triggerEOREffectItem(b.item,b,self) if b.itemActive? + # Harvest, Pickup + BattleHandlers.triggerEORGainItemAbility(b.ability,b,self) if b.abilityActive? + end + pbGainExp + return if @decision>0 + # Form checks + priority.each { |b| b.pbCheckForm(true) } + # Switch Pokémon in if possible + pbEORSwitch + return if @decision>0 + # In battles with at least one side of size 3+, move battlers around if none + # are near to any foes + pbEORShiftDistantBattlers + # Try to make Trace work, check for end of primordial weather + priority.each { |b| b.pbContinualAbilityChecks } + # Reset/count down battler-specific effects (no messages) + eachBattler do |b| + b.effects[PBEffects::BanefulBunker] = false + b.effects[PBEffects::Charge] -= 1 if b.effects[PBEffects::Charge]>0 + b.effects[PBEffects::Counter] = -1 + b.effects[PBEffects::CounterTarget] = -1 + b.effects[PBEffects::Electrify] = false + b.effects[PBEffects::Endure] = false + b.effects[PBEffects::FirstPledge] = 0 + b.effects[PBEffects::Flinch] = false + b.effects[PBEffects::FocusPunch] = false + b.effects[PBEffects::FollowMe] = 0 + b.effects[PBEffects::HelpingHand] = false + b.effects[PBEffects::HyperBeam] -= 1 if b.effects[PBEffects::HyperBeam]>0 + b.effects[PBEffects::KingsShield] = false + b.effects[PBEffects::LaserFocus] -= 1 if b.effects[PBEffects::LaserFocus]>0 + if b.effects[PBEffects::LockOn]>0 # Also Mind Reader + b.effects[PBEffects::LockOn] -= 1 + b.effects[PBEffects::LockOnPos] = -1 if b.effects[PBEffects::LockOn]==0 + end + b.effects[PBEffects::MagicBounce] = false + b.effects[PBEffects::MagicCoat] = false + b.effects[PBEffects::MirrorCoat] = -1 + b.effects[PBEffects::MirrorCoatTarget] = -1 + b.effects[PBEffects::Powder] = false + b.effects[PBEffects::Prankster] = false + b.effects[PBEffects::PriorityAbility] = false + b.effects[PBEffects::PriorityItem] = false + b.effects[PBEffects::Protect] = false + b.effects[PBEffects::RagePowder] = false + b.effects[PBEffects::Roost] = false + b.effects[PBEffects::Snatch] = 0 + b.effects[PBEffects::SpikyShield] = false + b.effects[PBEffects::Spotlight] = 0 + b.effects[PBEffects::ThroatChop] -= 1 if b.effects[PBEffects::ThroatChop]>0 + b.lastHPLost = 0 + b.lastHPLostFromFoe = 0 + b.tookDamage = false + b.tookPhysicalHit = false + b.lastRoundMoveFailed = b.lastMoveFailed + b.lastAttacker.clear + b.lastFoeAttacker.clear + end + # Reset/count down side-specific effects (no messages) + for side in 0...2 + @sides[side].effects[PBEffects::CraftyShield] = false + if !@sides[side].effects[PBEffects::EchoedVoiceUsed] + @sides[side].effects[PBEffects::EchoedVoiceCounter] = 0 + end + @sides[side].effects[PBEffects::EchoedVoiceUsed] = false + @sides[side].effects[PBEffects::MatBlock] = false + @sides[side].effects[PBEffects::QuickGuard] = false + @sides[side].effects[PBEffects::Round] = false + @sides[side].effects[PBEffects::WideGuard] = false + end + # Reset/count down field-specific effects (no messages) + @field.effects[PBEffects::IonDeluge] = false + @field.effects[PBEffects::FairyLock] -= 1 if @field.effects[PBEffects::FairyLock]>0 + @field.effects[PBEffects::FusionBolt] = false + @field.effects[PBEffects::FusionFlare] = false + @endOfRound = false + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/003_PBBattleTerrains.rb b/Data/Scripts/011_Battle/003_PBBattleTerrains.rb new file mode 100644 index 000000000..5410ae558 --- /dev/null +++ b/Data/Scripts/011_Battle/003_PBBattleTerrains.rb @@ -0,0 +1,25 @@ +# These are in-battle terrain effects caused by moves like Electric Terrain. +begin + module PBBattleTerrains + None = 0 + Electric = 1 + Grassy = 2 + Misty = 3 + Psychic = 4 + + def self.animationName(terrain) + case terrain + when Electric; return "ElectricTerrain" + when Grassy; return "GrassyTerrain" + when Misty; return "MistyTerrain" + when Psychic; return "PsychicTerrain" + end + return nil + end + end + +rescue Exception + if $!.is_a?(SystemExit) || "#{$!.class}"=="Reset" + raise $! + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/004_AI/001_PokeBattle_AI.rb b/Data/Scripts/011_Battle/004_AI/001_PokeBattle_AI.rb new file mode 100644 index 000000000..fd206b723 --- /dev/null +++ b/Data/Scripts/011_Battle/004_AI/001_PokeBattle_AI.rb @@ -0,0 +1,69 @@ +# AI skill levels: +# 0: Wild Pokémon +# 1-31: Basic trainer (young/inexperienced) +# 32-47: Some skill +# 48-99: High skill +# 100+: Best trainers (Gym Leaders, Elite Four, Champion) +# NOTE: A trainer's skill value can range from 0-255, but by default only four +# distinct skill levels exist. The skill value is typically the same as +# the trainer's base money value. +module PBTrainerAI + # Minimum skill level to be in each AI category. + def self.minimumSkill; return 1; end + def self.mediumSkill; return 32; end + def self.highSkill; return 48; end + def self.bestSkill; return 100; end +end + + + +class PokeBattle_AI + def initialize(battle) + @battle = battle + end + + def pbAIRandom(x); return rand(x); end + + def pbStdDev(choices) + sum = 0 + n = 0 + choices.each do |c| + sum += c[1] + n += 1 + end + return 0 if n<2 + mean = sum.to_f/n.to_f + varianceTimesN = 0 + choices.each do |c| + next if c[1]<=0 + deviation = c[1].to_f-mean + varianceTimesN += deviation*deviation + end + # Using population standard deviation + # [(n-1) makes it a sample std dev, would be 0 with only 1 sample] + return Math.sqrt(varianceTimesN/n) + end + + #============================================================================= + # Decide whether the opponent should Mega Evolve their Pokémon + #============================================================================= + def pbEnemyShouldMegaEvolve?(idxBattler) + battler = @battle.battlers[idxBattler] + if @battle.pbCanMegaEvolve?(idxBattler) # Simple "always should if possible" + PBDebug.log("[AI] #{battler.pbThis} (#{idxBattler}) will Mega Evolve") + return true + end + return false + end + + #============================================================================= + # Choose an action + #============================================================================= + def pbDefaultChooseEnemyCommand(idxBattler) + return if pbEnemyShouldUseItem?(idxBattler) + return if pbEnemyShouldWithdraw?(idxBattler) + return if @battle.pbAutoFightMenu(idxBattler) + @battle.pbRegisterMegaEvolution(idxBattler) if pbEnemyShouldMegaEvolve?(idxBattler) + pbChooseMoves(idxBattler) + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/004_AI/002_AI_Item.rb b/Data/Scripts/011_Battle/004_AI/002_AI_Item.rb new file mode 100644 index 000000000..62c857d26 --- /dev/null +++ b/Data/Scripts/011_Battle/004_AI/002_AI_Item.rb @@ -0,0 +1,182 @@ +class PokeBattle_AI + #============================================================================= + # Decide whether the opponent should use an item on the Pokémon + #============================================================================= + def pbEnemyShouldUseItem?(idxBattler) + user = @battle.battlers[idxBattler] + item, idxTarget = pbEnemyItemToUse(idxBattler) + return false if item==0 + # Determine target of item (always the Pokémon choosing the action) + useType = pbGetItemData(item,ITEM_BATTLE_USE) + if useType && (useType==1 || useType==6) # Use on Pokémon + idxTarget = @battle.battlers[idxTarget].pokemonIndex # Party Pokémon + end + # Register use of item + @battle.pbRegisterItem(idxBattler,item,idxTarget) + PBDebug.log("[AI] #{user.pbThis} (#{user.index}) will use item #{PBItems.getName(item)}") + return true + end + + # NOTE: The AI will only consider using an item on the Pokémon it's currently + # choosing an action for. + def pbEnemyItemToUse(idxBattler) + return 0 if !@internalBattle + items = @battle.pbGetOwnerItems(idxBattler) + return 0 if !items || items.length==0 + # Determine target of item (always the Pokémon choosing the action) + idxTarget = idxBattler # Battler using the item + battler = @battle.battlers[idxTarget] + pkmn = battler.pokemon + # Item categories + hpItems = { + :POTION => 20, + :SUPERPOTION => 50, + :HYPERPOTION => 200, + :MAXPOTION => 999, + :BERRYJUICE => 20, + :SWEETHEART => 20, + :FRESHWATER => 50, + :SODAPOP => 60, + :LEMONADE => 80, + :MOOMOOMILK => 100, + :ORANBERRY => 10, + :SITRUSBERRY => battler.totalhp/4, + :ENERGYPOWDER => 50, + :ENERGYROOT => 200 + } + hpItems[:RAGECANDYBAR] = 20 if !NEWEST_BATTLE_MECHANICS + fullRestoreItems = [ + :FULLRESTORE + ] + oneStatusItems = [ # Preferred over items that heal all status problems + :AWAKENING,:CHESTOBERRY,:BLUEFLUTE, + :ANTIDOTE,:PECHABERRY, + :BURNHEAL,:RAWSTBERRY, + :PARALYZEHEAL,:PARLYZHEAL,:CHERIBERRY, + :ICEHEAL,:ASPEARBERRY + ] + allStatusItems = [ + :FULLHEAL,:LAVACOOKIE,:OLDGATEAU,:CASTELIACONE,:LUMIOSEGALETTE, + :SHALOURSABLE,:BIGMALASADA,:LUMBERRY,:HEALPOWDER + ] + allStatusItems.push(:RAGECANDYBAR) if NEWEST_BATTLE_MECHANICS + xItems = { + :XATTACK => [PBStats::ATTACK,(NEWEST_BATTLE_MECHANICS) ? 2 : 1], + :XATTACK2 => [PBStats::ATTACK,2], + :XATTACK3 => [PBStats::ATTACK,3], + :XATTACK6 => [PBStats::ATTACK,6], + :XDEFENSE => [PBStats::DEFENSE,(NEWEST_BATTLE_MECHANICS) ? 2 : 1], + :XDEFENSE2 => [PBStats::DEFENSE,2], + :XDEFENSE3 => [PBStats::DEFENSE,3], + :XDEFENSE6 => [PBStats::DEFENSE,6], + :XDEFEND => [PBStats::DEFENSE,(NEWEST_BATTLE_MECHANICS) ? 2 : 1], + :XDEFEND2 => [PBStats::DEFENSE,2], + :XDEFEND3 => [PBStats::DEFENSE,3], + :XDEFEND6 => [PBStats::DEFENSE,6], + :XSPATK => [PBStats::SPATK,(NEWEST_BATTLE_MECHANICS) ? 2 : 1], + :XSPATK2 => [PBStats::SPATK,2], + :XSPATK3 => [PBStats::SPATK,3], + :XSPATK6 => [PBStats::SPATK,6], + :XSPECIAL => [PBStats::SPATK,(NEWEST_BATTLE_MECHANICS) ? 2 : 1], + :XSPECIAL2 => [PBStats::SPATK,2], + :XSPECIAL3 => [PBStats::SPATK,3], + :XSPECIAL6 => [PBStats::SPATK,6], + :XSPDEF => [PBStats::SPDEF,(NEWEST_BATTLE_MECHANICS) ? 2 : 1], + :XSPDEF2 => [PBStats::SPDEF,2], + :XSPDEF3 => [PBStats::SPDEF,3], + :XSPDEF6 => [PBStats::SPDEF,6], + :XSPEED => [PBStats::SPEED,(NEWEST_BATTLE_MECHANICS) ? 2 : 1], + :XSPEED2 => [PBStats::SPEED,2], + :XSPEED3 => [PBStats::SPEED,3], + :XSPEED6 => [PBStats::SPEED,6], + :XACCURACY => [PBStats::ACCURACY,(NEWEST_BATTLE_MECHANICS) ? 2 : 1], + :XACCURACY2 => [PBStats::ACCURACY,2], + :XACCURACY3 => [PBStats::ACCURACY,3], + :XACCURACY6 => [PBStats::ACCURACY,6] + } + losthp = battler.totalhp-battler.hp + preferFullRestore = (battler.hp<=battler.totalhp*2/3 && + (battler.status!=PBStatuses::NONE || battler.effects[PBEffects::Confusion]>0)) + # Find all usable items + usableHPItems = [] + usableStatusItems = [] + usableXItems = [] + items.each do |i| + next if !i || i==0 + next if !@battle.pbCanUseItemOnPokemon?(i,pkmn,battler,@battle.scene,false) + next if !ItemHandlers.triggerCanUseInBattle(i,pkmn,battler,nil, + false,self,@battle.scene,false) + checkedItem = false + # Log HP healing items + if losthp>0 + hpItems.each do |item, power| + next if !isConst?(i,PBItems,item) + checkedItem = true + usableHPItems.push([i,5,power]) + end + next if checkedItem + end + # Log Full Restores (HP healer and status curer) + if losthp>0 || battler.status!=PBStatuses::NONE + fullRestoreItems.each do |item| + next if !isConst?(i,PBItems,item) + checkedItem = true + usableHPItems.push([i,(preferFullRestore) ? 3 : 7,999]) + usableStatusItems.push([i,(preferFullRestore) ? 3 : 9]) + end + next if checkedItem + end + # Log single status-curing items + if battler.status!=PBStatuses::NONE + oneStatusItems.each do |item| + next if !isConst?(i,PBItems,item) + checkedItem = true + usableStatusItems.push([i,5]) + end + next if checkedItem + # Log Full Heal-type items + allStatusItems.each do |item| + next if !isConst?(i,PBItems,item) + checkedItem = true + usableStatusItems.push([i,7]) + end + next if checkedItem + end + # Log stat-raising items + xItems.each do |item, data| + next if !isConst?(i,PBItems,item) + checkedItem = true + usableXItems.push([i,battler.stages[data[0]],data[1]]) + end + next if checkedItem + end + # Prioritise using a HP restoration item + if usableHPItems.length>0 && (battler.hp<=battler.totalhp/4 || + (battler.hp<=battler.totalhp/2 && pbAIRandom(100)<30)) + usableHPItems.sort! { |a,b| (a[1]==b[1]) ? a[2]<=>b[2] : a[1]<=>b[1] } + prevItem = nil + usableHPItems.each do |i| + return i[0],idxTarget if i[2]>=losthp + prevItem = i + end + return prevItem[0],idxTarget + end + # Next prioritise using a status-curing item + if usableStatusItems.length>0 && pbAIRandom(100)<40 + usableStatusItems.sort! { |a,b| a[1]<=>b[1] } + return usableStatusItems[0][0],idxTarget + end + # Next try using an X item + if usableXItems.length>0 && pbAIRandom(100)<30 + usableXItems.sort! { |a,b| (a[1]==b[1]) ? a[2]<=>b[2] : a[1]<=>b[1] } + prevItem = nil + usableXItems.each do |i| + break if prevItem && i[1]>prevItem[1] + return i[0],idxTarget if i[1]+i[2]>=6 + prevItem = i + end + return prevItem[0],idxTarget + end + return 0 + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/004_AI/003_AI_Switch.rb b/Data/Scripts/011_Battle/004_AI/003_AI_Switch.rb new file mode 100644 index 000000000..98d194863 --- /dev/null +++ b/Data/Scripts/011_Battle/004_AI/003_AI_Switch.rb @@ -0,0 +1,182 @@ +class PokeBattle_AI + #============================================================================= + # Decide whether the opponent should switch Pokémon + #============================================================================= + def pbEnemyShouldWithdraw?(idxBattler) + return pbEnemyShouldWithdrawEx?(idxBattler,false) + end + + def pbEnemyShouldWithdrawEx?(idxBattler,forceSwitch) + return false if @battle.wildBattle? + shouldSwitch = forceSwitch + batonPass = -1 + moveType = -1 + skill = @battle.pbGetOwnerFromBattlerIndex(idxBattler).skill || 0 + battler = @battle.battlers[idxBattler] + # If Pokémon is within 6 levels of the foe, and foe's last move was + # super-effective and powerful + if !shouldSwitch && battler.turnCount>0 && skill>=PBTrainerAI.highSkill + target = battler.pbDirectOpposing(true) + if !target.fainted? && target.lastMoveUsed>0 && + (target.level-battler.level).abs<=6 + moveData = pbGetMoveData(target.lastMoveUsed) + moveType = moveData[MOVE_TYPE] + typeMod = pbCalcTypeMod(moveType,target,battler) + if PBTypes.superEffective?(typeMod) && moveData[MOVE_BASE_DAMAGE]>50 + switchChance = (moveData[MOVE_BASE_DAMAGE]>70) ? 30 : 20 + shouldSwitch = (pbAIRandom(100)=5 + shouldSwitch = true + end + # Pokémon is Perish Songed and has Baton Pass + if skill>=PBTrainerAI.highSkill && battler.effects[PBEffects::PerishSong]==1 + battler.eachMoveWithIndex do |m,i| + next if m.function!="0ED" # Baton Pass + next if !@battle.pbCanChooseMove?(idxBattler,i,false) + batonPass = i + break + end + end + # Pokémon will faint because of bad poisoning at the end of this round, but + # would survive at least one more round if it were regular poisoning instead + if battler.status==PBStatuses::POISON && battler.statusCount>0 && + skill>=PBTrainerAI.highSkill + toxicHP = battler.totalhp/16 + nextToxicHP = toxicHP*(battler.effects[PBEffects::Toxic]+1) + if battler.hp<=nextToxicHP && battler.hp>toxicHP*2 + shouldSwitch = true if pbAIRandom(100)<80 + end + end + # Pokémon is Encored into an unfavourable move + if battler.effects[PBEffects::Encore]>0 && skill>=PBTrainerAI.mediumSkill + idxEncoredMove = battler.pbEncoredMoveIndex + if idxEncoredMove>=0 + scoreSum = 0 + scoreCount = 0 + battler.eachOpposing do |b| + scoreSum += pbGetMoveScore(battler.moves[idxEncoredMove],battler,b,skill) + scoreCount += 1 + end + if scoreCount>0 && scoreSum/scoreCount<=20 + shouldSwitch = true if pbAIRandom(100)<80 + end + end + end + # If there is a single foe and it is resting after Hyper Beam or is + # Truanting (i.e. free turn) + if @battle.pbSideSize(battler.index+1)==1 && + !battler.pbDirectOpposing.fainted? && skill>=PBTrainerAI.highSkill + opp = battler.pbDirectOpposing + if opp.effects[PBEffects::HyperBeam]>0 || + (opp.hasActiveAbility?(:TRUANT) && opp.effects[PBEffects::Truant]) + shouldSwitch = false if pbAIRandom(100)<80 + end + end + # Sudden Death rule - I'm not sure what this means + if @battle.rules["suddendeath"] && battler.turnCount>0 + if battler.hp<=battler.totalhp/4 && pbAIRandom(100)<30 + shouldSwitch = true + elsif battler.hp<=battler.totalhp/2 && pbAIRandom(100)<80 + shouldSwitch = true + end + end + # Pokémon is about to faint because of Perish Song + if battler.effects[PBEffects::PerishSong]==1 + shouldSwitch = true + end + if shouldSwitch + list = [] + @battle.pbParty(idxBattler).each_with_index do |pkmn,i| + next if !@battle.pbCanSwitch?(idxBattler,i) + # If perish count is 1, it may be worth it to switch + # even with Spikes, since Perish Song's effect will end + if battler.effects[PBEffects::PerishSong]!=1 + # Will contain effects that recommend against switching + spikes = battler.pbOwnSide.effects[PBEffects::Spikes] + # Don't switch to this if too little HP + if spikes>0 + spikesDmg = [8,6,4][spikes-1] + if pkmn.hp<=pkmn.totalhp/spikesDmg + next if !pkmn.hasType?(:FLYING) && !pkmn.hasActiveAbility?(:LEVITATE) + end + end + end + # moveType is the type of the target's last used move + if moveType>=0 && PBTypes.ineffective?(pbCalcTypeMod(moveType,battler,battler)) + weight = 65 + typeMod = pbCalcTypeModPokemon(pkmn,battler.pbDirectOpposing(true)) + if PBTypes.superEffective?(typeMod.to_f/PBTypeEffectivenesss::NORMAL_EFFECTIVE) + # Greater weight if new Pokemon's type is effective against target + weight = 85 + end + list.unshift(i) if pbAIRandom(100)=0 && PBTypes.resistant?(pbCalcTypeMod(moveType,battler,battler)) + weight = 40 + typeMod = pbCalcTypeModPokemon(pkmn,battler.pbDirectOpposing(true)) + if PBTypes.superEffective?(typeMod.to_f/PBTypeEffectivenesss::NORMAL_EFFECTIVE) + # Greater weight if new Pokemon's type is effective against target + weight = 60 + end + list.unshift(i) if pbAIRandom(100)0 + if batonPass>=0 && @battle.pbRegisterMove(idxBattler,batonPass,false) + PBDebug.log("[AI] #{battler.pbThis} (#{idxBattler}) will use Baton Pass to avoid Perish Song") + return true + end + if @battle.pbRegisterSwitch(idxBattler,list[0]) + PBDebug.log("[AI] #{battler.pbThis} (#{idxBattler}) will switch with " + + "#{@battle.pbParty(idxBattler)[list[0]].name}") + return + end + end + end + return false + end + + #============================================================================= + # Choose a replacement Pokémon + #============================================================================= + def pbDefaultChooseNewEnemy(idxBattler,party) + enemies = [] + party.each_with_index do |p,i| + enemies.push(i) if @battle.pbCanSwitchLax?(idxBattler,i) + end + return -1 if enemies.length==0 + return pbChooseBestNewEnemy(idxBattler,party,enemies) + end + + def pbChooseBestNewEnemy(idxBattler,party,enemies) + return -1 if !enemies || enemies.length==0 + best = -1 + bestSum = 0 + movesData = pbLoadMovesData + enemies.each do |i| + pkmn = party[i] + sum = 0 + pkmn.moves.each do |m| + next if m.id==0 + moveData = movesData[m.id] + next if moveData[MOVE_BASE_DAMAGE]==0 + @battle.battlers[idxBattler].eachOpposing do |b| + bTypes = b.pbTypes(true) + sum += PBTypes.getCombinedEffectiveness(moveData[MOVE_TYPE], + bTypes[0],bTypes[1],bTypes[2]) + end + end + if best==-1 || sum>bestSum + best = i + bestSum = sum + end + end + return best + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/004_AI/004_AI_Move.rb b/Data/Scripts/011_Battle/004_AI/004_AI_Move.rb new file mode 100644 index 000000000..60a938905 --- /dev/null +++ b/Data/Scripts/011_Battle/004_AI/004_AI_Move.rb @@ -0,0 +1,287 @@ +class PokeBattle_AI + #============================================================================= + # Main move-choosing method (moves with higher scores are more likely to be + # chosen) + #============================================================================= + def pbChooseMoves(idxBattler) + user = @battle.battlers[idxBattler] + wildBattler = (@battle.wildBattle? && @battle.opposes?(idxBattler)) + skill = 0 + if !wildBattler + skill = @battle.pbGetOwnerFromBattlerIndex(user.index).skill || 0 + end + # Get scores and targets for each move + # NOTE: A move is only added to the choices array if it has a non-zero + # score. + choices = [] + user.eachMoveWithIndex do |m,i| + next if !@battle.pbCanChooseMove?(idxBattler,i,false) + if wildBattler + pbRegisterMoveWild(user,i,choices) + else + pbRegisterMoveTrainer(user,i,choices,skill) + end + end + # Figure out useful information about the choices + totalScore = 0 + maxScore = 0 + choices.each do |c| + totalScore += c[1] + maxScore = c[1] if maxScore=0 + logMsg += ", " if i=PBTrainerAI.highSkill && maxScore>100 + stDev = pbStdDev(choices) + if stDev>=40 && pbAIRandom(100)<90 + preferredMoves = [] + choices.each do |c| + next if c[1]<200 && c[1]0 + m = preferredMoves[pbAIRandom(preferredMoves.length)] + PBDebug.log("[AI] #{user.pbThis} (#{user.index}) prefers #{user.moves[m[0]].name}") + @battle.pbRegisterMove(idxBattler,m[0],false) + @battle.pbRegisterTarget(idxBattler,m[2]) if m[2]>=0 + return + end + end + end + # Decide whether all choices are bad, and if so, try switching instead + if !wildBattler && skill>=PBTrainerAI.highSkill + badMoves = false + if (maxScore<=20 && user.turnCount>2) || + (maxScore<=40 && user.turnCount>5) + badMoves = true if pbAIRandom(100)<80 + end + if !badMoves && totalScore<100 && user.turnCount>1 + badMoves = true + choices.each do |c| + next if !user.moves[c[0]].damagingMove? + badMoves = false + break + end + badMoves = false if badMoves && pbAIRandom(100)<10 + end + if badMoves && pbEnemyShouldWithdrawEx?(idxBattler,true) + if $INTERNAL + PBDebug.log("[AI] #{user.pbThis} (#{user.index}) will switch due to terrible moves") + end + return + end + end + # Randomly choose a move to use + if choices.length==0 + # If there are no calculated choices, use Struggle (or an Encored move) + @battle.pbAutoChooseMove(idxBattler) + else + # Randomly choose a move from the choices and register it + randNum = pbAIRandom(totalScore) + choices.each do |c| + randNum -= c[1] + next if randNum>=0 + @battle.pbRegisterMove(idxBattler,c[0],false) + @battle.pbRegisterTarget(idxBattler,c[2]) if c[2]>=0 + break + end + end + # Log the result + if @battle.choices[idxBattler][2] + PBDebug.log("[AI] #{user.pbThis} (#{user.index}) will use #{@battle.choices[user.index][2].name}") + end + end + + #============================================================================= + # Get scores for the given move against each possible target + #============================================================================= + # Wild Pokémon choose their moves randomly. + def pbRegisterMoveWild(user,idxMove,choices) + choices.push([idxMove,100,-1]) # Move index, score, target + end + + # Trainer Pokémon calculate how much they want to use each of their moves. + def pbRegisterMoveTrainer(user,idxMove,choices,skill) + move = user.moves[idxMove] + targetType = move.pbTarget(user) + if PBTargets.multipleTargets?(targetType) + # If move affects multiple battlers and you don't choose a particular one + totalScore = 0 + @battle.eachBattler do |b| + next if !@battle.pbMoveCanTarget?(user.index,b.index,targetType) + score = pbGetMoveScore(move,user,b,skill) + totalScore += ((user.opposes?(b)) ? score : -score) + end + choices.push([idxMove,totalScore,-1]) if totalScore>0 + elsif PBTargets.noTargets?(targetType) + # If move has no targets, affects the user, a side or the whole field + score = pbGetMoveScore(move,user,user,skill) + choices.push([idxMove,score,-1]) if score>0 + else + # If move affects one battler and you have to choose which one + scoresAndTargets = [] + @battle.eachBattler do |b| + next if !@battle.pbMoveCanTarget?(user.index,b.index,targetType) + next if PBTargets.canChooseFoeTarget?(targetType) && !user.opposes?(b) + score = pbGetMoveScore(move,user,b,skill) + scoresAndTargets.push([score,b.index]) if score>0 + end + if scoresAndTargets.length>0 + # Get the one best target for the move + scoresAndTargets.sort! { |a,b| b[0]<=>a[0] } + choices.push([idxMove,scoresAndTargets[0][0],scoresAndTargets[0][1]]) + end + end + end + + #============================================================================= + # Get a score for the given move being used against the given target + #============================================================================= + def pbGetMoveScore(move,user,target,skill=100) + skill = PBTrainerAI.minimumSkill if skill=PBTrainerAI.mediumSkill + # Prefer damaging moves if AI has no more Pokémon or AI is less clever + if @battle.pbAbleNonActiveCount(user.idxOwnSide)==0 + if !(skill>=PBTrainerAI.highSkill && @battle.pbAbleNonActiveCount(target.idxOwnSide)>0) + if move.statusMove? + score /= 1.5 + elsif target.hp<=target.totalhp/2 + score *= 1.5 + end + end + end + # Don't prefer attacking the target if they'd be semi-invulnerable + if skill>=PBTrainerAI.highSkill && move.accuracy>0 && + (target.semiInvulnerable? || target.effects[PBEffects::SkyDrop]>=0) + miss = true + miss = false if user.hasActiveAbility?(:NOGUARD) || target.hasActiveAbility?(:NOGUARD) + if miss && pbRoughStat(user,PBStats::SPEED,skill)>pbRoughStat(target,PBStats::SPEED,skill) + # Knows what can get past semi-invulnerability + if target.effects[PBEffects::SkyDrop]>=0 + miss = false if move.hitsFlyingTargets? + else + if target.inTwoTurnAttack?("0C9","0CC","0CE") # Fly, Bounce, Sky Drop + miss = false if move.hitsFlyingTargets? + elsif target.inTwoTurnAttack?("0CA") # Dig + miss = false if move.hitsDiggingTargets? + elsif target.inTwoTurnAttack?("0CB") # Dive + miss = false if move.hitsDivingTargets? + end + end + end + score -= 80 if miss + end + # Pick a good move for the Choice items + if user.hasActiveItem?([:CHOICEBAND,:CHOICESPECS,:CHOICESCARF]) + if move.baseDamage>=60; score += 60 + elsif move.damagingMove?; score += 30 + elsif move.function=="0F2"; score += 70 # Trick + else; score -= 60 + end + end + # If user is asleep, prefer moves that are usable while asleep + if user.status==PBStatuses::SLEEP && !move.usableWhenAsleep? + hasSleepMove = false + user.eachMove do |m| + next unless m.usableWhenAsleep? + score -= 60 + break + end + end + # If user is frozen, prefer a move that can thaw the user + if user.status==PBStatuses::FROZEN + if move.thawsUser? + score += 40 + else + user.eachMove do |m| + next unless m.thawsUser? + score -= 60 + break + end + end + end + # If target is frozen, don't prefer moves that could thaw them + if target.status==PBStatuses::FROZEN + user.eachMove do |m| + next if m.thawsUser? + score -= 60 + break + end + end + end + # Adjust score based on how much damage it can deal + if move.damagingMove? + score = pbGetMoveScoreDamage(score,move,user,target,skill) + else # Status moves + # Don't prefer attacks which don't deal damage + score -= 10 + # Account for accuracy of move + accuracy = pbRoughAccuracy(move,user,target,skill) + score *= accuracy/100.0 + score = 0 if score<=10 && skill>=PBTrainerAI.highSkill + end + score = score.to_i + score = 0 if score<0 + return score + end + + #============================================================================= + # Add to a move's score based on how much damage it will deal (as a percentage + # of the target's current HP) + #============================================================================= + def pbGetMoveScoreDamage(score,move,user,target,skill) + # Don't prefer moves that are ineffective because of abilities or effects + return 0 if score<=0 || pbCheckMoveImmunity(score,move,user,target,skill) + # Calculate how much damage the move will do (roughly) + baseDmg = pbMoveBaseDamage(move,user,target,skill) + realDamage = pbRoughDamage(move,user,target,skill,baseDmg) + # Account for accuracy of move + accuracy = pbRoughAccuracy(move,user,target,skill) + realDamage *= accuracy/100.0 + # Two-turn attacks waste 2 turns to deal one lot of damage + if move.chargingTurnMove? || move.function=="0C2" # Hyper Beam + realDamage *= 2/3 # Not halved because semi-invulnerable during use or hits first turn + end + # Prefer flinching external effects (note that move effects which cause + # flinching are dealt with in the function code part of score calculation) + if skill>=PBTrainerAI.mediumSkill + if !target.hasActiveAbility?(:INNERFOCUS) && + !target.hasActiveAbility?(:SHIELDDUST) && + target.effects[PBEffects::Substitute]==0 + canFlinch = false + if move.canKingsRock? && user.hasActiveItem?([:KINGSROCK,:RAZORFANG]) + canFlinch = true + end + if user.hasActiveAbility?(:STENCH) && !move.flinchingMove? + canFlinch = true + end + realDamage *= 1.3 if canFlinch + end + end + # Convert damage to percentage of target's remaining HP + damagePercentage = realDamage*100.0/target.hp + # Don't prefer weak attacks +# damagePercentage /= 2 if damagePercentage<20 + # Prefer damaging attack if level difference is significantly high + damagePercentage *= 1.2 if user.level-10>target.level + # Adjust score + damagePercentage = 120 if damagePercentage>120 # Treat all lethal moves the same + damagePercentage += 40 if damagePercentage>100 # Prefer moves likely to be lethal + score += damagePercentage.to_i + return score + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/004_AI/005_AI_Move_EffectScores.rb b/Data/Scripts/011_Battle/004_AI/005_AI_Move_EffectScores.rb new file mode 100644 index 000000000..4658dc36c --- /dev/null +++ b/Data/Scripts/011_Battle/004_AI/005_AI_Move_EffectScores.rb @@ -0,0 +1,3075 @@ +class PokeBattle_AI + #============================================================================= + # Get a score for the given move based on its effect + #============================================================================= + def pbGetMoveScoreFunctionCode(score,move,user,target,skill=100) + case move.function + #--------------------------------------------------------------------------- + when "000" # No extra effect + #--------------------------------------------------------------------------- + when "001" + score -= 95 + score = 0 if skill>=PBTrainerAI.highSkill + #--------------------------------------------------------------------------- + when "002" # Struggle + #--------------------------------------------------------------------------- + when "003" + if target.pbCanSleep?(user,false) + score += 30 + if skill>=PBTrainerAI.mediumSkill + score -= 30 if target.effects[PBEffects::Yawn]>0 + end + if skill>=PBTrainerAI.highSkill + score -= 30 if target.hasActiveAbility?(:MARVELSCALE) + end + if skill>=PBTrainerAI.bestSkill + if target.pbHasMoveFunction?("011","0B4") # Snore, Sleep Talk + score -= 50 + end + end + else + if skill>=PBTrainerAI.mediumSkill + score -= 90 if move.statusMove? + end + end + #--------------------------------------------------------------------------- + when "004" + if target.effects[PBEffects::Yawn]>0 || !target.pbCanSleep?(user,false) + score -= 90 if skill>=PBTrainerAI.mediumSkill + else + score += 30 + if skill>=PBTrainerAI.highSkill + score -= 30 if target.hasActiveAbility?(:MARVELSCALE) + end + if skill>=PBTrainerAI.bestSkill + if target.pbHasMoveFunction?("011","0B4") # Snore, Sleep Talk + score -= 50 + end + end + end + #--------------------------------------------------------------------------- + when "005", "006", "0BE" + if target.pbCanPoison?(user,false) + score += 30 + if skill>=PBTrainerAI.mediumSkill + score += 30 if target.hp<=target.totalhp/4 + score += 50 if target.hp<=target.totalhp/8 + score -= 40 if target.effects[PBEffects::Yawn]>0 + end + if skill>=PBTrainerAI.highSkill + score += 10 if pbRoughStat(target,PBStats::DEFENSE,skill)>100 + score += 10 if pbRoughStat(target,PBStats::SPDEF,skill)>100 + score -= 40 if target.hasActiveAbility?([:GUTS,:MARVELSCALE,:TOXICBOOST]) + end + else + if skill>=PBTrainerAI.mediumSkill + score -= 90 if move.statusMove? + end + end + #--------------------------------------------------------------------------- + when "007", "008", "009", "0C5" + if target.pbCanParalyze?(user,false) && + !(skill>=PBTrainerAI.mediumSkill && + isConst?(move.id,PBMoves,:THUNDERWAVE) && + PBTypes.ineffective?(pbCalcTypeMod(move.type,user,target))) + score += 30 + if skill>=PBTrainerAI.mediumSkill + aspeed = pbRoughStat(user,PBStats::SPEED,skill) + ospeed = pbRoughStat(target,PBStats::SPEED,skill) + if aspeedospeed + score -= 40 + end + end + if skill>=PBTrainerAI.highSkill + score -= 40 if target.hasActiveAbility?([:GUTS,:MARVELSCALE,:QUICKFEET]) + end + else + if skill>=PBTrainerAI.mediumSkill + score -= 90 if move.statusMove? + end + end + #--------------------------------------------------------------------------- + when "00A", "00B", "0C6" + if target.pbCanBurn?(user,false) + score += 30 + if skill>=PBTrainerAI.highSkill + score -= 40 if target.hasActiveAbility?([:GUTS,:MARVELSCALE,:QUICKFEET,:FLAREBOOST]) + end + else + if skill>=PBTrainerAI.mediumSkill + score -= 90 if move.statusMove? + end + end + #--------------------------------------------------------------------------- + when "00C", "00D", "00E" + if target.pbCanFreeze?(user,false) + score += 30 + if skill>=PBTrainerAI.highSkill + score -= 20 if target.hasActiveAbility?(:MARVELSCALE) + end + else + if skill>=PBTrainerAI.mediumSkill + score -= 90 if move.statusMove? + end + end + #--------------------------------------------------------------------------- + when "00F" + score += 30 + if skill>=PBTrainerAI.highSkill + score += 30 if !target.hasActiveAbility?(:INNERFOCUS) && + target.effects[PBEffects::Substitute]==0 + end + #--------------------------------------------------------------------------- + when "010" + if skill>=PBTrainerAI.highSkill + score += 30 if !target.hasActiveAbility?(:INNERFOCUS) && + target.effects[PBEffects::Substitute]==0 + end + score += 30 if target.effects[PBEffects::Minimize] + #--------------------------------------------------------------------------- + when "011" + if user.asleep? + score += 100 # Because it can only be used while asleep + if skill>=PBTrainerAI.highSkill + score += 30 if !target.hasActiveAbility?(:INNERFOCUS) && + target.effects[PBEffects::Substitute]==0 + end + else + score -= 90 # Because it will fail here + score = 0 if skill>=PBTrainerAI.bestSkill + end + #--------------------------------------------------------------------------- + when "012" + if user.turnCount==0 + if skill>=PBTrainerAI.highSkill + score += 30 if !target.hasActiveAbility?(:INNERFOCUS) && + target.effects[PBEffects::Substitute]==0 + end + else + score -= 90 # Because it will fail here + score = 0 if skill>=PBTrainerAI.bestSkill + end + #--------------------------------------------------------------------------- + when "013", "014", "015" + if target.pbCanConfuse?(user,false) + score += 30 + else + if skill>=PBTrainerAI.mediumSkill + score -= 90 if move.statusMove? + end + end + #--------------------------------------------------------------------------- + when "016" + canattract = true + agender = user.gender + ogender = target.gender + if agender==2 || ogender==2 || agender==ogender + score -= 90; canattract = false + elsif target.effects[PBEffects::Attract]>=0 + score -= 80; canattract = false + elsif skill>=PBTrainerAI.bestSkill && target.hasActiveAbility?(:OBLIVIOUS) + score -= 80; canattract = false + end + if skill>=PBTrainerAI.highSkill + if canattract && target.hasActiveItem?(:DESTINYKNOT) && + user.pbCanAttract?(target,false) + score -= 30 + end + end + #--------------------------------------------------------------------------- + when "017" + score += 30 if target.status==PBStatuses::NONE + #--------------------------------------------------------------------------- + when "018" + case user.status + when PBStatuses::POISON + score += 40 + if skill>=PBTrainerAI.mediumSkill + if user.hp=PBTrainerAI.highSkill && + user.hp<(user.effects[PBEffects::Toxic]+1)*user.totalhp/16 + score += 60 + end + end + when PBStatuses::BURN, PBStatuses::PARALYSIS + score += 40 + else + score -= 90 + end + #--------------------------------------------------------------------------- + when "019" + statuses = 0 + @battle.pbParty(user.index).each do |pkmn| + statuses += 1 if pkmn && pkmn.status!=PBStatuses::NONE + end + if statuses==0 + score -= 80 + else + score += 20*statuses + end + #--------------------------------------------------------------------------- + when "01A" + if user.pbOwnSide.effects[PBEffects::Safeguard]>0 + score -= 80 + elsif user.status!=0 + score -= 40 + else + score += 30 + end + #--------------------------------------------------------------------------- + when "01B" + if user.status==PBStatuses::NONE + score -= 90 + else + score += 40 + end + #--------------------------------------------------------------------------- + when "01C" + if move.statusMove? + if user.statStageAtMax?(PBStats::ATTACK) + score -= 90 + else + score -= user.stages[PBStats::ATTACK]*20 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + user.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + if hasPhysicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + else + score += 20 if user.stages[PBStats::ATTACK]<0 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + user.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + score += 20 if hasPhysicalAttack + end + end + #--------------------------------------------------------------------------- + when "01D", "01E", "0C8" + if move.statusMove? + if user.statStageAtMax?(PBStats::DEFENSE) + score -= 90 + else + score -= user.stages[PBStats::DEFENSE]*20 + end + else + score += 20 if user.stages[PBStats::DEFENSE]<0 + end + #--------------------------------------------------------------------------- + when "01F" + if move.statusMove? + if user.statStageAtMax?(PBStats::SPEED) + score -= 90 + else + score -= user.stages[PBStats::SPEED]*10 + if skill>=PBTrainerAI.highSkill + aspeed = pbRoughStat(user,PBStats::SPEED,skill) + ospeed = pbRoughStat(target,PBStats::SPEED,skill) + score += 30 if aspeedospeed + end + end + else + score += 20 if user.stages[PBStats::SPEED]<0 + end + #--------------------------------------------------------------------------- + when "020" + if move.statusMove? + if user.statStageAtMax?(PBStats::SPATK) + score -= 90 + else + score -= user.stages[PBStats::SPATK]*20 + if skill>=PBTrainerAI.mediumSkill + hasSpecicalAttack = false + user.eachMove do |m| + next if !m.specialMove?(m.type) + hasSpecicalAttack = true + break + end + if hasSpecicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + else + score += 20 if user.stages[PBStats::SPATK]<0 + if skill>=PBTrainerAI.mediumSkill + hasSpecicalAttack = false + user.eachMove do |m| + next if !m.specialMove?(m.type) + hasSpecicalAttack = true + break + end + score += 20 if hasSpecicalAttack + end + end + #--------------------------------------------------------------------------- + when "021" + foundMove = false + user.eachMove do |m| + next if !isConst?(m.type,PBTypes,:ELECTRIC) || !m.damagingMove? + foundMove = true + break + end + score += 20 if foundMove + if move.statusMove? + if user.statStageAtMax?(PBStats::SPDEF) + score -= 90 + else + score -= user.stages[PBStats::SPDEF]*20 + end + else + score += 20 if user.stages[PBStats::SPDEF]<0 + end + #--------------------------------------------------------------------------- + when "022" + if move.statusMove? + if user.statStageAtMax?(PBStats::EVASION) + score -= 90 + else + score -= user.stages[PBStats::EVASION]*10 + end + else + score += 20 if user.stages[PBStats::EVASION]<0 + end + #--------------------------------------------------------------------------- + when "023" + if move.statusMove? + if user.effects[PBEffects::FocusEnergy]>=2 + score -= 80 + else + score += 30 + end + else + score += 30 if user.effects[PBEffects::FocusEnergy]<2 + end + #--------------------------------------------------------------------------- + when "024" + if user.statStageAtMax?(PBStats::ATTACK) && + user.statStageAtMax?(PBStats::DEFENSE) + score -= 90 + else + score -= user.stages[PBStats::ATTACK]*10 + score -= user.stages[PBStats::DEFENSE]*10 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + user.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + if hasPhysicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + #--------------------------------------------------------------------------- + when "025" + if user.statStageAtMax?(PBStats::ATTACK) && + user.statStageAtMax?(PBStats::DEFENSE) && + user.statStageAtMax?(PBStats::ACCURACY) + score -= 90 + else + score -= user.stages[PBStats::ATTACK]*10 + score -= user.stages[PBStats::DEFENSE]*10 + score -= user.stages[PBStats::ACCURACY]*10 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + user.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + if hasPhysicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + #--------------------------------------------------------------------------- + when "026" + score += 40 if user.turnCount==0 # Dragon Dance tends to be popular + if user.statStageAtMax?(PBStats::ATTACK) && + user.statStageAtMax?(PBStats::SPEED) + score -= 90 + else + score -= user.stages[PBStats::ATTACK]*10 + score -= user.stages[PBStats::SPEED]*10 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + user.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + if hasPhysicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + if skill>=PBTrainerAI.highSkill + aspeed = pbRoughStat(user,PBStats::SPEED,skill) + ospeed = pbRoughStat(target,PBStats::SPEED,skill) + score += 20 if aspeedospeed + end + end + #--------------------------------------------------------------------------- + when "027", "028" + if user.statStageAtMax?(PBStats::ATTACK) && + user.statStageAtMax?(PBStats::SPATK) + score -= 90 + else + score -= user.stages[PBStats::ATTACK]*10 + score -= user.stages[PBStats::SPATK]*10 + if skill>=PBTrainerAI.mediumSkill + hasDamagingAttack = false + user.eachMove do |m| + next if !m.damagingMove? + hasDamagingAttack = true + break + end + if hasDamagingAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + if move.function=="028" # Growth + score += 20 if @battle.pbWeather==PBWeather::Sun || + @battle.pbWeather==PBWeather::HarshSun + end + end + #--------------------------------------------------------------------------- + when "029" + if user.statStageAtMax?(PBStats::ATTACK) && + user.statStageAtMax?(PBStats::ACCURACY) + score -= 90 + else + score -= user.stages[PBStats::ATTACK]*10 + score -= user.stages[PBStats::ACCURACY]*10 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + user.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + if hasPhysicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + #--------------------------------------------------------------------------- + when "02A" + if user.statStageAtMax?(PBStats::DEFENSE) && + user.statStageAtMax?(PBStats::SPDEF) + score -= 90 + else + score -= user.stages[PBStats::DEFENSE]*10 + score -= user.stages[PBStats::SPDEF]*10 + end + #--------------------------------------------------------------------------- + when "02B" + if user.statStageAtMax?(PBStats::SPEED) && + user.statStageAtMax?(PBStats::SPATK) && + user.statStageAtMax?(PBStats::SPDEF) + score -= 90 + else + score -= user.stages[PBStats::SPATK]*10 + score -= user.stages[PBStats::SPDEF]*10 + score -= user.stages[PBStats::SPEED]*10 + if skill>=PBTrainerAI.mediumSkill + hasSpecicalAttack = false + user.eachMove do |m| + next if !m.specialMove?(m.type) + hasSpecicalAttack = true + break + end + if hasSpecicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + if skill>=PBTrainerAI.highSkill + aspeed = pbRoughStat(user,PBStats::SPEED,skill) + ospeed = pbRoughStat(target,PBStats::SPEED,skill) + if aspeedospeed + score += 20 + end + end + end + #--------------------------------------------------------------------------- + when "02C" + if user.statStageAtMax?(PBStats::SPATK) && + user.statStageAtMax?(PBStats::SPDEF) + score -= 90 + else + score += 40 if user.turnCount==0 # Calm Mind tends to be popular + score -= user.stages[PBStats::SPATK]*10 + score -= user.stages[PBStats::SPDEF]*10 + if skill>=PBTrainerAI.mediumSkill + hasSpecicalAttack = false + user.eachMove do |m| + next if !m.specialMove?(m.type) + hasSpecicalAttack = true + break + end + if hasSpecicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + #--------------------------------------------------------------------------- + when "02D" + PBStats.eachMainBattleStat { |s| score += 10 if user.stages[s]<0 } + if skill>=PBTrainerAI.mediumSkill + hasDamagingAttack = false + user.eachMove do |m| + next if !m.damagingMove? + hasDamagingAttack = true + break + end + score += 20 if hasDamagingAttack + end + #--------------------------------------------------------------------------- + when "02E" + if move.statusMove? + if user.statStageAtMax?(PBStats::ATTACK) + score -= 90 + else + score += 40 if user.turnCount==0 + score -= user.stages[PBStats::ATTACK]*20 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + user.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + if hasPhysicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + else + score += 10 if user.turnCount==0 + score += 20 if user.stages[PBStats::ATTACK]<0 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + user.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + score += 20 if hasPhysicalAttack + end + end + #--------------------------------------------------------------------------- + when "02F" + if move.statusMove? + if user.statStageAtMax?(PBStats::DEFENSE) + score -= 90 + else + score += 40 if user.turnCount==0 + score -= user.stages[PBStats::DEFENSE]*20 + end + else + score += 10 if user.turnCount==0 + score += 20 if user.stages[PBStats::DEFENSE]<0 + end + #--------------------------------------------------------------------------- + when "030", "031" + if move.statusMove? + if user.statStageAtMax?(PBStats::SPEED) + score -= 90 + else + score += 20 if user.turnCount==0 + score -= user.stages[PBStats::SPEED]*10 + if skill>=PBTrainerAI.highSkill + aspeed = pbRoughStat(user,PBStats::SPEED,skill) + ospeed = pbRoughStat(target,PBStats::SPEED,skill) + score += 30 if aspeedospeed + end + end + else + score += 10 if user.turnCount==0 + score += 20 if user.stages[PBStats::SPEED]<0 + end + #--------------------------------------------------------------------------- + when "032" + if move.statusMove? + if user.statStageAtMax?(PBStats::SPATK) + score -= 90 + else + score += 40 if user.turnCount==0 + score -= user.stages[PBStats::SPATK]*20 + if skill>=PBTrainerAI.mediumSkill + hasSpecicalAttack = false + user.eachMove do |m| + next if !m.specialMove?(m.type) + hasSpecicalAttack = true + break + end + if hasSpecicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + else + score += 10 if user.turnCount==0 + score += 20 if user.stages[PBStats::SPATK]<0 + if skill>=PBTrainerAI.mediumSkill + hasSpecicalAttack = false + user.eachMove do |m| + next if !m.specialMove?(m.type) + hasSpecicalAttack = true + break + end + score += 20 if hasSpecicalAttack + end + end + #--------------------------------------------------------------------------- + when "033" + if move.statusMove? + if user.statStageAtMax?(PBStats::SPDEF) + score -= 90 + else + score += 40 if user.turnCount==0 + score -= user.stages[PBStats::SPDEF]*20 + end + else + score += 10 if user.turnCount==0 + score += 20 if user.stages[PBStats::SPDEF]<0 + end + #--------------------------------------------------------------------------- + when "034" + if move.statusMove? + if user.statStageAtMax?(PBStats::EVASION) + score -= 90 + else + score += 40 if user.turnCount==0 + score -= user.stages[PBStats::EVASION]*10 + end + else + score += 10 if user.turnCount==0 + score += 20 if user.stages[PBStats::EVASION]<0 + end + #--------------------------------------------------------------------------- + when "035" + score -= user.stages[PBStats::ATTACK]*20 + score -= user.stages[PBStats::SPEED]*20 + score -= user.stages[PBStats::SPATK]*20 + score += user.stages[PBStats::DEFENSE]*10 + score += user.stages[PBStats::SPDEF]*10 + if skill>=PBTrainerAI.mediumSkill + hasDamagingAttack = false + user.eachMove do |m| + next if !m.damagingMove? + hasDamagingAttack = true + break + end + score += 20 if hasDamagingAttack + end + #--------------------------------------------------------------------------- + when "036" + if user.statStageAtMax?(PBStats::ATTACK) && + user.statStageAtMax?(PBStats::SPEED) + score -= 90 + else + score -= user.stages[PBStats::ATTACK]*10 + score -= user.stages[PBStats::SPEED]*10 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + user.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + if hasPhysicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + if skill>=PBTrainerAI.highSkill + aspeed = pbRoughStat(user,PBStats::SPEED,skill) + ospeed = pbRoughStat(target,PBStats::SPEED,skill) + score += 30 if aspeedospeed + end + end + #--------------------------------------------------------------------------- + when "037" + avgStat = 0; canChangeStat = false + PBStats.eachBattleStat do |s| + next if target.statStageAtMax?(s) + avgStat -= target.stages[s] + canChangeStat = true + end + if canChangeStat + avgStat = avgStat/2 if avgStat<0 # More chance of getting even better + score += avgStat*10 + else + score -= 90 + end + #--------------------------------------------------------------------------- + when "038" + if move.statusMove? + if user.statStageAtMax?(PBStats::DEFENSE) + score -= 90 + else + score += 40 if user.turnCount==0 + score -= user.stages[PBStats::DEFENSE]*30 + end + else + score += 10 if user.turnCount==0 + score += 30 if user.stages[PBStats::DEFENSE]<0 + end + #--------------------------------------------------------------------------- + when "039" + if move.statusMove? + if user.statStageAtMax?(PBStats::SPATK) + score -= 90 + else + score += 40 if user.turnCount==0 + score -= user.stages[PBStats::SPATK]*30 + if skill>=PBTrainerAI.mediumSkill + hasSpecicalAttack = false + user.eachMove do |m| + next if !m.specialMove?(m.type) + hasSpecicalAttack = true + break + end + if hasSpecicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + else + score += 10 if user.turnCount==0 + score += 30 if user.stages[PBStats::SPATK]<0 + if skill>=PBTrainerAI.mediumSkill + hasSpecicalAttack = false + user.eachMove do |m| + next if !m.specialMove?(m.type) + hasSpecicalAttack = true + break + end + score += 30 if hasSpecicalAttack + end + end + #--------------------------------------------------------------------------- + when "03A" + if user.statStageAtMax?(PBStats::ATTACK) || + user.hp<=user.totalhp/2 + score -= 100 + else + score += (6-user.stages[PBStats::ATTACK])*10 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + user.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + if hasPhysicalAttack + score += 40 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + #--------------------------------------------------------------------------- + when "03B" + avg = user.stages[PBStats::ATTACK]*10 + avg += user.stages[PBStats::DEFENSE]*10 + score += avg/2 + #--------------------------------------------------------------------------- + when "03C" + avg = user.stages[PBStats::DEFENSE]*10 + avg += user.stages[PBStats::SPDEF]*10 + score += avg/2 + #--------------------------------------------------------------------------- + when "03D" + avg = user.stages[PBStats::DEFENSE]*10 + avg += user.stages[PBStats::SPEED]*10 + avg += user.stages[PBStats::SPDEF]*10 + score += (avg/3).floor + #--------------------------------------------------------------------------- + when "03E" + score += user.stages[PBStats::SPEED]*10 + #--------------------------------------------------------------------------- + when "03F" + score += user.stages[PBStats::SPATK]*10 + #--------------------------------------------------------------------------- + when "040" + if !target.pbCanConfuse?(user,false) + score -= 90 + else + score += 30 if target.stages[PBStats::SPATK]<0 + end + #--------------------------------------------------------------------------- + when "041" + if !target.pbCanConfuse?(user,false) + score -= 90 + else + score += 30 if target.stages[PBStats::ATTACK]<0 + end + #--------------------------------------------------------------------------- + when "042" + if move.statusMove? + if !target.pbCanLowerStatStage?(PBStats::ATTACK,user) + score -= 90 + else + score += target.stages[PBStats::ATTACK]*20 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + target.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + if hasPhysicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + else + score += 20 if target.stages[PBStats::ATTACK]>0 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + target.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + score += 20 if hasPhysicalAttack + end + end + #--------------------------------------------------------------------------- + when "043" + if move.statusMove? + if !target.pbCanLowerStatStage?(PBStats::DEFENSE,user) + score -= 90 + else + score += target.stages[PBStats::DEFENSE]*20 + end + else + score += 20 if target.stages[PBStats::DEFENSE]>0 + end + #--------------------------------------------------------------------------- + when "044" + if move.statusMove? + if !target.pbCanLowerStatStage?(PBStats::SPEED,user) + score -= 90 + else + score += target.stages[PBStats::SPEED]*10 + if skill>=PBTrainerAI.highSkill + aspeed = pbRoughStat(user,PBStats::SPEED,skill) + ospeed = pbRoughStat(target,PBStats::SPEED,skill) + score += 30 if aspeedospeed + end + end + else + score += 20 if user.stages[PBStats::SPEED]>0 + end + #--------------------------------------------------------------------------- + when "045" + if move.statusMove? + if !target.pbCanLowerStatStage?(PBStats::SPATK,user) + score -= 90 + else + score += user.stages[PBStats::SPATK]*20 + if skill>=PBTrainerAI.mediumSkill + hasSpecicalAttack = false + target.eachMove do |m| + next if !m.specialMove?(m.type) + hasSpecicalAttack = true + break + end + if hasSpecicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + else + score += 20 if user.stages[PBStats::SPATK]>0 + if skill>=PBTrainerAI.mediumSkill + hasSpecicalAttack = false + target.eachMove do |m| + next if !m.specialMove?(m.type) + hasSpecicalAttack = true + break + end + score += 20 if hasSpecicalAttack + end + end + #--------------------------------------------------------------------------- + when "046" + if move.statusMove? + if !target.pbCanLowerStatStage?(PBStats::SPDEF,user) + score -= 90 + else + score += target.stages[PBStats::SPDEF]*20 + end + else + score += 20 if target.stages[PBStats::SPDEF]>0 + end + #--------------------------------------------------------------------------- + when "047" + if move.statusMove? + if !target.pbCanLowerStatStage?(PBStats::ACCURACY,user) + score -= 90 + else + score += target.stages[PBStats::ACCURACY]*10 + end + else + score += 20 if target.stages[PBStats::ACCURACY]>0 + end + #--------------------------------------------------------------------------- + when "048" + if move.statusMove? + if !target.pbCanLowerStatStage?(PBStats::EVASION,user) + score -= 90 + else + score += target.stages[PBStats::EVASION]*10 + end + else + score += 20 if target.stages[PBStats::EVASION]>0 + end + #--------------------------------------------------------------------------- + when "049" + if move.statusMove? + if !target.pbCanLowerStatStage?(PBStats::EVASION,user) + score -= 90 + else + score += target.stages[PBStats::EVASION]*10 + end + else + score += 20 if target.stages[PBStats::EVASION]>0 + end + score += 30 if target.pbOwnSide.effects[PBEffects::AuroraVeil]>0 || + target.pbOwnSide.effects[PBEffects::Reflect]>0 || + target.pbOwnSide.effects[PBEffects::LightScreen]>0 || + target.pbOwnSide.effects[PBEffects::Mist]>0 || + target.pbOwnSide.effects[PBEffects::Safeguard]>0 + score -= 30 if target.pbOwnSide.effects[PBEffects::Spikes]>0 || + target.pbOwnSide.effects[PBEffects::ToxicSpikes]>0 || + target.pbOwnSide.effects[PBEffects::StealthRock] + #--------------------------------------------------------------------------- + when "04A" + avg = target.stages[PBStats::ATTACK]*10 + avg += target.stages[PBStats::DEFENSE]*10 + score += avg/2 + #--------------------------------------------------------------------------- + when "04B" + if move.statusMove? + if !target.pbCanLowerStatStage?(PBStats::ATTACK,user) + score -= 90 + else + score += 40 if user.turnCount==0 + score += target.stages[PBStats::ATTACK]*20 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + target.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + if hasPhysicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + else + score += 10 if user.turnCount==0 + score += 20 if target.stages[PBStats::ATTACK]>0 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + target.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + score += 20 if hasPhysicalAttack + end + end + #--------------------------------------------------------------------------- + when "04C" + if move.statusMove? + if !target.pbCanLowerStatStage?(PBStats::DEFENSE,user) + score -= 90 + else + score += 40 if user.turnCount==0 + score += target.stages[PBStats::DEFENSE]*20 + end + else + score += 10 if user.turnCount==0 + score += 20 if target.stages[PBStats::DEFENSE]>0 + end + #--------------------------------------------------------------------------- + when "04D" + if move.statusMove? + if !target.pbCanLowerStatStage?(PBStats::SPEED,user) + score -= 90 + else + score += 20 if user.turnCount==0 + score += target.stages[PBStats::SPEED]*20 + if skill>=PBTrainerAI.highSkill + aspeed = pbRoughStat(user,PBStats::SPEED,skill) + ospeed = pbRoughStat(target,PBStats::SPEED,skill) + score += 30 if aspeedospeed + end + end + else + score += 10 if user.turnCount==0 + score += 30 if target.stages[PBStats::SPEED]>0 + end + #--------------------------------------------------------------------------- + when "04E" + if user.gender==2 || target.gender==2 || user.gender==target.gender || + target.hasActiveAbility?(:OBLIVIOUS) + score -= 90 + elsif move.statusMove? + if !target.pbCanLowerStatStage?(PBStats::SPATK,user) + score -= 90 + else + score += 40 if user.turnCount==0 + score += target.stages[PBStats::SPATK]*20 + if skill>=PBTrainerAI.mediumSkill + hasSpecicalAttack = false + target.eachMove do |m| + next if !m.specialMove?(m.type) + hasSpecicalAttack = true + break + end + if hasSpecicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + else + score += 10 if user.turnCount==0 + score += 20 if target.stages[PBStats::SPATK]>0 + if skill>=PBTrainerAI.mediumSkill + hasSpecicalAttack = false + target.eachMove do |m| + next if !m.specialMove?(m.type) + hasSpecicalAttack = true + break + end + score += 30 if hasSpecicalAttack + end + end + #--------------------------------------------------------------------------- + when "04F" + if move.statusMove? + if !target.pbCanLowerStatStage?(PBStats::SPDEF,user) + score -= 90 + else + score += 40 if user.turnCount==0 + score += target.stages[PBStats::SPDEF]*20 + end + else + score += 10 if user.turnCount==0 + score += 20 if target.stages[PBStats::SPDEF]>0 + end + #--------------------------------------------------------------------------- + when "050" + if target.effects[PBEffects::Substitute]>0 + score -= 90 + else + avg = 0; anyChange = false + PBStats.eachBattleStat do |s| + next if target.stages[s]==0 + avg += target.stages[s] + anyChange = true + end + if anyChange + score += avg*10 + else + score -= 90 + end + end + #--------------------------------------------------------------------------- + when "051" + if skill>=PBTrainerAI.mediumSkill + stages = 0 + @battle.eachBattler do |b| + totalStages = 0 + PBStats.eachBattleStat { |s| totalStages += b.stages[s] } + if b.opposes?(user) + stages += totalStages + else + stages -= totalStages + end + end + score += stages*10 + end + #--------------------------------------------------------------------------- + when "052" + if skill>=PBTrainerAI.mediumSkill + aatk = user.stages[PBStats::ATTACK] + aspa = user.stages[PBStats::SPATK] + oatk = target.stages[PBStats::ATTACK] + ospa = target.stages[PBStats::SPATK] + if aatk>=oatk && aspa>=ospa + score -= 80 + else + score += (oatk-aatk)*10 + score += (ospa-aspa)*10 + end + else + score -= 50 + end + #--------------------------------------------------------------------------- + when "053" + if skill>=PBTrainerAI.mediumSkill + adef = user.stages[PBStats::DEFENSE] + aspd = user.stages[PBStats::SPDEF] + odef = target.stages[PBStats::DEFENSE] + ospd = target.stages[PBStats::SPDEF] + if adef>=odef && aspd>=ospd + score -= 80 + else + score += (odef-adef)*10 + score += (ospd-aspd)*10 + end + else + score -= 50 + end + #--------------------------------------------------------------------------- + when "054" + if skill>=PBTrainerAI.mediumSkill + userStages = 0; targetStages = 0 + PBStats.eachBattleStat do |s| + userStages += user.stages[s] + targetStages += target.stages[s] + end + score += (targetStages-userStages)*10 + else + score -= 50 + end + #--------------------------------------------------------------------------- + when "055" + if skill>=PBTrainerAI.mediumSkill + equal = true + PBStats.eachBattleStat do |s| + stagediff = target.stages[s]-user.stages[s] + score += stagediff*10 + equal = false if stagediff!=0 + end + score -= 80 if equal + else + score -= 50 + end + #--------------------------------------------------------------------------- + when "056" + score -= 80 if user.pbOwnSide.effects[PBEffects::Mist]>0 + #--------------------------------------------------------------------------- + when "057" + if skill>=PBTrainerAI.mediumSkill + aatk = pbRoughStat(user,PBStats::ATTACK,skill) + adef = pbRoughStat(user,PBStats::DEFENSE,skill) + if aatk==adef || + user.effects[PBEffects::PowerTrick] # No flip-flopping + score -= 90 + elsif adef>aatk # Prefer a higher Attack + score += 30 + else + score -= 30 + end + else + score -= 30 + end + #--------------------------------------------------------------------------- + when "058" + if skill>=PBTrainerAI.mediumSkill + aatk = pbRoughStat(user,PBStats::ATTACK,skill) + aspatk = pbRoughStat(user,PBStats::SPATK,skill) + oatk = pbRoughStat(target,PBStats::ATTACK,skill) + ospatk = pbRoughStat(target,PBStats::SPATK,skill) + if aatk=PBTrainerAI.mediumSkill + adef = pbRoughStat(user,PBStats::DEFENSE,skill) + aspdef = pbRoughStat(user,PBStats::SPDEF,skill) + odef = pbRoughStat(target,PBStats::DEFENSE,skill) + ospdef = pbRoughStat(target,PBStats::SPDEF,skill) + if adef0 + score -= 90 + elsif user.hp>=(user.hp+target.hp)/2 + score -= 90 + else + score += 40 + end + #--------------------------------------------------------------------------- + when "05B" + score -= 90 if user.pbOwnSide.effects[PBEffects::Tailwind]>0 + #--------------------------------------------------------------------------- + when "05C" + moveBlacklist = [ + "002", # Struggle + "014", # Chatter + "05C", # Mimic + "05D", # Sketch + "0B6" # Metronome + ] + lastMoveData = pbGetMoveData(target.lastRegularMoveUsed) + if user.effects[PBEffects::Transform] || + target.lastRegularMoveUsed<=0 || + moveBlacklist.include?(lastMoveData[MOVE_FUNCTION_CODE]) || + isConst?(lastMoveData[MOVE_TYPE],PBTypes,:SHADOW) + score -= 90 + end + user.eachMove do |m| + next if m.id!=target.lastRegularMoveUsed + score -= 90 + break + end + #--------------------------------------------------------------------------- + when "05D" + moveBlacklist = [ + "002", # Struggle + "014", # Chatter + "05D" # Sketch + ] + lastMoveData = pbGetMoveData(target.lastRegularMoveUsed) + if user.effects[PBEffects::Transform] || + target.lastRegularMoveUsed<=0 || + moveBlacklist.include?(lastMoveData[MOVE_FUNCTION_CODE]) || + isConst?(lastMoveData[MOVE_TYPE],PBTypes,:SHADOW) + score -= 90 + end + user.eachMove do |m| + next if m.id!=target.lastRegularMoveUsed + score -= 90 # User already knows the move that will be Sketched + break + end + #--------------------------------------------------------------------------- + when "05E" + if isConst?(user.ability,PBAbilities,:MULTITYPE) || + isConst?(user.ability,PBAbilities,:RKSSYSTEM) + score -= 90 + else + types = [] + user.eachMove do |m| + next if m.id==@id + next if PBTypes.isPseudoType?(m.type) + next if user.pbHasType?(m.type) + types.push(m.type) if !types.include?(m.type) + end + score -= 90 if types.length==0 + end + #--------------------------------------------------------------------------- + when "05F" + if isConst?(user.ability,PBAbilities,:MULTITYPE) || + isConst?(user.ability,PBAbilities,:RKSSYSTEM) + score -= 90 + elsif target.lastMoveUsed<=0 || + PBTypes.isPseudoType?(pbGetMoveData(target.lastMoveUsed,MOVE_TYPE)) + score -= 90 + else + aType = -1 + target.eachMove do |m| + next if m.id!=target.lastMoveUsed + aType = m.pbCalcType(user) + break + end + if aType<0 + score -= 90 + else + types = [] + for i in 0..PBTypes.maxValue + next if user.pbHasType?(i) + types.push(i) if PBTypes.resistant?(aType,i) + end + score -= 90 if types.length==0 + end + end + #--------------------------------------------------------------------------- + when "060" + if isConst?(user.ability,PBAbilities,:MULTITYPE) || + isConst?(user.ability,PBAbilities,:RKSSYSTEM) + score -= 90 + elsif skill>=PBTrainerAI.mediumSkill + envtypes = [ + :NORMAL, # None + :GRASS, # Grass + :GRASS, # Tall grass + :WATER, # Moving water + :WATER, # Still water + :WATER, # Underwater + :ROCK, # Rock + :ROCK, # Cave + :GROUND # Sand + ] + type = envtypes[@environment] + score -= 90 if user.pbHasType?(type) + end + #--------------------------------------------------------------------------- + when "061" + if target.effects[PBEffects::Substitute]>0 || + isConst?(target.ability,PBAbilities,:MULTITYPE) || + isConst?(target.ability,PBAbilities,:RKSSYSTEM) + score -= 90 + elsif target.pbHasType?(:WATER) + score -= 90 + end + #--------------------------------------------------------------------------- + when "062" + if isConst?(user.ability,PBAbilities,:MULTITYPE) || + isConst?(user.ability,PBAbilities,:RKSSYSTEM) + score -= 90 + elsif user.pbHasType?(target.type1) && + user.pbHasType?(target.type2) && + target.pbHasType?(user.type1) && + target.pbHasType?(user.type2) + score -= 90 + end + #--------------------------------------------------------------------------- + when "063" + if target.effects[PBEffects::Substitute]>0 + score -= 90 + elsif skill>=PBTrainerAI.mediumSkill + if isConst?(target.ability,PBAbilities,:MULTITYPE) || + isConst?(target.ability,PBAbilities,:RKSSYSTEM) || + isConst?(target.ability,PBAbilities,:SIMPLE) || + isConst?(target.ability,PBAbilities,:TRUANT) + score -= 90 + end + end + #--------------------------------------------------------------------------- + when "064" + if target.effects[PBEffects::Substitute]>0 + score -= 90 + elsif skill>=PBTrainerAI.mediumSkill + if isConst?(target.ability,PBAbilities,:INSOMNIA) || + isConst?(target.ability,PBAbilities,:MULTITYPE) || + isConst?(target.ability,PBAbilities,:RKSSYSTEM) || + isConst?(target.ability,PBAbilities,:TRUANT) + score -= 90 + end + end + #--------------------------------------------------------------------------- + when "065" + score -= 40 # don't prefer this move + if skill>=PBTrainerAI.mediumSkill + if target.ability==0 || user.ability==target.ability || + isConst?(user.ability,PBAbilities,:MULTITYPE) || + isConst?(user.ability,PBAbilities,:RKSSYSTEM) || + isConst?(target.ability,PBAbilities,:FLOWERGIFT) || + isConst?(target.ability,PBAbilities,:FORECAST) || + isConst?(target.ability,PBAbilities,:ILLUSION) || + isConst?(target.ability,PBAbilities,:IMPOSTER) || + isConst?(target.ability,PBAbilities,:MULTITYPE) || + isConst?(target.ability,PBAbilities,:RKSSYSTEM) || + isConst?(target.ability,PBAbilities,:TRACE) || + isConst?(target.ability,PBAbilities,:WONDERGUARD) || + isConst?(target.ability,PBAbilities,:ZENMODE) + score -= 90 + end + end + if skill>=PBTrainerAI.highSkill + if isConst?(target.ability,PBAbilities,:TRUANT) && + user.opposes?(target) + score -= 90 + elsif isConst?(target.ability,PBAbilities,:SLOWSTART) && + user.opposes?(target) + score -= 90 + end + end + #--------------------------------------------------------------------------- + when "066" + score -= 40 # don't prefer this move + if target.effects[PBEffects::Substitute]>0 + score -= 90 + elsif skill>=PBTrainerAI.mediumSkill + if user.ability==0 || user.ability==target.ability || + isConst?(target.ability,PBAbilities,:MULTITYPE) || + isConst?(target.ability,PBAbilities,:RKSSYSTEM) || + isConst?(target.ability,PBAbilities,:TRUANT) || + isConst?(user.ability,PBAbilities,:FLOWERGIFT) || + isConst?(user.ability,PBAbilities,:FORECAST) || + isConst?(user.ability,PBAbilities,:ILLUSION) || + isConst?(user.ability,PBAbilities,:IMPOSTER) || + isConst?(user.ability,PBAbilities,:MULTITYPE) || + isConst?(user.ability,PBAbilities,:RKSSYSTEM) || + isConst?(user.ability,PBAbilities,:TRACE) || + isConst?(user.ability,PBAbilities,:ZENMODE) + score -= 90 + end + if skill>=PBTrainerAI.highSkill + if isConst?(user.ability,PBAbilities,:TRUANT) && + user.opposes?(target) + score += 90 + elsif isConst?(user.ability,PBAbilities,:SLOWSTART) && + user.opposes?(target) + score += 90 + end + end + end + #--------------------------------------------------------------------------- + when "067" + score -= 40 # don't prefer this move + if skill>=PBTrainerAI.mediumSkill + if (user.ability==0 && target.ability==0) || + user.ability==target.ability || + isConst?(user.ability,PBAbilities,:ILLUSION) || + isConst?(user.ability,PBAbilities,:MULTITYPE) || + isConst?(user.ability,PBAbilities,:RKSSYSTEM) || + isConst?(user.ability,PBAbilities,:WONDERGUARD) || + isConst?(target.ability,PBAbilities,:ILLUSION) || + isConst?(target.ability,PBAbilities,:MULTITYPE) || + isConst?(target.ability,PBAbilities,:RKSSYSTEM) || + isConst?(target.ability,PBAbilities,:WONDERGUARD) + score -= 90 + end + end + if skill>=PBTrainerAI.highSkill + if isConst?(target.ability,PBAbilities,:TRUANT) && + user.opposes?(target) + score -= 90 + elsif isConst?(target.ability,PBAbilities,:SLOWSTART) && + user.opposes?(target) + score -= 90 + end + end + #--------------------------------------------------------------------------- + when "068" + if target.effects[PBEffects::Substitute]>0 || + target.effects[PBEffects::GastroAcid] + score -= 90 + elsif skill>=PBTrainerAI.highSkill + score -= 90 if isConst?(target.ability,PBAbilities,:MULTITYPE) + score -= 90 if isConst?(target.ability,PBAbilities,:RKSSYSTEM) + score -= 90 if isConst?(target.ability,PBAbilities,:SLOWSTART) + score -= 90 if isConst?(target.ability,PBAbilities,:TRUANT) + end + #--------------------------------------------------------------------------- + when "069" + score -= 70 + #--------------------------------------------------------------------------- + when "06A" + if target.hp<=20 + score += 80 + elsif target.level>=25 + score -= 60 # Not useful against high-level Pokemon + end + #--------------------------------------------------------------------------- + when "06B" + score += 80 if target.hp<=40 + #--------------------------------------------------------------------------- + when "06C" + score -= 50 + score += target.hp*100/target.totalhp + #--------------------------------------------------------------------------- + when "06D" + score += 80 if target.hp<=user.level + #--------------------------------------------------------------------------- + when "06E" + if user.hp>=target.hp + score -= 90 + elsif user.hpuser.level + #--------------------------------------------------------------------------- + when "071" + if target.effects[PBEffects::HyperBeam]>0 + score -= 90 + else + attack = pbRoughStat(user,PBStats::ATTACK,skill) + spatk = pbRoughStat(user,PBStats::SPATK,skill) + if attack*1.5=PBTrainerAI.mediumSkill && target.lastMoveUsed>0 + moveData = pbGetMoveData(target.lastMoveUsed) + if moveData[MOVE_BASE_DAMAGE]>0 && + (MOVE_CATEGORY_PER_MOVE && moveData[MOVE_CATEGORY]==0) || + (!MOVE_CATEGORY_PER_MOVE && PBTypes.isPhysicalType?(moveData[MOVE_TYPE])) + score -= 60 + end + end + end + #--------------------------------------------------------------------------- + when "072" + if target.effects[PBEffects::HyperBeam]>0 + score -= 90 + else + attack = pbRoughStat(user,PBStats::ATTACK,skill) + spatk = pbRoughStat(user,PBStats::SPATK,skill) + if attack>spatk*1.5 + score -= 60 + elsif skill>=PBTrainerAI.mediumSkill && target.lastMoveUsed>0 + moveData = pbGetMoveData(target.lastMoveUsed) + if moveData[MOVE_BASE_DAMAGE]>0 && + (MOVE_CATEGORY_PER_MOVE && moveData[MOVE_CATEGORY]==1) || + (!MOVE_CATEGORY_PER_MOVE && !PBTypes.isSpecialType?(moveData[MOVE_TYPE])) + score -= 60 + end + end + end + #--------------------------------------------------------------------------- + when "073" + score -= 90 if target.effects[PBEffects::HyperBeam]>0 + #--------------------------------------------------------------------------- + when "074" + target.eachAlly do |b| + next if !b.near?(target) + score += 10 + end + #--------------------------------------------------------------------------- + when "075" + #--------------------------------------------------------------------------- + when "076" + #--------------------------------------------------------------------------- + when "077" + #--------------------------------------------------------------------------- + when "078" + if skill>=PBTrainerAI.highSkill + score += 30 if !target.hasActiveAbility?(:INNERFOCUS) && + target.effects[PBEffects::Substitute]==0 + end + #--------------------------------------------------------------------------- + when "079" + #--------------------------------------------------------------------------- + when "07A" + #--------------------------------------------------------------------------- + when "07B" + #--------------------------------------------------------------------------- + when "07C" + score -= 20 if target.status==PBStatuses::PARALYSIS # Will cure status + #--------------------------------------------------------------------------- + when "07D" + score -= 20 if target.status==PBStatuses::SLEEP && # Will cure status + target.statusCount>1 + #--------------------------------------------------------------------------- + when "07E" + #--------------------------------------------------------------------------- + when "07F" + #--------------------------------------------------------------------------- + when "080" + #--------------------------------------------------------------------------- + when "081" + attspeed = pbRoughStat(user,PBStats::SPEED,skill) + oppspeed = pbRoughStat(target,PBStats::SPEED,skill) + score += 30 if oppspeed>attspeed + #--------------------------------------------------------------------------- + when "082" + score += 20 if @battle.pbOpposingBattlerCount(user)>1 + #--------------------------------------------------------------------------- + when "083" + if skill>=PBTrainerAI.mediumSkill + user.eachAlly do |b| + next if !b.pbHasMove?(move.id) + score += 20 + end + end + #--------------------------------------------------------------------------- + when "084" + attspeed = pbRoughStat(user,PBStats::SPEED,skill) + oppspeed = pbRoughStat(target,PBStats::SPEED,skill) + score += 30 if oppspeed>attspeed + #--------------------------------------------------------------------------- + when "085" + #--------------------------------------------------------------------------- + when "086" + #--------------------------------------------------------------------------- + when "087" + #--------------------------------------------------------------------------- + when "088" + #--------------------------------------------------------------------------- + when "089" + #--------------------------------------------------------------------------- + when "08A" + #--------------------------------------------------------------------------- + when "08B" + #--------------------------------------------------------------------------- + when "08C" + #--------------------------------------------------------------------------- + when "08D" + #--------------------------------------------------------------------------- + when "08E" + #--------------------------------------------------------------------------- + when "08F" + #--------------------------------------------------------------------------- + when "090" + #--------------------------------------------------------------------------- + when "091" + #--------------------------------------------------------------------------- + when "092" + #--------------------------------------------------------------------------- + when "093" + score += 25 if user.effects[PBEffects::Rage] + #--------------------------------------------------------------------------- + when "094" + #--------------------------------------------------------------------------- + when "095" + #--------------------------------------------------------------------------- + when "096" + score -= 90 if !pbIsBerry?(user.item) || !user.itemActive? + #--------------------------------------------------------------------------- + when "097" + #--------------------------------------------------------------------------- + when "098" + #--------------------------------------------------------------------------- + when "099" + #--------------------------------------------------------------------------- + when "09A" + #--------------------------------------------------------------------------- + when "09B" + #--------------------------------------------------------------------------- + when "09C" + hasAlly = false + user.eachAlly do |b| + hasAlly = true + score += 30 + break + end + score -= 90 if !hasAlly + #--------------------------------------------------------------------------- + when "09D" + score -= 90 if user.effects[PBEffects::MudSport] + #--------------------------------------------------------------------------- + when "09E" + score -= 90 if user.effects[PBEffects::WaterSport] + #--------------------------------------------------------------------------- + when "09F" + #--------------------------------------------------------------------------- + when "0A0" + #--------------------------------------------------------------------------- + when "0A1" + score -= 90 if user.pbOwnSide.effects[PBEffects::LuckyChant]>0 + #--------------------------------------------------------------------------- + when "0A2" + score -= 90 if user.pbOwnSide.effects[PBEffects::Reflect]>0 + #--------------------------------------------------------------------------- + when "0A3" + score -= 90 if user.pbOwnSide.effects[PBEffects::LightScreen]>0 + #--------------------------------------------------------------------------- + when "0A4" + #--------------------------------------------------------------------------- + when "0A5" + #--------------------------------------------------------------------------- + when "0A6" + score -= 90 if target.effects[PBEffects::Substitute]>0 + score -= 90 if user.effects[PBEffects::LockOn]>0 + #--------------------------------------------------------------------------- + when "0A7" + if target.effects[PBEffects::Foresight] + score -= 90 + elsif target.pbHasType?(:GHOST) + score += 70 + elsif target.stages[PBStats::EVASION]<=0 + score -= 60 + end + #--------------------------------------------------------------------------- + when "0A8" + if target.effects[PBEffects::MiracleEye] + score -= 90 + elsif target.pbHasType?(:DARK) + score += 70 + elsif target.stages[PBStats::EVASION]<=0 + score -= 60 + end + #--------------------------------------------------------------------------- + when "0A9" + #--------------------------------------------------------------------------- + when "0AA" + if user.effects[PBEffects::ProtectRate]>1 || + target.effects[PBEffects::HyperBeam]>0 + score -= 90 + else + if skill>=PBTrainerAI.mediumSkill + score -= user.effects[PBEffects::ProtectRate]*40 + end + score += 50 if user.turnCount==0 + score += 30 if target.effects[PBEffects::TwoTurnAttack]>0 + end + #--------------------------------------------------------------------------- + when "0AB" + #--------------------------------------------------------------------------- + when "0AC" + #--------------------------------------------------------------------------- + when "0AD" + #--------------------------------------------------------------------------- + when "0AE" + score -= 40 + if skill>=PBTrainerAI.highSkill + score -= 100 if target.lastRegularMoveUsed<=0 || + !pbGetMoveData(target.lastRegularMoveUsed,MOVE_FLAGS)[/e/] # Not copyable by Mirror Move + end + #--------------------------------------------------------------------------- + when "0AF" + #--------------------------------------------------------------------------- + when "0B0" + #--------------------------------------------------------------------------- + when "0B1" + #--------------------------------------------------------------------------- + when "0B2" + #--------------------------------------------------------------------------- + when "0B3" + #--------------------------------------------------------------------------- + when "0B4" + if user.asleep? + score += 100 # Because it can only be used while asleep + else + score -= 90 + end + #--------------------------------------------------------------------------- + when "0B5" + #--------------------------------------------------------------------------- + when "0B6" + #--------------------------------------------------------------------------- + when "0B7" + score -= 90 if target.effects[PBEffects::Torment] + #--------------------------------------------------------------------------- + when "0B8" + score -= 90 if user.effects[PBEffects::Imprison] + #--------------------------------------------------------------------------- + when "0B9" + score -= 90 if target.effects[PBEffects::Disable]>0 + #--------------------------------------------------------------------------- + when "0BA" + score -= 90 if target.effects[PBEffects::Taunt]>0 + #--------------------------------------------------------------------------- + when "0BB" + score -= 90 if target.effects[PBEffects::HealBlock]>0 + #--------------------------------------------------------------------------- + when "0BC" + aspeed = pbRoughStat(user,PBStats::SPEED,skill) + ospeed = pbRoughStat(target,PBStats::SPEED,skill) + if target.effects[PBEffects::Encore]>0 + score -= 90 + elsif aspeed>ospeed + if target.lastMoveUsed<=0 + score -= 90 + else + moveData = pbGetMoveData(target.lastRegularMoveUsed) + if moveData[MOVE_CATEGORY]==2 && # Status move + (moveData[MOVE_TARGET]==PBTargets::User || + moveData[MOVE_TARGET]==PBTargets::BothSides) + score += 60 + elsif moveData[MOVE_CATEGORY]!=2 && # Damaging move + moveData[MOVE_TARGET]==PBTargets::NearOther && + PBTypes.ineffective?(pbCalcTypeMod(moveData[MOVE_TYPE],target,user)) + score += 60 + end + end + end + #--------------------------------------------------------------------------- + when "0BD" + #--------------------------------------------------------------------------- + when "0BF" + #--------------------------------------------------------------------------- + when "0C0" + #--------------------------------------------------------------------------- + when "0C1" + #--------------------------------------------------------------------------- + when "0C2" + #--------------------------------------------------------------------------- + when "0C3" + #--------------------------------------------------------------------------- + when "0C4" + #--------------------------------------------------------------------------- + when "0C7" + score += 20 if user.effects[PBEffects::FocusEnergy]>0 + if skill>=PBTrainerAI.highSkill + score += 20 if !target.hasActiveAbility?(:INNERFOCUS) && + target.effects[PBEffects::Substitute]==0 + end + #--------------------------------------------------------------------------- + when "0C9" + #--------------------------------------------------------------------------- + when "0CA" + #--------------------------------------------------------------------------- + when "0CB" + #--------------------------------------------------------------------------- + when "0CC" + #--------------------------------------------------------------------------- + when "0CD" + #--------------------------------------------------------------------------- + when "0CE" + #--------------------------------------------------------------------------- + when "0CF" + score += 40 if target.effects[PBEffects::Trapping]==0 + #--------------------------------------------------------------------------- + when "0D0" + score += 40 if target.effects[PBEffects::Trapping]==0 + #--------------------------------------------------------------------------- + when "0D1" + #--------------------------------------------------------------------------- + when "0D2" + #--------------------------------------------------------------------------- + when "0D3" + #--------------------------------------------------------------------------- + when "0D4" + if user.hp<=user.totalhp/4 + score -= 90 + elsif user.hp<=user.totalhp/2 + score -= 50 + end + #--------------------------------------------------------------------------- + when "0D5", "0D6" + if user.hp==user.totalhp || (skill>=PBTrainerAI.mediumSkill && !user.canHeal?) + score -= 90 + else + score += 50 + score -= user.hp*100/user.totalhp + end + #--------------------------------------------------------------------------- + when "0D7" + score -= 90 if @battle.positions[user.index].effects[PBEffects::Wish]>0 + #--------------------------------------------------------------------------- + when "0D8" + if user.hp==user.totalhp || (skill>=PBTrainerAI.mediumSkill && !user.canHeal?) + score -= 90 + else + case @battle.pbWeather + when PBWeather::Sun, PBWeather::HarshSun + score += 30 + when PBWeather::None + else + score -= 30 + end + score += 50 + score -= user.hp*100/user.totalhp + end + #--------------------------------------------------------------------------- + when "0D9" + if user.hp==user.totalhp || !user.pbCanSleep?(user,false,nil,true) + score -= 90 + else + score += 70 + score -= user.hp*140/user.totalhp + score += 30 if user.status!=0 + end + #--------------------------------------------------------------------------- + when "0DA" + score -= 90 if user.effects[PBEffects::AquaRing] + #--------------------------------------------------------------------------- + when "0DB" + score -= 90 if user.effects[PBEffects::Ingrain] + #--------------------------------------------------------------------------- + when "0DC" + if target.effects[PBEffects::LeechSeed]>=0 + score -= 90 + elsif skill>=PBTrainerAI.mediumSkill && target.pbHasType?(:GRASS) + score -= 90 + else + score += 60 if user.turnCount==0 + end + #--------------------------------------------------------------------------- + when "0DD" + if skill>=PBTrainerAI.highSkill && target.hasActiveAbility?(:LIQUIDOOZE) + score -= 70 + else + score += 20 if user.hp<=user.totalhp/2 + end + #--------------------------------------------------------------------------- + when "0DE" + if !target.asleep? + score -= 100 + elsif skill>=PBTrainerAI.highSkill && target.hasActiveAbility?(:LIQUIDOOZE) + score -= 70 + else + score += 20 if user.hp<=user.totalhp/2 + end + #--------------------------------------------------------------------------- + when "0DF" + if user.opposes?(target) + score -= 100 + else + score += 20 if target.hp=PBTrainerAI.mediumSkill && reserves==0 && foes>0 + score -= 100 # don't want to lose + elsif skill>=PBTrainerAI.highSkill && reserves==0 && foes==0 + score += 80 # want to draw + else + score -= user.hp*100/user.totalhp + end + #--------------------------------------------------------------------------- + when "0E1" + #--------------------------------------------------------------------------- + when "0E2" + if !target.pbCanLowerStatStage?(PBStats::ATTACK,user) && + !target.pbCanLowerStatStage?(PBStats::SPATK,user) + score -= 100 + elsif @battle.pbAbleNonActiveCount(user.idxOwnSide)==0 + score -= 100 + else + score += target.stages[PBStats::ATTACK]*10 + score += target.stages[PBStats::SPATK]*10 + score -= user.hp*100/user.totalhp + end + #--------------------------------------------------------------------------- + when "0E3", "0E4" + score -= 70 + #--------------------------------------------------------------------------- + when "0E5" + if @battle.pbAbleNonActiveCount(user.idxOwnSide)==0 + score -= 90 + else + score -= 90 if target.effects[PBEffects::PerishSong]>0 + end + #--------------------------------------------------------------------------- + when "0E6" + score += 50 + score -= user.hp*100/user.totalhp + score += 30 if user.hp<=user.totalhp/10 + #--------------------------------------------------------------------------- + when "0E7" + score += 50 + score -= user.hp*100/user.totalhp + score += 30 if user.hp<=user.totalhp/10 + #--------------------------------------------------------------------------- + when "0E8" + score -= 25 if user.hp>user.totalhp/2 + if skill>=PBTrainerAI.mediumSkill + score -= 90 if user.effects[PBEffects::ProtectRate]>1 + score -= 90 if target.effects[PBEffects::HyperBeam]>0 + else + score -= user.effects[PBEffects::ProtectRate]*40 + end + #--------------------------------------------------------------------------- + when "0E9" + if target.hp==1 + score -= 90 + elsif target.hp<=target.totalhp/8 + score -= 60 + elsif target.hp<=target.totalhp/4 + score -= 30 + end + #--------------------------------------------------------------------------- + when "0EA" + score -= 100 if @battle.trainerBattle? + #--------------------------------------------------------------------------- + when "0EB" + if target.effects[PBEffects::Ingrain] || + (skill>=PBTrainerAI.highSkill && target.hasActiveAbility?(:SUCTIONCUPS)) + score -= 90 + else + ch = 0 + @battle.pbParty(target.index).each_with_index do |pkmn,i| + ch += 1 if @battle.pbCanSwitchLax?(target.index,i) + end + score -= 90 if ch==0 + end + if score>20 + score += 50 if target.pbOwnSide.effects[PBEffects::Spikes]>0 + score += 50 if target.pbOwnSide.effects[PBEffects::ToxicSpikes]>0 + score += 50 if target.pbOwnSide.effects[PBEffects::StealthRock] + end + #--------------------------------------------------------------------------- + when "0EC" + if !target.effects[PBEffects::Ingrain] && + !(skill>=PBTrainerAI.highSkill && target.hasActiveAbility?(:SUCTIONCUPS)) + score += 40 if target.pbOwnSide.effects[PBEffects::Spikes]>0 + score += 40 if target.pbOwnSide.effects[PBEffects::ToxicSpikes]>0 + score += 40 if target.pbOwnSide.effects[PBEffects::StealthRock] + end + #--------------------------------------------------------------------------- + when "0ED" + if !@battle.pbCanChooseNonActive?(user.index) + score -= 80 + else + score -= 40 if user.effects[PBEffects::Confusion]>0 + total = 0 + PBStats.eachBattleStat { |s| total += user.stages[s] } + if total<=0 || user.turnCount==0 + score -= 60 + else + score += total*10 + # special case: user has no damaging moves + hasDamagingMove = false + user.eachMove do |m| + next if !m.damagingMove? + hasDamagingMove = true + break + end + score += 75 if !hasDamagingMove + end + end + #--------------------------------------------------------------------------- + when "0EE" + #--------------------------------------------------------------------------- + when "0EF" + score -= 90 if target.effects[PBEffects::MeanLook]>=0 + #--------------------------------------------------------------------------- + when "0F0" + if skill>=PBTrainerAI.highSkill + score += 20 if target.item!=0 + end + #--------------------------------------------------------------------------- + when "0F1" + if skill>=PBTrainerAI.highSkill + if user.item==0 && target.item!=0 + score += 40 + else + score -= 90 + end + else + score -= 80 + end + #--------------------------------------------------------------------------- + when "0F2" + if user.item==0 && target.item==0 + score -= 90 + elsif skill>=PBTrainerAI.highSkill && target.hasActiveAbility?(:STICKYHOLD) + score -= 90 + elsif user.hasActiveItem?([:FLAMEORB,:TOXICORB,:STICKYBARB,:IRONBALL, + :CHOICEBAND,:CHOICESCARF,:CHOICESPECS]) + score += 50 + elsif user.item==0 && target.item!=0 + score -= 30 if pbGetMoveData(user.lastMoveUsed,MOVE_FUNCTION_CODE)=="0F2" # Trick/Switcheroo + end + #--------------------------------------------------------------------------- + when "0F3" + if user.item==0 || target.item!=0 + score -= 90 + else + if user.hasActiveItem?([:FLAMEORB,:TOXICORB,:STICKYBARB,:IRONBALL, + :CHOICEBAND,:CHOICESCARF,:CHOICESPECS]) + score += 50 + else + score -= 80 + end + end + #--------------------------------------------------------------------------- + when "0F4", "0F5" + if target.effects[PBEffects::Substitute]==0 + if skill>=PBTrainerAI.highSkill && pbIsBerry?(target.item) + score += 30 + end + end + #--------------------------------------------------------------------------- + when "0F6" + if user.recycleItem==0 || user.item!=0 + score -= 80 + elsif user.recycleItem!=0 + score += 30 + end + #--------------------------------------------------------------------------- + when "0F7" + if user.item==0 || !user.itemActive? || + user.unlosableItem?(user.item) || pbIsPokeBall?(user.item) + score -= 90 + end + #--------------------------------------------------------------------------- + when "0F8" + score -= 90 if target.effects[PBEffects::Embargo]>0 + #--------------------------------------------------------------------------- + when "0F9" + if @battle.field.effects[PBEffects::MagicRoom]>0 + score -= 90 + else + score += 30 if user.item==0 && target.item!=0 + end + #--------------------------------------------------------------------------- + when "0FA" + score -= 25 + #--------------------------------------------------------------------------- + when "0FB" + score -= 30 + #--------------------------------------------------------------------------- + when "0FC" + score -= 40 + #--------------------------------------------------------------------------- + when "0FD" + score -= 30 + if target.pbCanParalyze?(user,false) + score += 30 + if skill>=PBTrainerAI.mediumSkill + aspeed = pbRoughStat(user,PBStats::SPEED,skill) + ospeed = pbRoughStat(target,PBStats::SPEED,skill) + if aspeedospeed + score -= 40 + end + end + if skill>=PBTrainerAI.highSkill + score -= 40 if target.hasActiveAbility?([:GUTS,:MARVELSCALE,:QUICKFEET]) + end + end + #--------------------------------------------------------------------------- + when "0FE" + score -= 30 + if target.pbCanBurn?(user,false) + score += 30 + if skill>=PBTrainerAI.highSkill + score -= 40 if target.hasActiveAbility?([:GUTS,:MARVELSCALE,:QUICKFEET,:FLAREBOOST]) + end + end + #--------------------------------------------------------------------------- + when "0FF" + if @battle.pbCheckGlobalAbility(:AIRLOCK) || + @battle.pbCheckGlobalAbility(:CLOUDNINE) + score -= 90 + elsif @battle.pbWeather==PBWeather::Sun + score -= 90 + else + user.eachMove do |m| + next if !m.damagingMove? || !isConst?(m.type,PBTypes,:FIRE) + score += 20 + end + end + #--------------------------------------------------------------------------- + when "100" + if @battle.pbCheckGlobalAbility(:AIRLOCK) || + @battle.pbCheckGlobalAbility(:CLOUDNINE) + score -= 90 + elsif @battle.pbWeather==PBWeather::Rain + score -= 90 + else + user.eachMove do |m| + next if !m.damagingMove? || !isConst?(m.type,PBTypes,:WATER) + score += 20 + end + end + #--------------------------------------------------------------------------- + when "101" + if @battle.pbCheckGlobalAbility(:AIRLOCK) || + @battle.pbCheckGlobalAbility(:CLOUDNINE) + score -= 90 + elsif @battle.pbWeather==PBWeather::Sandstorm + score -= 90 + end + #--------------------------------------------------------------------------- + when "102" + if @battle.pbCheckGlobalAbility(:AIRLOCK) || + @battle.pbCheckGlobalAbility(:CLOUDNINE) + score -= 90 + elsif @battle.pbWeather==PBWeather::Hail + score -= 90 + end + #--------------------------------------------------------------------------- + when "103" + if user.pbOpposingSide.effects[PBEffects::Spikes]>=3 + score -= 90 + else + canChoose = false + user.eachOpposing do |b| + next if !@battle.pbCanChooseNonActive?(b.index) + canChoose = true + break + end + if !canChoose + # Opponent can't switch in any Pokemon + score -= 90 + else + score += 10*@battle.pbAbleNonActiveCount(user.idxOpposingSide) + score += [40,26,13][user.pbOpposingSide.effects[PBEffects::Spikes]] + end + end + #--------------------------------------------------------------------------- + when "104" + if user.pbOpposingSide.effects[PBEffects::ToxicSpikes]>=2 + score -= 90 + else + canChoose = false + user.eachOpposing do |b| + next if !@battle.pbCanChooseNonActive?(b.index) + canChoose = true + break + end + if !canChoose + # Opponent can't switch in any Pokemon + score -= 90 + else + score += 8*@battle.pbAbleNonActiveCount(user.idxOpposingSide) + score += [26,13][user.pbOpposingSide.effects[PBEffects::ToxicSpikes]] + end + end + #--------------------------------------------------------------------------- + when "105" + if user.pbOpposingSide.effects[PBEffects::StealthRock] + score -= 90 + else + canChoose = false + user.eachOpposing do |b| + next if !@battle.pbCanChooseNonActive?(b.index) + canChoose = true + break + end + if !canChoose + # Opponent can't switch in any Pokemon + score -= 90 + else + score += 10*@battle.pbAbleNonActiveCount(user.idxOpposingSide) + end + end + #--------------------------------------------------------------------------- + when "106" + #--------------------------------------------------------------------------- + when "107" + #--------------------------------------------------------------------------- + when "108" + #--------------------------------------------------------------------------- + when "109" + #--------------------------------------------------------------------------- + when "10A" + score += 20 if user.pbOpposingSide.effects[PBEffects::AuroraVeil]>0 + score += 20 if user.pbOpposingSide.effects[PBEffects::Reflect]>0 + score += 20 if user.pbOpposingSide.effects[PBEffects::LightScreen]>0 + #--------------------------------------------------------------------------- + when "10B" + score += 10*(user.stages[PBStats::ACCURACY]-target.stages[PBStats::EVASION]) + #--------------------------------------------------------------------------- + when "10C" + if user.effects[PBEffects::Substitute]>0 + score -= 90 + elsif user.hp<=user.totalhp/4 + score -= 90 + end + #--------------------------------------------------------------------------- + when "10D" + if user.pbHasType?(:GHOST) + if target.effects[PBEffects::Curse] + score -= 90 + elsif user.hp<=user.totalhp/2 + if @battle.pbAbleNonActiveCount(user.idxOwnSide)==0 + score -= 90 + else + score -= 50 + score -= 30 if @battle.switchStyle + end + end + else + avg = user.stages[PBStats::SPEED]*10 + avg -= user.stages[PBStats::ATTACK]*10 + avg -= user.stages[PBStats::DEFENSE]*10 + score += avg/3 + end + #--------------------------------------------------------------------------- + when "10E" + score -= 40 + #--------------------------------------------------------------------------- + when "10F" + if target.effects[PBEffects::Nightmare] || + target.effects[PBEffects::Substitute]>0 + score -= 90 + elsif !target.asleep? + score -= 90 + else + score -= 90 if target.statusCount<=1 + score += 50 if target.statusCount>3 + end + #--------------------------------------------------------------------------- + when "110" + score += 30 if user.effects[PBEffects::Trapping]>0 + score += 30 if user.effects[PBEffects::LeechSeed]>=0 + if @battle.pbAbleNonActiveCount(user.idxOwnSide)>0 + score += 80 if user.pbOwnSide.effects[PBEffects::Spikes]>0 + score += 80 if user.pbOwnSide.effects[PBEffects::ToxicSpikes]>0 + score += 80 if user.pbOwnSide.effects[PBEffects::StealthRock] + end + #--------------------------------------------------------------------------- + when "111" + if @battle.positions[target.index].effects[PBEffects::FutureSightCounter]>0 + score -= 100 + elsif @battle.pbAbleNonActiveCount(user.idxOwnSide)==0 + # Future Sight tends to be wasteful if down to last Pokemon + score -= 70 + end + #--------------------------------------------------------------------------- + when "112" + avg = 0 + avg -= user.stages[PBStats::DEFENSE]*10 + avg -= user.stages[PBStats::SPDEF]*10 + score += avg/2 + if user.effects[PBEffects::Stockpile]>=3 + score -= 80 + else + # More preferable if user also has Spit Up/Swallow + score += 20 if user.pbHasMoveFunction?("113","114") # Spit Up, Swallow + end + #--------------------------------------------------------------------------- + when "113" + score -= 100 if user.effects[PBEffects::Stockpile]==0 + #--------------------------------------------------------------------------- + when "114" + if user.effects[PBEffects::Stockpile]==0 + score -= 90 + elsif user.hp==user.totalhp + score -= 90 + else + mult = [0,25,50,100][user.effects[PBEffects::Stockpile]] + score += mult + score -= user.hp*mult*2/user.totalhp + end + #--------------------------------------------------------------------------- + when "115" + score += 50 if target.effects[PBEffects::HyperBeam]>0 + score -= 35 if target.hp<=target.totalhp/2 # If target is weak, no + score -= 70 if target.hp<=target.totalhp/4 # need to risk this move + #--------------------------------------------------------------------------- + when "116" + #--------------------------------------------------------------------------- + when "117" + hasAlly = false + user.eachAlly do |b| + hasAlly = true + break + end + score -= 90 if !hasAlly + #--------------------------------------------------------------------------- + when "118" + if @battle.field.effects[PBEffects::Gravity]>0 + score -= 90 + elsif skill>=PBTrainerAI.mediumSkill + score -= 30 + score -= 20 if user.effects[PBEffects::SkyDrop]>=0 + score -= 20 if user.effects[PBEffects::MagnetRise]>0 + score -= 20 if user.effects[PBEffects::Telekinesis]>0 + score -= 20 if user.pbHasType?(:FLYING) + score -= 20 if user.hasActiveAbility?(:LEVITATE) + score -= 20 if user.hasActiveItem?(:AIRBALLOON) + score += 20 if target.effects[PBEffects::SkyDrop]>=0 + score += 20 if target.effects[PBEffects::MagnetRise]>0 + score += 20 if target.effects[PBEffects::Telekinesis]>0 + score += 20 if target.inTwoTurnAttack?("0C9","0CC","0CE") # Fly, Bounce, Sky Drop + score += 20 if target.pbHasType?(:FLYING) + score += 20 if target.hasActiveAbility?(:LEVITATE) + score += 20 if target.hasActiveItem?(:AIRBALLOON) + end + #--------------------------------------------------------------------------- + when "119" + if user.effects[PBEffects::MagnetRise]>0 || + user.effects[PBEffects::Ingrain] || + user.effects[PBEffects::SmackDown] + score -= 90 + end + #--------------------------------------------------------------------------- + when "11A" + if target.effects[PBEffects::Telekinesis]>0 || + target.effects[PBEffects::Ingrain] || + target.effects[PBEffects::SmackDown] + score -= 90 + end + #--------------------------------------------------------------------------- + when "11B" + #--------------------------------------------------------------------------- + when "11C" + if skill>=PBTrainerAI.mediumSkill + score += 20 if target.effects[PBEffects::MagnetRise]>0 + score += 20 if target.effects[PBEffects::Telekinesis]>0 + score += 20 if target.inTwoTurnAttack?("0C9","0CC") # Fly, Bounce + score += 20 if target.pbHasType?(:FLYING) + score += 20 if target.hasActiveAbility?(:LEVITATE) + score += 20 if target.hasActiveItem?(:AIRBALLOON) + end + #--------------------------------------------------------------------------- + when "11D" + #--------------------------------------------------------------------------- + when "11E" + #--------------------------------------------------------------------------- + when "11F" + #--------------------------------------------------------------------------- + when "120" + #--------------------------------------------------------------------------- + when "121" + #--------------------------------------------------------------------------- + when "122" + #--------------------------------------------------------------------------- + when "123" + if !target.pbHasType?(user.type1) && + !target.pbHasType?(user.type2) + score -= 90 + end + #--------------------------------------------------------------------------- + when "124" + #--------------------------------------------------------------------------- + when "125" + #--------------------------------------------------------------------------- + when "126" + score += 20 # Shadow moves are more preferable + #--------------------------------------------------------------------------- + when "127" + score += 20 # Shadow moves are more preferable + if target.pbCanParalyze?(user,false) + score += 30 + if skill>=PBTrainerAI.mediumSkill + aspeed = pbRoughStat(user,PBStats::SPEED,skill) + ospeed = pbRoughStat(target,PBStats::SPEED,skill) + if aspeedospeed + score -= 40 + end + end + if skill>=PBTrainerAI.highSkill + score -= 40 if target.hasActiveAbility?([:GUTS,:MARVELSCALE,:QUICKFEET]) + end + end + #--------------------------------------------------------------------------- + when "128" + score += 20 # Shadow moves are more preferable + if target.pbCanBurn?(user,false) + score += 30 + if skill>=PBTrainerAI.highSkill + score -= 40 if target.hasActiveAbility?([:GUTS,:MARVELSCALE,:QUICKFEET,:FLAREBOOST]) + end + end + #--------------------------------------------------------------------------- + when "129" + score += 20 # Shadow moves are more preferable + if target.pbCanFreeze?(user,false) + score += 30 + if skill>=PBTrainerAI.highSkill + score -= 20 if target.hasActiveAbility?(:MARVELSCALE) + end + end + #--------------------------------------------------------------------------- + when "12A" + score += 20 # Shadow moves are more preferable + if target.pbCanConfuse?(user,false) + score += 30 + else + if skill>=PBTrainerAI.mediumSkill + score -= 90 + end + end + #--------------------------------------------------------------------------- + when "12B" + score += 20 # Shadow moves are more preferable + if !target.pbCanLowerStatStage?(PBStats::DEFENSE,user) + score -= 90 + else + score += 40 if user.turnCount==0 + score += target.stages[PBStats::DEFENSE]*20 + end + #--------------------------------------------------------------------------- + when "12C" + score += 20 # Shadow moves are more preferable + if !target.pbCanLowerStatStage?(PBStats::EVASION,user) + score -= 90 + else + score += target.stages[PBStats::EVASION]*15 + end + #--------------------------------------------------------------------------- + when "12D" + score += 20 # Shadow moves are more preferable + #--------------------------------------------------------------------------- + when "12E" + score += 20 # Shadow moves are more preferable + score += 20 if target.hp>=target.totalhp/2 + score -= 20 if user.hp=0 + #--------------------------------------------------------------------------- + when "130" + score += 20 # Shadow moves are more preferable + score -= 40 + #--------------------------------------------------------------------------- + when "131" + score += 20 # Shadow moves are more preferable + if @battle.pbCheckGlobalAbility(:AIRLOCK) || + @battle.pbCheckGlobalAbility(:CLOUDNINE) + score -= 90 + elsif @battle.pbWeather==PBWeather::ShadowSky + score -= 90 + end + #--------------------------------------------------------------------------- + when "132" + score += 20 # Shadow moves are more preferable + if target.pbOwnSide.effects[PBEffects::AuroraVeil]>0 || + target.pbOwnSide.effects[PBEffects::Reflect]>0 || + target.pbOwnSide.effects[PBEffects::LightScreen]>0 || + target.pbOwnSide.effects[PBEffects::Safeguard]>0 + score += 30 + score -= 90 if user.pbOwnSide.effects[PBEffects::AuroraVeil]>0 || + user.pbOwnSide.effects[PBEffects::Reflect]>0 || + user.pbOwnSide.effects[PBEffects::LightScreen]>0 || + user.pbOwnSide.effects[PBEffects::Safeguard]>0 + else + score -= 110 + end + #--------------------------------------------------------------------------- + when "133", "134" + score -= 95 + score = 0 if skill>=PBTrainerAI.highSkill + #--------------------------------------------------------------------------- + when "135" + if target.pbCanFreeze?(user,false) + score += 30 + if skill>=PBTrainerAI.highSkill + score -= 20 if target.hasActiveAbility?(:MARVELSCALE) + end + end + #--------------------------------------------------------------------------- + when "136" + score += 20 if user.stages[PBStats::DEFENSE]<0 + #--------------------------------------------------------------------------- + when "137" + hasEffect = user.statStageAtMax?(PBStats::DEFENSE) && + user.statStageAtMax?(PBStats::SPDEF) + user.eachAlly do |b| + next if b.statStageAtMax?(PBStats::DEFENSE) && b.statStageAtMax?(PBStats::SPDEF) + hasEffect = true + score -= b.stages[PBStats::DEFENSE]*10 + score -= b.stages[PBStats::SPDEF]*10 + end + if hasEffect + score -= user.stages[PBStats::DEFENSE]*10 + score -= user.stages[PBStats::SPDEF]*10 + else + score -= 90 + end + #--------------------------------------------------------------------------- + when "138" + if target.statStageAtMax?(PBStats::SPDEF) + score -= 90 + else + score -= target.stages[PBStats::SPDEF]*10 + end + #--------------------------------------------------------------------------- + when "139" + if !target.pbCanLowerStatStage?(PBStats::ATTACK,user) + score -= 90 + else + score += target.stages[PBStats::ATTACK]*20 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + target.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + if hasPhysicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + #--------------------------------------------------------------------------- + when "13A" + avg = target.stages[PBStats::ATTACK]*10 + avg += target.stages[PBStats::SPATK]*10 + score += avg/2 + #--------------------------------------------------------------------------- + when "13B" + if !isConst?(user.species,PBSpecies,:HOOPA) || user.form!=1 + score -= 100 + else + score += 20 if target.stages[PBStats::DEFENSE]>0 + end + #--------------------------------------------------------------------------- + when "13C" + score += 20 if target.stages[PBStats::SPATK]>0 + #--------------------------------------------------------------------------- + when "13D" + if !target.pbCanLowerStatStage?(PBStats::SPATK,user) + score -= 90 + else + score += 40 if user.turnCount==0 + score += target.stages[PBStats::SPATK]*20 + end + #--------------------------------------------------------------------------- + when "13E" + count = 0 + @battle.eachBattler do |b| + if b.pbHasType?(:GRASS) && !b.airborne? && + (!b.statStageAtMax?(PBStats::ATTACK) || !b.statStageAtMax?(PBStats::SPATK)) + count += 1 + if user.opposes?(b) + score -= 20 + else + score -= user.stages[PBStats::ATTACK]*10 + score -= user.stages[PBStats::SPATK]*10 + end + end + end + score -= 95 if count==0 + #--------------------------------------------------------------------------- + when "13F" + count = 0 + @battle.eachBattler do |b| + if b.pbHasType?(:GRASS) && !b.statStageAtMax?(PBStats::DEFENSE) + count += 1 + if user.opposes?(b) + score -= 20 + else + score -= user.stages[PBStats::DEFENSE]*10 + end + end + end + score -= 95 if count==0 + #--------------------------------------------------------------------------- + when "140" + count=0 + @battle.eachBattler do |b| + if b.poisoned? && + (!b.statStageAtMin?(PBStats::ATTACK) || + !b.statStageAtMin?(PBStats::SPATK) || + !b.statStageAtMin?(PBStats::SPEED)) + count += 1 + if user.opposes?(b) + score += user.stages[PBStats::ATTACK]*10 + score += user.stages[PBStats::SPATK]*10 + score += user.stages[PBStats::SPEED]*10 + else + score -= 20 + end + end + end + score -= 95 if count==0 + #--------------------------------------------------------------------------- + when "141" + if target.effects[PBEffects::Substitute]>0 + score -= 90 + else + numpos = 0; numneg = 0 + PBStats.eachBattleStat do |s| + numpos += target.stages[s] if target.stages[s]>0 + numneg += target.stages[s] if target.stages[s]<0 + end + if numpos!=0 || numneg!=0 + score += (numpos-numneg)*10 + else + score -= 95 + end + end + #--------------------------------------------------------------------------- + when "142" + score -= 90 if target.pbHasType?(:GHOST) + #--------------------------------------------------------------------------- + when "143" + score -= 90 if target.pbHasType?(:GRASS) + #--------------------------------------------------------------------------- + when "144" + #--------------------------------------------------------------------------- + when "145" + aspeed = pbRoughStat(user,PBStats::SPEED,skill) + ospeed = pbRoughStat(target,PBStats::SPEED,skill) + score -= 90 if aspeed>ospeed + #--------------------------------------------------------------------------- + when "146" + #--------------------------------------------------------------------------- + when "147" + #--------------------------------------------------------------------------- + when "148" + aspeed = pbRoughStat(user,PBStats::SPEED,skill) + ospeed = pbRoughStat(target,PBStats::SPEED,skill) + if aspeed>ospeed + score -= 90 + else + score += 30 if target.pbHasMoveType?(:FIRE) + end + #--------------------------------------------------------------------------- + when "149" + if user.turnCount==0 + score += 30 + else + score -= 90 # Because it will fail here + score = 0 if skill>=PBTrainerAI.bestSkill + end + #--------------------------------------------------------------------------- + when "14A" + #--------------------------------------------------------------------------- + when "14B", "14C" + if user.effects[PBEffects::ProtectRate]>1 || + target.effects[PBEffects::HyperBeam]>0 + score -= 90 + else + if skill>=PBTrainerAI.mediumSkill + score -= user.effects[PBEffects::ProtectRate]*40 + end + score += 50 if user.turnCount==0 + score += 30 if target.effects[PBEffects::TwoTurnAttack]>0 + end + #--------------------------------------------------------------------------- + when "14D" + #--------------------------------------------------------------------------- + when "14E" + if user.statStageAtMax?(PBStats::SPATK) && + user.statStageAtMax?(PBStats::SPDEF) && + user.statStageAtMax?(PBStats::SPEED) + score -= 90 + else + score -= user.stages[PBStats::SPATK]*10 # Only *10 isntead of *20 + score -= user.stages[PBStats::SPDEF]*10 # because two-turn attack + score -= user.stages[PBStats::SPEED]*10 + if skill>=PBTrainerAI.mediumSkill + hasSpecialAttack = false + user.eachMove do |m| + next if !m.specialMove?(m.type) + hasSpecialAttack = true + break + end + if hasSpecialAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + if skill>=PBTrainerAI.highSkill + aspeed = pbRoughStat(user,PBStats::SPEED,skill) + ospeed = pbRoughStat(target,PBStats::SPEED,skill) + score += 30 if aspeedospeed + end + end + #--------------------------------------------------------------------------- + when "14F" + if skill>=PBTrainerAI.highSkill && target.hasActiveAbility?(:LIQUIDOOZE) + score -= 80 + else + score += 40 if user.hp<=user.totalhp/2 + end + #--------------------------------------------------------------------------- + when "150" + score += 20 if !user.statStageAtMax?(PBStats::ATTACK) && target.hp<=target.totalhp/4 + #--------------------------------------------------------------------------- + when "151" + avg = target.stages[PBStats::ATTACK]*10 + avg += target.stages[PBStats::SPATK]*10 + score += avg/2 + #--------------------------------------------------------------------------- + when "152" + #--------------------------------------------------------------------------- + when "153" + score -= 95 if target.pbOwnSide.effects[PBEffects::StickyWeb] + #--------------------------------------------------------------------------- + when "154" + #--------------------------------------------------------------------------- + when "155" + #--------------------------------------------------------------------------- + when "156" + #--------------------------------------------------------------------------- + when "157" + score -= 90 + #--------------------------------------------------------------------------- + when "158" + score -= 90 if !user.belched? + #--------------------------------------------------------------------------- + when "159" + if !target.pbCanPoison?(user,false) && !target.pbCanLowerStatStage?(PBStats::SPEED,user) + score -= 90 + else + if target.pbCanPoison?(user,false) + score += 30 + if skill>=PBTrainerAI.mediumSkill + score += 30 if target.hp<=target.totalhp/4 + score += 50 if target.hp<=target.totalhp/8 + score -= 40 if target.effects[PBEffects::Yawn]>0 + end + if skill>=PBTrainerAI.highSkill + score += 10 if pbRoughStat(target,PBStats::DEFENSE,skill)>100 + score += 10 if pbRoughStat(target,PBStats::SPDEF,skill)>100 + score -= 40 if target.hasActiveAbility?([:GUTS,:MARVELSCALE,:TOXICBOOST]) + end + end + if target.pbCanLowerStatStage?(PBStats::SPEED,user) + score += target.stages[PBStats::SPEED]*10 + if skill>=PBTrainerAI.highSkill + aspeed = pbRoughStat(user,PBStats::SPEED,skill) + ospeed = pbRoughStat(target,PBStats::SPEED,skill) + score += 30 if aspeedospeed + end + end + end + #--------------------------------------------------------------------------- + when "15A" + if target.opposes?(user) + score -= 40 if target.status==PBStatuses::BURN + else + score += 40 if target.status==PBStatuses::BURN + end + #--------------------------------------------------------------------------- + when "15B" + if target.status==PBStatuses::NONE + score -= 90 + elsif user.hp==user.totalhp && target.opposes?(user) + score -= 90 + else + score += (user.totalhp-user.hp)*50/user.totalhp + score -= 30 if target.opposes?(user) + end + #--------------------------------------------------------------------------- + when "15C" + hasEffect = user.statStageAtMax?(PBStats::ATTACK) && + user.statStageAtMax?(PBStats::SPATK) + user.eachAlly do |b| + next if b.statStageAtMax?(PBStats::ATTACK) && b.statStageAtMax?(PBStats::SPATK) + hasEffect = true + score -= b.stages[PBStats::ATTACK]*10 + score -= b.stages[PBStats::SPATK]*10 + end + if hasEffect + score -= user.stages[PBStats::ATTACK]*10 + score -= user.stages[PBStats::SPATK]*10 + else + score -= 90 + end + #--------------------------------------------------------------------------- + when "15D" + numStages = 0 + PBStats.eachBattleStat do |s| + next if target.stages[s]<=0 + numStages += target.stages[s] + end + score += numStages*20 + #--------------------------------------------------------------------------- + when "15E" + if user.effects[PBEffects::LaserFocus]>0 + score -= 90 + else + score += 40 + end + #--------------------------------------------------------------------------- + when "15F" + score += user.stages[PBStats::DEFENSE]*10 + #--------------------------------------------------------------------------- + when "160" + if target.statStageAtMin?(PBStats::ATTACK) + score -= 90 + else + if target.pbCanLowerStatStage?(PBStats::ATTACK,user) + score += target.stages[PBStats::ATTACK]*20 + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + target.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + if hasPhysicalAttack + score += 20 + elsif skill>=PBTrainerAI.highSkill + score -= 90 + end + end + end + score += (user.totalhp-user.hp)*50/user.totalhp + end + #--------------------------------------------------------------------------- + when "161" + if skill>=PBTrainerAI.mediumSkill + if user.speed>target.speed + score += 50 + else + score -= 70 + end + end + #--------------------------------------------------------------------------- + when "162" + score -= 90 if !user.pbHasType?(:FIRE) + #--------------------------------------------------------------------------- + when "163" + #--------------------------------------------------------------------------- + when "164" + #--------------------------------------------------------------------------- + when "165" + if skill>=PBTrainerAI.mediumSkill + userSpeed = pbRoughStat(user,PBStats::SPEED,skill) + targetSpeed = pbRoughStat(target,PBStats::SPEED,skill) + if userSpeed0 || @battle.pbWeather!=PBWeather::Hail + score -= 90 + else + score += 40 + end + #--------------------------------------------------------------------------- + when "168" + if user.effects[PBEffects::ProtectRate]>1 || + target.effects[PBEffects::HyperBeam]>0 + score -= 90 + else + if skill>=PBTrainerAI.mediumSkill + score -= user.effects[PBEffects::ProtectRate]*40 + end + score += 50 if user.turnCount==0 + score += 30 if target.effects[PBEffects::TwoTurnAttack]>0 + score += 20 # Because of possible poisoning + end + #--------------------------------------------------------------------------- + when "169" + #--------------------------------------------------------------------------- + when "16A" + hasAlly = false + target.eachAlly do |b| + hasAlly = true + break + end + score -= 90 if !hasAlly + #--------------------------------------------------------------------------- + when "16B" + if skill>=PBTrainerAI.mediumSkill + if target.lastRegularMoveUsed<0 || + !target.pbHasMove?(target.lastRegularMoveUsed) || + target.usingMultiTurnAttack? + score -= 90 + else + # Without lots of code here to determine good/bad moves and relative + # speeds, using this move is likely to just be a waste of a turn + score -= 50 + end + end + #--------------------------------------------------------------------------- + when "16C" + if target.effects[PBEffects::ThroatChop]==0 && skill>=PBTrainerAI.highSkill + hasSoundMove = false + user.eachMove do |m| + next if !m.soundMove? + hasSoundMove = true + break + end + score += 40 if hasSoundMove + end + #--------------------------------------------------------------------------- + when "16D" + if user.hp==user.totalhp || (skill>=PBTrainerAI.mediumSkill && !user.canHeal?) + score -= 90 + else + score += 50 + score -= user.hp*100/user.totalhp + score += 30 if @battle.pbWeather==PBWeather::Sandstorm + end + #--------------------------------------------------------------------------- + when "16E" + if user.hp==user.totalhp || (skill>=PBTrainerAI.mediumSkill && !user.canHeal?) + score -= 90 + else + score += 50 + score -= user.hp*100/user.totalhp + if skill>=PBTrainerAI.mediumSkill + score += 30 if @battle.field.terrain==PBBattleTerrains::Grassy + end + end + #--------------------------------------------------------------------------- + when "16F" + if !target.opposes?(user) + if target.hp==target.totalhp || (skill>=PBTrainerAI.mediumSkill && !target.canHeal?) + score -= 90 + else + score += 50 + score -= target.hp*100/target.totalhp + end + end + #--------------------------------------------------------------------------- + when "170" + reserves = @battle.pbAbleNonActiveCount(user.idxOwnSide) + foes = @battle.pbAbleNonActiveCount(user.idxOpposingSide) + if @battle.pbCheckGlobalAbility(:DAMP) + score -= 100 + elsif skill>=PBTrainerAI.mediumSkill && reserves==0 && foes>0 + score -= 100 # don't want to lose + elsif skill>=PBTrainerAI.highSkill && reserves==0 && foes==0 + score += 80 # want to draw + else + score -= (user.total.hp-user.hp)*75/user.totalhp + end + #--------------------------------------------------------------------------- + when "171" + if skill>=PBTrainerAI.mediumSkill + hasPhysicalAttack = false + target.eachMove do |m| + next if !m.physicalMove?(m.type) + hasPhysicalAttack = true + break + end + score -= 80 if !hasPhysicalAttack + end + #--------------------------------------------------------------------------- + when "172" + score += 20 # Because of possible burning + #--------------------------------------------------------------------------- + when "173" + #--------------------------------------------------------------------------- + when "174" + score -= 90 if user.turnCount>0 || user.lastRoundMoved>=0 + #--------------------------------------------------------------------------- + when "175" + score += 30 if target.effects[PBEffects::Minimize] + #--------------------------------------------------------------------------- + end + return score + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/004_AI/006_AI_Move_Utilities.rb b/Data/Scripts/011_Battle/004_AI/006_AI_Move_Utilities.rb new file mode 100644 index 000000000..8f0b8ad8f --- /dev/null +++ b/Data/Scripts/011_Battle/004_AI/006_AI_Move_Utilities.rb @@ -0,0 +1,675 @@ +class PokeBattle_AI + #============================================================================= + # + #============================================================================= + def pbTargetsMultiple?(move,user) + numTargets = 0 + case move.pbTarget(user) + when PBTargets::AllNearFoes + @battle.eachOtherSideBattler(user) { |b| numTargets += 1 if b.near?(user) } + return numTargets>1 + when PBTargets::AllNearOthers + @battle.eachBattler { |b| numTargets += 1 if b.near?(user) } + return numTargets>1 + when PBTargets::UserAndAllies + @battle.eachSameSideBattler(user) { |b| numTargets += 1 } + return numTargets>1 + when PBTargets::AllFoes + @battle.eachOtherSideBattler(user) { |b| numTargets += 1 } + return numTargets>1 + when PBTargets::AllBattlers + @battle.eachBattler { |b| numTargets += 1 } + return numTargets>1 + end + return false + end + + #============================================================================= + # Move's type effectiveness + #============================================================================= + def pbCalcTypeModSingle(moveType,defType,user,target) + ret = PBTypes.getEffectiveness(moveType,defType) + # Ring Target + if target.hasActiveItem?(:RINGTARGET) + ret = PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE if PBTypes.ineffective?(moveType,defType) + end + # Foresight + if user.hasActiveAbility?(:SCRAPPY) || target.effects[PBEffects::Foresight] + ret = PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE if isConst?(defType,PBTypes,:GHOST) && + PBTypes.ineffective?(moveType,defType) + end + # Miracle Eye + if target.effects[PBEffects::MiracleEye] + ret = PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE if isConst?(defType,PBTypes,:DARK) && + PBTypes.ineffective?(moveType,defType) + end + # Delta Stream's weather + if @battle.pbWeather==PBWeather::StrongWinds + ret = PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE if isConst?(defType,PBTypes,:FLYING) && + PBTypes.superEffective?(moveType,defType) + end + # Grounded Flying-type Pokémon become susceptible to Ground moves + if !target.airborne? + ret = PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE if isConst?(defType,PBTypes,:FLYING) && + isConst?(moveType,PBTypes,:GROUND) + end + return ret + end + + def pbCalcTypeMod(moveType,user,target) + return PBTypeEffectiveness::NORMAL_EFFECTIVE if moveType<0 + return PBTypeEffectiveness::NORMAL_EFFECTIVE if isConst?(moveType,PBTypes,:GROUND) && + target.pbHasType?(:FLYING) && target.hasActiveItem?(:IRONBALL) + # Determine types + tTypes = target.pbTypes(true) + # Get effectivenesses + typeMods = [PBTypeEffectiveness::NORMAL_EFFECTIVE_ONE] * 3 # 3 types max + tTypes.each_with_index do |type,i| + typeMods[i] = pbCalcTypeModSingle(moveType,type,user,target) + end + # Multiply all effectivenesses together + ret = 1 + typeMods.each { |m| ret *= m } + return ret + end + + # For switching. Determines the effectiveness of a potential switch-in against + # an opposing battler. + def pbCalcTypeModPokemon(battlerThis,battlerOther) + mod1 = PBTypes.getCombinedEffectiveness(battlerThis.type1,target.type1,target.type2) + mod2 = PBTypeEffectiveness::NORMAL_EFFECTIVE + if battlerThis.type1!=battlerThis.type2 + mod2 = PBTypes.getCombinedEffectiveness(battlerThis.type2,target.type1,target.type2) + end + return mod1*mod2 # Normal effectiveness is 64 here + end + + #============================================================================= + # Immunity to a move because of the target's ability, item or other effects + #============================================================================= + def pbCheckMoveImmunity(score,move,user,target,skill) + type = pbRoughType(move,user,skill) + typeMod = pbCalcTypeMod(type,user,target) + # Type effectiveness + return true if PBTypes.ineffective?(typeMod) || score<=0 + # Immunity due to ability/item/other effects + if skill>=PBTrainerAI.mediumSkill + if isConst?(move.type,PBTypes,:GROUND) + return true if target.airborne? && !move.hitsFlyingTargets? + elsif isConst?(move.type,PBTypes,:FIRE) + return true if target.hasActiveAbility?(:FLASHFIRE) + elsif isConst?(move.type,PBTypes,:WATER) + return true if target.hasActiveAbility?([:DRYSKIN,:STORMDRAIN,:WATERABSORB]) + elsif isConst?(move.type,PBTypes,:GRASS) + return true if target.hasActiveAbility?(:SAPSIPPER) + elsif isConst?(move.type,PBTypes,:ELECTRIC) + return true if target.hasActiveAbility?([:LIGHTNINGROD,:MOTORDRIVE,:VOLTABSORB]) + end + return true if PBTypes.notVeryEffective?(typeMod) && + target.hasActiveAbility?(:WONDERGUARD) + return true if move.damagingMove? && user.index!=target.index && !target.opposes?(user) && + target.hasActiveAbility?(:TELEPATHY) + return true if move.canMagicCoat? && target.hasActiveAbility?(:MAGICBOUNCE) && + target.opposes?(user) + return true if move.soundMove? && target.hasActiveAbility?(:SOUNDPROOF) + return true if move.bombMove? && target.hasActiveAbility?(:BULLETPROOF) + if move.powderMove? + return true if target.pbHasType?(:GRASS) + return true if target.hasActiveAbility?(:OVERCOAT) + return true if target.hasActiveItem?(:SAFETYGOGGLES) + end + return true if target.effects[PBEffects::Substitute]>0 && move.statusMove? && + !move.ignoresSubstitute?(user) && user.index!=target.index + return true if NEWEST_BATTLE_MECHANICS && user.hasActiveAbility?(:PRANKSTER) && + target.pbHasType?(:DARK) && target.opposes?(user) + return true if move.priority>0 && @battle.field.terrain==PBBattleTerrains::Psychic && + target.affectedByTerrain? && target.opposes?(user) + end + return false + end + + #============================================================================= + # Get approximate properties for a battler + #============================================================================= + def pbRoughType(move,user,skill) + ret = move.type + if skill>=PBTrainerAI.highSkill + ret = move.pbCalcType(user) + end + return ret + end + + def pbRoughStat(battler,stat,skill) + return battler.pbSpeed if skill>=PBTrainerAI.highSkill && stat==PBStats::SPEED + stageMul = [2,2,2,2,2,2, 2, 3,4,5,6,7,8] + stageDiv = [8,7,6,5,4,3, 2, 2,2,2,2,2,2] + stage = battler.stages[stat]+6 + value = 0 + case stat + when PBStats::ATTACK; value = battler.attack + when PBStats::DEFENSE; value = battler.defense + when PBStats::SPATK; value = battler.spatk + when PBStats::SPDEF; value = battler.spdef + when PBStats::SPEED; value = battler.speed + end + return (value.to_f*stageMul[stage]/stageDiv[stage]).floor + end + + #============================================================================= + # Get a better move's base damage value + #============================================================================= + def pbMoveBaseDamage(move,user,target,skill) + baseDmg = move.baseDamage + baseDmg = 60 if baseDmg==1 + return baseDmg if skill=PBTrainerAI.mediumSkill && target.effects[PBEffects::Minimize] + # Sonic Boom, Dragon Rage, Super Fang, Night Shade, Endeavor + when "06A", "06B", "06C", "06D", "06E" + baseDmg = move.pbFixedDamage(user,target) + when "06F" # Psywave + baseDmg = user.level + when "070" # OHKO + baseDmg = 200 + when "071", "072", "073" # Counter, Mirror Coat, Metal Burst + baseDmg = 60 + when "075", "076", "0D0", "12D" # Surf, Earthquake, Whirlpool, Shadow Storm + baseDmg = move.pbModifyDamage(baseDmg,user,target) + # Gust, Twister, Venoshock, Smelling Salts, Wake-Up Slap, Facade, Hex, Brine, + # Retaliate, Weather Ball, Return, Frustration, Eruption, Crush Grip, + # Stored Power, Punishment, Hidden Power, Fury Cutter, Echoed Voice, + # Trump Card, Flail, Electro Ball, Low Kick, Fling, Spit Up + when "077", "078", "07B", "07C", "07D", "07E", "07F", "080", "085", "087", + "089", "08A", "08B", "08C", "08E", "08F", "090", "091", "092", "097", + "098", "099", "09A", "0F7", "113" + baseDmg = move.pbBaseDamage(baseDmg,user,target) + when "086" # Acrobatics + baseDmg *= 2 if user.item==0 || user.hasActiveItem?(:FLYINGGEM) + when "08D" # Gyro Ball + targetSpeed = pbRoughStat(target,PBStats::SPEED,skill) + userSpeed = pbRoughStat(user,PBStats::SPEED,skill) + baseDmg = [[(25*targetSpeed/userSpeed).floor,150].min,1].max + when "094" # Present + baseDmg = 50 + when "095" # Magnitude + baseDmg = 71 + baseDmg *= 2 if target.inTwoTurnAttack?("0CA") # Dig + when "096" # Natural Gift + baseDmg = move.pbNaturalGiftBaseDamage(user.item) + when "09B" # Heavy Slam + baseDmg = move.pbBaseDamage(baseDmg,user,target) + baseDmg *= 2 if NEWEST_BATTLE_MECHANICS && skill>=PBTrainerAI.mediumSkill && + target.effects[PBEffects::Minimize] + when "0A0", "0BD", "0BE" # Frost Breath, Double Kick, Twineedle + baseDmg *= 2 + when "0BF" # Triple Kick + baseDmg *= 6 # Hits do x1, x2, x3 baseDmg in turn, for x6 in total + when "0C0" # Fury Attack + if user.hasActiveAbility?(:SKILLLINK) + baseDmg *= 5 + else + baseDmg = (baseDmg*19/6).floor # Average damage dealt + end + when "0C1" # Beat Up + mult = 0 + @battle.eachInTeamFromBattlerIndex(user.index) do |pkmn,i| + mult += 1 if pkmn && pkmn.able? && pkmn.status==PBStatuses::NONE + end + baseDmg *= mult + when "0C4" # Solar Beam + baseDmg = move.pbBaseDamageMultiplier(baseDmg,user,target) + when "0D3" # Rollout + baseDmg *= 2 if user.effects[PBEffects::DefenseCurl] + when "0D4" # Bide + baseDmg = 40 + when "0E1" # Final Gambit + baseDmg = user.hp + when "144" # Flying Press + type = getConst(PBTypes,:FLYING) || -1 + if type>=0 + if skill>=PBTrainerAI.highSkill + targetTypes = target.pbTypes(true) + mult = PBTypes.getCombinedEffectiveness(type, + targetTypes[0],targetTypes[1],targetTypes[2]) + baseDmg = (baseDmg.to_f*mult/PBTypeEffectiveness::NORMAL_EFFECTIVE).round + else + mult = PBTypes.getCombinedEffectiveness(type, + target.type1,target.type2,target.effects[PBEffects::Type3]) + baseDmg = (baseDmg.to_f*mult/PBTypeEffectiveness::NORMAL_EFFECTIVE).round + end + end + baseDmg *= 2 if skill>=PBTrainerAI.mediumSkill && target.effects[PBEffects::Minimize] + when "166" # Stomping Tantrum + baseDmg *= 2 if user.lastRoundMoveFailed + when "175" # Double Iron Bash + baseDmg *= 2 + baseDmg *= 2 if skill>=PBTrainerAI.mediumSkill && target.effects[PBEffects::Minimize] + end + return baseDmg + end + + #============================================================================= + # Damage calculation + #============================================================================= + def pbRoughDamage(move,user,target,skill,baseDmg) + # Fixed damage moves + return baseDmg if move.is_a?(PokeBattle_FixedDamageMove) + # Get the move's type + type = pbRoughType(move,user,skill) + ##### Calculate user's attack stat ##### + atk = pbRoughStat(user,PBStats::ATTACK,skill) + if move.function=="121" # Foul Play + atk = pbRoughStat(target,PBStats::ATTACK,skill) + elsif move.specialMove?(type) + if move.function=="121" # Foul Play + atk = pbRoughStat(target,PBStats::SPATK,skill) + else + atk = pbRoughStat(user,PBStats::SPATK,skill) + end + end + ##### Calculate target's defense stat ##### + defense = pbRoughStat(target,PBStats::DEFENSE,skill) + if move.specialMove?(type) && move.function!="122" # Psyshock + defense = pbRoughStat(target,PBStats::SPDEF,skill) + end + ##### Calculate all multiplier effects ##### + multipliers = [0x1000,0x1000,0x1000,0x1000] + # Ability effects that alter damage + moldBreaker = false + if skill>=PBTrainerAI.highSkill && target.hasMoldBreaker? + moldBreaker = true + end + if skill>=PBTrainerAI.mediumSkill && user.abilityActive? + # NOTE: These abilities aren't suitable for checking at the start of the + # round. + abilityBlacklist = [:ANALYTIC,:SNIPER,:TINTEDLENS,:AERILATE,:PIXILATE,:REFRIGERATE] + canCheck = true + abilityBlacklist.each do |m| + next if !isConst?(move.id,PBMoves,m) + canCheck = false + break + end + if canCheck + BattleHandlers.triggerDamageCalcUserAbility(user.ability, + user,target,move,multipliers,baseDmg,type) + end + end + if skill>=PBTrainerAI.mediumSkill && !moldBreaker + user.eachAlly do |b| + next if !b.abilityActive? + BattleHandlers.triggerDamageCalcUserAllyAbility(b.ability, + user,target,move,multipliers,baseDmg,type) + end + end + if skill>=PBTrainerAI.bestSkill && !moldBreaker && target.abilityActive? + # NOTE: These abilities aren't suitable for checking at the start of the + # round. + abilityBlacklist = [:FILTER,:SOLIDROCK] + canCheck = true + abilityBlacklist.each do |m| + next if !isConst?(move.id,PBMoves,m) + canCheck = false + break + end + if canCheck + BattleHandlers.triggerDamageCalcTargetAbility(target.ability, + user,target,move,multipliers,baseDmg,type) + end + end + if skill>=PBTrainerAI.bestSkill && !moldBreaker + target.eachAlly do |b| + next if !b.abilityActive? + BattleHandlers.triggerDamageCalcTargetAllyAbility(b.ability, + user,target,move,multipliers,baseDmg,type) + end + end + # Item effects that alter damage + # NOTE: Type-boosting gems aren't suitable for checking at the start of the + # round. + if skill>=PBTrainerAI.mediumSkill && user.itemActive? + # NOTE: These items aren't suitable for checking at the start of the + # round. + itemBlacklist = [:EXPERTBELT,:LIFEORB] + canCheck = true + itemBlacklist.each do |i| + next if !isConst?(user.item,PBItems,i) + canCheck = false + break + end + if canCheck + BattleHandlers.triggerDamageCalcUserItem(user.item, + user,target,move,multipliers,baseDmg,type) + end + end + if skill>=PBTrainerAI.bestSkill && target.itemActive? + # NOTE: Type-weakening berries aren't suitable for checking at the start + # of the round. + if !pbIsBerry?(target.item) + BattleHandlers.triggerDamageCalcTargetItem(target.item, + user,target,move,multipliers,baseDmg,type) + end + end + # Global abilities + if skill>=PBTrainerAI.mediumSkill + if (@battle.pbCheckGlobalAbility(:DARKAURA) && isConst?(type,PBTypes,:DARK)) || + (@battle.pbCheckGlobalAbility(:FAIRYAURA) && isConst?(type,PBTypes,:FAIRY)) + if @battle.pbCheckGlobalAbility(:AURABREAK) + multipliers[BASE_DMG_MULT] *= 2/3 + else + multipliers[BASE_DMG_MULT] *= 4/3 + end + end + end + # Parental Bond + if skill>=PBTrainerAI.mediumSkill && user.hasActiveAbility?(:PARENTALBOND) + multipliers[BASE_DMG_MULT] = (multipliers[BASE_DMG_MULT]*1.25).floor + end + # Me First + # TODO + # Helping Hand - n/a + # Charge + if skill>=PBTrainerAI.mediumSkill + if user.effects[PBEffects::Charge]>0 && isConst?(type,PBTypes,:ELECTRIC) + multipliers[BASE_DMG_MULT] *= 2 + end + end + # Mud Sport and Water Sport + if skill>=PBTrainerAI.mediumSkill + if isConst?(type,PBTypes,:ELECTRIC) + @battle.eachBattler do |b| + next if !b.effects[PBEffects::MudSport] + multipliers[BASE_DMG_MULT] /= 3 + break + end + if @battle.field.effects[PBEffects::MudSportField]>0 + multipliers[BASE_DMG_MULT] /= 3 + end + end + if isConst?(type,PBTypes,:FIRE) + @battle.eachBattler do |b| + next if !b.effects[PBEffects::WaterSport] + multipliers[BASE_DMG_MULT] /= 3 + break + end + if @battle.field.effects[PBEffects::WaterSportField]>0 + multipliers[BASE_DMG_MULT] /= 3 + end + end + end + # Terrain moves + if user.affectedByTerrain? && skill>=PBTrainerAI.mediumSkill + case @battle.field.terrain + when PBBattleTerrains::Electric + if isConst?(type,PBTypes,:ELECTRIC) + multipliers[BASE_DMG_MULT] = (multipliers[BASE_DMG_MULT]*1.5).round + end + when PBBattleTerrains::Grassy + if isConst?(type,PBTypes,:GRASS) + multipliers[BASE_DMG_MULT] = (multipliers[BASE_DMG_MULT]*1.5).round + end + when PBBattleTerrains::Psychic + if isConst?(type,PBTypes,:PSYCHIC) + multipliers[BASE_DMG_MULT] = (multipliers[BASE_DMG_MULT]*1.5).round + end + end + end + if target.affectedByTerrain? && skill>=PBTrainerAI.mediumSkill + if @battle.field.terrain==PBBattleTerrains::Misty && isConst?(type,PBTypes,:DRAGON) + multipliers[BASE_DMG_MULT] /= 2 + end + end + # Badge multipliers + if skill>=PBTrainerAI.highSkill + if @battle.internalBattle + # Don't need to check the Atk/Sp Atk-boosting badges because the AI + # won't control the player's Pokémon. + if target.pbOwnedByPlayer? + if move.physicalMove?(type) && @battle.pbPlayer.numbadges>=NUM_BADGES_BOOST_DEFENSE + multipliers[DEF_MULT] = (multipliers[DEF_MULT]*1.1).round + elsif move.specialMove?(type) && @battle.pbPlayer.numbadges>=NUM_BADGES_BOOST_SPDEF + multipliers[DEF_MULT] = (multipliers[DEF_MULT]*1.1).round + end + end + end + end + # Multi-targeting attacks + if skill>=PBTrainerAI.highSkill + if pbTargetsMultiple?(move,user) + multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*0.75).round + end + end + # Weather + if skill>=PBTrainerAI.mediumSkill + case @battle.pbWeather + when PBWeather::Sun, PBWeather::HarshSun + if isConst?(type,PBTypes,:FIRE) + multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*1.5).round + elsif isConst?(type,PBTypes,:WATER) + multipliers[FINAL_DMG_MULT] /= 2 + end + when PBWeather::Rain, PBWeather::HeavyRain + if isConst?(type,PBTypes,:FIRE) + multipliers[FINAL_DMG_MULT] /= 2 + elsif isConst?(type,PBTypes,:WATER) + multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*1.5).round + end + when PBWeather::Sandstorm + if target.pbHasType?(:ROCK) && move.specialMove?(type) && move.function!="122" # Psyshock + multipliers[DEF_MULT] = (multipliers[DEF_MULT]*1.5).round + end + end + end + # Critical hits - n/a + # Random variance - n/a + # STAB + if skill>=PBTrainerAI.mediumSkill + if type>=0 && user.pbHasType?(type) + if user.hasActiveAbility?(:ADAPTABILITY) + multipliers[FINAL_DMG_MULT] *= 2 + else + multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*1.5).round + end + end + end + # Type effectiveness + if skill>=PBTrainerAI.mediumSkill + typemod = pbCalcTypeMod(type,user,target) + multipliers[FINAL_DMG_MULT] *= typemod.to_f/PBTypeEffectiveness::NORMAL_EFFECTIVE + multipliers[FINAL_DMG_MULT] = multipliers[FINAL_DMG_MULT].round + end + # Burn + if skill>=PBTrainerAI.highSkill + if user.status==PBStatuses::BURN && move.physicalMove?(type) && + !user.hasActiveAbility?(:GUTS) && + !(NEWEST_BATTLE_MECHANICS && move.function=="07E") # Facade + multipliers[FINAL_DMG_MULT] /= 2 + end + end + # Aurora Veil, Reflect, Light Screen + if skill>=PBTrainerAI.highSkill + if !move.ignoresReflect? && !user.hasActiveAbility?(:INFILTRATOR) + if target.pbOwnSide.effects[PBEffects::AuroraVeil]>0 + if @battle.pbSideBattlerCount(target)>1 + multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*2/3).round + else + multipliers[FINAL_DMG_MULT] /= 2 + end + elsif target.pbOwnSide.effects[PBEffects::Reflect]>0 && move.physicalMove?(type) + if @battle.pbSideBattlerCount(target)>1 + multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*2/3).round + else + multipliers[FINAL_DMG_MULT] /= 2 + end + elsif target.pbOwnSide.effects[PBEffects::LightScreen]>0 && move.specialMove?(type) + if @battle.pbSideBattlerCount(target)>1 + multipliers[FINAL_DMG_MULT] = (multipliers[FINAL_DMG_MULT]*2/3).round + else + multipliers[FINAL_DMG_MULT] /= 2 + end + end + end + end + # Minimize + if skill>=PBTrainerAI.highSkill + if target.effects[PBEffects::Minimize] && move.tramplesMinimize?(2) + multipliers[FINAL_DMG_MULT] *= 2 + end + end + # Move-specific base damage modifiers + # TODO + # Move-specific final damage modifiers + # TODO + ##### Main damage calculation ##### + baseDmg = [(baseDmg * multipliers[BASE_DMG_MULT] / 0x1000).round,1].max + atk = [(atk * multipliers[ATK_MULT] / 0x1000).round,1].max + defense = [(defense * multipliers[DEF_MULT] / 0x1000).round,1].max + damage = (((2.0*user.level/5+2).floor*baseDmg*atk/defense).floor/50).floor+2 + damage = [(damage * multipliers[FINAL_DMG_MULT] / 0x1000).round,1].max + # "AI-specific calculations below" + # Increased critical hit rates + if skill>=PBTrainerAI.mediumSkill + c = 0 + # Ability effects that alter critical hit rate + if c>=0 && user.abilityActive? + c = BattleHandlers.triggerCriticalCalcUserAbility(user.ability,user,target,c) + end + if skill>=PBTrainerAI.bestSkill + if c>=0 && !moldBreaker && target.abilityActive? + c = BattleHandlers.triggerCriticalCalcTargetAbility(target.ability,user,target,c) + end + end + # Item effects that alter critical hit rate + if c>=0 && user.itemActive? + c = BattleHandlers.triggerCriticalCalcUserItem(user.item,user,target,c) + end + if skill>=PBTrainerAI.bestSkill + if c>=0 && target.itemActive? + c = BattleHandlers.triggerCriticalCalcTargetItem(target.item,user,target,c) + end + end + # Other efffects + c = -1 if target.pbOwnSide.effects[PBEffects::LuckyChant]>0 + if c>=0 + c += 1 if move.highCriticalRate? + c += user.effects[PBEffects::FocusEnergy] + c += 1 if user.inHyperMode? && isConst?(move.type,PBTypes,:SHADOW) + end + if c>=0 + c = 4 if c>4 + damage += damage*0.1*c + end + end + return damage.floor + end + + #============================================================================= + # Accuracy calculation + #============================================================================= + def pbRoughAccuracy(move,user,target,skill) + # "Always hit" effects and "always hit" accuracy + if skill>=PBTrainerAI.mediumSkill + return 125 if target.effects[PBEffects::Minimize] && move.tramplesMinimize?(1) + return 125 if target.effects[PBEffects::Telekinesis]>0 + end + baseAcc = move.accuracy + if skill>=PBTrainerAI.highSkill + baseAcc = move.pbBaseAccuracy(user,target) + end + return 125 if baseAcc==0 && skill>=PBTrainerAI.mediumSkill + # Get the move's type + type = pbRoughType(move,user,skill) + # Calculate all modifier effects + modifiers = [] + modifiers[BASE_ACC] = baseAcc + modifiers[ACC_STAGE] = user.stages[PBStats::ACCURACY] + modifiers[EVA_STAGE] = target.stages[PBStats::EVASION] + modifiers[ACC_MULT] = 0x1000 + modifiers[EVA_MULT] = 0x1000 + pbCalcAccuracyModifiers(user,target,modifiers,move,type,skill) + # Check if move can't miss + return 125 if modifiers[BASE_ACC]==0 + # Calculation + accStage = [[modifiers[ACC_STAGE],-6].max,6].min + 6 + evaStage = [[modifiers[EVA_STAGE],-6].max,6].min + 6 + stageMul = [3,3,3,3,3,3, 3, 4,5,6,7,8,9] + stageDiv = [9,8,7,6,5,4, 3, 3,3,3,3,3,3] + accuracy = 100.0 * stageMul[accStage] / stageDiv[accStage] + evasion = 100.0 * stageMul[evaStage] / stageDiv[evaStage] + accuracy = (accuracy * modifiers[ACC_MULT] / 0x1000).round + evasion = (evasion * modifiers[EVA_MULT] / 0x1000).round + evasion = 1 if evasion<1 + return modifiers[BASE_ACC] * accuracy / evasion + end + + def pbCalcAccuracyModifiers(user,target,modifiers,move,type,skill) + moldBreaker = false + if skill>=PBTrainerAI.highSkill && target.hasMoldBreaker? + moldBreaker = true + end + # Ability effects that alter accuracy calculation + if skill>=PBTrainerAI.mediumSkill + if user.abilityActive? + BattleHandlers.triggerAccuracyCalcUserAbility(user.ability, + modifiers,user,target,move,type) + end + user.eachAlly do |b| + next if !b.abilityActive? + BattleHandlers.triggerAccuracyCalcUserAllyAbility(b.ability, + modifiers,user,target,move,type) + end + end + if skill>=PBTrainerAI.bestSkill + if target.abilityActive? && !moldBreaker + BattleHandlers.triggerAccuracyCalcTargetAbility(target.ability, + modifiers,user,target,move,type) + end + end + # Item effects that alter accuracy calculation + if skill>=PBTrainerAI.mediumSkill + if user.itemActive? + BattleHandlers.triggerAccuracyCalcUserItem(user.item, + modifiers,user,target,move,type) + end + end + if skill>=PBTrainerAI.bestSkill + if target.itemActive? + BattleHandlers.triggerAccuracyCalcTargetItem(target.item, + modifiers,user,target,move,type) + end + end + # Other effects, inc. ones that set ACC_MULT or EVA_STAGE to specific values + if skill>=PBTrainerAI.mediumSkill + if @battle.field.effects[PBEffects::Gravity]>0 + modifiers[ACC_MULT] = (modifiers[ACC_MULT]*5/3).round + end + if user.effects[PBEffects::MicleBerry] + modifiers[ACC_MULT] = (modifiers[ACC_MULT]*1.2).round + end + modifiers[EVA_STAGE] = 0 if target.effects[PBEffects::Foresight] && modifiers[EVA_STAGE]>0 + modifiers[EVA_STAGE] = 0 if target.effects[PBEffects::MiracleEye] && modifiers[EVA_STAGE]>0 + end + # "AI-specific calculations below" + if skill>=PBTrainerAI.mediumSkill + modifiers[EVA_STAGE] = 0 if move.function=="0A9" # Chip Away + modifiers[BASE_ACC] = 0 if ["0A5","139","13A","13B","13C", # "Always hit" + "147"].include?(move.function) + modifiers[BASE_ACC] = 0 if user.effects[PBEffects::LockOn]>0 && + user.effects[PBEffects::LockOnPos]==target.index + end + if skill>=PBTrainerAI.highSkill + if move.function=="006" # Toxic + modifiers[BASE_ACC] = 0 if NEWEST_BATTLE_MECHANICS && move.statusMove? && + user.pbHasType?(:POISON) + end + if move.function=="070" # OHKO moves + modifiers[BASE_ACC] = move.accuracy+user.level-target.level + modifiers[ACC_MULT] = 0 if target.level>user.level + if skill>=PBTrainerAI.bestSkill + modifiers[ACC_MULT] = 0 if target.hasActiveAbility?(:STURDY) + end + end + end + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/004_PBTargets.rb b/Data/Scripts/011_Battle/004_PBTargets.rb new file mode 100644 index 000000000..44983d050 --- /dev/null +++ b/Data/Scripts/011_Battle/004_PBTargets.rb @@ -0,0 +1,70 @@ +module PBTargets + # NOTE: These numbers are all over the place because of backwards + # compatibility. As untidy as they are, they need to be left like this. + None = 1 # Bide, Counter, Metal Burst, Mirror Coat (calculate a target) + User = 10 + NearAlly = 100 # Aromatic Mist, Helping Hand, Hold Hands + UserOrNearAlly = 200 # Acupressure + NearFoe = 400 # Me First + AllNearFoes = 4 + RandomNearFoe = 2 # Petal Dance, Outrage, Struggle, Thrash, Uproar + Foe = 9 # For throwing a Poké Ball + NearOther = 0 + AllNearOthers = 8 + Other = 3 # Most Flying-type moves, pulse moves (hits non-near targets) + UserSide = 40 + FoeSide = 80 # Entry hazards + BothSides = 20 + UserAndAllies = 5 # Aromatherapy, Gear Up, Heal Bell, Life Dew, Magnetic Flux, Howl (in Gen 8+) + AllFoes = 6 # Unused (for completeness) + AllBattlers = 7 # Flower Shield, Perish Song, Rototiller, Teatime + + def self.noTargets?(target) + return target==None || + target==User || + target==UserSide || + target==FoeSide || + target==BothSides + end + + # Used to determine if you are able to choose a target for the move. + def self.oneTarget?(target) + return !PBTargets.noTargets?(target) && + !PBTargets.multipleTargets?(target) + end + + def self.multipleTargets?(target) + return target==AllNearFoes || + target==AllNearOthers || + target==UserAndAllies || + target==AllFoes || + target==AllBattlers + end + + # These moves do not target specific Pokémon but are still affected by Pressure. + def self.targetsFoeSide?(target) + return target==FoeSide || + target==BothSides + end + + def self.canChooseDistantTarget?(target) + return target==Other + end + + # These moves can be redirected to a different target. + def self.canChooseOneFoeTarget?(target) + return target==NearFoe || + target==NearOther || + target==Other || + target==RandomNearFoe + end + + # Used by the AI to avoid targeting an ally with a move if that move could + # target an opponent instead. + def self.canChooseFoeTarget?(target) + return target==NearFoe || + target==NearOther || + target==Other || + target==RandomNearFoe + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/005_Battle scene/001_PokeBattle_Animation.rb b/Data/Scripts/011_Battle/005_Battle scene/001_PokeBattle_Animation.rb new file mode 100644 index 000000000..a4ec2bc79 --- /dev/null +++ b/Data/Scripts/011_Battle/005_Battle scene/001_PokeBattle_Animation.rb @@ -0,0 +1,264 @@ +class PokeBattle_Animation + def initialize(sprites,viewport) + @sprites = sprites + @viewport = viewport + @pictureEx = [] # For all the PictureEx + @pictureSprites = [] # For all the sprites + @tempSprites = [] # For sprites that exist only for this animation + @animDone = false + createProcesses + end + + def dispose + @tempSprites.each { |s| s.dispose if s } + end + + def createProcesses; end + def empty?; return @pictureEx.length==0; end + def animDone?; return @animDone; end + + def addSprite(s,origin=PictureOrigin::TopLeft) + num = @pictureEx.length + picture = PictureEx.new(s.z) + picture.x = s.x + picture.y = s.y + picture.visible = s.visible + picture.tone = s.tone.clone + picture.setOrigin(0,origin) + @pictureEx[num] = picture + @pictureSprites[num] = s + return picture + end + + def addNewSprite(x,y,name,origin=PictureOrigin::TopLeft) + num = @pictureEx.length + picture = PictureEx.new(num) + picture.setXY(0,x,y) + picture.setName(0,name) + picture.setOrigin(0,origin) + @pictureEx[num] = picture + s = IconSprite.new(x,y,@viewport) + s.setBitmap(name) + @pictureSprites[num] = s + @tempSprites.push(s) + return picture + end + + def update + return if @animDone + @tempSprites.each { |s| s.update if s } + finished = true + @pictureEx.each_with_index do |p,i| + next if !p.running? + finished = false + p.update + setPictureIconSprite(@pictureSprites[i],p) + end + @animDone = true if finished + end +end + + + +module PokeBattle_BallAnimationMixin + # Returns the color that the Pokémon turns when it goes into or out of its + # Poké Ball. + def getBattlerColorFromBallType(ballType) + case ballType + when 1; return Color.new(132, 189, 247) # Great Ball + when 2; return Color.new(189, 247, 165) # Safari Ball + when 3; return Color.new(255, 255, 123) # Ultra Ball + when 4; return Color.new(189, 165, 231) # Master Ball + when 5; return Color.new(173, 255, 206) # Net Ball + when 6; return Color.new( 99, 206, 247) # Dive Ball + when 7; return Color.new(247, 222, 82) # Nest Ball + when 8; return Color.new(255, 198, 132) # Repeat Ball + when 9; return Color.new(239, 247, 247) # Timer Ball + when 10; return Color.new(255, 140, 82) # Luxury Ball + when 11; return Color.new(255, 74, 82) # Premier Ball + when 12; return Color.new(115, 115, 140) # Dusk Ball + when 13; return Color.new(255, 198, 231) # Heal Ball + when 14; return Color.new(140, 214, 255) # Quick Ball + when 15; return Color.new(247, 66, 41) # Cherish Ball + end + return Color.new(255, 181, 247) # Poké Ball, Sport Ball, Apricorn Balls, others + end + + def addBallSprite(ballX,ballY,ballType) + ball = addNewSprite(ballX,ballY, + sprintf("Graphics/Battle animations/ball_%02d",ballType),PictureOrigin::Center) + @ballSprite = @pictureSprites.last + if @ballSprite.bitmap.width>=@ballSprite.bitmap.height + @ballSprite.src_rect.width = @ballSprite.bitmap.height/2 + ball.setSrcSize(0,@ballSprite.bitmap.height/2,@ballSprite.bitmap.height) + end + return ball + end + + def ballTracksHand(ball,traSprite,safariThrow=false) + # Back sprite isn't animated, no hand-tracking needed + if traSprite.bitmap.width=@ballSprite.bitmap.height + # 2* because each frame is twice as tall as it is wide + numFrames = 2*@ballSprite.bitmap.width/@ballSprite.bitmap.height + end + if numFrames>1 + curFrame = 0 + for i in 1..duration + thisFrame = numFrames*numTumbles*i/duration + if thisFrame>curFrame + curFrame = thisFrame + ball.setSrc(delay+i-1,(curFrame%numFrames)*@ballSprite.bitmap.height/2,0) + end + end + ball.setSrc(delay+duration,0,0) + end + # Rotate ball + ball.moveAngle(delay,duration,360*3) + ball.setAngle(delay+duration,0) + end + + def ballSetOpen(ball,delay,ballType) + ball.setName(delay,sprintf("Graphics/Battle animations/ball_%02d_open",ballType)) + if @ballSprite && @ballSprite.bitmap.width>=@ballSprite.bitmap.height + ball.setSrcSize(delay,@ballSprite.bitmap.height/2,@ballSprite.bitmap.height) + end + end + + def ballSetClosed(ball,delay,ballType) + ball.setName(delay,sprintf("Graphics/Battle animations/ball_%02d",ballType)) + if @ballSprite && @ballSprite.bitmap.width>=@ballSprite.bitmap.height + ball.setSrcSize(delay,@ballSprite.bitmap.height/2,@ballSprite.bitmap.height) + end + end + + def ballOpenUp(ball,delay,ballType,showSquish=true,playSE=true) + if showSquish + ball.moveZoomXY(delay,1,120,80) # Squish + ball.moveZoom(delay+5,1,100) # Unsquish + delay += 6 + end + ball.setSE(delay,"Battle recall") if playSE + ballSetOpen(ball,delay,ballType) + end + + def battlerAppear(battler,delay,battlerX,battlerY,batSprite,color) + battler.setVisible(delay,true) + battler.setOpacity(delay,255) + battler.moveXY(delay,5,battlerX,battlerY) + battler.moveZoom(delay,5,100,[batSprite,:pbPlayIntroAnimation]) + # NOTE: As soon as the battler sprite finishes zooming, and just as it + # starts changing its tone to normal, it plays its intro animation. + color.alpha = 0 + battler.moveColor(delay+5,10,color) + end + + def battlerAbsorb(battler,delay,battlerX,battlerY,color) + color.alpha = 255 + battler.moveColor(delay,10,color) + delay = battler.totalDuration + battler.moveXY(delay,5,battlerX,battlerY) + battler.moveZoom(delay,5,0) + battler.setVisible(delay+5,false) + end + + # The regular Poké Ball burst animation. + def ballBurst(delay,ballX,ballY,ballType) + end + + # The Poké Ball burst animation used when absorbing a wild Pokémon during a + # capture attempt. + def ballBurstCapture(delay,ballX,ballY,ballType) + end + + def ballCaptureSuccess(ball,delay,ballX,ballY) + ball.setSE(delay,"Battle catch click") + ball.moveTone(delay,4,Tone.new(-64,-64,-64,128)) + end + + # The Poké Ball burst animation used when recalling a Pokémon. + def ballBurstRecall(delay,ballX,ballY,ballType) + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/005_Battle scene/002_PokeBattle_SceneAnimations.rb b/Data/Scripts/011_Battle/005_Battle scene/002_PokeBattle_SceneAnimations.rb new file mode 100644 index 000000000..53f2fdaae --- /dev/null +++ b/Data/Scripts/011_Battle/005_Battle scene/002_PokeBattle_SceneAnimations.rb @@ -0,0 +1,882 @@ +#=============================================================================== +# Shows the battle scene fading in while elements slide around into place +#=============================================================================== +class BattleIntroAnimation < PokeBattle_Animation + def initialize(sprites,viewport,battle) + @battle = battle + super(sprites,viewport) + end + + def createProcesses + appearTime = 20 # This is in 1/20 seconds + # Background + if @sprites["battle_bg2"] + makeSlideSprite("battle_bg",0.5,appearTime) + makeSlideSprite("battle_bg2",0.5,appearTime) + end + # Bases + makeSlideSprite("base_0",1,appearTime,PictureOrigin::Bottom) + makeSlideSprite("base_1",-1,appearTime,PictureOrigin::Center) + # Player sprite, partner trainer sprite + @battle.player.each_with_index do |p,i| + makeSlideSprite("player_#{i+1}",1,appearTime,PictureOrigin::Bottom) + end + # Opposing trainer sprite(s) or wild Pokémon sprite(s) + if @battle.trainerBattle? + @battle.opponent.each_with_index do |p,i| + makeSlideSprite("trainer_#{i+1}",-1,appearTime,PictureOrigin::Bottom) + end + else # Wild battle + @battle.pbParty(1).each_with_index do |pkmn,i| + idxBattler = 2*i+1 + makeSlideSprite("pokemon_#{idxBattler}",-1,appearTime,PictureOrigin::Bottom) + end + end + # Shadows + for i in 0...@battle.battlers.length + makeSlideSprite("shadow_#{i}",((i%2)==0) ? 1 : -1,appearTime,PictureOrigin::Center) + end + # Fading blackness over whole screen + blackScreen = addNewSprite(0,0,"Graphics/Battle animations/black_screen") + blackScreen.setZ(0,999) + blackScreen.moveOpacity(0,8,0) + # Fading blackness over command bar + blackBar = addNewSprite(@sprites["cmdBar_bg"].x,@sprites["cmdBar_bg"].y, + "Graphics/Battle animations/black_bar") + blackBar.setZ(0,998) + blackBar.moveOpacity(appearTime*3/4,appearTime/4,0) + end + + def makeSlideSprite(spriteName,deltaMult,appearTime,origin=nil) + # If deltaMult is positive, the sprite starts off to the right and moves + # left (for sprites on the player's side and the background). + return if !@sprites[spriteName] + s = addSprite(@sprites[spriteName],origin) + s.setDelta(0,(Graphics.width*deltaMult).floor,0) + s.moveDelta(0,appearTime,(-Graphics.width*deltaMult).floor,0) + end +end + + + +#=============================================================================== +# Shows wild Pokémon fading back to their normal color, and triggers their intro +# animations +#=============================================================================== +class BattleIntroAnimation2 < PokeBattle_Animation + def initialize(sprites,viewport,sideSize) + @sideSize = sideSize + super(sprites,viewport) + end + + def createProcesses + for i in 0...@sideSize + idxBattler = 2*i+1 + next if !@sprites["pokemon_#{idxBattler}"] + battler = addSprite(@sprites["pokemon_#{idxBattler}"],PictureOrigin::Bottom) + battler.moveTone(0,4,Tone.new(0,0,0,0)) + battler.setCallback(10*i,[@sprites["pokemon_#{idxBattler}"],:pbPlayIntroAnimation]) + end + end +end + + + +#=============================================================================== +# Makes a side's party bar and balls appear +#=============================================================================== +class LineupAppearAnimation < PokeBattle_Animation + BAR_DISPLAY_WIDTH = 248 + + def initialize(sprites,viewport,side,party,partyStarts,fullAnim) + @side = side + @party = party + @partyStarts = partyStarts + @fullAnim = fullAnim # True at start of battle, false when switching + resetGraphics(sprites) + super(sprites,viewport) + end + + def resetGraphics(sprites) + bar = sprites["partyBar_#{@side}"] + case @side + when 0 # Player's lineup + barX = Graphics.width - BAR_DISPLAY_WIDTH + barY = Graphics.height - 142 + ballX = barX + 44 + ballY = barY - 30 + when 1 # Opposing lineup + barX = BAR_DISPLAY_WIDTH + barY = 114 + ballX = barX - 44 - 30 # 30 is width of ball icon + ballY = barY - 30 + barX -= bar.bitmap.width + end + ballXdiff = 32*(1-2*@side) + bar.x = barX + bar.y = barY + bar.opacity = 255 + bar.visible = false + for i in 0...PokeBattle_SceneConstants::NUM_BALLS + ball = sprites["partyBall_#{@side}_#{i}"] + ball.x = ballX + ball.y = ballY + ball.opacity = 255 + ball.visible = false + ballX += ballXdiff + end + end + + def getPartyIndexFromBallIndex(idxBall) + # Player's lineup (just show balls for player's party) + if @side==0 + return idxBall if @partyStarts.length<2 + return idxBall if idxBall<@partyStarts[1] + return -1 + end + # Opposing lineup + # NOTE: This doesn't work well for 4+ opposing trainers. + ballsPerTrainer = PokeBattle_SceneConstants::NUM_BALLS/@partyStarts.length # 6/3/2 + startsIndex = idxBall/ballsPerTrainer + teamIndex = idxBall%ballsPerTrainer + ret = @partyStarts[startsIndex]+teamIndex + if startsIndex<@partyStarts.length-1 + # There is a later trainer, don't spill over into its team + return -1 if ret>=@partyStarts[startsIndex+1] + end + return ret + end + + def createProcesses + bar = addSprite(@sprites["partyBar_#{@side}"]) + bar.setVisible(0,true) + dir = (@side==0) ? 1 : -1 + bar.setDelta(0,dir*Graphics.width/2,0) + bar.moveDelta(0,8,-dir*Graphics.width/2,0) + delay = bar.totalDuration + for i in 0...PokeBattle_SceneConstants::NUM_BALLS + createBall(i,(@fullAnim) ? delay+i*2 : 0,dir) + end + end + + def createBall(idxBall,delay,dir) + # Choose ball's graphic + idxParty = getPartyIndexFromBallIndex(idxBall) + graphicFilename = "Graphics/Pictures/Battle/icon_ball_empty" + if idxParty>=0 && idxParty<@party.length && @party[idxParty] + if !@party[idxParty].able? + graphicFilename = "Graphics/Pictures/Battle/icon_ball_faint" + elsif @party[idxParty].status!=PBStatuses::NONE + graphicFilename = "Graphics/Pictures/Battle/icon_ball_status" + else + graphicFilename = "Graphics/Pictures/Battle/icon_ball" + end + end + # Set up ball sprite + ball = addSprite(@sprites["partyBall_#{@side}_#{idxBall}"]) + ball.setVisible(delay,true) + ball.setName(delay,graphicFilename) + ball.setDelta(delay,dir*Graphics.width/2,0) + ball.moveDelta(delay,8,-dir*Graphics.width/2,0) + end +end + + + +#=============================================================================== +# Makes a Pokémon's data box appear +#=============================================================================== +class DataBoxAppearAnimation < PokeBattle_Animation + def initialize(sprites,viewport,idxBox) + @idxBox = idxBox + super(sprites,viewport) + end + + def createProcesses + return if !@sprites["dataBox_#{@idxBox}"] + box = addSprite(@sprites["dataBox_#{@idxBox}"]) + box.setVisible(0,true) + dir = ((@idxBox%2)==0) ? 1 : -1 + box.setDelta(0,dir*Graphics.width/2,0) + box.moveDelta(0,8,-dir*Graphics.width/2,0) + end +end + + + +#=============================================================================== +# Makes a Pokémon's data box disappear +#=============================================================================== +class DataBoxDisappearAnimation < PokeBattle_Animation + def initialize(sprites,viewport,idxBox) + @idxBox = idxBox + super(sprites,viewport) + end + + def createProcesses + return if !@sprites["dataBox_#{@idxBox}"] || !@sprites["dataBox_#{@idxBox}"].visible + box = addSprite(@sprites["dataBox_#{@idxBox}"]) + dir = ((@idxBox%2)==0) ? 1 : -1 + box.moveDelta(0,8,dir*Graphics.width/2,0) + box.setVisible(8,false) + end +end + + + +#=============================================================================== +# Makes a Pokémon's ability bar appear +#=============================================================================== +class AbilitySplashAppearAnimation < PokeBattle_Animation + def initialize(sprites,viewport,side) + @side = side + super(sprites,viewport) + end + + def createProcesses + return if !@sprites["abilityBar_#{@side}"] + bar = addSprite(@sprites["abilityBar_#{@side}"]) + bar.setVisible(0,true) + dir = (@side==0) ? 1 : -1 + bar.moveDelta(0,8,dir*Graphics.width/2,0) + end +end + + + +#=============================================================================== +# Makes a Pokémon's ability bar disappear +#=============================================================================== +class AbilitySplashDisappearAnimation < PokeBattle_Animation + def initialize(sprites,viewport,side) + @side = side + super(sprites,viewport) + end + + def createProcesses + return if !@sprites["abilityBar_#{@side}"] + bar = addSprite(@sprites["abilityBar_#{@side}"]) + dir = (@side==0) ? -1 : 1 + bar.moveDelta(0,8,dir*Graphics.width/2,0) + bar.setVisible(8,false) + end +end + + + +#=============================================================================== +# Make an enemy trainer slide on-screen from the right. Makes the previous +# trainer slide off to the right first if it is on-screen. +# Used at the end of battle. +#=============================================================================== +class TrainerAppearAnimation < PokeBattle_Animation + def initialize(sprites,viewport,idxTrainer) + @idxTrainer = idxTrainer + super(sprites,viewport) + end + + def createProcesses + delay = 0 + # Make old trainer sprite move off-screen first if necessary + if @idxTrainer>0 && @sprites["trainer_#{@idxTrainer}"].visible + oldTrainer = addSprite(@sprites["trainer_#{@idxTrainer}"],PictureOrigin::Bottom) + oldTrainer.moveDelta(delay,8,Graphics.width/4,0) + oldTrainer.setVisible(delay+8,false) + delay = oldTrainer.totalDuration + end + # Make new trainer sprite move on-screen + if @sprites["trainer_#{@idxTrainer+1}"] + trainerX, trainerY = PokeBattle_SceneConstants.pbTrainerPosition(1) + trainerX += 64+Graphics.width/4 + newTrainer = addSprite(@sprites["trainer_#{@idxTrainer+1}"],PictureOrigin::Bottom) + newTrainer.setVisible(delay,true) + newTrainer.setXY(delay,trainerX,trainerY) + newTrainer.moveDelta(delay,8,-Graphics.width/4,0) + end + end +end + + + +#=============================================================================== +# Shows the player (and partner) and the player party lineup sliding off screen. +# Shows the player's/partner's throwing animation (if they have one). +# Doesn't show the ball thrown or the Pokémon. +#=============================================================================== +class PlayerFadeAnimation < PokeBattle_Animation + def initialize(sprites,viewport,fullAnim=false) + @fullAnim = fullAnim # True at start of battle, false when switching + super(sprites,viewport) + end + + def createProcesses + # NOTE: The movement speeds of trainers/bar/balls are all different. + # Move trainer sprite(s) off-screen + spriteNameBase = "player" + i = 1 + while @sprites[spriteNameBase+"_#{i}"] + pl = @sprites[spriteNameBase+"_#{i}"] + i += 1 + next if !pl.visible || pl.x<0 + trainer = addSprite(pl,PictureOrigin::Bottom) + trainer.moveDelta(0,16,-Graphics.width/2,0) + # Animate trainer sprite(s) if they have multiple frames + if pl.bitmap && !pl.bitmap.disposed? && pl.bitmap.width>=pl.bitmap.height*2 + size = pl.src_rect.width # Width per frame + trainer.setSrc(0,size,0) + trainer.setSrc(5,size*2,0) + trainer.setSrc(7,size*3,0) + trainer.setSrc(9,size*4,0) + end + trainer.setVisible(16,false) + end + # Move and fade party bar/balls + delay = 3 + if @sprites["partyBar_0"] && @sprites["partyBar_0"].visible + partyBar = addSprite(@sprites["partyBar_0"]) + partyBar.moveDelta(delay,16,-Graphics.width/4,0) if @fullAnim + partyBar.moveOpacity(delay,12,0) + partyBar.setVisible(delay+12,false) + partyBar.setOpacity(delay+12,255) + end + for i in 0...PokeBattle_SceneConstants::NUM_BALLS + next if !@sprites["partyBall_0_#{i}"] || !@sprites["partyBall_0_#{i}"].visible + partyBall = addSprite(@sprites["partyBall_0_#{i}"]) + partyBall.moveDelta(delay+2*i,16,-Graphics.width,0) if @fullAnim + partyBall.moveOpacity(delay,12,0) + partyBall.setVisible(delay+12,false) + partyBall.setOpacity(delay+12,255) + end + end +end + + + +#=============================================================================== +# Shows the enemy trainer(s) and the enemy party lineup sliding off screen. +# Doesn't show the ball thrown or the Pokémon. +#=============================================================================== +class TrainerFadeAnimation < PokeBattle_Animation + def initialize(sprites,viewport,fullAnim=false) + @fullAnim = fullAnim # True at start of battle, false when switching + super(sprites,viewport) + end + + def createProcesses + # NOTE: The movement speeds of trainers/bar/balls are all different. + # Move trainer sprite(s) off-screen + spriteNameBase = "trainer" + i = 1 + while @sprites[spriteNameBase+"_#{i}"] + trSprite = @sprites[spriteNameBase+"_#{i}"] + i += 1 + next if !trSprite.visible || trSprite.x>Graphics.width + trainer = addSprite(trSprite,PictureOrigin::Bottom) + trainer.moveDelta(0,16,Graphics.width/2,0) + trainer.setVisible(16,false) + end + # Move and fade party bar/balls + delay = 3 + if @sprites["partyBar_1"] && @sprites["partyBar_1"].visible + partyBar = addSprite(@sprites["partyBar_1"]) + partyBar.moveDelta(delay,16,Graphics.width/4,0) if @fullAnim + partyBar.moveOpacity(delay,12,0) + partyBar.setVisible(delay+12,false) + partyBar.setOpacity(delay+12,255) + end + for i in 0...PokeBattle_SceneConstants::NUM_BALLS + next if !@sprites["partyBall_1_#{i}"] || !@sprites["partyBall_1_#{i}"].visible + partyBall = addSprite(@sprites["partyBall_1_#{i}"]) + partyBall.moveDelta(delay+2*i,16,Graphics.width,0) if @fullAnim + partyBall.moveOpacity(delay,12,0) + partyBall.setVisible(delay+12,false) + partyBall.setOpacity(delay+12,255) + end + end +end + + + +#=============================================================================== +# Shows a Pokémon being sent out on the player's side (including by a partner). +# Includes the Poké Ball being thrown. +#=============================================================================== +class PokeballPlayerSendOutAnimation < PokeBattle_Animation + include PokeBattle_BallAnimationMixin + + def initialize(sprites,viewport,idxTrainer,battler,startBattle,idxOrder=0) + @idxTrainer = idxTrainer + @battler = battler + @showingTrainer = startBattle + @idxOrder = idxOrder + @trainer = @battler.battle.pbGetOwnerFromBattlerIndex(@battler.index) + sprites["pokemon_#{battler.index}"].visible = false + @shadowVisible = sprites["shadow_#{battler.index}"].visible + sprites["shadow_#{battler.index}"].visible = false + super(sprites,viewport) + end + + def createProcesses + batSprite = @sprites["pokemon_#{@battler.index}"] + shaSprite = @sprites["shadow_#{@battler.index}"] + traSprite = @sprites["player_#{@idxTrainer}"] + # Calculate the Poké Ball graphic to use + ballType = 0 + if !batSprite.pkmn.nil? + ballType = batSprite.pkmn.ballused || 0 + end + # Calculate the color to turn the battler sprite + col = getBattlerColorFromBallType(ballType) + col.alpha = 255 + # Calculate start and end coordinates for battler sprite movement + ballPos = PokeBattle_SceneConstants.pbBattlerPosition(@battler.index,batSprite.sideSize) + battlerStartX = ballPos[0] # Is also where the Ball needs to end + battlerStartY = ballPos[1] # Is also where the Ball needs to end + 18 + battlerEndX = batSprite.x + battlerEndY = batSprite.y + # Calculate start and end coordinates for Poké Ball sprite movement + ballStartX = -6 + ballStartY = 202 + ballMidX = 0 # Unused in trajectory calculation + ballMidY = battlerStartY-144 + # Set up Poké Ball sprite + ball = addBallSprite(ballStartX,ballStartY,ballType) + ball.setZ(0,25) + ball.setVisible(0,false) + # Poké Ball tracking the player's hand animation (if trainer is visible) + if @showingTrainer && traSprite && traSprite.x>0 + ball.setZ(0,traSprite.z-1) + ballStartX, ballStartY = ballTracksHand(ball,traSprite) + end + delay = ball.totalDuration # 0 or 7 + # Poké Ball trajectory animation + createBallTrajectory(ball,delay,12, + ballStartX,ballStartY,ballMidX,ballMidY,battlerStartX,battlerStartY-18) + ball.setZ(9,batSprite.z-1) + delay = ball.totalDuration+4 + delay += 10*@idxOrder # Stagger appearances if multiple Pokémon are sent out at once + ballOpenUp(ball,delay-2,ballType) + ballBurst(delay,battlerStartX,battlerStartY-18,ballType) + ball.moveOpacity(delay+2,2,0) + # Set up battler sprite + battler = addSprite(batSprite,PictureOrigin::Bottom) + battler.setXY(0,battlerStartX,battlerStartY) + battler.setZoom(0,0) + battler.setColor(0,col) + # Battler animation + battlerAppear(battler,delay,battlerEndX,battlerEndY,batSprite,col) + if @shadowVisible + # Set up shadow sprite + shadow = addSprite(shaSprite,PictureOrigin::Center) + shadow.setOpacity(0,0) + # Shadow animation + shadow.setVisible(delay,@shadowVisible) + shadow.moveOpacity(delay+5,10,255) + end + end +end + + + +#=============================================================================== +# Shows a Pokémon being sent out on the opposing side. +# Includes the Poké Ball being "thrown" (although here the Poké Ball just +# appears in the spot where it opens up rather than being thrown to there). +#=============================================================================== +class PokeballTrainerSendOutAnimation < PokeBattle_Animation + include PokeBattle_BallAnimationMixin + + def initialize(sprites,viewport,idxTrainer,battler,startBattle,idxOrder) + @idxTrainer = idxTrainer + @battler = battler + @showingTrainer = startBattle + @idxOrder = idxOrder + sprites["pokemon_#{battler.index}"].visible = false + @shadowVisible = sprites["shadow_#{battler.index}"].visible + sprites["shadow_#{battler.index}"].visible = false + super(sprites,viewport) + end + + def createProcesses + batSprite = @sprites["pokemon_#{@battler.index}"] + shaSprite = @sprites["shadow_#{@battler.index}"] + # Calculate the Poké Ball graphic to use + ballType = 0 + if !batSprite.pkmn.nil? + ballType = batSprite.pkmn.ballused || 0 + end + # Calculate the color to turn the battler sprite + col = getBattlerColorFromBallType(ballType) + col.alpha = 255 + # Calculate start and end coordinates for battler sprite movement + ballPos = PokeBattle_SceneConstants.pbBattlerPosition(@battler.index,batSprite.sideSize) + battlerStartX = ballPos[0] + battlerStartY = ballPos[1] + battlerEndX = batSprite.x + battlerEndY = batSprite.y + # Set up Poké Ball sprite + ball = addBallSprite(0,0,ballType) + ball.setZ(0,batSprite.z-1) + # Poké Ball animation + createBallTrajectory(ball,battlerStartX,battlerStartY) + delay = ball.totalDuration+6 + delay += 10 if @showingTrainer # Give time for trainer to slide off screen + delay += 10*@idxOrder # Stagger appearances if multiple Pokémon are sent out at once + ballOpenUp(ball,delay-2,ballType) + ballBurst(delay,battlerStartX,battlerStartY-18,ballType) + ball.moveOpacity(delay+2,2,0) + # Set up battler sprite + battler = addSprite(batSprite,PictureOrigin::Bottom) + battler.setXY(0,battlerStartX,battlerStartY) + battler.setZoom(0,0) + battler.setColor(0,col) + # Battler animation + battlerAppear(battler,delay,battlerEndX,battlerEndY,batSprite,col) + if @shadowVisible + # Set up shadow sprite + shadow = addSprite(shaSprite,PictureOrigin::Center) + shadow.setOpacity(0,0) + # Shadow animation + shadow.setVisible(delay,@shadowVisible) + shadow.moveOpacity(delay+5,10,255) + end + end + + def createBallTrajectory(ball,destX,destY) + # NOTE: In HGSS, there isn't a Poké Ball arc under any circumstance (neither + # when throwing out the first Pokémon nor when switching/replacing a + # fainted Pokémon). This is probably worth changing. + ball.setXY(0,destX,destY-4) + end +end + + + +#=============================================================================== +# Shows a Pokémon being recalled into its Poké Ball +#=============================================================================== +class BattlerRecallAnimation < PokeBattle_Animation + include PokeBattle_BallAnimationMixin + + def initialize(sprites,viewport,idxBattler) + @idxBattler = idxBattler + super(sprites,viewport) + end + + def createProcesses + batSprite = @sprites["pokemon_#{@idxBattler}"] + shaSprite = @sprites["shadow_#{@idxBattler}"] + # Calculate the Poké Ball graphic to use + ballType = 0 + if !batSprite.pkmn.nil? + ballType = batSprite.pkmn.ballused || 0 + end + # Calculate the color to turn the battler sprite + col = getBattlerColorFromBallType(ballType) + col.alpha = 0 + # Calculate start and end coordinates for battler sprite movement + battlerStartX = batSprite.x + battlerStartY = batSprite.y + ballPos = PokeBattle_SceneConstants.pbBattlerPosition(@idxBattler,batSprite.sideSize) + battlerEndX = ballPos[0] + battlerEndY = ballPos[1] + # Set up battler sprite + battler = addSprite(batSprite,PictureOrigin::Bottom) + battler.setVisible(0,true) + battler.setColor(0,col) + # Set up Poké Ball sprite + ball = addBallSprite(battlerEndX,battlerEndY,ballType) + ball.setZ(0,batSprite.z+1) + # Poké Ball animation + ballOpenUp(ball,0,ballType) + delay = ball.totalDuration + ballBurstRecall(delay,battlerEndX,battlerEndY,ballType) + ball.moveOpacity(10,2,0) + # Battler animation + battlerAbsorb(battler,delay,battlerEndX,battlerEndY,col) + if shaSprite.visible + # Set up shadow sprite + shadow = addSprite(shaSprite,PictureOrigin::Center) + # Shadow animation + shadow.moveOpacity(0,10,0) + shadow.setVisible(delay,false) + end + end +end + + + +#=============================================================================== +# Shows a Pokémon flashing after taking damage +#=============================================================================== +class BattlerDamageAnimation < PokeBattle_Animation + def initialize(sprites,viewport,idxBattler,effectiveness) + @idxBattler = idxBattler + @effectiveness = effectiveness + super(sprites,viewport) + end + + def createProcesses + batSprite = @sprites["pokemon_#{@idxBattler}"] + shaSprite = @sprites["shadow_#{@idxBattler}"] + # Set up battler/shadow sprite + battler = addSprite(batSprite,PictureOrigin::Bottom) + shadow = addSprite(shaSprite,PictureOrigin::Center) + # Animation + delay = 0 + case @effectiveness + when 0; battler.setSE(delay,"Battle damage normal") + when 1; battler.setSE(delay,"Battle damage weak") + when 2; battler.setSE(delay,"Battle damage super") + end + 4.times do # 4 flashes, each lasting 0.2 (4/20) seconds + battler.setVisible(delay,false) + shadow.setVisible(delay,false) + battler.setVisible(delay+2,true) if batSprite.visible + shadow.setVisible(delay+2,true) if shaSprite.visible + delay += 4 + end + # Restore original battler/shadow sprites visibilities + battler.setVisible(delay,batSprite.visible) + shadow.setVisible(delay,shaSprite.visible) + end +end + + + +#=============================================================================== +# Shows a Pokémon fainting +#=============================================================================== +class BattlerFaintAnimation < PokeBattle_Animation + def initialize(sprites,viewport,idxBattler,battle) + @idxBattler = idxBattler + @battle = battle + super(sprites,viewport) + end + + def createProcesses + batSprite = @sprites["pokemon_#{@idxBattler}"] + shaSprite = @sprites["shadow_#{@idxBattler}"] + # Set up battler/shadow sprite + battler = addSprite(batSprite,PictureOrigin::Bottom) + shadow = addSprite(shaSprite,PictureOrigin::Center) + # Get approx duration depending on sprite's position/size. Min 20 frames. + battlerTop = batSprite.y-batSprite.height + cropY = PokeBattle_SceneConstants.pbBattlerPosition(@idxBattler, + @battle.pbSideSize(@idxBattler))[1] + cropY += 8 + duration = (cropY-battlerTop)/8 + duration = 10 if duration<10 # Min 0.5 seconds + # Animation + # Play cry + delay = 10 + cry = pbCryFile(batSprite.pkmn) + if cry + battler.setSE(0,pbCryFile(batSprite.pkmn)) + delay = pbCryFrameLength(batSprite.pkmn)*20/Graphics.frame_rate + end + # Sprite drops down + shadow.setVisible(delay,false) + battler.setSE(delay,"Pkmn faint") + battler.moveOpacity(delay,duration,0) + battler.moveDelta(delay,duration,0,cropY-battlerTop) + battler.setCropBottom(delay,cropY) + battler.setVisible(delay+duration,false) + battler.setOpacity(delay+duration,255) + end +end + + + +#=============================================================================== +# Shows the player's Poké Ball being thrown to capture a Pokémon +#=============================================================================== +class PokeballThrowCaptureAnimation < PokeBattle_Animation + include PokeBattle_BallAnimationMixin + + def initialize(sprites,viewport, + ballType,numShakes,critCapture,battler,showingTrainer) + @ballType = ballType + @numShakes = (critCapture) ? 1 : numShakes + @critCapture = critCapture + @battler = battler + @showingTrainer = showingTrainer # Only true if a Safari Zone battle + @shadowVisible = sprites["shadow_#{battler.index}"].visible + @trainer = battler.battle.pbPlayer + super(sprites,viewport) + end + + def createProcesses + # Calculate start and end coordinates for battler sprite movement + batSprite = @sprites["pokemon_#{@battler.index}"] + shaSprite = @sprites["shadow_#{@battler.index}"] + traSprite = @sprites["player_1"] + ballPos = PokeBattle_SceneConstants.pbBattlerPosition(@battler.index,batSprite.sideSize) + battlerStartX = batSprite.x + battlerStartY = batSprite.y + ballStartX = -6 + ballStartY = 246 + ballMidX = 0 # Unused in arc calculation + ballMidY = 78 + ballEndX = ballPos[0] + ballEndY = 112 + ballGroundY = ballPos[1]-4 + # Set up Poké Ball sprite + ball = addBallSprite(ballStartX,ballStartY,@ballType) + ball.setZ(0,batSprite.z+1) + @ballSpriteIndex = (@numShakes>=4 || @critCapture) ? @tempSprites.length-1 : -1 + # Set up trainer sprite (only visible in Safari Zone battles) + if @showingTrainer && traSprite + if traSprite.bitmap.width>=traSprite.bitmap.height*2 + trainer = addSprite(traSprite,PictureOrigin::Bottom) + # Trainer animation + ballStartX, ballStartY = trainerThrowingFrames(ball,trainer,traSprite) + end + end + delay = ball.totalDuration # 0 or 7 + # Poké Ball arc animation + ball.setSE(delay,"Battle throw") + createBallTrajectory(ball,delay,16, + ballStartX,ballStartY,ballMidX,ballMidY,ballEndX,ballEndY) + ball.setZ(9,batSprite.z+1) + ball.setSE(delay+16,"Battle ball hit") + # Poké Ball opens up + delay = ball.totalDuration+6 + ballOpenUp(ball,delay,@ballType,true,false) + # Set up battler sprite + battler = addSprite(batSprite,PictureOrigin::Bottom) + # Poké Ball absorbs battler + delay = ball.totalDuration + ballBurstCapture(delay,ballEndX,ballEndY,@ballType) + delay = ball.totalDuration+4 + # NOTE: The Pokémon does not change color while being absorbed into a Poké + # Ball during a capture attempt. This may be an oversight in HGSS. + battler.setSE(delay,"Battle jump to ball") + battler.moveXY(delay,5,ballEndX,ballEndY) + battler.moveZoom(delay,5,0) + battler.setVisible(delay+5,false) + if @shadowVisible + # Set up shadow sprite + shadow = addSprite(shaSprite,PictureOrigin::Center) + # Shadow animation + shadow.moveOpacity(delay,5,0) + shadow.moveZoom(delay,5,0) + shadow.setVisible(delay+5,false) + end + # Poké Ball closes + delay = battler.totalDuration + ballSetClosed(ball,delay,@ballType) + ball.moveTone(delay,3,Tone.new(96,64,-160,160)) + ball.moveTone(delay+5,3,Tone.new(0,0,0,0)) + # Poké Ball critical capture animation + delay = ball.totalDuration+3 + if @critCapture + ball.setSE(delay,"Battle ball shake") + ball.moveXY(delay,1,ballEndX+4,ballEndY) + ball.moveXY(delay+1,2,ballEndX-4,ballEndY) + ball.moveXY(delay+3,2,ballEndX+4,ballEndY) + ball.setSE(delay+4,"Battle ball shake") + ball.moveXY(delay+5,2,ballEndX-4,ballEndY) + ball.moveXY(delay+7,1,ballEndX,ballEndY) + delay = ball.totalDuration+3 + end + # Poké Ball drops to the ground + for i in 0...4 + t = [4,4,3,2][i] # Time taken to rise or fall for each bounce + d = [1,2,4,8][i] # Fraction of the starting height each bounce rises to + delay -= t if i==0 + if i>0 + ball.setZoomXY(delay,100+5*(5-i),100-5*(5-i)) # Squish + ball.moveZoom(delay,2,100) # Unsquish + ball.moveXY(delay,t,ballEndX,ballGroundY-(ballGroundY-ballEndY)/d) + end + ball.moveXY(delay+t,t,ballEndX,ballGroundY) + ball.setSE(delay+2*t,"Battle ball drop",100-i*7) + delay = ball.totalDuration + end + battler.setXY(ball.totalDuration,ballEndX,ballGroundY) + # Poké Ball shakes + delay = ball.totalDuration+12 + for i in 0...[@numShakes,3].min + ball.setSE(delay,"Battle ball shake") + ball.moveXY(delay,2,ballEndX-2*(4-i),ballGroundY) + ball.moveAngle(delay,2,5*(4-i)) # positive means counterclockwise + ball.moveXY(delay+2,4,ballEndX+2*(4-i),ballGroundY) + ball.moveAngle(delay+2,4,-5*(4-i)) # negative means clockwise + ball.moveXY(delay+6,2,ballEndX,ballGroundY) + ball.moveAngle(delay+6,2,0) + delay = ball.totalDuration+8 + end + if @numShakes==0 || (@numShakes<4 && !@critCapture) + # Poké Ball opens + ball.setZ(delay,batSprite.z-1) + ballOpenUp(ball,delay,@ballType,false) + ballBurst(delay,ballEndX,ballGroundY,@ballType) + ball.moveOpacity(delay+2,2,0) + # Battler emerges + col = getBattlerColorFromBallType(@ballType) + col.alpha = 255 + battler.setColor(delay,col) + battlerAppear(battler,delay,battlerStartX,battlerStartY,batSprite,col) + if @shadowVisible + shadow.setVisible(delay+5,true) + shadow.setZoom(delay+5,100) + shadow.moveOpacity(delay+5,10,255) + end + else + # Pokémon was caught + ballCaptureSuccess(ball,delay,ballEndX,ballGroundY) + end + end + + def dispose + if @ballSpriteIndex>=0 + # Capture was successful, the Poké Ball sprite should stay around after + # this animation has finished. + @sprites["captureBall"] = @tempSprites[@ballSpriteIndex] + @tempSprites[@ballSpriteIndex] = nil + end + super + end +end + + + +#=============================================================================== +# Shows the player throwing a Poké Ball and it being deflected +#=============================================================================== +class PokeballThrowDeflectAnimation < PokeBattle_Animation + include PokeBattle_BallAnimationMixin + + def initialize(sprites,viewport,ballType,battler) + @ballType = ballType + @battler = battler + super(sprites,viewport) + end + + def createProcesses + # Calculate start and end coordinates for battler sprite movement + batSprite = @sprites["pokemon_#{@battler.index}"] + ballPos = PokeBattle_SceneConstants.pbBattlerPosition(@battler.index,batSprite.sideSize) + ballStartX = -6 + ballStartY = 246 + ballMidX = 190 # Unused in arc calculation + ballMidY = 78 + ballEndX = ballPos[0] + ballEndY = 112 + # Set up Poké Ball sprite + ball = addBallSprite(ballStartX,ballStartY,@ballType) + ball.setZ(0,90) + # Set up battler sprite + battler = addSprite(batSprite,PictureOrigin::Bottom) + # Poké Ball arc animation + ball.setSE(0,"Battle throw") + createBallTrajectory(ball,0,16, + ballStartX,ballStartY,ballMidX,ballMidY,ballEndX,ballEndY) + # Poké Ball knocked back + delay = ball.totalDuration + ball.setSE(delay,"Battle ball drop") + ball.moveXY(delay,8,-32,Graphics.height-96+32) # Back to player's corner + createBallTumbling(ball,delay,8) + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/005_Battle scene/003_PokeBattle_SceneConstants.rb b/Data/Scripts/011_Battle/005_Battle scene/003_PokeBattle_SceneConstants.rb new file mode 100644 index 000000000..59f2315f5 --- /dev/null +++ b/Data/Scripts/011_Battle/005_Battle scene/003_PokeBattle_SceneConstants.rb @@ -0,0 +1,63 @@ +module PokeBattle_SceneConstants + USE_ABILITY_SPLASH = true + # Text colors + MESSAGE_BASE_COLOR = Color.new(80,80,88) + MESSAGE_SHADOW_COLOR = Color.new(160,160,168) + + # The number of party balls to show in each side's lineup. + NUM_BALLS = 6 + + # Centre bottom of the player's side base graphic + PLAYER_BASE_X = 128 + PLAYER_BASE_Y = Graphics.height - 80 + + # Centre middle of the foe's side base graphic + FOE_BASE_X = Graphics.width - 128 + FOE_BASE_Y = (Graphics.height * 3/4) - 112 + + # Returns where the centre bottom of a battler's sprite should be, given its + # index and the number of battlers on its side, assuming the battler has + # metrics of 0 (those are added later). + def self.pbBattlerPosition(index,sideSize=1) + # Start at the centre of the base for the appropriate side + if (index&1)==0; ret = [PLAYER_BASE_X,PLAYER_BASE_Y] + else; ret = [FOE_BASE_X,FOE_BASE_Y] + end + # Shift depending on index (no shifting needed for sideSize of 1) + case sideSize + when 2 + ret[0] += [-48, 48, 32, -32][index] + ret[1] += [ 0, 0, 16, -16][index] + when 3 + ret[0] += [-80, 80, 0, 0, 80, -80][index] + ret[1] += [ 0, 0, 8, -8, 16, -16][index] + end + return ret + end + + # Returns where the centre bottom of a trainer's sprite should be, given its + # side (0/1), index and the number of trainers on its side. + def self.pbTrainerPosition(side,index=0,sideSize=1) + # Start at the centre of the base for the appropriate side + if side==0; ret = [PLAYER_BASE_X,PLAYER_BASE_Y-16] + else; ret = [FOE_BASE_X,FOE_BASE_Y+6] + end + # Shift depending on index (no shifting needed for sideSize of 1) + case sideSize + when 2 + ret[0] += [-48, 48, 32, -32][2*index+side] + ret[1] += [ 0, 0, 0, -16][2*index+side] + when 3 + ret[0] += [-80, 80, 0, 0, 80, -80][2*index+side] + ret[1] += [ 0, 0, 0, -8, 0, -16][2*index+side] + end + return ret + end + + # Default focal points of user and target in animations - do not change! + # Is the centre middle of each sprite + FOCUSUSER_X = 128 # 144 + FOCUSUSER_Y = 224 # 188 + FOCUSTARGET_X = 384 # 352 + FOCUSTARGET_Y = 96 # 108, 98 +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/005_Battle scene/004_PokeBattle_SceneElements.rb b/Data/Scripts/011_Battle/005_Battle scene/004_PokeBattle_SceneElements.rb new file mode 100644 index 000000000..527bfd4e0 --- /dev/null +++ b/Data/Scripts/011_Battle/005_Battle scene/004_PokeBattle_SceneElements.rb @@ -0,0 +1,657 @@ +#=============================================================================== +# Data box for regular battles +#=============================================================================== +class PokemonDataBox < SpriteWrapper + attr_reader :battler + attr_accessor :selected + attr_reader :animatingHP + attr_reader :animatingExp + + # Time in seconds to fully fill the Exp bar (from empty). + EXP_BAR_FILL_TIME = 1.75 + # Maximum time in seconds to make a change to the HP bar. + HP_BAR_CHANGE_TIME = 1.0 + STATUS_ICON_HEIGHT = 16 + NAME_BASE_COLOR = Color.new(72,72,72) + NAME_SHADOW_COLOR = Color.new(184,184,184) + MALE_BASE_COLOR = Color.new(48,96,216) + MALE_SHADOW_COLOR = NAME_SHADOW_COLOR + FEMALE_BASE_COLOR = Color.new(248,88,40) + FEMALE_SHADOW_COLOR = NAME_SHADOW_COLOR + + def initialize(battler,sideSize,viewport=nil) + super(viewport) + @battler = battler + @sprites = {} + @spriteX = 0 + @spriteY = 0 + @spriteBaseX = 0 + @selected = 0 + @frame = 0 + @showHP = false # Specifically, show the HP numbers + @animatingHP = false + @showExp = false # Specifically, show the Exp bar + @animatingExp = false + @expFlash = 0 + initializeDataBoxGraphic(sideSize) + initializeOtherGraphics(viewport) + refresh + end + + def initializeDataBoxGraphic(sideSize) + onPlayerSide = ((@battler.index%2)==0) + # Get the data box graphic and set whether the HP numbers/Exp bar are shown + if sideSize==1 # One Pokémon on side, use the regular dara box BG + bgFilename = ["Graphics/Pictures/Battle/databox_normal", + "Graphics/Pictures/Battle/databox_normal_foe"][@battler.index%2] + if onPlayerSide + @showHP = true + @showExp = true + end + else # Multiple Pokémon on side, use the thin dara box BG + bgFilename = ["Graphics/Pictures/Battle/databox_thin", + "Graphics/Pictures/Battle/databox_thin_foe"][@battler.index%2] + end + @databoxBitmap = AnimatedBitmap.new(bgFilename) + # Determine the co-ordinates of the data box and the left edge padding width + if onPlayerSide + @spriteX = Graphics.width - 244 + @spriteY = Graphics.height - 192 + @spriteBaseX = 34 + else + @spriteX = -16 + @spriteY = 36 + @spriteBaseX = 16 + end + case sideSize + when 2 + @spriteX += [-12, 12, 0, 0][@battler.index] + @spriteY += [-20, -34, 34, 20][@battler.index] + when 3 + @spriteX += [-12, 12, -6, 6, 0, 0][@battler.index] + @spriteY += [-42, -46, 4, 0, 50, 46][@battler.index] + end + end + + def initializeOtherGraphics(viewport) + # Create other bitmaps + @numbersBitmap = AnimatedBitmap.new(_INTL("Graphics/Pictures/Battle/icon_numbers")) + @hpBarBitmap = AnimatedBitmap.new(_INTL("Graphics/Pictures/Battle/overlay_hp")) + @expBarBitmap = AnimatedBitmap.new(_INTL("Graphics/Pictures/Battle/overlay_exp")) + # Create sprite to draw HP numbers on + @hpNumbers = BitmapSprite.new(124,16,viewport) + pbSetSmallFont(@hpNumbers.bitmap) + @sprites["hpNumbers"] = @hpNumbers + # Create sprite wrapper that displays HP bar + @hpBar = SpriteWrapper.new(viewport) + @hpBar.bitmap = @hpBarBitmap.bitmap + @hpBar.src_rect.height = @hpBarBitmap.height/3 + @sprites["hpBar"] = @hpBar + # Create sprite wrapper that displays Exp bar + @expBar = SpriteWrapper.new(viewport) + @expBar.bitmap = @expBarBitmap.bitmap + @sprites["expBar"] = @expBar + # Create sprite wrapper that displays everything except the above + @contents = BitmapWrapper.new(@databoxBitmap.width,@databoxBitmap.height) + self.bitmap = @contents + self.visible = false + self.z = 150+((@battler.index)/2)*5 + pbSetSystemFont(self.bitmap) + end + + def dispose + pbDisposeSpriteHash(@sprites) + @databoxBitmap.dispose + @numbersBitmap.dispose + @hpBarBitmap.dispose + @expBarBitmap.dispose + @contents.dispose + super + end + + def x=(value) + super + @hpBar.x = value+@spriteBaseX+102 + @expBar.x = value+@spriteBaseX+6 + @hpNumbers.x = value+@spriteBaseX+80 + end + + def y=(value) + super + @hpBar.y = value+40 + @expBar.y = value+74 + @hpNumbers.y = value+52 + end + + def z=(value) + super + @hpBar.z = value+1 + @expBar.z = value+1 + @hpNumbers.z = value+2 + end + + def opacity=(value) + super + for i in @sprites + i[1].opacity = value if !i[1].disposed? + end + end + + def visible=(value) + super + for i in @sprites + i[1].visible = value if !i[1].disposed? + end + @expBar.visible = (value && @showExp) + end + + def color=(value) + super + for i in @sprites + i[1].color = value if !i[1].disposed? + end + end + + def battler=(b) + @battler = b + self.visible = (@battler && !@battler.fainted?) + end + + def hp + return (@animatingHP) ? @currentHP : @battler.hp + end + + def expFraction + return (@animatingExp) ? @currentExp.to_f/@rangeExp : @battler.pokemon.expFraction + end + + def animateHP(oldHP,newHP,rangeHP) + @currentHP = oldHP + @endHP = newHP + @rangeHP = rangeHP + # NOTE: A change in HP takes the same amount of time to animate, no matter + # how big a change it is. + @hpIncPerFrame = (newHP-oldHP).abs/(HP_BAR_CHANGE_TIME*Graphics.frame_rate) + # minInc is the smallest amount that HP is allowed to change per frame. + # This avoids a tiny change in HP still taking HP_BAR_CHANGE_TIME seconds. + minInc = (rangeHP*4)/(@hpBarBitmap.width*HP_BAR_CHANGE_TIME*Graphics.frame_rate) + @hpIncPerFrame = minInc if @hpIncPerFrame116 + textPos.push([@battler.name,@spriteBaseX+8-nameOffset,6,false,NAME_BASE_COLOR,NAME_SHADOW_COLOR]) + # Draw Pokémon's gender symbol + case @battler.displayGender + when 0 # Male + textPos.push([_INTL("♂"),@spriteBaseX+126,6,false,MALE_BASE_COLOR,MALE_SHADOW_COLOR]) + when 1 # Female + textPos.push([_INTL("♀"),@spriteBaseX+126,6,false,FEMALE_BASE_COLOR,FEMALE_SHADOW_COLOR]) + end + pbDrawTextPositions(self.bitmap,textPos) + # Draw Pokémon's level + imagePos.push(["Graphics/Pictures/Battle/overlay_lv",@spriteBaseX+140,16]) + pbDrawNumber(@battler.level,self.bitmap,@spriteBaseX+162,16) + # Draw shiny icon + if @battler.shiny? + shinyX = (@battler.opposes?(0)) ? 206 : -6 # Foe's/player's + imagePos.push(["Graphics/Pictures/shiny",@spriteBaseX+shinyX,36]) + end + # Draw Mega Evolution/Primal Reversion icon + if @battler.mega? + imagePos.push(["Graphics/Pictures/Battle/icon_mega",@spriteBaseX+8,34]) + elsif @battler.primal? + primalX = (@battler.opposes?) ? 208 : -28 # Foe's/player's + if isConst?(@battler.pokemon.species,PBSpecies,:KYOGRE) + imagePos.push(["Graphics/Pictures/Battle/icon_primal_Kyogre",@spriteBaseX+primalX,4]) + elsif isConst?(@battler.pokemon.species,PBSpecies,:GROUDON) + imagePos.push(["Graphics/Pictures/Battle/icon_primal_Groudon",@spriteBaseX+primalX,4]) + end + end + # Draw owned icon (foe Pokémon only) + if @battler.owned? && @battler.opposes?(0) + imagePos.push(["Graphics/Pictures/Battle/icon_own",@spriteBaseX+8,36]) + end + # Draw status icon + if @battler.status>0 + s = @battler.status + s = 6 if s==PBStatuses::POISON && @battler.statusCount>0 # Badly poisoned + imagePos.push(["Graphics/Pictures/Battle/icon_statuses",@spriteBaseX+24,36, + 0,(s-1)*STATUS_ICON_HEIGHT,-1,STATUS_ICON_HEIGHT]) + end + pbDrawImagePositions(self.bitmap,imagePos) + refreshHP + refreshExp + end + + def refreshHP + @hpNumbers.bitmap.clear + return if !@battler.pokemon + # Show HP numbers + if @showHP + pbDrawNumber(self.hp,@hpNumbers.bitmap,54,2,1) + pbDrawNumber(-1,@hpNumbers.bitmap,54,2) # / char + pbDrawNumber(@battler.totalhp,@hpNumbers.bitmap,70,2) + end + # Resize HP bar + w = 0 + if self.hp>0 + w = @hpBarBitmap.width.to_f*self.hp/@battler.totalhp + w = 1 if w<1 + # NOTE: The line below snaps the bar's width to the nearest 2 pixels, to + # fit in with the rest of the graphics which are doubled in size. + w = ((w/2.0).round)*2 + end + @hpBar.src_rect.width = w + hpColor = 0 # Green bar + hpColor = 1 if self.hp<=@battler.totalhp/2 # Yellow bar + hpColor = 2 if self.hp<=@battler.totalhp/4 # Red bar + @hpBar.src_rect.y = hpColor*@hpBarBitmap.height/3 + end + + def refreshExp + return if !@showExp + w = self.expFraction*@expBarBitmap.width + # NOTE: The line below snaps the bar's width to the nearest 2 pixels, to + # fit in with the rest of the graphics which are doubled in size. + w = ((w/2).round)*2 + @expBar.src_rect.width = w + end + + def updateHPAnimation + return if !@animatingHP + if @currentHP<@endHP # Gaining HP + @currentHP += @hpIncPerFrame + @currentHP = @endHP if @currentHP>=@endHP + elsif @currentHP>@endHP # Losing HP + @currentHP -= @hpIncPerFrame + @currentHP = @endHP if @currentHP<=@endHP + end + # Refresh the HP bar/numbers + refreshHP + @animatingHP = false if @currentHP==@endHP + end + + def updateExpAnimation + return if !@animatingExp + if !@showExp # Not showing the Exp bar, no need to waste time animating it + @currentExp = @endExp + @animatingExp = false + return + end + if @currentExp<@endExp # Gaining Exp + @currentExp += @expIncPerFrame + @currentExp = @endExp if @currentExp>=@endExp + elsif @currentExp>@endExp # Losing Exp + @currentExp -= @expIncPerFrame + @currentExp = @endExp if @currentExp<=@endExp + end + # Refresh the Exp bar + refreshExp + return if @currentExp!=@endExp # Exp bar still has more to animate + # Exp bar is completely filled, level up with a flash and sound effect + if @currentExp>=@rangeExp + if @expFlash==0 + pbSEStop + @expFlash = Graphics.frame_rate/5 + pbSEPlay("Exp full") + self.flash(Color.new(64,200,248,192),@expFlash) + for i in @sprites + i[1].flash(Color.new(64,200,248,192),@expFlash) if !i[1].disposed? + end + else + @expFlash -= 1 + @animatingExp = false if @expFlash==0 + end + else + pbSEStop + # Exp bar has finished filling, end animation + @animatingExp = false + end + end + + QUARTER_ANIM_PERIOD = Graphics.frame_rate*3/20 + + def updatePositions(frameCounter) + self.x = @spriteX + self.y = @spriteY + # Data box bobbing while Pokémon is selected + if @selected==1 || @selected==2 # Choosing commands/targeted or damaged + case (frameCounter/QUARTER_ANIM_PERIOD).floor + when 1; self.y = @spriteY-2 + when 3; self.y = @spriteY+2 + end + end + end + + def update(frameCounter=0) + super() + # Animate HP bar + updateHPAnimation + # Animate Exp bar + updateExpAnimation + # Update coordinates of the data box + updatePositions(frameCounter) + pbUpdateSpriteHash(@sprites) + end +end + + + +#=============================================================================== +# Splash bar to announce a triggered ability +#=============================================================================== +class AbilitySplashBar < SpriteWrapper + attr_reader :battler + + TEXT_BASE_COLOR = Color.new(0,0,0) + TEXT_SHADOW_COLOR = Color.new(248,248,248) + + def initialize(side,viewport=nil) + super(viewport) + @side = side + @battler = nil + # Create sprite wrapper that displays background graphic + @bgBitmap = AnimatedBitmap.new(_INTL("Graphics/Pictures/Battle/ability_bar")) + @bgSprite = SpriteWrapper.new(viewport) + @bgSprite.bitmap = @bgBitmap.bitmap + @bgSprite.src_rect.y = (side==0) ? 0 : @bgBitmap.height/2 + @bgSprite.src_rect.height = @bgBitmap.height/2 + # Create bitmap that displays the text + @contents = BitmapWrapper.new(@bgBitmap.width,@bgBitmap.height/2) + self.bitmap = @contents + pbSetSystemFont(self.bitmap) + # Position the bar + self.x = (side==0) ? -Graphics.width/2 : Graphics.width + self.y = (side==0) ? 180 : 80 + self.z = 120 + self.visible = false + end + + def dispose + @bgSprite.dispose + @bgBitmap.dispose + @contents.dispose + super + end + + def x=(value) + super + @bgSprite.x = value + end + + def y=(value) + super + @bgSprite.y = value + end + + def z=(value) + super + @bgSprite.z = value-1 + end + + def opacity=(value) + super + @bgSprite.opacity = value + end + + def visible=(value) + super + @bgSprite.visible = value + end + + def color=(value) + super + @bgSprite.color = value + end + + def battler=(value) + @battler = value + refresh + end + + def refresh + self.bitmap.clear + return if !@battler + textPos = [] + textX = (@side==0) ? 10 : self.bitmap.width-8 + # Draw Pokémon's name + textPos.push([_INTL("{1}'s",@battler.name),textX,2,@side==1, + TEXT_BASE_COLOR,TEXT_SHADOW_COLOR,true]) + # Draw Pokémon's ability + textPos.push([@battler.abilityName,textX,32,@side==1, + TEXT_BASE_COLOR,TEXT_SHADOW_COLOR,true]) + pbDrawTextPositions(self.bitmap,textPos) + end + + def update + super + @bgSprite.update + end +end + + + +#=============================================================================== +# Pokémon sprite (used in battle) +#=============================================================================== +class PokemonBattlerSprite < RPG::Sprite + attr_reader :pkmn + attr_accessor :index + attr_accessor :selected + attr_reader :sideSize + + def initialize(viewport,sideSize,index,battleAnimations) + super(viewport) + @pkmn = nil + @sideSize = sideSize + @index = index + @battleAnimations = battleAnimations + # @selected: 0 = not selected, 1 = choosing action bobbing for this Pokémon, + # 2 = flashing when targeted + @selected = 0 + @frame = 0 + @updating = false + @spriteX = 0 # Actual x coordinate + @spriteY = 0 # Actual y coordinate + @spriteXExtra = 0 # Offset due to "bobbing" animation + @spriteYExtra = 0 # Offset due to "bobbing" animation + @_iconBitmap = nil + self.visible = false + end + + def dispose + @_iconBitmap.dispose if @_iconBitmap + @_iconBitmap = nil + self.bitmap = nil if !self.disposed? + super + end + + def x; return @spriteX; end + def y; return @spriteY; end + + def x=(value) + @spriteX = value + super(value+@spriteXExtra) + end + + def y=(value) + @spriteY = value + super(value+@spriteYExtra) + end + + def width; return (self.bitmap) ? self.bitmap.width : 0; end + def height; return (self.bitmap) ? self.bitmap.height : 0; end + + def visible=(value) + @spriteVisible = value if !@updating # For selection/targeting flashing + super + end + + # Set sprite's origin to bottom middle + def pbSetOrigin + return if !@_iconBitmap + self.ox = @_iconBitmap.width/2 + self.oy = @_iconBitmap.height + end + + def pbSetPosition + return if !@_iconBitmap + pbSetOrigin + if (@index%2)==0 + self.z = 50+5*@index/2 + else + self.z = 50-5*(@index+1)/2 + end + # Set original position + p = PokeBattle_SceneConstants.pbBattlerPosition(@index,@sideSize) + @spriteX = p[0] + @spriteY = p[1] + # Apply metrics + pbApplyBattlerMetricsToSprite(self,@index,@pkmn.fSpecies) + end + + def setPokemonBitmap(pkmn,back=false) + @pkmn = pkmn + @_iconBitmap.dispose if @_iconBitmap + @_iconBitmap = pbLoadPokemonBitmap(@pkmn,back) + self.bitmap = (@_iconBitmap) ? @_iconBitmap.bitmap : nil + pbSetPosition + end + + # This method plays the battle entrance animation of a Pokémon. By default + # this is just playing the Pokémon's cry, but you can expand on it. The + # recommendation is to create a PictureEx animation and push it into the + # @battleAnimations array. + def pbPlayIntroAnimation(pictureEx=nil) + return if !@pkmn + cry = pbCryFile(@pkmn) + pbSEPlay(cry) if cry + end + + QUARTER_ANIM_PERIOD = Graphics.frame_rate*3/20 + SIXTH_ANIM_PERIOD = Graphics.frame_rate*2/20 + + def update(frameCounter=0) + return if !@_iconBitmap + @updating = true + # Update bitmap + @_iconBitmap.update + self.bitmap = @_iconBitmap.bitmap + # Pokémon sprite bobbing while Pokémon is selected + @spriteYExtra = 0 + if @selected==1 # When choosing commands for this Pokémon + case (frameCounter/QUARTER_ANIM_PERIOD).floor + when 1; @spriteYExtra = 2 + when 3; @spriteYExtra = -2 + end + end + self.x = self.x + self.y = self.y + self.visible = @spriteVisible + # Pokémon sprite blinking when targeted + if @selected==2 && @spriteVisible + case (frameCounter/SIXTH_ANIM_PERIOD).floor + when 2, 5; self.visible = false + else; self.visible = true + end + end + @updating = false + end +end + + + +#=============================================================================== +# Shadow sprite for Pokémon (used in battle) +#=============================================================================== +class PokemonBattlerShadowSprite < RPG::Sprite + attr_reader :pkmn + attr_accessor :index + attr_accessor :selected + + def initialize(viewport,sideSize,index) + super(viewport) + @pkmn = nil + @sideSize = sideSize + @index = index + @_iconBitmap = nil + self.visible = false + end + + def dispose + @_iconBitmap.dispose if @_iconBitmap + @_iconBitmap = nil + self.bitmap = nil if !self.disposed? + super + end + + def width; return (self.bitmap) ? self.bitmap.width : 0; end + def height; return (self.bitmap) ? self.bitmap.height : 0; end + + # Set sprite's origin to centre + def pbSetOrigin + return if !@_iconBitmap + self.ox = @_iconBitmap.width/2 + self.oy = @_iconBitmap.height/2 + end + + def pbSetPosition + return if !@_iconBitmap + pbSetOrigin + self.z = 3 + # Set original position + p = PokeBattle_SceneConstants.pbBattlerPosition(@index,@sideSize) + self.x = p[0] + self.y = p[1] + # Apply metrics + pbApplyBattlerMetricsToSprite(self,@index,@pkmn.fSpecies,true) + end + + def setPokemonBitmap(pkmn) + @pkmn = pkmn + @_iconBitmap.dispose if @_iconBitmap + @_iconBitmap = pbLoadPokemonShadowBitmap(@pkmn) + self.bitmap = (@_iconBitmap) ? @_iconBitmap.bitmap : nil + pbSetPosition + end + + def update(frameCounter=0) + return if !@_iconBitmap + # Update bitmap + @_iconBitmap.update + self.bitmap = @_iconBitmap.bitmap + end +end diff --git a/Data/Scripts/011_Battle/005_Battle scene/005_PokeBattle_SceneMenus.rb b/Data/Scripts/011_Battle/005_Battle scene/005_PokeBattle_SceneMenus.rb new file mode 100644 index 000000000..0f35d9383 --- /dev/null +++ b/Data/Scripts/011_Battle/005_Battle scene/005_PokeBattle_SceneMenus.rb @@ -0,0 +1,548 @@ +#=============================================================================== +# Base class for all three menu classes below +#=============================================================================== +class BattleMenuBase + attr_accessor :x + attr_accessor :y + attr_reader :z + attr_reader :visible + attr_reader :color + attr_reader :index + attr_reader :mode + # NOTE: Button width is half the width of the graphic containing them all. + BUTTON_HEIGHT = 46 + TEXT_BASE_COLOR = PokeBattle_SceneConstants::MESSAGE_BASE_COLOR + TEXT_SHADOW_COLOR = PokeBattle_SceneConstants::MESSAGE_SHADOW_COLOR + + def initialize(viewport=nil) + @x = 0 + @y = 0 + @z = 0 + @visible = false + @color = Color.new(0,0,0,0) + @index = 0 + @mode = 0 + @disposed = false + @sprites = {} + @visibility = {} + end + + def dispose + return if disposed? + pbDisposeSpriteHash(@sprites) + @disposed = true + end + + def disposed?; return @disposed; end + + def z=(value) + @z = value + for i in @sprites + i[1].z = value if !i[1].disposed? + end + end + + def visible=(value) + @visible = value + for i in @sprites + i[1].visible = (value && @visibility[i[0]]) if !i[1].disposed? + end + end + + def color=(value) + @color = value + for i in @sprites + i[1].color = value if !i[1].disposed? + end + end + + def index=(value) + oldValue = @index + @index = value + @cmdWindow.index = @index if @cmdWindow + refresh if @index!=oldValue + end + + def mode=(value) + oldValue = @mode + @mode = value + refresh if @mode!=oldValue + end + + def addSprite(key,sprite) + @sprites[key] = sprite + @visibility[key] = true + end + + def setIndexAndMode(index,mode) + oldIndex = @index + oldMode = @mode + @index = index + @mode = mode + @cmdWindow.index = @index if @cmdWindow + refresh if @index!=oldIndex || @mode!=oldMode + end + + def refresh; end + + def update + pbUpdateSpriteHash(@sprites) + end +end + + + +#=============================================================================== +# Command menu (Fight/Pokémon/Bag/Run) +#=============================================================================== +class CommandMenuDisplay < BattleMenuBase + # If true, displays graphics from Graphics/Pictures/Battle/overlay_command.png + # and Graphics/Pictures/Battle/cursor_command.png. + # If false, just displays text and the command window over the graphic + # Graphics/Pictures/Battle/overlay_message.png. You will need to edit def + # pbShowWindow to make the graphic appear while the command menu is being + # displayed. + USE_GRAPHICS = true + # Lists of which button graphics to use in different situations/types of battle. + MODES = [ + [0,2,1,3], # 0 = Regular battle + [0,2,1,9], # 1 = Regular battle with "Cancel" instead of "Run" + [0,2,1,4], # 2 = Regular battle with "Call" instead of "Run" + [5,7,6,3], # 3 = Safari Zone + [0,8,1,3] # 4 = Bug Catching Contest + ] + + def initialize(viewport,z) + super(viewport) + self.x = 0 + self.y = Graphics.height-96 + # Create message box (shows "What will X do?") + @msgBox = Window_UnformattedTextPokemon.newWithSize("", + self.x+16,self.y+2,220,Graphics.height-self.y,viewport) + @msgBox.baseColor = TEXT_BASE_COLOR + @msgBox.shadowColor = TEXT_SHADOW_COLOR + @msgBox.windowskin = nil + addSprite("msgBox",@msgBox) + if USE_GRAPHICS + # Create background graphic + background = IconSprite.new(self.x,self.y,viewport) + background.setBitmap("Graphics/Pictures/Battle/overlay_command") + addSprite("background",background) + # Create bitmaps + @buttonBitmap = AnimatedBitmap.new(_INTL("Graphics/Pictures/Battle/cursor_command")) + # Create action buttons + @buttons = Array.new(4) do |i| # 4 command options, therefore 4 buttons + button = SpriteWrapper.new(viewport) + button.bitmap = @buttonBitmap.bitmap + button.x = self.x+Graphics.width-260 + button.x += (((i%2)==0) ? 0 : @buttonBitmap.width/2-4) + button.y = self.y+6 + button.y += (((i/2)==0) ? 0 : BUTTON_HEIGHT-4) + button.src_rect.width = @buttonBitmap.width/2 + button.src_rect.height = BUTTON_HEIGHT + addSprite("button_#{i}",button) + next button + end + else + # Create command window (shows Fight/Bag/Pokémon/Run) + @cmdWindow = Window_CommandPokemon.newWithSize([], + self.x+Graphics.width-240,self.y,240,Graphics.height-self.y,viewport) + @cmdWindow.columns = 2 + @cmdWindow.columnSpacing = 4 + @cmdWindow.ignore_input = true + addSprite("cmdWindow",@cmdWindow) + end + self.z = z + refresh + end + + def dispose + super + @buttonBitmap.dispose if @buttonBitmap + end + + def z=(value) + super + @msgBox.z += 1 + @cmdWindow.z += 1 if @cmdWindow + end + + def setTexts(value) + @msgBox.text = value[0] + return if USE_GRAPHICS + commands = [] + for i in 1..4 + commands.push(value[i]) if value[i] && value[i]!=nil + end + @cmdWindow.commands = commands + end + + def refreshButtons + return if !USE_GRAPHICS + for i in 0...4 + button = @buttons[i] + button.src_rect.x = (i==@index) ? @buttonBitmap.width/2 : 0 + button.src_rect.y = MODES[@mode][i]*BUTTON_HEIGHT + button.z = self.z + ((i==@index) ? 3 : 2) + end + end + + def refresh + @msgBox.refresh + @cmdWindow.refresh if @cmdWindow + refreshButtons + end +end + + + +#=============================================================================== +# Fight menu (choose a move) +#=============================================================================== +class FightMenuDisplay < BattleMenuBase + attr_reader :battler + attr_reader :shiftMode + + # If true, displays graphics from Graphics/Pictures/Battle/overlay_fight.png + # and Graphics/Pictures/Battle/cursor_fight.png. + # If false, just displays text and the command window over the graphic + # Graphics/Pictures/Battle/overlay_message.png. You will need to edit def + # pbShowWindow to make the graphic appear while the command menu is being + # displayed. + USE_GRAPHICS = true + TYPE_ICON_HEIGHT = 28 + # Text colours of PP of selected move + PP_COLORS = [ + Color.new(248,72,72),Color.new(136,48,48), # Red, zero PP + Color.new(248,136,32),Color.new(144,72,24), # Orange, 1/4 of total PP or less + Color.new(248,192,0),Color.new(144,104,0), # Yellow, 1/2 of total PP or less + TEXT_BASE_COLOR,TEXT_SHADOW_COLOR # Black, more than 1/2 of total PP + ] + MAX_MOVES = 4 # Number of moves to display at once + + def initialize(viewport,z) + super(viewport) + self.x = 0 + self.y = Graphics.height-96 + @battler = nil + @shiftMode = 0 + # NOTE: @mode is for the display of the Mega Evolution button. + # 0=don't show, 1=show unpressed, 2=show pressed + if USE_GRAPHICS + # Create bitmaps + @buttonBitmap = AnimatedBitmap.new(_INTL("Graphics/Pictures/Battle/cursor_fight")) + @typeBitmap = AnimatedBitmap.new(_INTL("Graphics/Pictures/types")) + @megaEvoBitmap = AnimatedBitmap.new(_INTL("Graphics/Pictures/Battle/cursor_mega")) + @shiftBitmap = AnimatedBitmap.new(_INTL("Graphics/Pictures/Battle/cursor_shift")) + # Create background graphic + background = IconSprite.new(0,Graphics.height-96,viewport) + background.setBitmap("Graphics/Pictures/Battle/overlay_fight") + addSprite("background",background) + # Create move buttons + @buttons = Array.new(MAX_MOVES) do |i| + button = SpriteWrapper.new(viewport) + button.bitmap = @buttonBitmap.bitmap + button.x = self.x+4 + button.x += (((i%2)==0) ? 0 : @buttonBitmap.width/2-4) + button.y = self.y+6 + button.y += (((i/2)==0) ? 0 : BUTTON_HEIGHT-4) + button.src_rect.width = @buttonBitmap.width/2 + button.src_rect.height = BUTTON_HEIGHT + addSprite("button_#{i}",button) + next button + end + # Create overlay for buttons (shows move names) + @overlay = BitmapSprite.new(Graphics.width,Graphics.height-self.y,viewport) + @overlay.x = self.x + @overlay.y = self.y + pbSetNarrowFont(@overlay.bitmap) + addSprite("overlay",@overlay) + # Create overlay for selected move's info (shows move's PP) + @infoOverlay = BitmapSprite.new(Graphics.width,Graphics.height-self.y,viewport) + @infoOverlay.x = self.x + @infoOverlay.y = self.y + pbSetNarrowFont(@infoOverlay.bitmap) + addSprite("infoOverlay",@infoOverlay) + # Create type icon + @typeIcon = SpriteWrapper.new(viewport) + @typeIcon.bitmap = @typeBitmap.bitmap + @typeIcon.x = self.x+416 + @typeIcon.y = self.y+20 + @typeIcon.src_rect.height = TYPE_ICON_HEIGHT + addSprite("typeIcon",@typeIcon) + # Create Mega Evolution button + @megaButton = SpriteWrapper.new(viewport) + @megaButton.bitmap = @megaEvoBitmap.bitmap + @megaButton.x = self.x+146 + @megaButton.y = self.y-@megaEvoBitmap.height/2 + @megaButton.src_rect.height = @megaEvoBitmap.height/2 + addSprite("megaButton",@megaButton) + # Create Shift button + @shiftButton = SpriteWrapper.new(viewport) + @shiftButton.bitmap = @shiftBitmap.bitmap + @shiftButton.x = self.x+4 + @shiftButton.y = self.y-@shiftBitmap.height + addSprite("shiftButton",@shiftButton) + else + # Create message box (shows type and PP of selected move) + @msgBox = Window_AdvancedTextPokemon.newWithSize("", + self.x+320,self.y,Graphics.width-320,Graphics.height-self.y,viewport) + @msgBox.baseColor = TEXT_BASE_COLOR + @msgBox.shadowColor = TEXT_SHADOW_COLOR + pbSetNarrowFont(@msgBox.contents) + addSprite("msgBox",@msgBox) + # Create command window (shows moves) + @cmdWindow = Window_CommandPokemon.newWithSize([], + self.x,self.y,320,Graphics.height-self.y,viewport) + @cmdWindow.columns = 2 + @cmdWindow.columnSpacing = 4 + @cmdWindow.ignore_input = true + pbSetNarrowFont(@cmdWindow.contents) + addSprite("cmdWindow",@cmdWindow) + end + self.z = z + end + + def dispose + super + @buttonBitmap.dispose if @buttonBitmap + @typeBitmap.dispose if @typeBitmap + @megaEvoBitmap.dispose if @megaEvoBitmap + @shiftBitmap.dispose if @shiftBitmap + end + + def z=(value) + super + @msgBox.z += 1 if @msgBox + @cmdWindow.z += 2 if @cmdWindow + @overlay.z += 5 if @overlay + @infoOverlay.z += 6 if @infoOverlay + @typeIcon.z += 1 if @typeIcon + end + + def battler=(value) + @battler = value + refresh + refreshButtonNames + end + + def shiftMode=(value) + oldValue = @shiftMode + @shiftMode = value + refreshShiftButton if @shiftMode!=oldValue + end + + def refreshButtonNames + moves = (@battler) ? @battler.moves : [] + if !USE_GRAPHICS + # Fill in command window + commands = [] + moves.each { |m| commands.push((m && m.id>0) ? m.name : "-") } + @cmdWindow.commands = commands + return + end + # Draw move names onto overlay + @overlay.bitmap.clear + textPos = [] + moves.each_with_index do |m,i| + button = @buttons[i] + next if !@visibility["button_#{i}"] + x = button.x-self.x+button.src_rect.width/2 + y = button.y-self.y+8 + moveNameBase = TEXT_BASE_COLOR + if m.type>=0 + # NOTE: This takes a colour from a particular pixel in the button + # graphic and makes the move name's base colour that same colour. + # The pixel is at coordinates 10,34 in the button box. If you + # change the graphic, you may want to change/remove the below line + # of code to ensure the font is an appropriate colour. + moveNameBase = button.bitmap.get_pixel(10,button.src_rect.y+34) + end + textPos.push([m.name,x,y,2,moveNameBase,TEXT_SHADOW_COLOR]) + end + pbDrawTextPositions(@overlay.bitmap,textPos) + end + + def refreshSelection + moves = (@battler) ? @battler.moves : [] + if USE_GRAPHICS + # Choose appropriate button graphics and z positions + @buttons.each_with_index do |button,i| + if !moves[i] || moves[i].id==0 + @visibility["button_#{i}"] = false + next + end + @visibility["button_#{i}"] = true + button.src_rect.x = (i==@index) ? @buttonBitmap.width/2 : 0 + button.src_rect.y = moves[i].type*BUTTON_HEIGHT + button.z = self.z + ((i==@index) ? 4 : 3) + end + end + refreshMoveData(moves[@index]) + end + + def refreshMoveData(move) + # Write PP and type of the selected move + if !USE_GRAPHICS + moveType = PBTypes.getName(move.type) + if move.totalpp<=0 + @msgBox.text = _INTL("PP: ---
TYPE/{1}",moveType) + else + @msgBox.text = _ISPRINTF("PP: {1: 2d}/{2: 2d}
TYPE/{3:s}", + move.pp,move.totalpp,moveType) + end + return + end + @infoOverlay.bitmap.clear + if !move || move.id==0 + @visibility["typeIcon"] = false + return + end + @visibility["typeIcon"] = true + # Type icon + @typeIcon.src_rect.y = move.type*TYPE_ICON_HEIGHT + # PP text + if move.totalpp>0 + ppFraction = [(4.0*move.pp/move.totalpp).ceil,3].min + textPos = [] + textPos.push([_INTL("PP: {1}/{2}",move.pp,move.totalpp), + 448,50,2,PP_COLORS[ppFraction*2],PP_COLORS[ppFraction*2+1]]) + pbDrawTextPositions(@infoOverlay.bitmap,textPos) + end + end + + def refreshMegaEvolutionButton + return if !USE_GRAPHICS + @megaButton.src_rect.y = (@mode-1)*@megaEvoBitmap.height/2 + @megaButton.z = self.z - 1 + end + + def refreshShiftButton + return if !USE_GRAPHICS + @shiftButton.src_rect.y = (@shiftMode-1)*@shiftBitmap.height + @shiftButton.z = self.z - 1 + end + + def refresh + return if !@battler + refreshSelection + refreshMegaEvolutionButton + refreshShiftButton + end +end + + + +#=============================================================================== +# Target menu (choose a move's target) +# NOTE: Unlike the command and fight menus, this one doesn't have a textbox-only +# version. +#=============================================================================== +class TargetMenuDisplay < BattleMenuBase + attr_accessor :mode + + # Lists of which button graphics to use in different situations/types of battle. + MODES = [ + [0,2,1,3], # 0 = Regular battle + [0,2,1,9], # 1 = Regular battle with "Cancel" instead of "Run" + [0,2,1,4], # 2 = Regular battle with "Call" instead of "Run" + [5,7,6,3], # 3 = Safari Zone + [0,8,1,3] # 4 = Bug Catching Contest + ] + CMD_BUTTON_WIDTH_SMALL = 170 + TEXT_BASE_COLOR = Color.new(240,248,224) + TEXT_SHADOW_COLOR = Color.new(64,64,64) + + def initialize(viewport,z,sideSizes) + super(viewport) + @sideSizes = sideSizes + maxIndex = (@sideSizes[0]>@sideSizes[1]) ? (@sideSizes[0]-1)*2 : @sideSizes[1]*2-1 + @smallButtons = (@sideSizes.max>2) + self.x = 0 + self.y = Graphics.height-96 + @texts = [] + # NOTE: @mode is for which buttons are shown as selected. + # 0=select 1 button (@index), 1=select all buttons with text + # Create bitmaps + @buttonBitmap = AnimatedBitmap.new(_INTL("Graphics/Pictures/Battle/cursor_target")) + # Create target buttons + @buttons = Array.new(maxIndex+1) do |i| + numButtons = @sideSizes[i%2] + next if numButtons<=i/2 + # NOTE: Battler indexes go from left to right from the perspective of + # that side's trainer, so inc is different for each side for the + # same value of i/2. + inc = ((i%2)==0) ? i/2 : numButtons-1-i/2 + button = SpriteWrapper.new(viewport) + button.bitmap = @buttonBitmap.bitmap + button.src_rect.width = (@smallButtons) ? CMD_BUTTON_WIDTH_SMALL : @buttonBitmap.width/2 + button.src_rect.height = BUTTON_HEIGHT + if @smallButtons + button.x = self.x+170-[0,82,166][numButtons-1] + else + button.x = self.x+138-[0,116][numButtons-1] + end + button.x += (button.src_rect.width-4)*inc + button.y = self.y+6 + button.y += (BUTTON_HEIGHT-4)*((i+1)%2) + addSprite("button_#{i}",button) + next button + end + # Create overlay (shows target names) + @overlay = BitmapSprite.new(Graphics.width,Graphics.height-self.y,viewport) + @overlay.x = self.x + @overlay.y = self.y + pbSetNarrowFont(@overlay.bitmap) + addSprite("overlay",@overlay) + self.z = z + refresh + end + + def dispose + super + @buttonBitmap.dispose if @buttonBitmap + end + + def z=(value) + super + @overlay.z += 5 if @overlay + end + + def setDetails(texts,mode) + @texts = texts + @mode = mode + refresh + end + + def refreshButtons + # Choose appropriate button graphics and z positions + @buttons.each_with_index do |button,i| + next if !button + sel = false + buttonType = 0 + if @texts[i] + sel ||= (@mode==0 && i==@index) + sel ||= (@mode==1) + buttonType = ((i%2)==0) ? 1 : 2 + end + buttonType = 2*buttonType + ((@smallButtons) ? 1 : 0) + button.src_rect.x = (sel) ? @buttonBitmap.width/2 : 0 + button.src_rect.y = buttonType*BUTTON_HEIGHT + button.z = self.z + ((sel) ? 3 : 2) + end + # Draw target names onto overlay + @overlay.bitmap.clear + textpos = [] + @buttons.each_with_index do |button,i| + next if !button || @texts[i].nil? || @texts[i]=="" + x = button.x-self.x+button.src_rect.width/2 + y = button.y-self.y+8 + textpos.push([@texts[i],x,y,2,TEXT_BASE_COLOR,TEXT_SHADOW_COLOR]) + end + pbDrawTextPositions(@overlay.bitmap,textpos) + end + + def refresh + refreshButtons + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/005_Battle scene/006_PokeBattle_Scene.rb b/Data/Scripts/011_Battle/005_Battle scene/006_PokeBattle_Scene.rb new file mode 100644 index 000000000..1afdbc543 --- /dev/null +++ b/Data/Scripts/011_Battle/005_Battle scene/006_PokeBattle_Scene.rb @@ -0,0 +1,351 @@ +# Battle scene (the visuals of the battle) +class PokeBattle_Scene + attr_accessor :abortable # For non-interactive battles, can quit immediately + attr_reader :viewport + attr_reader :sprites + + BLANK = 0 + MESSAGE_BOX = 1 + COMMAND_BOX = 2 + FIGHT_BOX = 3 + TARGET_BOX = 4 + + MESSAGE_PAUSE_TIME = (Graphics.frame_rate*1.0).floor # 1 second + + #============================================================================= + # Updating and refreshing + #============================================================================= + def pbUpdate(cw=nil) + pbGraphicsUpdate + pbInputUpdate + pbFrameUpdate(cw) + end + + def pbGraphicsUpdate + # Update lineup animations + if @animations.length>0 + shouldCompact = false + @animations.each_with_index do |a,i| + a.update + if a.animDone? + a.dispose + @animations[i] = nil + shouldCompact = true + end + end + @animations.compact! if shouldCompact + end + # Update other graphics + @sprites["battle_bg"].update if @sprites["battle_bg"].respond_to?("update") + Graphics.update + @frameCounter += 1 + @frameCounter = @frameCounter%(Graphics.frame_rate*12/20) + end + + def pbInputUpdate + Input.update + if Input.trigger?(Input::B) && @abortable && !@aborted + @aborted = true + @battle.pbAbort + end + end + + def pbFrameUpdate(cw=nil) + cw.update if cw + @battle.battlers.each_with_index do |b,i| + next if !b + @sprites["dataBox_#{i}"].update(@frameCounter) if @sprites["dataBox_#{i}"] + @sprites["pokemon_#{i}"].update(@frameCounter) if @sprites["pokemon_#{i}"] + @sprites["shadow_#{i}"].update(@frameCounter) if @sprites["shadow_#{i}"] + end + end + + def pbRefresh + @battle.battlers.each_with_index do |b,i| + next if !b + @sprites["dataBox_#{i}"].refresh if @sprites["dataBox_#{i}"] + end + end + + def pbRefreshOne(idxBattler) + @sprites["dataBox_#{idxBattler}"].refresh if @sprites["dataBox_#{idxBattler}"] + end + + #============================================================================= + # Party lineup + #============================================================================= + # Returns whether the party line-ups are currently coming on-screen + def inPartyAnimation? + return @animations.length>0 + end + + #============================================================================= + # Window displays + #============================================================================= + def pbShowWindow(windowType) + # NOTE: If you are not using fancy graphics for the command/fight menus, you + # will need to make "messageBox" also visible if the windowtype if + # COMMAND_BOX/FIGHT_BOX respectively. + @sprites["messageBox"].visible = (windowType==MESSAGE_BOX) + @sprites["messageWindow"].visible = (windowType==MESSAGE_BOX) + @sprites["commandWindow"].visible = (windowType==COMMAND_BOX) + @sprites["fightWindow"].visible = (windowType==FIGHT_BOX) + @sprites["targetWindow"].visible = (windowType==TARGET_BOX) + end + + # This is for the end of brief messages, which have been lingering on-screen + # while other things happened. This is only called when another message wants + # to be shown, and makes the brief message linger for one more second first. + # Some animations skip this extra second by setting @briefMessage to false + # despite not having any other messages to show. + def pbWaitMessage + return if !@briefMessage + pbShowWindow(MESSAGE_BOX) + cw = @sprites["messageWindow"] + MESSAGE_PAUSE_TIME.times do + pbUpdate(cw) + end + cw.text = "" + cw.visible = false + @briefMessage = false + end + + # NOTE: A regular message is displayed for 1 second after it fully appears (or + # less if B/C is pressed) and disappears automatically after that time. + def pbDisplayMessage(msg,brief=false) + pbWaitMessage + pbShowWindow(MESSAGE_BOX) + cw = @sprites["messageWindow"] + cw.setText(msg) + PBDebug.log(msg) + yielded = false + i = 0 + loop do + pbUpdate(cw) + if !cw.busy? + if !yielded + yield if block_given? # For playing SE as soon as the message is all shown + yielded = true + end + if brief + # NOTE: A brief message lingers on-screen while other things happen. A + # regular message has to end before the game can continue. + @briefMessage = true + break + end + if i>=MESSAGE_PAUSE_TIME # Autoclose after 1 second + cw.text = "" + cw.visible = false + break + end + i += 1 + end + if Input.trigger?(Input::B) || Input.trigger?(Input::C) || @abortable + if cw.busy? + pbPlayDecisionSE if cw.pausing? && !@abortable + cw.skipAhead + elsif !@abortable + cw.text = "" + cw.visible = false + break + end + end + end + end + alias pbDisplay pbDisplayMessage + + # NOTE: A paused message has the arrow in the bottom corner indicating there + # is another message immediately afterward. It is displayed for 3 + # seconds after it fully appears (or less if B/C is pressed) and + # disappears automatically after that time, except at the end of battle. + def pbDisplayPausedMessage(msg) + pbWaitMessage + pbShowWindow(MESSAGE_BOX) + cw = @sprites["messageWindow"] + cw.text = _INTL("{1}\1",msg) + PBDebug.log(msg) + yielded = false + i = 0 + loop do + pbUpdate(cw) + if !cw.busy? + if !yielded + yield if block_given? # For playing SE as soon as the message is all shown + yielded = true + end + if !@battleEnd + if i>=MESSAGE_PAUSE_TIME*3 # Autoclose after 3 seconds + cw.text = "" + cw.visible = false + break + end + i += 1 + end + end + if Input.trigger?(Input::B) || Input.trigger?(Input::C) || @abortable + if cw.busy? + pbPlayDecisionSE if cw.pausing? && !@abortable + cw.skipAhead + elsif !@abortable + cw.text = "" + pbPlayDecisionSE + break + end + end + end + end + + def pbDisplayConfirmMessage(msg) + return pbShowCommands(msg,[_INTL("Yes"),_INTL("No")],1)==0 + end + + def pbShowCommands(msg,commands,defaultValue) + pbWaitMessage + pbShowWindow(MESSAGE_BOX) + dw = @sprites["messageWindow"] + dw.text = msg + cw = Window_CommandPokemon.new(commands) + cw.x = Graphics.width-cw.width + cw.y = Graphics.height-cw.height-dw.height + cw.z = dw.z+1 + cw.index = 0 + cw.viewport = @viewport + PBDebug.log(msg) + loop do + cw.visible = (!dw.busy?) + pbUpdate(cw) + dw.update + if Input.trigger?(Input::B) && defaultValue>=0 + if dw.busy? + pbPlayDecisionSE if dw.pausing? + dw.resume + else + cw.dispose + dw.text = "" + return defaultValue + end + elsif Input.trigger?(Input::C) + if dw.busy? + pbPlayDecisionSE if dw.pausing? + dw.resume + else + cw.dispose + dw.text = "" + return cw.index + end + end + end + end + + #============================================================================= + # Sprites + #============================================================================= + def pbAddSprite(id,x,y,filename,viewport) + sprite = IconSprite.new(x,y,viewport) + if filename + sprite.setBitmap(filename) rescue nil + end + @sprites[id] = sprite + return sprite + end + + def pbAddPlane(id,filename,viewport) + sprite = AnimatedPlane.new(viewport) + if filename + sprite.setBitmap(filename) + end + @sprites[id] = sprite + return sprite + end + + def pbDisposeSprites + pbDisposeSpriteHash(@sprites) + end + + # Used by Ally Switch. + def pbSwapBattlerSprites(idxA,idxB) + @sprites["pokemon_#{idxA}"], @sprites["pokemon_#{idxB}"] = @sprites["pokemon_#{idxB}"], @sprites["pokemon_#{idxA}"] + @sprites["shadow_#{idxA}"], @sprites["shadow_#{idxB}"] = @sprites["shadow_#{idxB}"], @sprites["shadow_#{idxA}"] + @lastCmd[idxA], @lastCmd[idxB] = @lastCmd[idxB], @lastCmd[idxA] + @lastMove[idxA], @lastMove[idxB] = @lastMove[idxB], @lastMove[idxA] + [idxA,idxB].each do |i| + @sprites["pokemon_#{i}"].index = i + @sprites["pokemon_#{i}"].pbSetPosition + @sprites["shadow_#{i}"].index = i + @sprites["shadow_#{i}"].pbSetPosition + @sprites["dataBox_#{i}"].battler = @battle.battlers[i] + end + pbRefresh + end + + #============================================================================= + # Phases + #============================================================================= + def pbBeginCommandPhase + @sprites["messageWindow"].text = "" + end + + def pbBeginAttackPhase + pbSelectBattler(-1) + pbShowWindow(MESSAGE_BOX) + end + + def pbBeginEndOfRoundPhase + end + + def pbEndBattle(result) + @abortable = false + pbShowWindow(BLANK) + # Fade out all sprites + pbBGMFade(1.0) + pbFadeOutAndHide(@sprites) + pbDisposeSprites + end + + #============================================================================= + # + #============================================================================= + def pbSelectBattler(idxBattler,selectMode=1) + numWindows = @battle.sideSizes.max*2 + for i in 0...numWindows + sel = (idxBattler.is_a?(Array)) ? !idxBattler[i].nil? : i==idxBattler + selVal = (sel) ? selectMode : 0 + @sprites["dataBox_#{i}"].selected = selVal if @sprites["dataBox_#{i}"] + @sprites["pokemon_#{i}"].selected = selVal if @sprites["pokemon_#{i}"] + end + end + + def pbChangePokemon(idxBattler,pkmn) + idxBattler = idxBattler.index if idxBattler.respond_to?("index") + pkmnSprite = @sprites["pokemon_#{idxBattler}"] + shadowSprite = @sprites["shadow_#{idxBattler}"] + back = !@battle.opposes?(idxBattler) + pkmnSprite.setPokemonBitmap(pkmn,back) + shadowSprite.setPokemonBitmap(pkmn) + # Set visibility of battler's shadow + if shadowSprite && !back + shadowSprite.visible = showShadow?(pkmn.fSpecies) + end + end + + def pbResetMoveIndex(idxBattler) + @lastMove[idxBattler] = 0 + end + + #============================================================================= + # + #============================================================================= + # This method is called when the player wins a wild Pokémon battle. + # This method can change the battle's music for example. + def pbWildBattleSuccess + @battleEnd = true + pbBGMPlay(pbGetWildVictoryME) + end + + # This method is called when the player wins a trainer battle. + # This method can change the battle's music for example. + def pbTrainerBattleSuccess + @battleEnd = true + pbBGMPlay(pbGetTrainerVictoryME(@battle.opponent)) + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/005_Battle scene/007_Scene_Initialize.rb b/Data/Scripts/011_Battle/005_Battle scene/007_Scene_Initialize.rb new file mode 100644 index 000000000..0bc8e2fcf --- /dev/null +++ b/Data/Scripts/011_Battle/005_Battle scene/007_Scene_Initialize.rb @@ -0,0 +1,191 @@ +class PokeBattle_Scene + #============================================================================= + # Create the battle scene and its elements + #============================================================================= + def initialize + @battle = nil + @abortable = false + @aborted = false + @battleEnd = false + @animations = [] + @frameCounter = 0 + end + + # Called whenever the battle begins. + def pbStartBattle(battle) + @battle = battle + @viewport = Viewport.new(0,0,Graphics.width,Graphics.height) + @viewport.z = 99999 + @lastCmd = Array.new(@battle.battlers.length,0) + @lastMove = Array.new(@battle.battlers.length,0) + pbInitSprites + pbBattleIntroAnimation + end + + def pbInitSprites + @sprites = {} + # The background image and each side's base graphic + pbCreateBackdropSprites + # Create message box graphic + messageBox = pbAddSprite("messageBox",0,Graphics.height-96, + "Graphics/Pictures/Battle/overlay_message",@viewport) + messageBox.z = 195 + # Create message window (displays the message) + msgWindow = Window_AdvancedTextPokemon.newWithSize("", + 16,Graphics.height-96+2,Graphics.width-32,96,@viewport) + msgWindow.z = 200 + msgWindow.opacity = 0 + msgWindow.baseColor = PokeBattle_SceneConstants::MESSAGE_BASE_COLOR + msgWindow.shadowColor = PokeBattle_SceneConstants::MESSAGE_SHADOW_COLOR + msgWindow.letterbyletter = true + @sprites["messageWindow"] = msgWindow + # Create command window + @sprites["commandWindow"] = CommandMenuDisplay.new(@viewport,200) + # Create fight window + @sprites["fightWindow"] = FightMenuDisplay.new(@viewport,200) + # Create targeting window + @sprites["targetWindow"] = TargetMenuDisplay.new(@viewport,200,@battle.sideSizes) + pbShowWindow(MESSAGE_BOX) + # The party lineup graphics (bar and balls) for both sides + for side in 0...2 + partyBar = pbAddSprite("partyBar_#{side}",0,0, + "Graphics/Pictures/Battle/overlay_lineup",@viewport) + partyBar.z = 120 + partyBar.mirror = true if side==0 # Player's lineup bar only + partyBar.visible = false + for i in 0...PokeBattle_SceneConstants::NUM_BALLS + ball = pbAddSprite("partyBall_#{side}_#{i}",0,0,nil,@viewport) + ball.z = 121 + ball.visible = false + end + # Ability splash bars + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + @sprites["abilityBar_#{side}"] = AbilitySplashBar.new(side,@viewport) + end + end + # Player's and partner trainer's back sprite + @battle.player.each_with_index do |p,i| + pbCreateTrainerBackSprite(i,p.trainertype,@battle.player.length) + end + # Opposing trainer(s) sprites + if @battle.trainerBattle? + @battle.opponent.each_with_index do |p,i| + pbCreateTrainerFrontSprite(i,p.trainertype,@battle.opponent.length) + end + end + # Data boxes and Pokémon sprites + @battle.battlers.each_with_index do |b,i| + next if !b + @sprites["dataBox_#{i}"] = PokemonDataBox.new(b,@battle.pbSideSize(i),@viewport) + pbCreatePokemonSprite(i) + end + # Wild battle, so set up the Pokémon sprite(s) accordingly + if @battle.wildBattle? + @battle.pbParty(1).each_with_index do |pkmn,i| + index = i*2+1 + pbChangePokemon(index,pkmn) + pkmnSprite = @sprites["pokemon_#{index}"] + pkmnSprite.tone = Tone.new(-80,-80,-80) + pkmnSprite.visible = true + end + end + end + + def pbCreateBackdropSprites + case @battle.time + when 1; time = "eve" + when 2; time = "night" + end + # Put everything together into backdrop, bases and message bar filenames + backdropFilename = @battle.backdrop + baseFilename = @battle.backdrop + baseFilename = sprintf("%s_%s",baseFilename,@battle.backdropBase) if @battle.backdropBase + messageFilename = @battle.backdrop + if time + trialName = sprintf("%s_%s",backdropFilename,time) + if pbResolveBitmap(sprintf("Graphics/Battlebacks/"+trialName+"_bg")) + backdropFilename = trialName + end + trialName = sprintf("%s_%s",baseFilename,time) + if pbResolveBitmap(sprintf("Graphics/Battlebacks/"+trialName+"_base0")) + baseFilename = trialName + end + trialName = sprintf("%s_%s",messageFilename,time) + if pbResolveBitmap(sprintf("Graphics/Battlebacks/"+trialName+"_message")) + messageFilename = trialName + end + end + if !pbResolveBitmap(sprintf("Graphics/Battlebacks/"+baseFilename+"_base0")) && + @battle.backdropBase + baseFilename = @battle.backdropBase + if time + trialName = sprintf("%s_%s",baseFilename,time) + if pbResolveBitmap(sprintf("Graphics/Battlebacks/"+trialName+"_base0")) + baseFilename = trialName + end + end + end + # Finalise filenames + battleBG = "Graphics/Battlebacks/"+backdropFilename+"_bg" + playerBase = "Graphics/Battlebacks/"+baseFilename+"_base0" + enemyBase = "Graphics/Battlebacks/"+baseFilename+"_base1" + messageBG = "Graphics/Battlebacks/"+messageFilename+"_message" + # Apply graphics + bg = pbAddSprite("battle_bg",0,0,battleBG,@viewport) + bg.z = 0 + bg = pbAddSprite("battle_bg2",-Graphics.width,0,battleBG,@viewport) + bg.z = 0 + bg.mirror = true + for side in 0...2 + baseX, baseY = PokeBattle_SceneConstants.pbBattlerPosition(side) + base = pbAddSprite("base_#{side}",baseX,baseY, + (side==0) ? playerBase : enemyBase,@viewport) + base.z = 1 + if base.bitmap + base.ox = base.bitmap.width/2 + base.oy = (side==0) ? base.bitmap.height : base.bitmap.height/2 + end + end + cmdBarBG = pbAddSprite("cmdBar_bg",0,Graphics.height-96,messageBG,@viewport) + cmdBarBG.z = 180 + end + + def pbCreateTrainerBackSprite(idxTrainer,trainerType,numTrainers=1) + if idxTrainer==0 # Player's sprite + trainerFile = pbPlayerSpriteBackFile(trainerType) + else # Partner trainer's sprite + trainerFile = pbTrainerSpriteBackFile(trainerType) + end + spriteX, spriteY = PokeBattle_SceneConstants.pbTrainerPosition(0,idxTrainer,numTrainers) + trainer = pbAddSprite("player_#{idxTrainer+1}",spriteX,spriteY,trainerFile,@viewport) + return if !trainer.bitmap + # Alter position of sprite + trainer.z = 30+idxTrainer + if trainer.bitmap.width>trainer.bitmap.height*2 + trainer.src_rect.x = 0 + trainer.src_rect.width = trainer.bitmap.width/5 + end + trainer.ox = trainer.src_rect.width/2 + trainer.oy = trainer.bitmap.height + end + + def pbCreateTrainerFrontSprite(idxTrainer,trainerType,numTrainers=1) + trainerFile = pbTrainerSpriteFile(trainerType) + spriteX, spriteY = PokeBattle_SceneConstants.pbTrainerPosition(1,idxTrainer,numTrainers) + trainer = pbAddSprite("trainer_#{idxTrainer+1}",spriteX,spriteY,trainerFile,@viewport) + return if !trainer.bitmap + # Alter position of sprite + trainer.z = 7+idxTrainer + trainer.ox = trainer.src_rect.width/2 + trainer.oy = trainer.bitmap.height + end + + def pbCreatePokemonSprite(idxBattler) + sideSize = @battle.pbSideSize(idxBattler) + batSprite = PokemonBattlerSprite.new(@viewport,sideSize,idxBattler,@animations) + @sprites["pokemon_#{idxBattler}"] = batSprite + shaSprite = PokemonBattlerShadowSprite.new(@viewport,sideSize,idxBattler) + shaSprite.visible = false + @sprites["shadow_#{idxBattler}"] = shaSprite + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/005_Battle scene/008_Scene_Commands.rb b/Data/Scripts/011_Battle/005_Battle scene/008_Scene_Commands.rb new file mode 100644 index 000000000..ecb59e9f2 --- /dev/null +++ b/Data/Scripts/011_Battle/005_Battle scene/008_Scene_Commands.rb @@ -0,0 +1,467 @@ +class PokeBattle_Scene + #============================================================================= + # The player chooses a main command for a Pokémon + # Return values: -1=Cancel, 0=Fight, 1=Bag, 2=Pokémon, 3=Run, 4=Call + #============================================================================= + def pbCommandMenu(idxBattler,firstAction) + shadowTrainer = (hasConst?(PBTypes,:SHADOW) && @battle.trainerBattle?) + cmds = [ + _INTL("What will\n{1} do?",@battle.battlers[idxBattler].name), + _INTL("Fight"), + _INTL("Bag"), + _INTL("Pokémon"), + (shadowTrainer) ? _INTL("Call") : (firstAction) ? _INTL("Run") : _INTL("Cancel") + ] + ret = pbCommandMenuEx(idxBattler,cmds,(shadowTrainer) ? 2 : (firstAction) ? 0 : 1) + ret = 4 if ret==3 && shadowTrainer # Convert "Run" to "Call" + ret = -1 if ret==3 && !firstAction # Convert "Run" to "Cancel" + return ret + end + + # Mode: 0 = regular battle with "Run" (first choosable action in the round only) + # 1 = regular battle with "Cancel" + # 2 = regular battle with "Call" (for Shadow Pokémon battles) + # 3 = Safari Zone + # 4 = Bug Catching Contest + def pbCommandMenuEx(idxBattler,texts,mode=0) + pbShowWindow(COMMAND_BOX) + cw = @sprites["commandWindow"] + cw.setTexts(texts) + cw.setIndexAndMode(@lastCmd[idxBattler],mode) + pbSelectBattler(idxBattler) + ret = -1 + loop do + oldIndex = cw.index + pbUpdate(cw) + # Update selected command + if Input.trigger?(Input::LEFT) + cw.index -= 1 if (cw.index&1)==1 + elsif Input.trigger?(Input::RIGHT) + cw.index += 1 if (cw.index&1)==0 + elsif Input.trigger?(Input::UP) + cw.index -= 2 if (cw.index&2)==2 + elsif Input.trigger?(Input::DOWN) + cw.index += 2 if (cw.index&2)==0 + end + pbPlayCursorSE if cw.index!=oldIndex + # Actions + if Input.trigger?(Input::C) # Confirm choice + pbPlayDecisionSE + ret = cw.index + @lastCmd[idxBattler] = ret + break + elsif Input.trigger?(Input::B) && mode==1 # Cancel + pbPlayCancelSE + break + elsif Input.trigger?(Input::F9) && $DEBUG # Debug menu + pbPlayDecisionSE + ret = -2 + break + end + end + return ret + end + + #============================================================================= + # The player chooses a move for a Pokémon to use + #============================================================================= + def pbFightMenu(idxBattler,megaEvoPossible=false) + battler = @battle.battlers[idxBattler] + cw = @sprites["fightWindow"] + cw.battler = battler + moveIndex = 0 + if battler.moves[@lastMove[idxBattler]] && battler.moves[@lastMove[idxBattler]].id>0 + moveIndex = @lastMove[idxBattler] + end + cw.shiftMode = (@battle.pbCanShift?(idxBattler)) ? 1 : 0 + cw.setIndexAndMode(moveIndex,(megaEvoPossible) ? 1 : 0) + needFullRefresh = true + needRefresh = false + loop do + # Refresh view if necessary + if needFullRefresh + pbShowWindow(FIGHT_BOX) + pbSelectBattler(idxBattler) + needFullRefresh = false + end + if needRefresh + if megaEvoPossible + newMode = (@battle.pbRegisteredMegaEvolution?(idxBattler)) ? 2 : 1 + cw.mode = newMode if newMode!=cw.mode + end + needRefresh = false + end + oldIndex = cw.index + # General update + pbUpdate(cw) + # Update selected command + if Input.trigger?(Input::LEFT) + cw.index -= 1 if (cw.index&1)==1 + elsif Input.trigger?(Input::RIGHT) + if battler.moves[cw.index+1] && battler.moves[cw.index+1].id>0 + cw.index += 1 if (cw.index&1)==0 + end + elsif Input.trigger?(Input::UP) + cw.index -= 2 if (cw.index&2)==2 + elsif Input.trigger?(Input::DOWN) + if battler.moves[cw.index+2] && battler.moves[cw.index+2].id>0 + cw.index += 2 if (cw.index&2)==0 + end + end + pbPlayCursorSE if cw.index!=oldIndex + # Actions + if Input.trigger?(Input::C) # Confirm choice + pbPlayDecisionSE + break if yield cw.index + needFullRefresh = true + needRefresh = true + elsif Input.trigger?(Input::B) # Cancel fight menu + pbPlayCancelSE + break if yield -1 + needRefresh = true + elsif Input.trigger?(Input::A) # Toggle Mega Evolution + if megaEvoPossible + pbPlayDecisionSE + break if yield -2 + needRefresh = true + end + elsif Input.trigger?(Input::F5) # Shift + if cw.shiftMode>0 + pbPlayDecisionSE + break if yield -3 + needRefresh = true + end + end + end + @lastMove[idxBattler] = cw.index + end + + #============================================================================= + # Opens the party screen to choose a Pokémon to switch in (or just view its + # summary screens) + #============================================================================= + def pbPartyScreen(idxBattler,canCancel=false) + # Fade out and hide all sprites + visibleSprites = pbFadeOutAndHide(@sprites) + # Get player's party + party = @battle.pbParty(idxBattler) + partyPos = @battle.pbPartyOrder(idxBattler) + partyStart, partyEnd = @battle.pbTeamIndexRangeFromBattlerIndex(idxBattler) + modParty = @battle.pbPlayerDisplayParty(idxBattler) + # Start party screen + scene = PokemonParty_Scene.new + switchScreen = PokemonPartyScreen.new(scene,modParty) + switchScreen.pbStartScene(_INTL("Choose a Pokémon."),@battle.pbNumPositions(0,0)) + # Loop while in party screen + loop do + # Select a Pokémon + scene.pbSetHelpText(_INTL("Choose a Pokémon.")) + idxParty = switchScreen.pbChoosePokemon + if idxParty<0 + next if !canCancel + break + end + # Choose a command for the selected Pokémon + cmdSwitch = -1 + cmdSummary = -1 + commands = [] + commands[cmdSwitch = commands.length] = _INTL("Switch In") if modParty[idxParty].able? + commands[cmdSummary = commands.length] = _INTL("Summary") + commands[commands.length] = _INTL("Cancel") + command = scene.pbShowCommands(_INTL("Do what with {1}?",modParty[idxParty].name),commands) + if cmdSwitch>=0 && command==cmdSwitch # Switch In + idxPartyRet = -1 + partyPos.each_with_index do |pos,i| + next if pos!=idxParty+partyStart + idxPartyRet = i + break + end + break if yield idxPartyRet, switchScreen + elsif cmdSummary>=0 && command==cmdSummary # Summary + scene.pbSummary(idxParty,true) + end + end + # Close party screen + switchScreen.pbEndScene + # Fade back into battle screen + pbFadeInAndShow(@sprites,visibleSprites) + end + + #============================================================================= + # Opens the Bag screen and chooses an item to use + #============================================================================= + def pbItemMenu(idxBattler,firstAction) + # Fade out and hide all sprites + visibleSprites = pbFadeOutAndHide(@sprites) + # Set Bag starting positions + oldLastPocket = $PokemonBag.lastpocket + oldChoices = $PokemonBag.getAllChoices + $PokemonBag.lastpocket = @bagLastPocket if @bagLastPocket!=nil + $PokemonBag.setAllChoices(@bagChoices) if @bagChoices!=nil + # Start Bag screen + itemScene = PokemonBag_Scene.new + itemScene.pbStartScene($PokemonBag,true,Proc.new { |item| + useType = pbGetItemData(item,ITEM_BATTLE_USE) + next useType && useType>0 + },false) + # Loop while in Bag screen + wasTargeting = false + loop do + # Select an item + item = itemScene.pbChooseItem + break if item==0 + # Choose a command for the selected item + itemName = PBItems.getName(item) + useType = pbGetItemData(item,ITEM_BATTLE_USE) + cmdUse = -1 + commands = [] + commands[cmdUse = commands.length] = _INTL("Use") if useType && useType!=0 + commands[commands.length] = _INTL("Cancel") + command = itemScene.pbShowCommands(_INTL("{1} is selected.",itemName),commands) + next unless cmdUse>=0 && command==cmdUse # Use + # Use types: + # 0 = not usable in battle + # 1 = use on Pokémon (lots of items), consumed + # 2 = use on Pokémon's move (Ethers), consumed + # 3 = use on battler (X items, Persim Berry), consumed + # 4 = use on opposing battler (Poké Balls), consumed + # 5 = use no target (Poké Doll, Guard Spec., Launcher items), consumed + # 6 = use on Pokémon (Blue Flute), not consumed + # 7 = use on Pokémon's move, not consumed + # 8 = use on battler (Red/Yellow Flutes), not consumed + # 9 = use on opposing battler, not consumed + # 10 = use no target (Poké Flute), not consumed + case useType + when 1, 2, 3, 6, 7, 8 # Use on Pokémon/Pokémon's move/battler + # Auto-choose the Pokémon/battler whose action is being decided if they + # are the only available Pokémon/battler to use the item on + case useType + when 1, 6 # Use on Pokémon + if @battle.pbTeamLengthFromBattlerIndex(idxBattler)==1 + break if yield item, useType, @battle.battlers[idxBattler].pokemonIndex, -1, itemScene + end + when 3, 8 # Use on battler + if @battle.pbPlayerBattlerCount==1 + break if yield item, useType, @battle.battlers[idxBattler].pokemonIndex, -1, itemScene + end + end + # Fade out and hide Bag screen + itemScene.pbFadeOutScene + # Get player's party + party = @battle.pbParty(idxBattler) + partyPos = @battle.pbPartyOrder(idxBattler) + partyStart, partyEnd = @battle.pbTeamIndexRangeFromBattlerIndex(idxBattler) + modParty = @battle.pbPlayerDisplayParty(idxBattler) + # Start party screen + pkmnScene = PokemonParty_Scene.new + pkmnScreen = PokemonPartyScreen.new(pkmnScene,modParty) + pkmnScreen.pbStartScene(_INTL("Use on which Pokémon?"),@battle.pbNumPositions(0,0)) + idxParty = -1 + # Loop while in party screen + loop do + # Select a Pokémon + pkmnScene.pbSetHelpText(_INTL("Use on which Pokémon?")) + idxParty = pkmnScreen.pbChoosePokemon + break if idxParty<0 + idxPartyRet = -1 + partyPos.each_with_index do |pos,i| + next if pos!=idxParty+partyStart + idxPartyRet = i + break + end + next if idxPartyRet<0 + pkmn = party[idxPartyRet] + next if !pkmn || pkmn.egg? + idxMove = -1 + if useType==2 || useType==7 # Use on Pokémon's move + idxMove = pkmnScreen.pbChooseMove(pkmn,_INTL("Restore which move?")) + next if idxMove<0 + end + break if yield item, useType, idxPartyRet, idxMove, pkmnScene + end + pkmnScene.pbEndScene + break if idxParty>=0 + # Cancelled choosing a Pokémon; show the Bag screen again + itemScene.pbFadeInScene + when 4, 9 # Use on opposing battler (Poké Balls) + idxTarget = -1 + if @battle.pbOpposingBattlerCount(idxBattler)==1 + @battle.eachOtherSideBattler(idxBattler) { |b| idxTarget = b.index } + break if yield item, useType, idxTarget, -1, itemScene + else + wasTargeting = true + # Fade out and hide Bag screen + itemScene.pbFadeOutScene + # Fade in and show the battle screen, choosing a target + tempVisibleSprites = visibleSprites.clone + tempVisibleSprites["commandWindow"] = false + tempVisibleSprites["targetWindow"] = true + idxTarget = pbChooseTarget(idxBattler,PBTargets::Foe,tempVisibleSprites) + if idxTarget>=0 + break if yield item, useType, idxTarget, -1, self + end + # Target invalid/cancelled choosing a target; show the Bag screen again + wasTargeting = false + pbFadeOutAndHide(@sprites) + itemScene.pbFadeInScene + end + when 5, 10 # Use with no target + break if yield item, useType, idxBattler, -1, itemScene + end + end + @bagLastPocket = $PokemonBag.lastpocket + @bagChoices = $PokemonBag.getAllChoices + $PokemonBag.lastpocket = oldLastPocket + $PokemonBag.setAllChoices(oldChoices) + # Close Bag screen + itemScene.pbEndScene + # Fade back into battle screen (if not already showing it) + pbFadeInAndShow(@sprites,visibleSprites) if !wasTargeting + end + + #============================================================================= + # The player chooses a target battler for a move/item (non-single battles only) + #============================================================================= + # Returns an array containing battler names to display when choosing a move's + # target. + # nil means can't select that position, "" means can select that position but + # there is no battler there, otherwise is a battler's name. + def pbCreateTargetTexts(idxBattler,targetType) + texts = Array.new(@battle.battlers.length) do |i| + next nil if !@battle.battlers[i] + showName = false + case targetType + when PBTargets::None, PBTargets::User, PBTargets::RandomNearFoe + showName = (i==idxBattler) + when PBTargets::UserSide, PBTargets::UserAndAllies + showName = !@battle.opposes?(i,idxBattler) + when PBTargets::FoeSide, PBTargets::AllFoes + showName = @battle.opposes?(i,idxBattler) + when PBTargets::BothSides, PBTargets::AllBattlers + showName = true + else + showName = @battle.pbMoveCanTarget?(i,idxBattler,targetType) + end + next nil if !showName + next (@battle.battlers[i].fainted?) ? "" : @battle.battlers[i].name + end + return texts + end + + # Returns the initial position of the cursor when choosing a target for a move + # in a non-single battle. + def pbFirstTarget(idxBattler,targetType) + case targetType + when PBTargets::NearAlly + @battle.eachSameSideBattler(idxBattler) do |b| + next if b.index==idxBattler || !@battle.nearBattlers?(b,idxBattler) + next if b.fainted? + return b.index + end + @battle.eachSameSideBattler(idxBattler) do |b| + next if b.index==idxBattler || !@battle.nearBattlers?(b,idxBattler) + return b.index + end + when PBTargets::NearFoe, PBTargets::NearOther + indices = @battle.pbGetOpposingIndicesInOrder(idxBattler) + indices.each { |i| return i if @battle.nearBattlers?(i,idxBattler) && !@battle.battlers[i].fainted? } + indices.each { |i| return i if @battle.nearBattlers?(i,idxBattler) } + when PBTargets::Foe, PBTargets::Other + indices = @battle.pbGetOpposingIndicesInOrder(idxBattler) + indices.each { |i| return i if !@battle.battlers[i].fainted? } + indices.each { |i| return i } + end + return idxBattler + end + + def pbChooseTarget(idxBattler,targetType,visibleSprites=nil) + pbShowWindow(TARGET_BOX) + cw = @sprites["targetWindow"] + # Create an array of battler names (only valid targets are named) + texts = pbCreateTargetTexts(idxBattler,targetType) + # Determine mode based on targetType + mode = (PBTargets.oneTarget?(targetType)) ? 0 : 1 + cw.setDetails(texts,mode) + cw.index = pbFirstTarget(idxBattler,targetType) + pbSelectBattler((mode==0) ? cw.index : texts,2) # Select initial battler/data box + pbFadeInAndShow(@sprites,visibleSprites) if visibleSprites + ret = -1 + loop do + oldIndex = cw.index + pbUpdate(cw) + # Update selected command + if mode==0 # Choosing just one target, can change index + if Input.trigger?(Input::LEFT) || Input.trigger?(Input::RIGHT) + inc = ((cw.index%2)==0) ? -2 : 2 + inc *= -1 if Input.trigger?(Input::RIGHT) + indexLength = @battle.sideSizes[cw.index%2]*2 + newIndex = cw.index + loop do + newIndex += inc + break if newIndex<0 || newIndex>=indexLength + next if texts[newIndex].nil? + cw.index = newIndex + break + end + elsif (Input.trigger?(Input::UP) && (cw.index%2)==0) || + (Input.trigger?(Input::DOWN) && (cw.index%2)==1) + tryIndex = @battle.pbGetOpposingIndicesInOrder(cw.index) + tryIndex.each do |idxBattlerTry| + next if texts[idxBattlerTry].nil? + cw.index = idxBattlerTry + break + end + end + if cw.index!=oldIndex + pbPlayCursorSE + pbSelectBattler(cw.index,2) # Select the new battler/data box + end + end + if Input.trigger?(Input::C) # Confirm + ret = cw.index + pbPlayDecisionSE + break + elsif Input.trigger?(Input::B) # Cancel + ret = -1 + pbPlayCancelSE + break + end + end + pbSelectBattler(-1) # Deselect all battlers/data boxes + return ret + end + + #============================================================================= + # Opens a Pokémon's summary screen to try to learn a new move + #============================================================================= + # Called whenever a Pokémon should forget a move. It should return -1 if the + # selection is canceled, or 0 to 3 to indicate the move to forget. It should + # not allow HM moves to be forgotten. + def pbForgetMove(pkmn,moveToLearn) + ret = -1 + pbFadeOutIn { + scene = PokemonSummary_Scene.new + screen = PokemonSummaryScreen.new(scene) + ret = screen.pbStartForgetScreen([pkmn],0,moveToLearn) + } + return ret + end + + #============================================================================= + # Opens the nicknaming screen for a newly caught Pokémon + #============================================================================= + def pbNameEntry(helpText,pkmn) + return pbEnterPokemonName(helpText,0,PokeBattle_Pokemon::MAX_POKEMON_NAME_SIZE,"",pkmn) + end + + #============================================================================= + # Shows the Pokédex entry screen for a newly caught Pokémon + #============================================================================= + def pbShowPokedex(species) + pbFadeOutIn { + scene = PokemonPokedexInfo_Scene.new + screen = PokemonPokedexInfoScreen.new(scene) + screen.pbDexEntry(species) + } + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/005_Battle scene/009_Scene_Animations.rb b/Data/Scripts/011_Battle/005_Battle scene/009_Scene_Animations.rb new file mode 100644 index 000000000..36a3b381b --- /dev/null +++ b/Data/Scripts/011_Battle/005_Battle scene/009_Scene_Animations.rb @@ -0,0 +1,542 @@ +class PokeBattle_Scene + #============================================================================= + # Animates the battle intro + #============================================================================= + def pbBattleIntroAnimation + # Make everything appear + introAnim = BattleIntroAnimation.new(@sprites,@viewport,@battle) + loop do + introAnim.update + pbUpdate + break if introAnim.animDone? + end + introAnim.dispose + # Post-appearance activities + # Trainer battle: get ready to show the party lineups (they are brought + # on-screen by a separate animation) + if @battle.trainerBattle? + # NOTE: Here is where you'd make trainer sprites animate if they had an + # entrance animation. Be sure to set it up like a Pokémon entrance + # animation, i.e. add them to @animations so that they can play out + # while party lineups appear and messages show. + pbShowPartyLineup(0,true) + pbShowPartyLineup(1,true) + return + end + # Wild battle: play wild Pokémon's intro animations (including cry), show + # data box(es), return the wild Pokémon's sprite(s) to normal colour, show + # shiny animation(s) + # Set up data box animation + for i in 0...@battle.sideSizes[1] + idxBattler = 2*i+1 + next if !@battle.battlers[idxBattler] + dataBoxAnim = DataBoxAppearAnimation.new(@sprites,@viewport,idxBattler) + @animations.push(dataBoxAnim) + end + # Set up wild Pokémon returning to normal colour and playing intro + # animations (including cry) + @animations.push(BattleIntroAnimation2.new(@sprites,@viewport,@battle.sideSizes[1])) + # Play all the animations + while inPartyAnimation?; pbUpdate; end + # Show shiny animation for wild Pokémon + if @battle.showAnims + for i in 0...@battle.sideSizes[1] + idxBattler = 2*i+1 + next if !@battle.battlers[idxBattler] || !@battle.battlers[idxBattler].shiny? + pbCommonAnimation("Shiny",@battle.battlers[idxBattler]) + end + end + end + + #============================================================================= + # Animates a party lineup appearing for the given side + #============================================================================= + def pbShowPartyLineup(side,fullAnim=false) + @animations.push(LineupAppearAnimation.new(@sprites,@viewport, + side,@battle.pbParty(side),@battle.pbPartyStarts(side),fullAnim)) + if !fullAnim + while inPartyAnimation?; pbUpdate; end + end + end + + #============================================================================= + # Animates an opposing trainer sliding in from off-screen. Will animate a + # previous trainer that is already on-screen slide off first. Used at the end + # of battle. + #============================================================================= + def pbShowOpponent(idxTrainer) + # Set up trainer appearing animation + appearAnim = TrainerAppearAnimation.new(@sprites,@viewport,idxTrainer) + @animations.push(appearAnim) + # Play the animation + while inPartyAnimation?; pbUpdate; end + end + + #============================================================================= + # Animates a trainer's sprite and party lineup hiding (if they are visible). + # Animates a Pokémon being sent out into battle, then plays the shiny + # animation for it if relevant. + # sendOuts is an array; each element is itself an array: [idxBattler,pkmn] + #============================================================================= + def pbSendOutBattlers(sendOuts,startBattle=false) + return if sendOuts.length==0 + # If party balls are still appearing, wait for them to finish showing up, as + # the FadeAnimation will make them disappear. + while inPartyAnimation?; pbUpdate; end + @briefMessage = false + # Make all trainers and party lineups disappear (player-side trainers may + # animate throwing a Poké Ball) + if @battle.opposes?(sendOuts[0][0]) + fadeAnim = TrainerFadeAnimation.new(@sprites,@viewport,startBattle) + else + fadeAnim = PlayerFadeAnimation.new(@sprites,@viewport,startBattle) + end + # For each battler being sent out, set the battler's sprite and create two + # animations (the Poké Ball moving and battler appearing from it, and its + # data box appearing) + sendOutAnims = [] + sendOuts.each_with_index do |b,i| + pkmn = @battle.battlers[b[0]].effects[PBEffects::Illusion] || b[1] + pbChangePokemon(b[0],pkmn) + pbRefresh + if @battle.opposes?(b[0]) + sendOutAnim = PokeballTrainerSendOutAnimation.new(@sprites,@viewport, + @battle.pbGetOwnerIndexFromBattlerIndex(b[0])+1, + @battle.battlers[b[0]],startBattle,i) + else + sendOutAnim = PokeballPlayerSendOutAnimation.new(@sprites,@viewport, + @battle.pbGetOwnerIndexFromBattlerIndex(b[0])+1, + @battle.battlers[b[0]],startBattle,i) + end + dataBoxAnim = DataBoxAppearAnimation.new(@sprites,@viewport,b[0]) + sendOutAnims.push([sendOutAnim,dataBoxAnim,false]) + end + # Play all animations + loop do + fadeAnim.update + sendOutAnims.each do |a| + next if a[2] + a[0].update + a[1].update if a[0].animDone? + a[2] = true if a[1].animDone? + end + pbUpdate + if !inPartyAnimation? + break if !sendOutAnims.any? { |a| !a[2] } + end + end + fadeAnim.dispose + sendOutAnims.each { |a| a[0].dispose; a[1].dispose } + # Play shininess animations for shiny Pokémon + sendOuts.each do |b| + next if !@battle.showAnims || !@battle.battlers[b[0]].shiny? + pbCommonAnimation("Shiny",@battle.battlers[b[0]]) + end + end + + #============================================================================= + # Animates a Pokémon being recalled into its Poké Ball and its data box hiding + #============================================================================= + def pbRecall(idxBattler) + @briefMessage = false + # Recall animation + recallAnim = BattlerRecallAnimation.new(@sprites,@viewport,idxBattler) + loop do + recallAnim.update if recallAnim + pbUpdate + break if recallAnim.animDone? + end + recallAnim.dispose + # Data box disappear animation + dataBoxAnim = DataBoxDisappearAnimation.new(@sprites,@viewport,idxBattler) + loop do + dataBoxAnim.update + pbUpdate + break if dataBoxAnim.animDone? + end + dataBoxAnim.dispose + end + + #============================================================================= + # Ability splash bar animations + #============================================================================= + def pbShowAbilitySplash(battler) + return if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + side = battler.index%2 + pbHideAbilitySplash(battler) if @sprites["abilityBar_#{side}"].visible + @sprites["abilityBar_#{side}"].battler = battler + abilitySplashAnim = AbilitySplashAppearAnimation.new(@sprites,@viewport,side) + loop do + abilitySplashAnim.update + pbUpdate + break if abilitySplashAnim.animDone? + end + abilitySplashAnim.dispose + end + + def pbHideAbilitySplash(battler) + return if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + side = battler.index%2 + return if !@sprites["abilityBar_#{side}"].visible + abilitySplashAnim = AbilitySplashDisappearAnimation.new(@sprites,@viewport,side) + loop do + abilitySplashAnim.update + pbUpdate + break if abilitySplashAnim.animDone? + end + abilitySplashAnim.dispose + end + + def pbReplaceAbilitySplash(battler) + return if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + pbShowAbilitySplash(battler) + end + + #============================================================================= + # HP change animations + #============================================================================= + # Shows a HP-changing common animation and animates a data box's HP bar. + # Called by def pbReduceHP, def pbRecoverHP. + def pbHPChanged(battler,oldHP,showAnim=false) + @briefMessage = false + if battler.hp>oldHP + pbCommonAnimation("HealthUp",battler) if showAnim && @battle.showAnims + elsif battler.hp+{1}\r\nAttack+{2}\r\nDefense+{3}\r\nSp. Atk+{4}\r\nSp. Def+{5}\r\nSpeed+{6}", + pkmn.totalhp-oldTotalHP,pkmn.attack-oldAttack,pkmn.defense-oldDefense, + pkmn.spatk-oldSpAtk,pkmn.spdef-oldSpDef,pkmn.speed-oldSpeed)) + pbTopRightWindow( + _INTL("Max. HP{1}\r\nAttack{2}\r\nDefense{3}\r\nSp. Atk{4}\r\nSp. Def{5}\r\nSpeed{6}", + pkmn.totalhp,pkmn.attack,pkmn.defense,pkmn.spatk,pkmn.spdef,pkmn.speed)) + end + + #============================================================================= + # Animates a Pokémon fainting + #============================================================================= + def pbFaintBattler(battler) + @briefMessage = false + # Pokémon plays cry and drops down, data box disappears + faintAnim = BattlerFaintAnimation.new(@sprites,@viewport,battler.index,@battle) + dataBoxAnim = DataBoxDisappearAnimation.new(@sprites,@viewport,battler.index) + loop do + faintAnim.update + dataBoxAnim.update + pbUpdate + break if faintAnim.animDone? && dataBoxAnim.animDone? + end + faintAnim.dispose + dataBoxAnim.dispose + end + + #============================================================================= + # Animates throwing a Poké Ball at a Pokémon in an attempt to catch it + #============================================================================= + def pbThrow(ball,shakes,critical,targetBattler,showPlayer=false) + @briefMessage = false + captureAnim = PokeballThrowCaptureAnimation.new(@sprites,@viewport, + pbGetBallType(ball),shakes,critical,@battle.battlers[targetBattler],showPlayer) + loop do + captureAnim.update + pbUpdate + break if captureAnim.animDone? && !inPartyAnimation? + end + captureAnim.dispose + end + + def pbThrowSuccess + return if @battle.opponent + @briefMessage = false + pbMEPlay(pbGetWildCaptureME) + i = 0 + loop do + pbUpdate + break if i>=Graphics.frame_rate*3.5 # 3.5 seconds + i += 1 + end + pbMEStop + end + + def pbHideCaptureBall(idxBattler) + # NOTE: It's not really worth writing a whole PokeBattle_Animation class for + # making the capture ball fade out. + ball = @sprites["captureBall"] + return if !ball + # Data box disappear animation + dataBoxAnim = DataBoxDisappearAnimation.new(@sprites,@viewport,idxBattler) + loop do + dataBoxAnim.update + ball.opacity -= 12*20/Graphics.frame_rate if ball.opacity>0 + pbUpdate + break if dataBoxAnim.animDone? && ball.opacity<=0 + end + dataBoxAnim.dispose + end + + def pbThrowAndDeflect(ball,idxBattler) + @briefMessage = false + throwAnim = PokeballThrowDeflectAnimation.new(@sprites,@viewport, + pbGetBallType(ball),@battle.battlers[idxBattler]) + loop do + throwAnim.update + pbUpdate + break if throwAnim.animDone? + end + throwAnim.dispose + end + + #============================================================================= + # Hides all battler shadows before yielding to a move animation, and then + # restores the shadows afterwards + #============================================================================= + def pbSaveShadows + # Remember which shadows were visible + shadows = Array.new(@battle.battlers.length) do |i| + shadow = @sprites["shadow_#{i}"] + ret = (shadow) ? shadow.visible : false + shadow.visible = false if shadow + next ret + end + # Yield to other code, i.e. playing an animation + yield + # Restore shadow visibility + for i in 0...@battle.battlers.length + shadow = @sprites["shadow_#{i}"] + shadow.visible = shadows[i] if shadow + end + end + + #============================================================================= + # Loads a move/common animation + #============================================================================= + # Returns the animation ID to use for a given move/user. Returns nil if that + # move has no animations defined for it. + def pbFindMoveAnimDetails(move2anim,moveID,idxUser,hitNum=0) + noFlip = false + if (idxUser&1)==0 # On player's side + anim = move2anim[0][moveID] + else # On opposing side + anim = move2anim[1][moveID] + noFlip = true if anim + anim = move2anim[0][moveID] if !anim + end + return [anim+hitNum,noFlip] if anim + return nil + end + + # Returns the animation ID to use for a given move. If the move has no + # animations, tries to use a default move animation depending on the move's + # type. If that default move animation doesn't exist, trues to use Tackle's + # move animation. Returns nil if it can't find any of these animations to use. + def pbFindMoveAnimation(moveID,idxUser,hitNum) + begin + move2anim = pbLoadMoveToAnim + # Find actual animation requested (an opponent using the animation first + # looks for an OppMove version then a Move version) + anim = pbFindMoveAnimDetails(move2anim,moveID,idxUser,hitNum) + return anim if anim + # Actual animation not found, get the default animation for the move's type + moveData = pbGetMoveData(moveID) + moveType = moveData[MOVE_TYPE] + moveKind = moveData[MOVE_CATEGORY] + moveKind += 3 if PBTargets.multipleTargets?(moveData[MOVE_TARGET]) || + PBTargets.targetsFoeSide?(moveData[MOVE_TARGET]) + moveKind += 3 if moveKind==2 && moveData[MOVE_TARGET]!=PBTargets::User && + moveData[MOVE_TARGET]!=PBTargets::UserSide + # [one target physical, one target special, user status, + # multiple targets physical, multiple targets special, non-user status] + typeDefaultAnim = { + :NORMAL => [:TACKLE,:SONICBOOM,:DEFENSECURL,:EXPLOSION,:SWIFT,:TAILWHIP], + :FIGHTING => [:MACHPUNCH,:AURASPHERE,:DETECT,nil,nil,nil], + :FLYING => [:WINGATTACK,:GUST,:ROOST,nil,:AIRCUTTER,:FEATHERDANCE], + :POISON => [:POISONSTING,:SLUDGE,:ACIDARMOR,nil,:ACID,:POISONPOWDER], + :GROUND => [:SANDTOMB,:MUDSLAP,nil,:EARTHQUAKE,:EARTHPOWER,:MUDSPORT], + :ROCK => [:ROCKTHROW,:POWERGEM,:ROCKPOLISH,:ROCKSLIDE,nil,:SANDSTORM], + :BUG => [:TWINEEDLE,:BUGBUZZ,:QUIVERDANCE,nil,:STRUGGLEBUG,:STRINGSHOT], + :GHOST => [:LICK,:SHADOWBALL,:GRUDGE,nil,nil,:CONFUSERAY], + :STEEL => [:IRONHEAD,:MIRRORSHOT,:IRONDEFENSE,nil,nil,:METALSOUND], + :FIRE => [:FIREPUNCH,:EMBER,:SUNNYDAY,nil,:INCINERATE,:WILLOWISP], + :WATER => [:CRABHAMMER,:WATERGUN,:AQUARING,nil,:SURF,:WATERSPORT], + :GRASS => [:VINEWHIP,:MEGADRAIN,:COTTONGUARD,:RAZORLEAF,nil,:SPORE], + :ELECTRIC => [:THUNDERPUNCH,:THUNDERSHOCK,:CHARGE,nil,:DISCHARGE,:THUNDERWAVE], + :PSYCHIC => [:ZENHEADBUTT,:CONFUSION,:CALMMIND,nil,:SYNCHRONOISE,:MIRACLEEYE], + :ICE => [:ICEPUNCH,:ICEBEAM,:MIST,nil,:POWDERSNOW,:HAIL], + :DRAGON => [:DRAGONCLAW,:DRAGONRAGE,:DRAGONDANCE,nil,:TWISTER,nil], + :DARK => [:PURSUIT,:DARKPULSE,:HONECLAWS,nil,:SNARL,:EMBARGO], + :FAIRY => [:TACKLE,:FAIRYWIND,:MOONLIGHT,nil,:SWIFT,:SWEETKISS] + } + typeDefaultAnim.each do |type, anims| + next if !isConst?(moveType,PBTypes,type) + if anims[moveKind] && hasConst?(PBMoves,anims[moveKind]) + anim = pbFindMoveAnimDetails(move2anim,getConst(PBMoves,anims[moveKind]),idxUser) + end + break if anim + if moveKind>=3 && anims[moveKind-3] && hasConst?(PBMoves,anims[moveKind-3]) + anim = pbFindMoveAnimDetails(move2anim,getConst(PBMoves,anims[moveKind-3]),idxUser) + end + break if anim + if anims[2] && hasConst?(PBMoves,anims[2]) + anim = pbFindMoveAnimDetails(move2anim,getConst(PBMoves,anims[2]),idxUser) + end + break + end + return anim if anim + # Default animation for the move's type not found, use Tackle's animation + if hasConst?(PBMoves,:TACKLE) + return pbFindMoveAnimDetails(move2anim,getConst(PBMoves,:TACKLE),idxUser) + end + rescue + end + return nil + end + + #============================================================================= + # Plays a move/common animation + #============================================================================= + # Plays a move animation. + def pbAnimation(moveID,user,targets,hitNum=0) + animID = pbFindMoveAnimation(moveID,user.index,hitNum) + return if !animID + anim = animID[0] + target = (targets && targets.is_a?(Array)) ? targets[0] : targets + animations = pbLoadBattleAnimations + return if !animations + pbSaveShadows { + if animID[1] # On opposing side and using OppMove animation + pbAnimationCore(animations[anim],target,user,true) + else # On player's side, and/or using Move animation + pbAnimationCore(animations[anim],user,target) + end + } + end + + # Plays a common animation. + def pbCommonAnimation(animName,user=nil,target=nil,hitNum=0) + return if !animName || animName=="" + target = target[0] if target && target.is_a?(Array) + animations = pbLoadBattleAnimations + return if !animations + animations.each do |a| + next if !a || a.name!="Common:"+animName + pbAnimationCore(a,user,(target!=nil) ? target : user) + return + end + end + + def pbAnimationCore(animation,user,target,oppMove=false) + return if !animation + @briefMessage = false + userSprite = (user) ? @sprites["pokemon_#{user.index}"] : nil + targetSprite = (target) ? @sprites["pokemon_#{target.index}"] : nil + # Remember the original positions of Pokémon sprites + oldUserX = (userSprite) ? userSprite.x : 0 + oldUserY = (userSprite) ? userSprite.y : 0 + oldTargetX = (targetSprite) ? targetSprite.x : oldUserX + oldTargetY = (targetSprite) ? targetSprite.y : oldUserY + # Create the animation player + animPlayer = PBAnimationPlayerX.new(animation,user,target,self,oppMove) + # Apply a transformation to the animation based on where the user and target + # actually are. Get the centres of each sprite. + userHeight = (userSprite && userSprite.bitmap && !userSprite.bitmap.disposed?) ? userSprite.bitmap.height : 128 + if targetSprite + targetHeight = (targetSprite.bitmap && !targetSprite.bitmap.disposed?) ? targetSprite.bitmap.height : 128 + else + targetHeight = userHeight + end + animPlayer.setLineTransform( + PokeBattle_SceneConstants::FOCUSUSER_X,PokeBattle_SceneConstants::FOCUSUSER_Y, + PokeBattle_SceneConstants::FOCUSTARGET_X,PokeBattle_SceneConstants::FOCUSTARGET_Y, + oldUserX,oldUserY-userHeight/2, + oldTargetX,oldTargetY-targetHeight/2) + # Play the animation + animPlayer.start + loop do + animPlayer.update + pbUpdate + break if animPlayer.animDone? + end + animPlayer.dispose + # Return Pokémon sprites to their original positions + if userSprite + userSprite.x = oldUserX + userSprite.y = oldUserY + userSprite.pbSetOrigin + end + if targetSprite + targetSprite.x = oldTargetX + targetSprite.y = oldTargetY + targetSprite.pbSetOrigin + end + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/005_PBEffects.rb b/Data/Scripts/011_Battle/005_PBEffects.rb new file mode 100644 index 000000000..32b4bbfb9 --- /dev/null +++ b/Data/Scripts/011_Battle/005_PBEffects.rb @@ -0,0 +1,182 @@ +begin + module PBEffects + #=========================================================================== + # These effects apply to a battler + #=========================================================================== + AquaRing = 0 + Attract = 1 + BanefulBunker = 2 + BeakBlast = 3 + Bide = 4 + BideDamage = 5 + BideTarget = 6 + BurnUp = 7 + Charge = 8 + ChoiceBand = 9 + Confusion = 10 + Counter = 11 + CounterTarget = 12 + Curse = 13 + Dancer = 14 + DefenseCurl = 15 + DestinyBond = 16 + DestinyBondPrevious = 17 + DestinyBondTarget = 18 + Disable = 19 + DisableMove = 20 + Electrify = 21 + Embargo = 22 + Encore = 23 + EncoreMove = 24 + Endure = 25 + FirstPledge = 26 + FlashFire = 27 + Flinch = 28 + FocusEnergy = 29 + FocusPunch = 30 + FollowMe = 31 + Foresight = 32 + FuryCutter = 33 + GastroAcid = 34 + GemConsumed = 35 + Grudge = 36 + HealBlock = 37 + HelpingHand = 38 + HyperBeam = 39 + Illusion = 40 + Imprison = 41 + Ingrain = 42 + Instruct = 43 + Instructed = 44 + KingsShield = 45 + LaserFocus = 46 + LeechSeed = 47 + LockOn = 48 + LockOnPos = 49 + MagicBounce = 50 + MagicCoat = 51 + MagnetRise = 52 + MeanLook = 53 + MeFirst = 54 + Metronome = 55 + MicleBerry = 56 + Minimize = 57 + MiracleEye = 58 + MirrorCoat = 59 + MirrorCoatTarget = 60 + MoveNext = 61 + MudSport = 62 + Nightmare = 63 + Outrage = 64 + ParentalBond = 65 + PerishSong = 66 + PerishSongUser = 67 + PickupItem = 68 + PickupUse = 69 + Pinch = 70 # Battle Palace only + Powder = 71 + PowerTrick = 72 + Prankster = 73 + PriorityAbility = 74 + PriorityItem = 75 + Protect = 76 + ProtectRate = 77 + Pursuit = 78 + Quash = 79 + Rage = 80 + RagePowder = 81 # Used along with FollowMe + Revenge = 82 + Rollout = 83 + Roost = 84 + ShellTrap = 85 + SkyDrop = 86 + SlowStart = 87 + SmackDown = 88 + Snatch = 89 + SpikyShield = 90 + Spotlight = 91 + Stockpile = 92 + StockpileDef = 93 + StockpileSpDef = 94 + Substitute = 95 + Taunt = 96 + Telekinesis = 97 + ThroatChop = 98 + Torment = 99 + Toxic = 100 + Transform = 101 + TransformSpecies = 102 + Trapping = 103 # Trapping move + TrappingMove = 104 + TrappingUser = 105 + Truant = 106 + TwoTurnAttack = 107 + Type3 = 108 + Unburden = 109 + Uproar = 110 + WaterSport = 111 + WeightChange = 112 + Yawn = 113 + + #=========================================================================== + # These effects apply to a battler position + #=========================================================================== + FutureSightCounter = 0 + FutureSightMove = 1 + FutureSightUserIndex = 2 + FutureSightUserPartyIndex = 3 + HealingWish = 4 + LunarDance = 5 + Wish = 6 + WishAmount = 7 + WishMaker = 8 + + #=========================================================================== + # These effects apply to a side + #=========================================================================== + AuroraVeil = 0 + CraftyShield = 1 + EchoedVoiceCounter = 2 + EchoedVoiceUsed = 3 + LastRoundFainted = 4 + LightScreen = 5 + LuckyChant = 6 + MatBlock = 7 + Mist = 8 + QuickGuard = 9 + Rainbow = 10 + Reflect = 11 + Round = 12 + Safeguard = 13 + SeaOfFire = 14 + Spikes = 15 + StealthRock = 16 + StickyWeb = 17 + Swamp = 18 + Tailwind = 19 + ToxicSpikes = 20 + WideGuard = 21 + + #=========================================================================== + # These effects apply to the battle (i.e. both sides) + #=========================================================================== + AmuletCoin = 0 + FairyLock = 1 + FusionBolt = 2 + FusionFlare = 3 + Gravity = 4 + HappyHour = 5 + IonDeluge = 6 + MagicRoom = 7 + MudSportField = 8 + PayDay = 9 + TrickRoom = 10 + WaterSportField = 11 + WonderRoom = 12 + end + +rescue Exception + if $!.is_a?(SystemExit) || "#{$!.class}"=="Reset" + raise $! + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/006_BattleHandlers.rb b/Data/Scripts/011_Battle/006_BattleHandlers.rb new file mode 100644 index 000000000..cc87b028a --- /dev/null +++ b/Data/Scripts/011_Battle/006_BattleHandlers.rb @@ -0,0 +1,611 @@ +module BattleHandlers + # Battler's speed calculation + SpeedCalcAbility = AbilityHandlerHash.new + SpeedCalcItem = ItemHandlerHash.new + # Battler's weight calculation + WeightCalcAbility = AbilityHandlerHash.new + WeightCalcItem = ItemHandlerHash.new # Float Stone + # Battler's HP changed + HPHealItem = ItemHandlerHash.new + AbilityOnHPDroppedBelowHalf = AbilityHandlerHash.new + # Battler's status problem + StatusCheckAbilityNonIgnorable = AbilityHandlerHash.new # Comatose + StatusImmunityAbility = AbilityHandlerHash.new + StatusImmunityAbilityNonIgnorable = AbilityHandlerHash.new + StatusImmunityAllyAbility = AbilityHandlerHash.new + AbilityOnStatusInflicted = AbilityHandlerHash.new # Synchronize + StatusCureItem = ItemHandlerHash.new + StatusCureAbility = AbilityHandlerHash.new + # Battler's stat stages + StatLossImmunityAbility = AbilityHandlerHash.new + StatLossImmunityAbilityNonIgnorable = AbilityHandlerHash.new # Full Metal Body + StatLossImmunityAllyAbility = AbilityHandlerHash.new # Flower Veil + AbilityOnStatGain = AbilityHandlerHash.new # None! + AbilityOnStatLoss = AbilityHandlerHash.new + # Priority and turn order + PriorityChangeAbility = AbilityHandlerHash.new + PriorityBracketChangeAbility = AbilityHandlerHash.new # Stall + PriorityBracketChangeItem = ItemHandlerHash.new + PriorityBracketUseAbility = AbilityHandlerHash.new # None! + PriorityBracketUseItem = ItemHandlerHash.new + # Move usage failures + AbilityOnFlinch = AbilityHandlerHash.new # Steadfast + MoveBlockingAbility = AbilityHandlerHash.new + MoveImmunityTargetAbility = AbilityHandlerHash.new + # Move usage + MoveBaseTypeModifierAbility = AbilityHandlerHash.new + # Accuracy calculation + AccuracyCalcUserAbility = AbilityHandlerHash.new + AccuracyCalcUserAllyAbility = AbilityHandlerHash.new # Victory Star + AccuracyCalcTargetAbility = AbilityHandlerHash.new + AccuracyCalcUserItem = ItemHandlerHash.new + AccuracyCalcTargetItem = ItemHandlerHash.new + # Damage calculation + DamageCalcUserAbility = AbilityHandlerHash.new + DamageCalcUserAllyAbility = AbilityHandlerHash.new + DamageCalcTargetAbility = AbilityHandlerHash.new + DamageCalcTargetAbilityNonIgnorable = AbilityHandlerHash.new + DamageCalcTargetAllyAbility = AbilityHandlerHash.new + DamageCalcUserItem = ItemHandlerHash.new + DamageCalcTargetItem = ItemHandlerHash.new + # Critical hit calculation + CriticalCalcUserAbility = AbilityHandlerHash.new + CriticalCalcTargetAbility = AbilityHandlerHash.new + CriticalCalcUserItem = ItemHandlerHash.new + CriticalCalcTargetItem = ItemHandlerHash.new # None! + # Upon a move hitting a target + TargetAbilityOnHit = AbilityHandlerHash.new + UserAbilityOnHit = AbilityHandlerHash.new # Poison Touch + TargetItemOnHit = ItemHandlerHash.new + TargetItemOnHitPositiveBerry = ItemHandlerHash.new + # Abilities/items that trigger at the end of using a move + UserAbilityEndOfMove = AbilityHandlerHash.new + TargetItemAfterMoveUse = ItemHandlerHash.new + UserItemAfterMoveUse = ItemHandlerHash.new + TargetAbilityAfterMoveUse = AbilityHandlerHash.new + EndOfMoveItem = ItemHandlerHash.new # Leppa Berry + EndOfMoveStatRestoreItem = ItemHandlerHash.new # White Herb + # Experience and EV gain + ExpGainModifierItem = ItemHandlerHash.new # Lucky Egg + EVGainModifierItem = ItemHandlerHash.new + # Weather and terrin + WeatherExtenderItem = ItemHandlerHash.new + TerrainExtenderItem = ItemHandlerHash.new # Terrain Extender + TerrainStatBoostItem = ItemHandlerHash.new + # End Of Round + EORWeatherAbility = AbilityHandlerHash.new + EORHealingAbility = AbilityHandlerHash.new + EORHealingItem = ItemHandlerHash.new + EOREffectAbility = AbilityHandlerHash.new + EOREffectItem = ItemHandlerHash.new + EORGainItemAbility = AbilityHandlerHash.new + # Switching and fainting + CertainSwitchingUserAbility = AbilityHandlerHash.new # None! + CertainSwitchingUserItem = ItemHandlerHash.new # Shed Shell + TrappingTargetAbility = AbilityHandlerHash.new + TrappingTargetItem = ItemHandlerHash.new # None! + AbilityOnSwitchIn = AbilityHandlerHash.new + ItemOnSwitchIn = ItemHandlerHash.new # Air Balloon + ItemOnIntimidated = ItemHandlerHash.new # Adrenaline Orb + AbilityOnSwitchOut = AbilityHandlerHash.new + AbilityChangeOnBattlerFainting = AbilityHandlerHash.new + AbilityOnBattlerFainting = AbilityHandlerHash.new # Soul-Heart + # Running from battle + RunFromBattleAbility = AbilityHandlerHash.new # Run Away + RunFromBattleItem = ItemHandlerHash.new # Smoke Ball + + #============================================================================= + + def self.triggerSpeedCalcAbility(ability,battler,mult) + ret = SpeedCalcAbility.trigger(ability,battler,mult) + return (ret!=nil) ? ret : mult + end + + def self.triggerSpeedCalcItem(item,battler,mult) + ret = SpeedCalcItem.trigger(item,battler,mult) + return (ret!=nil) ? ret : mult + end + + #============================================================================= + + def self.triggerWeightCalcAbility(ability,battler,w) + ret = WeightCalcAbility.trigger(ability,battler,w) + return (ret!=nil) ? ret : w + end + + def self.triggerWeightCalcItem(item,battler,w) + ret = WeightCalcItem.trigger(item,battler,w) + return (ret!=nil) ? ret : w + end + + #============================================================================= + + def self.triggerHPHealItem(item,battler,battle,forced) + ret = HPHealItem.trigger(item,battler,battle,forced) + return (ret!=nil) ? ret : false + end + + def self.triggerAbilityOnHPDroppedBelowHalf(ability,user,battle) + ret = AbilityOnHPDroppedBelowHalf.trigger(ability,user,battle) + return (ret!=nil) ? ret : false + end + + #============================================================================= + + def self.triggerStatusCheckAbilityNonIgnorable(ability,battler,status) + ret = StatusCheckAbilityNonIgnorable.trigger(ability,battler,status) + return (ret!=nil) ? ret : false + end + + def self.triggerStatusImmunityAbility(ability,battler,status) + ret = StatusImmunityAbility.trigger(ability,battler,status) + return (ret!=nil) ? ret : false + end + + def self.triggerStatusImmunityAbilityNonIgnorable(ability,battler,status) + ret = StatusImmunityAbilityNonIgnorable.trigger(ability,battler,status) + return (ret!=nil) ? ret : false + end + + def self.triggerStatusImmunityAllyAbility(ability,battler,status) + ret = StatusImmunityAllyAbility.trigger(ability,battler,status) + return (ret!=nil) ? ret : false + end + + def self.triggerAbilityOnStatusInflicted(ability,battler,user,status) + AbilityOnStatusInflicted.trigger(ability,battler,user,status) + end + + def self.triggerStatusCureItem(item,battler,battle,forced) + ret = StatusCureItem.trigger(item,battler,battle,forced) + return (ret!=nil) ? ret : false + end + + def self.triggerStatusCureAbility(ability,battler) + ret = StatusCureAbility.trigger(ability,battler) + return (ret!=nil) ? ret : false + end + + #============================================================================= + + def self.triggerStatLossImmunityAbility(ability,battler,stat,battle,showMessages) + ret = StatLossImmunityAbility.trigger(ability,battler,stat,battle,showMessages) + return (ret!=nil) ? ret : false + end + + def self.triggerStatLossImmunityAbilityNonIgnorable(ability,battler,stat,battle,showMessages) + ret = StatLossImmunityAbilityNonIgnorable.trigger(ability,battler,stat,battle,showMessages) + return (ret!=nil) ? ret : false + end + + def self.triggerStatLossImmunityAllyAbility(ability,bearer,battler,stat,battle,showMessages) + ret = StatLossImmunityAllyAbility.trigger(ability,bearer,battler,stat,battle,showMessages) + return (ret!=nil) ? ret : false + end + + def self.triggerAbilityOnStatGain(ability,battler,stat,user) + AbilityOnStatGain.trigger(ability,battler,stat,user) + end + + def self.triggerAbilityOnStatLoss(ability,battler,stat,user) + AbilityOnStatLoss.trigger(ability,battler,stat,user) + end + + #============================================================================= + + def self.triggerPriorityChangeAbility(ability,battler,move,pri) + ret = PriorityChangeAbility.trigger(ability,battler,move,pri) + return (ret!=nil) ? ret : pri + end + + def self.triggerPriorityBracketChangeAbility(ability,battler,subPri,battle) + ret = PriorityBracketChangeAbility.trigger(ability,battler,subPri,battle) + return (ret!=nil) ? ret : subPri + end + + def self.triggerPriorityBracketChangeItem(item,battler,subPri,battle) + ret = PriorityBracketChangeItem.trigger(item,battler,subPri,battle) + return (ret!=nil) ? ret : subPri + end + + def self.triggerPriorityBracketUseAbility(ability,battler,battle) + PriorityBracketUseAbility.trigger(ability,battler,battle) + end + + def self.triggerPriorityBracketUseItem(item,battler,battle) + PriorityBracketUseItem.trigger(item,battler,battle) + end + + #============================================================================= + + def self.triggerAbilityOnFlinch(ability,battler,battle) + AbilityOnFlinch.trigger(ability,battler,battle) + end + + def self.triggerMoveBlockingAbility(ability,bearer,user,targets,move,battle) + ret = MoveBlockingAbility.trigger(ability,bearer,user,targets,move,battle) + return (ret!=nil) ? ret : false + end + + def self.triggerMoveImmunityTargetAbility(ability,user,target,move,type,battle) + ret = MoveImmunityTargetAbility.trigger(ability,user,target,move,type,battle) + return (ret!=nil) ? ret : false + end + + #============================================================================= + + def self.triggerMoveBaseTypeModifierAbility(ability,user,move,type) + ret = MoveBaseTypeModifierAbility.trigger(ability,user,move,type) + return (ret!=nil) ? ret : type + end + + #============================================================================= + + def self.triggerAccuracyCalcUserAbility(ability,mods,user,target,move,type) + AccuracyCalcUserAbility.trigger(ability,mods,user,target,move,type) + end + + def self.triggerAccuracyCalcUserAllyAbility(ability,mods,user,target,move,type) + AccuracyCalcUserAllyAbility.trigger(ability,mods,user,target,move,type) + end + + def self.triggerAccuracyCalcTargetAbility(ability,mods,user,target,move,type) + AccuracyCalcTargetAbility.trigger(ability,mods,user,target,move,type) + end + + def self.triggerAccuracyCalcUserItem(item,mods,user,target,move,type) + AccuracyCalcUserItem.trigger(item,mods,user,target,move,type) + end + + def self.triggerAccuracyCalcTargetItem(item,mods,user,target,move,type) + AccuracyCalcTargetItem.trigger(item,mods,user,target,move,type) + end + + #============================================================================= + + def self.triggerDamageCalcUserAbility(ability,user,target,move,mults,baseDmg,type) + DamageCalcUserAbility.trigger(ability,user,target,move,mults,baseDmg,type) + end + + def self.triggerDamageCalcUserAllyAbility(ability,user,target,move,mults,baseDmg,type) + DamageCalcUserAllyAbility.trigger(ability,user,target,move,mults,baseDmg,type) + end + + def self.triggerDamageCalcTargetAbility(ability,user,target,move,mults,baseDmg,type) + DamageCalcTargetAbility.trigger(ability,user,target,move,mults,baseDmg,type) + end + + def self.triggerDamageCalcTargetAbilityNonIgnorable(ability,user,target,move,mults,baseDmg,type) + DamageCalcTargetAbilityNonIgnorable.trigger(ability,user,target,move,mults,baseDmg,type) + end + + def self.triggerDamageCalcTargetAllyAbility(ability,user,target,move,mults,baseDmg,type) + DamageCalcTargetAllyAbility.trigger(ability,user,target,move,mults,baseDmg,type) + end + + def self.triggerDamageCalcUserItem(item,user,target,move,mults,baseDmg,type) + DamageCalcUserItem.trigger(item,user,target,move,mults,baseDmg,type) + end + + def self.triggerDamageCalcTargetItem(item,user,target,move,mults,baseDmg,type) + DamageCalcTargetItem.trigger(item,user,target,move,mults,baseDmg,type) + end + + #============================================================================= + + def self.triggerCriticalCalcUserAbility(ability,user,target,c) + ret = CriticalCalcUserAbility.trigger(ability,user,target,c) + return (ret!=nil) ? ret : c + end + + def self.triggerCriticalCalcTargetAbility(ability,user,target,c) + ret = CriticalCalcTargetAbility.trigger(ability,user,target,c) + return (ret!=nil) ? ret : c + end + + def self.triggerCriticalCalcUserItem(item,user,target,c) + ret = CriticalCalcUserItem.trigger(item,user,target,c) + return (ret!=nil) ? ret : c + end + + def self.triggerCriticalCalcTargetItem(item,user,target,c) + ret = CriticalCalcTargetItem.trigger(item,user,target,c) + return (ret!=nil) ? ret : c + end + + #============================================================================= + + def self.triggerTargetAbilityOnHit(ability,user,target,move,battle) + TargetAbilityOnHit.trigger(ability,user,target,move,battle) + end + + def self.triggerUserAbilityOnHit(ability,user,target,move,battle) + UserAbilityOnHit.trigger(ability,user,target,move,battle) + end + + def self.triggerTargetItemOnHit(item,user,target,move,battle) + TargetItemOnHit.trigger(item,user,target,move,battle) + end + + def self.triggerTargetItemOnHitPositiveBerry(item,battler,battle,forced) + ret = TargetItemOnHitPositiveBerry.trigger(item,battler,battle,forced) + return (ret!=nil) ? ret : false + end + + #============================================================================= + + def self.triggerUserAbilityEndOfMove(ability,user,targets,move,battle) + UserAbilityEndOfMove.trigger(ability,user,targets,move,battle) + end + + def self.triggerTargetItemAfterMoveUse(item,battler,user,move,switched,battle) + TargetItemAfterMoveUse.trigger(item,battler,user,move,switched,battle) + end + + def self.triggerUserItemAfterMoveUse(item,user,targets,move,numHits,battle) + UserItemAfterMoveUse.trigger(item,user,targets,move,numHits,battle) + end + + def self.triggerTargetAbilityAfterMoveUse(ability,target,user,move,switched,battle) + TargetAbilityAfterMoveUse.trigger(ability,target,user,move,switched,battle) + end + + def self.triggerEndOfMoveItem(item,battler,battle,forced) + ret = EndOfMoveItem.trigger(item,battler,battle,forced) + return (ret!=nil) ? ret : false + end + + def self.triggerEndOfMoveStatRestoreItem(item,battler,battle,forced) + ret = EndOfMoveStatRestoreItem.trigger(item,battler,battle,forced) + return (ret!=nil) ? ret : false + end + + #============================================================================= + + def self.triggerExpGainModifierItem(item,battler,exp) + ret = ExpGainModifierItem.trigger(item,battler,exp) + return (ret!=nil) ? ret : -1 + end + + def self.triggerEVGainModifierItem(item,battler,evarray) + return false if !EVGainModifierItem[item] + EVGainModifierItem.trigger(item,battler,evarray) + return true + end + + #============================================================================= + + def self.triggerWeatherExtenderItem(item,weather,duration,battler,battle) + ret = WeatherExtenderItem.trigger(item,weather,duration,battler,battle) + return (ret!=nil) ? ret : duration + end + + def self.triggerTerrainExtenderItem(item,terrain,duration,battler,battle) + ret = TerrainExtenderItem.trigger(item,terrain,duration,battler,battle) + return (ret!=nil) ? ret : duration + end + + def self.triggerTerrainStatBoostItem(item,battler,battle) + ret = TerrainStatBoostItem.trigger(item,battler,battle) + return (ret!=nil) ? ret : false + end + + #============================================================================= + + def self.triggerEORWeatherAbility(ability,weather,battler,battle) + EORWeatherAbility.trigger(ability,weather,battler,battle) + end + + def self.triggerEORHealingAbility(ability,battler,battle) + EORHealingAbility.trigger(ability,battler,battle) + end + + def self.triggerEORHealingItem(item,battler,battle) + EORHealingItem.trigger(item,battler,battle) + end + + def self.triggerEOREffectAbility(ability,battler,battle) + EOREffectAbility.trigger(ability,battler,battle) + end + + def self.triggerEOREffectItem(item,battler,battle) + EOREffectItem.trigger(item,battler,battle) + end + + def self.triggerEORGainItemAbility(ability,battler,battle) + EORGainItemAbility.trigger(ability,battler,battle) + end + + #============================================================================= + + def self.triggerCertainSwitchingUserAbility(ability,switcher,battle) + ret = CertainSwitchingUserAbility.trigger(ability,switcher,battle) + return (ret!=nil) ? ret : false + end + + def self.triggerCertainSwitchingUserItem(item,switcher,battle) + ret = CertainSwitchingUserItem.trigger(item,switcher,battle) + return (ret!=nil) ? ret : false + end + + def self.triggerTrappingTargetAbility(ability,switcher,bearer,battle) + ret = TrappingTargetAbility.trigger(ability,switcher,bearer,battle) + return (ret!=nil) ? ret : false + end + + def self.triggerTrappingTargetItem(item,switcher,bearer,battle) + ret = TrappingTargetItem.trigger(item,switcher,bearer,battle) + return (ret!=nil) ? ret : false + end + + def self.triggerAbilityOnSwitchIn(ability,battler,battle) + AbilityOnSwitchIn.trigger(ability,battler,battle) + end + + def self.triggerItemOnSwitchIn(item,battler,battle) + ItemOnSwitchIn.trigger(item,battler,battle) + end + + def self.triggerItemOnIntimidated(item,battler,battle) + ret = ItemOnIntimidated.trigger(item,battler,battle) + return (ret!=nil) ? ret : false + end + + def self.triggerAbilityOnSwitchOut(ability,battler,endOfBattle) + AbilityOnSwitchOut.trigger(ability,battler,endOfBattle) + end + + def self.triggerAbilityChangeOnBattlerFainting(ability,battler,fainted,battle) + AbilityChangeOnBattlerFainting.trigger(ability,battler,fainted,battle) + end + + def self.triggerAbilityOnBattlerFainting(ability,battler,fainted,battle) + AbilityOnBattlerFainting.trigger(ability,battler,fainted,battle) + end + + #============================================================================= + + def self.triggerRunFromBattleAbility(ability,battler) + ret = RunFromBattleAbility.trigger(ability,battler) + return (ret!=nil) ? ret : false + end + + def self.triggerRunFromBattleItem(item,battler) + ret = RunFromBattleItem.trigger(item,battler) + return (ret!=nil) ? ret : false + end +end + + + +BASE_ACC = 0 +ACC_STAGE = 1 +EVA_STAGE = 2 +ACC_MULT = 3 +EVA_MULT = 4 + +BASE_DMG_MULT = 0 +ATK_MULT = 1 +DEF_MULT = 2 +FINAL_DMG_MULT = 3 + +def pbBattleConfusionBerry(battler,battle,item,forced,flavor,confuseMsg) + return false if !forced && !battler.pbCanConsumeBerry?(item,false) + itemName = PBItems.getName(item) + battle.pbCommonAnimation("EatBerry",battler) if !forced + amt = (NEWEST_BATTLE_MECHANICS) ? battler.pbRecoverHP(battler.totalhp/2) : battler.pbRecoverHP(battler.totalhp/8) + if amt>0 + if forced + PBDebug.log("[Item triggered] #{battler.pbThis}'s #{itemName}") + battle.pbDisplay(_INTL("{1}'s HP was restored.",battler.pbThis)) + else + battle.pbDisplay(_INTL("{1} restored its health using its {2}!",battler.pbThis,itemName)) + end + end + nUp = PBNatures.getStatRaised(battler.nature) + nDn = PBNatures.getStatLowered(battler.nature) + if nUp!=nDn && nDn-1==flavor + battle.pbDisplay(confuseMsg) + battler.pbConfuse if battler.pbCanConfuseSelf?(false) + end + return true +end + +def pbBattleStatIncreasingBerry(battler,battle,item,forced,stat,increment=1) + return false if !forced && !battler.pbCanConsumeBerry?(item) + return false if !battler.pbCanRaiseStatStage?(stat,battler) + itemName = PBItems.getName(item) + if !forced + battle.pbCommonAnimation("EatBerry",battler) + return battler.pbRaiseStatStageByCause(stat,increment,battler,itemName) + end + PBDebug.log("[Item triggered] #{battler.pbThis}'s #{itemName}") + return battler.pbRaiseStatStage(stat,increment,battler) +end + +# For abilities that grant immunity to moves of a particular type, and raises +# one of the ability's bearer's stats instead. +def pbBattleMoveImmunityStatAbility(user,target,move,moveType,immuneType,stat,increment,battle) + return false if user.index==target.index + return false if !isConst?(moveType,PBTypes,immuneType) + battle.pbShowAbilitySplash(target) + if target.pbCanRaiseStatStage?(stat,target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + target.pbRaiseStatStage(stat,increment,target) + else + target.pbRaiseStatStageByCause(stat,increment,target,target.abilityName) + end + else + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true))) + else + battle.pbDisplay(_INTL("{1}'s {2} made {3} ineffective!", + target.pbThis,target.abilityName,move.name)) + end + end + battle.pbHideAbilitySplash(target) + return true +end + +# For abilities that grant immunity to moves of a particular type, and heals the +# ability's bearer by 1/4 of its total HP instead. +def pbBattleMoveImmunityHealAbility(user,target,move,moveType,immuneType,battle) + return false if user.index==target.index + return false if !isConst?(moveType,PBTypes,immuneType) + battle.pbShowAbilitySplash(target) + if target.canHeal? && target.pbRecoverHP(target.totalhp/4)>0 + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s HP was restored.",target.pbThis)) + else + battle.pbDisplay(_INTL("{1}'s {2} restored its HP.",target.pbThis,target.abilityName)) + end + else + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true))) + else + battle.pbDisplay(_INTL("{1}'s {2} made {3} ineffective!", + target.pbThis,target.abilityName,move.name)) + end + end + battle.pbHideAbilitySplash(target) + return true +end + +def pbBattleGem(user,type,move,mults,moveType) + # Pledge moves never consume Gems + return if move.is_a?(PokeBattle_PledgeMove) + return if !isConst?(moveType,PBTypes,type) + user.effects[PBEffects::GemConsumed] = user.item + if NEWEST_BATTLE_MECHANICS + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*1.3).round + else + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*1.5).round + end +end + +def pbBattleTypeWeakingBerry(type,moveType,target,mults) + return if !isConst?(moveType,PBTypes,type) + return if PBTypes.resistant?(target.damageState.typeMod) && !isConst?(moveType,PBTypes,:NORMAL) + mults[FINAL_DMG_MULT] = (mults[FINAL_DMG_MULT]/2).round + target.damageState.berryWeakened = true + target.battle.pbCommonAnimation("EatBerry",target) +end + +def pbBattleWeatherAbility(weather,battler,battle,ignorePrimal=false) + return if !ignorePrimal && + (battle.field.weather==PBWeather::HarshSun || + battle.field.weather==PBWeather::HeavyRain || + battle.field.weather==PBWeather::StrongWinds) + return if battle.field.weather==weather + battle.pbShowAbilitySplash(battler) + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s {2} activated!",battler.pbThis,battler.abilityName)) + end + fixedDuration = false + fixedDuration = true if NEWEST_BATTLE_MECHANICS && + weather!=PBWeather::HarshSun && + weather!=PBWeather::HeavyRain && + weather!=PBWeather::StrongWinds + battle.pbStartWeather(battler,weather,fixedDuration) + # NOTE: The ability splash is hidden again in def pbStartWeather. +end diff --git a/Data/Scripts/011_Battle/006_Other battle types/001_PokeBattle_AnimationPlayer.rb b/Data/Scripts/011_Battle/006_Other battle types/001_PokeBattle_AnimationPlayer.rb new file mode 100644 index 000000000..5a9ba8300 --- /dev/null +++ b/Data/Scripts/011_Battle/006_Other battle types/001_PokeBattle_AnimationPlayer.rb @@ -0,0 +1,879 @@ +#=============================================================================== +# +#=============================================================================== +class AnimFrame + X = 0 + Y = 1 + ZOOMX = 2 + ANGLE = 3 + MIRROR = 4 + BLENDTYPE = 5 + VISIBLE = 6 + PATTERN = 7 + OPACITY = 8 + ZOOMY = 11 + COLORRED = 12 + COLORGREEN = 13 + COLORBLUE = 14 + COLORALPHA = 15 + TONERED = 16 + TONEGREEN = 17 + TONEBLUE = 18 + TONEGRAY = 19 + LOCKED = 20 + FLASHRED = 21 + FLASHGREEN = 22 + FLASHBLUE = 23 + FLASHALPHA = 24 + PRIORITY = 25 + FOCUS = 26 +end + + + +#=============================================================================== +# +#=============================================================================== +def yaxisIntersect(x1,y1,x2,y2,px,py) + dx = x2-x1 + dy = y2-y1 + x = (dx==0) ? 0.0 : (px-x1).to_f/dx + y = (dy==0) ? 0.0 : (py-y1).to_f/dy + return [x,y] +end + +def repositionY(x1,y1,x2,y2,tx,ty) + dx = x2-x1 + dy = y2-y1 + x = x1+tx*dx.to_f + y = y1+ty*dy.to_f + return [x,y] +end + +def transformPoint(x1,y1,x2,y2, # Source line + x3,y3,x4,y4, # Destination line + px,py) # Source point + ret = yaxisIntersect(x1,y1,x2,y2,px,py) + ret2 = repositionY(x3,y3,x4,y4,ret[0],ret[1]) + return ret2 +end + +def getSpriteCenter(sprite) + return [0,0] if !sprite || sprite.disposed? + return [sprite.x,sprite.y] if !sprite.bitmap || sprite.bitmap.disposed? + centerX = sprite.src_rect.width/2 + centerY = sprite.src_rect.height/2 + offsetX = (centerX-sprite.ox)*sprite.zoom_x + offsetY = (centerY-sprite.oy)*sprite.zoom_y + return [sprite.x+offsetX,sprite.y+offsetY] +end + +def isReversed(src0,src1,dst0,dst1) + return false if src0==src1 + return (dst0>dst1) if src0=self.frames.length + totalframes = frame+otherAnim.frames.length+1 + for i in self.frames.length...totalframes + self.frames.push(RPG::Animation::Frame.new) + end + end + self.frame_max = self.frames.length + for i in 0...otherAnim.frame_max + thisframe = self.frames[frame+i] + otherframe = otherAnim.frames[i] + cellStart = thisframe.cell_max + thisframe.cell_max += otherframe.cell_max + thisframe.cell_data.resize(thisframe.cell_max,8) + for j in 0...otherframe.cell_max + thisframe.cell_data[cellStart+j,0] = otherframe.cell_data[j,0] + thisframe.cell_data[cellStart+j,1] = otherframe.cell_data[j,1]+x + thisframe.cell_data[cellStart+j,2] = otherframe.cell_data[j,2]+y + thisframe.cell_data[cellStart+j,3] = otherframe.cell_data[j,3] + thisframe.cell_data[cellStart+j,4] = otherframe.cell_data[j,4] + thisframe.cell_data[cellStart+j,5] = otherframe.cell_data[j,5] + thisframe.cell_data[cellStart+j,6] = otherframe.cell_data[j,6] + thisframe.cell_data[cellStart+j,7] = otherframe.cell_data[j,7] + end + end + for i in 0...otherAnim.timings.length + timing = RPG::Animation::Timing.new + othertiming = otherAnim.timings[i] + timing.frame = frame+othertiming.frame + timing.se = RPG::AudioFile.new( + othertiming.se.name.clone, + othertiming.se.volume, + othertiming.se.pitch) + timing.flash_scope = othertiming.flash_scope + timing.flash_color = othertiming.flash_color.clone + timing.flash_duration = othertiming.flash_duration + timing.condition = othertiming.condition + self.timings.push(timing) + end + self.timings.sort! { |a,b| a.frame<=>b.frame } + end +end + + + +#=============================================================================== +# +#=============================================================================== +class PBAnimTiming + attr_accessor :frame + attr_accessor :timingType # 0=play SE, 1=set bg, 2=bg mod + attr_accessor :name # Name of SE file or BG file + attr_accessor :volume + attr_accessor :pitch + attr_accessor :bgX # x coordinate of bg (or to move bg to) + attr_accessor :bgY # y coordinate of bg (or to move bg to) + attr_accessor :opacity # Opacity of bg (or to change bg to) + attr_accessor :colorRed # Color of bg (or to change bg to) + attr_accessor :colorGreen # Color of bg (or to change bg to) + attr_accessor :colorBlue # Color of bg (or to change bg to) + attr_accessor :colorAlpha # Color of bg (or to change bg to) + attr_accessor :duration # How long to spend changing to the new bg coords/color + attr_accessor :flashScope + attr_accessor :flashColor + attr_accessor :flashDuration + + def initialize(type=0) + @frame = 0 + @timingType = type + @name = "" + @volume = 80 + @pitch = 100 + @bgX = nil + @bgY = nil + @opacity = nil + @colorRed = nil + @colorGreen = nil + @colorBlue = nil + @colorAlpha = nil + @duration = 5 + @flashScope = 0 + @flashColor = Color.new(255,255,255,255) + @flashDuration = 5 + end + + def timingType + @timingType = 0 if !@timingType + return @timingType + end + + def duration + @duration = 5 if !@duration + return @duration + end + + def to_s + case self.timingType + when 0 + return "[#{@frame+1}] Play SE: #{name} (volume #{@volume}, pitch #{@pitch})" + when 1 + text = sprintf("[%d] Set BG: \"%s\"",@frame+1,name) + text += sprintf(" (color=%s,%s,%s,%s)", + (@colorRed!=nil) ? @colorRed.to_i : "-", + (@colorGreen!=nil) ? @colorGreen.to_i : "-", + (@colorBlue!=nil) ? @colorBlue.to_i : "-", + (@colorAlpha!=nil) ? @colorAlpha.to_i : "-") + text += sprintf(" (opacity=%s)",@opacity.to_i) + text += sprintf(" (coords=%s,%s)", + (@bgX!=nil) ? @bgX : "-", + (@bgY!=nil) ? @bgY : "-") + return text + when 2 + text = sprintf("[%d] Change BG: @%d",@frame+1,duration) + if @colorRed!=nil || @colorGreen!=nil || @colorBlue!=nil || @colorAlpha!=nil + text += sprintf(" (color=%s,%s,%s,%s)", + (@colorRed!=nil) ? @colorRed.to_i : "-", + (@colorGreen!=nil) ? @colorGreen.to_i : "-", + (@colorBlue!=nil) ? @colorBlue.to_i : "-", + (@colorAlpha!=nil) ? @colorAlpha.to_i : "-") + end + text += sprintf(" (opacity=%s)",@opacity.to_i) if @opacity!=nil + if @bgX!=nil || @bgY!=nil + text += sprintf(" (coords=%s,%s)", + (@bgX!=nil) ? @bgX : "-", + (@bgY!=nil) ? @bgY : "-") + end + return text + when 3 + text = sprintf("[%d] Set FG: \"%s\"",@frame+1,name) + text += sprintf(" (color=%s,%s,%s,%s)", + (@colorRed!=nil) ? @colorRed.to_i : "-", + (@colorGreen!=nil) ? @colorGreen.to_i : "-", + (@colorBlue!=nil) ? @colorBlue.to_i : "-", + (@colorAlpha!=nil) ? @colorAlpha.to_i : "-") + text += sprintf(" (opacity=%s)",@opacity.to_i) + text += sprintf(" (coords=%s,%s)", + (@bgX!=nil) ? @bgX : "-", + (@bgY!=nil) ? @bgY : "-") + return text + when 4 + text = sprintf("[%d] Change FG: @%d",@frame+1,duration) + if @colorRed!=nil || @colorGreen!=nil || @colorBlue!=nil || @colorAlpha!=nil + text += sprintf(" (color=%s,%s,%s,%s)", + (@colorRed!=nil) ? @colorRed.to_i : "-", + (@colorGreen!=nil) ? @colorGreen.to_i : "-", + (@colorBlue!=nil) ? @colorBlue.to_i : "-", + (@colorAlpha!=nil) ? @colorAlpha.to_i : "-") + end + text += sprintf(" (opacity=%s)",@opacity.to_i) if @opacity!=nil + if @bgX!=nil || @bgY!=nil + text += sprintf(" (coords=%s,%s)", + (@bgX!=nil) ? @bgX : "-", + (@bgY!=nil) ? @bgY : "-") + end + return text + end + return "" + end +end + + + +#=============================================================================== +# +#=============================================================================== +class PBAnimations < Array + include Enumerable + attr_reader :array + attr_accessor :selected + + def initialize(size=1) + @array = [] + @selected = 0 + size = 1 if size<1 # Always create at least one animation + size.times do + @array.push(PBAnimation.new) + end + end + + def length + return @array.length + end + + def each + @array.each { |i| yield i } + end + + def [](i) + return @array[i] + end + + def []=(i,value) + @array[i] = value + end + + def compact + @array.compact! + end + + def insert(index,val) + @array.insert(index,val) + end + + def delete_at(index) + @array.delete_at(index) + end + + def resize(len) + idxStart = @array.length + idxEnd = len + if idxStart>idxEnd + for i in idxEnd...idxStart + @array.pop + end + else + for i in idxStart...idxEnd + @array.push(PBAnimation.new) + end + end + self.selected = len if self.selected>=len + end +end + + + +#=============================================================================== +# +#=============================================================================== +class PBAnimation < Array + include Enumerable + attr_accessor :id + attr_accessor :name + attr_accessor :graphic + attr_accessor :hue + attr_accessor :position + attr_writer :speed + attr_reader :array + attr_reader :timing + MAX_SPRITES = 60 + + def speed + return @speed || 20 + end + + def initialize(size=1) + @id = -1 + @name = "" + @graphic = "" + @hue = 0 + @position = 4 # 1=target, 2=user, 3=user and target, 4=screen + @array = [] + size = 1 if size<1 # Always create at least one frame + size.times do; addFrame; end + @timing = [] + @scope = 0 + end + + def length + return @array.length + end + + def each + @array.each { |i| yield i } + end + + def [](i) + return @array[i] + end + + def []=(i,value) + @array[i] = value + end + + def insert(*arg) + return @array.insert(*arg) + end + + def delete_at(*arg) + return @array.delete_at(*arg) + end + + def resize(len) + if len<@array.length + @array[len,@array.length-len] = [] + elsif len>@array.length + (len-@array.length).times do + addFrame + end + end + end + + def addFrame + pos = @array.length + @array[pos] = [] + # Move's user + @array[pos][0] = pbCreateCel( + PokeBattle_SceneConstants::FOCUSUSER_X, + PokeBattle_SceneConstants::FOCUSUSER_Y,-1) + @array[pos][0][AnimFrame::FOCUS] = 2 + @array[pos][0][AnimFrame::LOCKED] = 1 + # Move's target + @array[pos][1] = pbCreateCel( + PokeBattle_SceneConstants::FOCUSTARGET_X, + PokeBattle_SceneConstants::FOCUSTARGET_Y,-2) + @array[pos][1][AnimFrame::FOCUS] = 1 + @array[pos][1][AnimFrame::LOCKED] = 1 + return @array[pos] + end + + def playTiming(frame,bgGraphic,bgColor,foGraphic,foColor,oldbg=[],oldfo=[],user=nil) + for i in @timing + next if i.frame!=frame + case i.timingType + when 0 # Play SE + if i.name && i.name!="" + pbSEPlay("Anim/"+i.name,i.volume,i.pitch) + else + poke = (user && user.pokemon) ? user.pokemon : 1 + name = (pbCryFile(poke) rescue nil) + pbSEPlay(name,i.volume,i.pitch) if name + end +# if sprite +# sprite.flash(i.flashColor,i.flashDuration*2) if i.flashScope==1 +# sprite.flash(nil,i.flashDuration*2) if i.flashScope==3 +# end + when 1 # Set background graphic (immediate) + if i.name && i.name!="" + bgGraphic.setBitmap("Graphics/Animations/"+i.name) + bgGraphic.ox = -i.bgX || 0 + bgGraphic.oy = -i.bgY || 0 + bgGraphic.color = Color.new(i.colorRed || 0,i.colorGreen || 0,i.colorBlue || 0,i.colorAlpha || 0) + bgGraphic.opacity = i.opacity || 0 + bgColor.opacity = 0 + else + bgGraphic.setBitmap(nil) + bgGraphic.opacity = 0 + bgColor.color = Color.new(i.colorRed || 0,i.colorGreen || 0,i.colorBlue || 0,i.colorAlpha || 0) + bgColor.opacity = i.opacity || 0 + end + when 2 # Move/recolour background graphic + if bgGraphic.bitmap!=nil + oldbg[0] = bgGraphic.ox || 0 + oldbg[1] = bgGraphic.oy || 0 + oldbg[2] = bgGraphic.opacity || 0 + oldbg[3] = bgGraphic.color.clone || Color.new(0,0,0,0) + else + oldbg[0] = 0 + oldbg[1] = 0 + oldbg[2] = bgColor.opacity || 0 + oldbg[3] = bgColor.color.clone || Color.new(0,0,0,0) + end + when 3 # Set foreground graphic (immediate) + if i.name && i.name!="" + foGraphic.setBitmap("Graphics/Animations/"+i.name) + foGraphic.ox = -i.bgX || 0 + foGraphic.oy = -i.bgY || 0 + foGraphic.color = Color.new(i.colorRed || 0,i.colorGreen || 0,i.colorBlue || 0,i.colorAlpha || 0) + foGraphic.opacity = i.opacity || 0 + foColor.opacity = 0 + else + foGraphic.setBitmap(nil) + foGraphic.opacity = 0 + foColor.color = Color.new(i.colorRed || 0,i.colorGreen || 0,i.colorBlue || 0,i.colorAlpha || 0) + foColor.opacity = i.opacity || 0 + end + when 4 # Move/recolour foreground graphic + if foGraphic.bitmap!=nil + oldfo[0] = foGraphic.ox || 0 + oldfo[1] = foGraphic.oy || 0 + oldfo[2] = foGraphic.opacity || 0 + oldfo[3] = foGraphic.color.clone || Color.new(0,0,0,0) + else + oldfo[0] = 0 + oldfo[1] = 0 + oldfo[2] = foColor.opacity || 0 + oldfo[3] = foColor.color.clone || Color.new(0,0,0,0) + end + end + end + for i in @timing + case i.timingType + when 2 + next if !i.duration || i.duration<=0 + next if framei.frame+i.duration + fraction = (frame-i.frame).to_f/i.duration + if bgGraphic.bitmap!=nil + bgGraphic.ox = oldbg[0]-(i.bgX-oldbg[0])*fraction if i.bgX!=nil + bgGraphic.oy = oldbg[1]-(i.bgY-oldbg[1])*fraction if i.bgY!=nil + bgGraphic.opacity = oldbg[2]+(i.opacity-oldbg[2])*fraction if i.opacity!=nil + cr = (i.colorRed!=nil) ? oldbg[3].red+(i.colorRed-oldbg[3].red)*fraction : oldbg[3].red + cg = (i.colorGreen!=nil) ? oldbg[3].green+(i.colorGreen-oldbg[3].green)*fraction : oldbg[3].green + cb = (i.colorBlue!=nil) ? oldbg[3].blue+(i.colorBlue-oldbg[3].blue)*fraction : oldbg[3].blue + ca = (i.colorAlpha!=nil) ? oldbg[3].alpha+(i.colorAlpha-oldbg[3].alpha)*fraction : oldbg[3].alpha + bgGraphic.color = Color.new(cr,cg,cb,ca) + else + bgColor.opacity = oldbg[2]+(i.opacity-oldbg[2])*fraction if i.opacity!=nil + cr = (i.colorRed!=nil) ? oldbg[3].red+(i.colorRed-oldbg[3].red)*fraction : oldbg[3].red + cg = (i.colorGreen!=nil) ? oldbg[3].green+(i.colorGreen-oldbg[3].green)*fraction : oldbg[3].green + cb = (i.colorBlue!=nil) ? oldbg[3].blue+(i.colorBlue-oldbg[3].blue)*fraction : oldbg[3].blue + ca = (i.colorAlpha!=nil) ? oldbg[3].alpha+(i.colorAlpha-oldbg[3].alpha)*fraction : oldbg[3].alpha + bgColor.color = Color.new(cr,cg,cb,ca) + end + when 4 + next if !i.duration || i.duration<=0 + next if framei.frame+i.duration + fraction = (frame-i.frame).to_f/i.duration + if foGraphic.bitmap!=nil + foGraphic.ox = oldfo[0]-(i.bgX-oldfo[0])*fraction if i.bgX!=nil + foGraphic.oy = oldfo[1]-(i.bgY-oldfo[1])*fraction if i.bgY!=nil + foGraphic.opacity = oldfo[2]+(i.opacity-oldfo[2])*fraction if i.opacity!=nil + cr = (i.colorRed!=nil) ? oldfo[3].red+(i.colorRed-oldfo[3].red)*fraction : oldfo[3].red + cg = (i.colorGreen!=nil) ? oldfo[3].green+(i.colorGreen-oldfo[3].green)*fraction : oldfo[3].green + cb = (i.colorBlue!=nil) ? oldfo[3].blue+(i.colorBlue-oldfo[3].blue)*fraction : oldfo[3].blue + ca = (i.colorAlpha!=nil) ? oldfo[3].alpha+(i.colorAlpha-oldfo[3].alpha)*fraction : oldfo[3].alpha + foGraphic.color = Color.new(cr,cg,cb,ca) + else + foColor.opacity = oldfo[2]+(i.opacity-oldfo[2])*fraction if i.opacity!=nil + cr = (i.colorRed!=nil) ? oldfo[3].red+(i.colorRed-oldfo[3].red)*fraction : oldfo[3].red + cg = (i.colorGreen!=nil) ? oldfo[3].green+(i.colorGreen-oldfo[3].green)*fraction : oldfo[3].green + cb = (i.colorBlue!=nil) ? oldfo[3].blue+(i.colorBlue-oldfo[3].blue)*fraction : oldfo[3].blue + ca = (i.colorAlpha!=nil) ? oldfo[3].alpha+(i.colorAlpha-oldfo[3].alpha)*fraction : oldfo[3].alpha + foColor.color = Color.new(cr,cg,cb,ca) + end + end + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +def pbSpriteSetAnimFrame(sprite,frame,user=nil,target=nil,inEditor=false) + return if !sprite + if !frame + sprite.visible = false + sprite.src_rect = Rect.new(0,0,1,1) + return + end + sprite.blend_type = frame[AnimFrame::BLENDTYPE] + sprite.angle = frame[AnimFrame::ANGLE] + sprite.mirror = (frame[AnimFrame::MIRROR]>0) + sprite.opacity = frame[AnimFrame::OPACITY] + sprite.visible = true + if !frame[AnimFrame::VISIBLE]==1 && inEditor + sprite.opacity /= 2 + else + sprite.visible = (frame[AnimFrame::VISIBLE]==1) + end + pattern = frame[AnimFrame::PATTERN] + if pattern>=0 + animwidth = 192 + sprite.src_rect.set((pattern%5)*animwidth,(pattern/5)*animwidth, + animwidth,animwidth) + else + sprite.src_rect.set(0,0, + (sprite.bitmap) ? sprite.bitmap.width : 128, + (sprite.bitmap) ? sprite.bitmap.height : 128) + end + sprite.zoom_x = frame[AnimFrame::ZOOMX]/100.0 + sprite.zoom_y = frame[AnimFrame::ZOOMY]/100.0 + sprite.color.set( + frame[AnimFrame::COLORRED], + frame[AnimFrame::COLORGREEN], + frame[AnimFrame::COLORBLUE], + frame[AnimFrame::COLORALPHA] + ) + sprite.tone.set( + frame[AnimFrame::TONERED], + frame[AnimFrame::TONEGREEN], + frame[AnimFrame::TONEBLUE], + frame[AnimFrame::TONEGRAY] + ) + sprite.ox = sprite.src_rect.width/2 + sprite.oy = sprite.src_rect.height/2 + sprite.x = frame[AnimFrame::X] + sprite.y = frame[AnimFrame::Y] + if sprite!=user && sprite!=target + case frame[AnimFrame::PRIORITY] + when 0 # Behind everything + sprite.z = 10 + when 1 # In front of everything + sprite.z = 80 + when 2 # Just behind focus + case frame[AnimFrame::FOCUS] + when 1 # Focused on target + sprite.z = (target) ? target.z-1 : 20 + when 2 # Focused on user + sprite.z = (user) ? user.z-1 : 20 + else # Focused on user and target, or screen + sprite.z = 20 + end + when 3 # Just in front of focus + case frame[AnimFrame::FOCUS] + when 1 # Focused on target + sprite.z = (target) ? target.z+1 : 80 + when 2 # Focused on user + sprite.z = (user) ? user.z+1 : 80 + else # Focused on user and target, or screen + sprite.z = 80 + end + else + sprite.z = 80 + end + end +end + + + +#=============================================================================== +# Animation player +#=============================================================================== +class PBAnimationPlayerX + attr_accessor :looping + MAX_SPRITES = 60 + + def initialize(animation,user,target,scene=nil,oppMove=false,inEditor=false) + @animation = animation + @user = (oppMove) ? target : user # Just used for playing user's cry + @usersprite = (user) ? scene.sprites["pokemon_#{user.index}"] : nil + @targetsprite = (target) ? scene.sprites["pokemon_#{target.index}"] : nil + @userbitmap = (@usersprite && @usersprite.bitmap) ? @usersprite.bitmap : nil # not to be disposed + @targetbitmap = (@targetsprite && @targetsprite.bitmap) ? @targetsprite.bitmap : nil # not to be disposed + @scene = scene + @viewport = (scene) ? scene.viewport : nil + @inEditor = inEditor + @looping = false + @animbitmap = nil # Animation sheet graphic + @frame = -1 + @framesPerTick = [Graphics.frame_rate/20,1].max # 20 ticks per second + @srcLine = nil + @dstLine = nil + @userOrig = getSpriteCenter(@usersprite) + @targetOrig = getSpriteCenter(@targetsprite) + @oldbg = [] + @oldfo = [] + initializeSprites + end + + def initializeSprites + # Create animation sprites (0=user's sprite, 1=target's sprite) + @animsprites = [] + @animsprites[0] = @usersprite + @animsprites[1] = @targetsprite + for i in 2...MAX_SPRITES + @animsprites[i] = Sprite.new(@viewport) + @animsprites[i].bitmap = nil + @animsprites[i].visible = false + end + # Create background colour sprite + @bgColor = ColoredPlane.new(Color.new(0,0,0),@viewport) + @bgColor.borderX = 64 if @inEditor + @bgColor.borderY = 64 if @inEditor + @bgColor.z = 5 + @bgColor.opacity = 0 + @bgColor.refresh + # Create background graphic sprite + @bgGraphic = AnimatedPlane.new(@viewport) + @bgGraphic.setBitmap(nil) + @bgGraphic.borderX = 64 if @inEditor + @bgGraphic.borderY = 64 if @inEditor + @bgGraphic.z = 5 + @bgGraphic.opacity = 0 + @bgGraphic.refresh + # Create foreground colour sprite + @foColor = ColoredPlane.new(Color.new(0,0,0),@viewport) + @foColor.borderX = 64 if @inEditor + @foColor.borderY = 64 if @inEditor + @foColor.z = 85 + @foColor.opacity = 0 + @foColor.refresh + # Create foreground graphic sprite + @foGraphic = AnimatedPlane.new(@viewport) + @foGraphic.setBitmap(nil) + @foGraphic.borderX = 64 if @inEditor + @foGraphic.borderY = 64 if @inEditor + @foGraphic.z = 85 + @foGraphic.opacity = 0 + @foGraphic.refresh + end + + def dispose + @animbitmap.dispose if @animbitmap + for i in 2...MAX_SPRITES + @animsprites[i].dispose if @animsprites[i] + end + @bgGraphic.dispose + @bgColor.dispose + @foGraphic.dispose + @foColor.dispose + end + + def start + @frame = 0 + end + + def animDone? + return @frame<0 + end + + def setLineTransform(x1,y1,x2,y2,x3,y3,x4,y4) + @srcLine = [x1,y1,x2,y2] + @dstLine = [x3,y3,x4,y4] + end + + def update + return if @frame<0 + animFrame = @frame/@framesPerTick + + # Loop or end the animation if the animation has reached the end + if animFrame >= @animation.length + @frame = (@looping) ? 0 : -1 + if @frame<0 + @animbitmap.dispose if @animbitmap + @animbitmap = nil + return + end + end + # Load the animation's spritesheet and assign it to all the sprites. + if !@animbitmap || @animbitmap.disposed? + @animbitmap = AnimatedBitmap.new("Graphics/Animations/"+@animation.graphic, + @animation.hue).deanimate + for i in 0...MAX_SPRITES + @animsprites[i].bitmap = @animbitmap if @animsprites[i] + end + end + # Update background and foreground graphics + @bgGraphic.update + @bgColor.update + @foGraphic.update + @foColor.update + + # Update all the sprites to depict the animation's next frame + if @framesPerTick==1 || (@frame%@framesPerTick)==0 + thisframe = @animation[animFrame] + # Make all cel sprites invisible + for i in 0...MAX_SPRITES + @animsprites[i].visible = false if @animsprites[i] + end + # Set each cel sprite acoordingly + for i in 0...thisframe.length + cel = thisframe[i] + next if !cel + sprite = @animsprites[i] + next if !sprite + # Set cel sprite's graphic + case cel[AnimFrame::PATTERN] + when -1 + sprite.bitmap = @userbitmap + when -2 + sprite.bitmap = @targetbitmap + else + sprite.bitmap = @animbitmap + end + # Apply settings to the cel sprite + pbSpriteSetAnimFrame(sprite,cel,@usersprite,@targetsprite) + case cel[AnimFrame::FOCUS] + when 1 # Focused on target + sprite.x = cel[AnimFrame::X]+@targetOrig[0]-PokeBattle_SceneConstants::FOCUSTARGET_X + sprite.y = cel[AnimFrame::Y]+@targetOrig[1]-PokeBattle_SceneConstants::FOCUSTARGET_Y + when 2 # Focused on user + sprite.x = cel[AnimFrame::X]+@userOrig[0]-PokeBattle_SceneConstants::FOCUSUSER_X + sprite.y = cel[AnimFrame::Y]+@userOrig[1]-PokeBattle_SceneConstants::FOCUSUSER_Y + when 3 # Focused on user and target + next if !@srcLine || !@dstLine + point = transformPoint( + @srcLine[0],@srcLine[1],@srcLine[2],@srcLine[3], + @dstLine[0],@dstLine[1],@dstLine[2],@dstLine[3], + sprite.x,sprite.y) + sprite.x = point[0] + sprite.y = point[1] + if isReversed(@srcLine[0],@srcLine[2],@dstLine[0],@dstLine[2]) && + cel[AnimFrame::PATTERN]>=0 + # Reverse direction + sprite.mirror = !sprite.mirror + end + end + sprite.x += 64 if @inEditor + sprite.y += 64 if @inEditor + end + # Play timings + @animation.playTiming(animFrame,@bgGraphic,@bgColor,@foGraphic,@foColor,@oldbg,@oldfo,@user) + end + @frame += 1 + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/006_Other battle types/002_PokeBattle_SafariZone.rb b/Data/Scripts/011_Battle/006_Other battle types/002_PokeBattle_SafariZone.rb new file mode 100644 index 000000000..072e14591 --- /dev/null +++ b/Data/Scripts/011_Battle/006_Other battle types/002_PokeBattle_SafariZone.rb @@ -0,0 +1,494 @@ +#=============================================================================== +# Simple battler class for the wild Pokémon in a Safari Zone battle +#=============================================================================== +class PokeBattle_FakeBattler + attr_reader :battle + attr_reader :index + attr_reader :pokemon + attr_reader :owned + + def initialize(battle,index) + @battle = battle + @pokemon = battle.party2[0] + @index = index + end + + def pokemonIndex; return 0; end + def species; return @pokemon.species; end + def gender; return @pokemon.gender; end + def status; return @pokemon.status; end + def hp; return @pokemon.hp; end + def level; return @pokemon.level; end + def name; return @pokemon.name; end + def totalhp; return @pokemon.totalhp; end + def displayGender; return @pokemon.gender; end + def shiny?; return @pokemon.shiny?; end + alias isShiny? shiny? + + def fainted?; return false; end + alias isFainted? fainted? + def shadowPokemon?; return false; end + alias isShadow? shadowPokemon? + def hasMega?; return false; end + def mega?; return false; end + alias isMega? mega? + def hasPrimal?; return false; end + def primal?; return false; end + alias isPrimal? primal? + def captured; return false; end + def captured=(value); end + + def owned? + return $Trainer.owned[pokemon.species] + end + + def pbThis(lowerCase=false) + return (lowerCase) ? _INTL("the wild {1}",name) : _INTL("The wild {1}",name) + end + + def opposes?(i) + i = i.index if i.is_a?(PokeBattle_FakeBattler) + return (@index&1)!=(i&1) + end + + def pbReset; end +end + + + +#=============================================================================== +# Data box for safari battles +#=============================================================================== +class SafariDataBox < SpriteWrapper + attr_accessor :selected + + def initialize(battle,viewport=nil) + super(viewport) + @selected = 0 + @battle = battle + @databox = AnimatedBitmap.new("Graphics/Pictures/Battle/databox_safari") + self.x = Graphics.width - 232 + self.y = Graphics.height - 184 + @contents = BitmapWrapper.new(@databox.width,@databox.height) + self.bitmap = @contents + self.visible = false + self.z = 50 + pbSetSystemFont(self.bitmap) + refresh + end + + def refresh + self.bitmap.clear + self.bitmap.blt(0,0,@databox.bitmap,Rect.new(0,0,@databox.width,@databox.height)) + base = Color.new(72,72,72) + shadow = Color.new(184,184,184) + textpos = [] + textpos.push([_INTL("Safari Balls"),30,8,false,base,shadow]) + textpos.push([_INTL("Left: {1}",@battle.ballCount),30,38,false,base,shadow]) + pbDrawTextPositions(self.bitmap,textpos) + end + + def update(frameCounter=0) + super() + end +end + + + +#=============================================================================== +# Shows the player throwing bait at a wild Pokémon in a Safari battle. +#=============================================================================== +class ThrowBaitAnimation < PokeBattle_Animation + include PokeBattle_BallAnimationMixin + + def initialize(sprites,viewport,battler) + @battler = battler + @trainer = battler.battle.pbGetOwnerFromBattlerIndex(battler.index) + super(sprites,viewport) + end + + def createProcesses + # Calculate start and end coordinates for battler sprite movement + batSprite = @sprites["pokemon_#{@battler.index}"] + shaSprite = @sprites["shadow_#{@battler.index}"] + traSprite = @sprites["player_1"] + ballPos = PokeBattle_SceneConstants.pbBattlerPosition(@battler.index,batSprite.sideSize) + ballStartX = traSprite.x + ballStartY = traSprite.y-traSprite.bitmap.height/2 + ballMidX = 0 # Unused in arc calculation + ballMidY = 122 + ballEndX = ballPos[0]-40 + ballEndY = ballPos[1]-4 + # Set up trainer sprite + trainer = addSprite(traSprite,PictureOrigin::Bottom) + # Set up bait sprite + ball = addNewSprite(ballStartX,ballStartY, + "Graphics/Battle animations/safari_bait",PictureOrigin::Center) + ball.setZ(0,batSprite.z+1) + # Trainer animation + if traSprite.bitmap.width>=traSprite.bitmap.height*2 + ballStartX, ballStartY = trainerThrowingFrames(ball,trainer,traSprite) + end + delay = ball.totalDuration # 0 or 7 + # Bait arc animation + ball.setSE(delay,"Battle throw") + createBallTrajectory(ball,delay,12, + ballStartX,ballStartY,ballMidX,ballMidY,ballEndX,ballEndY) + ball.setZ(9,batSprite.z+1) + delay = ball.totalDuration + ball.moveOpacity(delay+8,2,0) + ball.setVisible(delay+10,false) + # Set up battler sprite + battler = addSprite(batSprite,PictureOrigin::Bottom) + # Show Pokémon jumping before eating the bait + delay = ball.totalDuration+3 + 2.times do + battler.setSE(delay,"player jump") + battler.moveDelta(delay,3,0,-16) + battler.moveDelta(delay+4,3,0,16) + delay = battler.totalDuration+1 + end + # Show Pokémon eating the bait + delay = battler.totalDuration+3 + 2.times do + battler.moveAngle(delay,7,5) + battler.moveDelta(delay,7,0,6) + battler.moveAngle(delay+7,7,0) + battler.moveDelta(delay+7,7,0,-6) + delay = battler.totalDuration + end + end +end + + + +#=============================================================================== +# Shows the player throwing a rock at a wild Pokémon in a Safari battle. +#=============================================================================== +class ThrowRockAnimation < PokeBattle_Animation + include PokeBattle_BallAnimationMixin + + def initialize(sprites,viewport,battler) + @battler = battler + @trainer = battler.battle.pbGetOwnerFromBattlerIndex(battler.index) + super(sprites,viewport) + end + + def createProcesses + # Calculate start and end coordinates for battler sprite movement + batSprite = @sprites["pokemon_#{@battler.index}"] + shaSprite = @sprites["shadow_#{@battler.index}"] + traSprite = @sprites["player_1"] + ballStartX = traSprite.x + ballStartY = traSprite.y-traSprite.bitmap.height/2 + ballMidX = 0 # Unused in arc calculation + ballMidY = 122 + ballEndX = batSprite.x + ballEndY = batSprite.y-batSprite.bitmap.height/2 + # Set up trainer sprite + trainer = addSprite(traSprite,PictureOrigin::Bottom) + # Set up bait sprite + ball = addNewSprite(ballStartX,ballStartY, + "Graphics/Battle animations/safari_rock",PictureOrigin::Center) + ball.setZ(0,batSprite.z+1) + # Trainer animation + if traSprite.bitmap.width>=traSprite.bitmap.height*2 + ballStartX, ballStartY = trainerThrowingFrames(ball,trainer,traSprite) + end + delay = ball.totalDuration # 0 or 7 + # Bait arc animation + ball.setSE(delay,"Battle throw") + createBallTrajectory(ball,delay,12, + ballStartX,ballStartY,ballMidX,ballMidY,ballEndX,ballEndY) + ball.setZ(9,batSprite.z+1) + delay = ball.totalDuration + ball.setSE(delay,"Battle damage weak") + ball.moveOpacity(delay+2,2,0) + ball.setVisible(delay+4,false) + # Set up anger sprite + anger = addNewSprite(ballEndX-42,ballEndY-36, + "Graphics/Battle animations/safari_anger",PictureOrigin::Center) + anger.setVisible(0,false) + anger.setZ(0,batSprite.z+1) + # Show anger appearing + delay = ball.totalDuration+5 + 2.times do + anger.setSE(delay,"Player jump") + anger.setVisible(delay,true) + anger.moveZoom(delay,3,130) + anger.moveZoom(delay+3,3,100) + anger.setVisible(delay+6,false) + anger.setDelta(delay+6,96,-16) + delay = anger.totalDuration+3 + end + end +end + + + +#=============================================================================== +# Safari Zone battle scene (the visuals of the battle) +#=============================================================================== +class PokeBattle_Scene + def pbSafariStart + @briefMessage = false + @sprites["dataBox_0"] = SafariDataBox.new(@battle,@viewport) + dataBoxAnim = DataBoxAppearAnimation.new(@sprites,@viewport,0) + loop do + dataBoxAnim.update + pbUpdate + break if dataBoxAnim.animDone? + end + dataBoxAnim.dispose + pbRefresh + end + + def pbSafariCommandMenu(index) + pbCommandMenuEx(index,[ + _INTL("What will\n{1} throw?",@battle.pbPlayer.name), + _INTL("Ball"), + _INTL("Bait"), + _INTL("Rock"), + _INTL("Run") + ],3) + end + + def pbThrowBait + @briefMessage = false + baitAnim = ThrowBaitAnimation.new(@sprites,@viewport,@battle.battlers[1]) + loop do + baitAnim.update + pbUpdate + break if baitAnim.animDone? + end + baitAnim.dispose + end + + def pbThrowRock + @briefMessage = false + rockAnim = ThrowRockAnimation.new(@sprites,@viewport,@battle.battlers[1]) + loop do + rockAnim.update + pbUpdate + break if rockAnim.animDone? + end + rockAnim.dispose + end + + alias __safari__pbThrowSuccess pbThrowSuccess + def pbThrowSuccess + __safari__pbThrowSuccess + pbWildBattleSuccess if @battle.is_a?(PokeBattle_SafariZone) + end +end + + + +#=============================================================================== +# Safari Zone battle class +#=============================================================================== +class PokeBattle_SafariZone + attr_reader :battlers # Array of fake battler objects + attr_accessor :sideSizes # Array of number of battlers per side + attr_accessor :backdrop # Filename fragment used for background graphics + attr_accessor :backdropBase # Filename fragment used for base graphics + attr_accessor :time # Time of day (0=day, 1=eve, 2=night) + attr_accessor :environment # Battle surroundings (for mechanics purposes) + attr_reader :weather + attr_reader :player + attr_accessor :party2 + attr_accessor :canRun # True if player can run from battle + attr_accessor :canLose # True if player won't black out if they lose + attr_accessor :switchStyle # Switch/Set "battle style" option + attr_accessor :showAnims # "Battle scene" option (show anims) + attr_accessor :expGain # Whether Pokémon can gain Exp/EVs + attr_accessor :moneyGain # Whether the player can gain/lose money + attr_accessor :rules + attr_accessor :ballCount + + include PokeBattle_BattleCommon + + def pbRandom(x); return rand(x); end + + #============================================================================= + # Initialize the battle class + #============================================================================= + def initialize(scene,player,party2) + @scene = scene + @peer = PokeBattle_BattlePeer.create() + @backdrop = "" + @backdropBase = nil + @time = 0 + @environment = PBEnvironment::None # e.g. Tall grass, cave, still water + @weather = PBWeather::None + @decision = 0 + @caughtPokemon = [] + @player = [player] + @party2 = party2 + @sideSizes = [1,1] + @battlers = [ + PokeBattle_FakeBattler.new(self,0), + PokeBattle_FakeBattler.new(self,1) + ] + @rules = {} + @ballCount = 0 + end + + def defaultWeather=(value); @weather = value; end + def defaultTerrain=(value); end + + #============================================================================= + # Information about the type and size of the battle + #============================================================================= + def wildBattle?; return true; end + def trainerBattle?; return false; end + + def setBattleMode(mode); end + + def pbSideSize(index) + return @sideSizes[index%2] + end + + #============================================================================= + # Trainers and owner-related + #============================================================================= + def pbPlayer; return @player[0]; end + def opponent; return nil; end + + def pbGetOwnerFromBattlerIndex(idxBattler); return pbPlayer; end + + #============================================================================= + # Get party info (counts all teams on the same side) + #============================================================================= + def pbParty(idxBattler) + return (opposes?(idxBattler)) ? @party2 : nil + end + + def pbAllFainted?(idxBattler=0); return false; end + + #============================================================================= + # Battler-related + #============================================================================= + def opposes?(idxBattler1,idxBattler2=0) + idxBattler1 = idxBattler1.index if idxBattler1.respond_to?("index") + idxBattler2 = idxBattler2.index if idxBattler2.respond_to?("index") + return (idxBattler1&1)!=(idxBattler2&1) + end + + def pbRemoveFromParty(idxBattler,idxParty); end + def pbGainExp; end + + #============================================================================= + # Messages and animations + #============================================================================= + def pbDisplay(msg,&block) + @scene.pbDisplayMessage(msg,&block) + end + + def pbDisplayPaused(msg,&block) + @scene.pbDisplayPausedMessage(msg,&block) + end + + def pbDisplayBrief(msg) + @scene.pbDisplayMessage(msg,true) + end + + def pbDisplayConfirm(msg) + return @scene.pbDisplayConfirmMessage(msg) + end + + + + class BattleAbortedException < Exception; end + + def pbAbort + raise BattleAbortedException.new("Battle aborted") + end + + #============================================================================= + # Safari battle-specific methods + #============================================================================= + def pbEscapeRate(rareness) + return 125 if rareness<=45 # Escape factor 9 (45%) + return 100 if rareness<=60 # Escape factor 7 (35%) + return 75 if rareness<=120 # Escape factor 5 (25%) + return 50 if rareness<=250 # Escape factor 3 (15%) + return 25 # Escape factor 2 (10%) + end + + def pbStartBattle + begin + wildpoke = @party2[0] + self.pbPlayer.seen[wildpoke.species] = true + pbSeenForm(wildpoke) + @scene.pbStartBattle(self) + pbDisplayPaused(_INTL("Wild {1} appeared!",wildpoke.name)) + @scene.pbSafariStart + @scene.pbCommonAnimation(PBWeather.animationName(@weather)) + safariBall = getConst(PBItems,:SAFARIBALL) + rareness = pbGetSpeciesData(wildpoke.species,wildpoke.form,SpeciesRareness) + catchFactor = (rareness*100)/1275 + catchFactor = [[catchFactor,3].max,20].min + escapeFactor = (pbEscapeRate(rareness)*100)/1275 + escapeFactor = [[escapeFactor,2].max,20].min + begin + cmd = @scene.pbSafariCommandMenu(0) + case cmd + when 0 # Ball + if pbBoxesFull? + pbDisplay(_INTL("The boxes are full! You can't catch any more Pokémon!")) + next + end + @ballCount -= 1 + @scene.pbRefresh + rare = (catchFactor*1275)/100 + if safariBall + pbThrowPokeBall(1,safariBall,rare,true) + if @caughtPokemon.length>0 + pbRecordAndStoreCaughtPokemon + @decision = 4 + end + end + when 1 # Bait + pbDisplayBrief(_INTL("{1} threw some bait at the {2}!",self.pbPlayer.name,wildpoke.name)) + @scene.pbThrowBait + catchFactor /= 2 if pbRandom(100)<90 # Harder to catch + escapeFactor /= 2 # Less likely to escape + when 2 # Rock + pbDisplayBrief(_INTL("{1} threw a rock at the {2}!",self.pbPlayer.name,wildpoke.name)) + @scene.pbThrowRock + catchFactor *= 2 # Easier to catch + escapeFactor *= 2 if pbRandom(100)<90 # More likely to escape + when 3 # Run + pbDisplayPaused(_INTL("You got away safely!")) { pbSEPlay("Battle flee") } + @decision = 3 + end + catchFactor = [[catchFactor,3].max,20].min + escapeFactor = [[escapeFactor,2].max,20].min + # End of round + if @decision==0 + if @ballCount<=0 + pbDisplay(_INTL("PA: You have no Safari Balls left! Game over!")) + @decision = 2 + elsif pbRandom(100)<5*escapeFactor + pbDisplay(_INTL("{1} fled!",wildpoke.name)) { pbSEPlay("Battle flee") } + @decision = 3 + elsif cmd==1 # Bait + pbDisplay(_INTL("{1} is eating!",wildpoke.name)) + elsif cmd==2 # Rock + pbDisplay(_INTL("{1} is angry!",wildpoke.name)) + else + pbDisplay(_INTL("{1} is watching carefully!",wildpoke.name)) + end + # Weather continues + @scene.pbCommonAnimation(PBWeather.animationName(@weather)) + end + end while @decision==0 + @scene.pbEndBattle(@decision) + rescue BattleAbortedException + @decision = 0 + @scene.pbEndBattle(@decision) + end + return @decision + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/006_Other battle types/003_PokeBattle_BugContest.rb b/Data/Scripts/011_Battle/006_Other battle types/003_PokeBattle_BugContest.rb new file mode 100644 index 000000000..cddc5f4a1 --- /dev/null +++ b/Data/Scripts/011_Battle/006_Other battle types/003_PokeBattle_BugContest.rb @@ -0,0 +1,87 @@ +#=============================================================================== +# Bug Catching Contest battle scene (the visuals of the battle) +#=============================================================================== +class PokeBattle_Scene + alias _bugContest_pbInitSprites pbInitSprites + + def pbInitSprites + _bugContest_pbInitSprites + # "helpwindow" shows the currently caught Pokémon's details when asking if + # you want to replace it with a newly caught Pokémon. + @sprites["helpwindow"] = Window_UnformattedTextPokemon.newWithSize("",0,0,32,32,@viewport) + @sprites["helpwindow"].z = 90 + @sprites["helpwindow"].visible = false + end + + def pbShowHelp(text) + @sprites["helpwindow"].resizeToFit(text,Graphics.width) + @sprites["helpwindow"].y = 0 + @sprites["helpwindow"].x = 0 + @sprites["helpwindow"].text = text + @sprites["helpwindow"].visible = true + end + + def pbHideHelp + @sprites["helpwindow"].visible = false + end +end + + + +#=============================================================================== +# Bug Catching Contest battle class +#=============================================================================== +class PokeBattle_BugContestBattle < PokeBattle_Battle + attr_accessor :ballCount + + def initialize(*arg) + @ballCount = 0 + @ballConst = getConst(PBItems,:SPORTBALL) || -1 + super(*arg) + end + + def pbItemMenu(idxBattler,firstAction) + return pbRegisterItem(idxBattler,@ballConst,1) + end + + def pbCommandMenu(idxBattler,firstAction) + return @scene.pbCommandMenuEx(idxBattler,[ + _INTL("Sport Balls: {1}",@ballCount), + _INTL("Fight"), + _INTL("Ball"), + _INTL("Pokémon"), + _INTL("Run") + ],4) + end + + def pbConsumeItemInBag(item,idxBattler) + @ballCount -= 1 if @ballCount>0 + end + + def pbStorePokemon(pkmn) + if pbBugContestState.lastPokemon + lastPokemon = pbBugContestState.lastPokemon + pbDisplayPaused(_INTL("You already caught a {1}.",lastPokemon.name)) + helptext = _INTL("STOCK POKéMON:\n {1} Lv.{2} MaxHP: {3}\nTHIS POKéMON:\n {4} Lv.{5} MaxHP: {6}", + lastPokemon.name,lastPokemon.level,lastPokemon.totalhp, + pkmn.name,pkmn.level,pkmn.totalhp + ) + @scene.pbShowHelp(helptext) + if pbDisplayConfirm(_INTL("Switch Pokémon?")) + pbBugContestState.lastPokemon = pkmn + @scene.pbHideHelp + else + @scene.pbHideHelp + return + end + else + pbBugContestState.lastPokemon = pkmn + end + pbDisplay(_INTL("Caught {1}!",pkmn.name)) + end + + def pbEndOfRoundPhase + super + @decision = 3 if @ballCount<=0 && @decision==0 + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/006_Other battle types/004_PokeBattle_BattlePalace.rb b/Data/Scripts/011_Battle/006_Other battle types/004_PokeBattle_BattlePalace.rb new file mode 100644 index 000000000..b73849dd7 --- /dev/null +++ b/Data/Scripts/011_Battle/006_Other battle types/004_PokeBattle_BattlePalace.rb @@ -0,0 +1,275 @@ +#=============================================================================== +# +#=============================================================================== +class PokeBattle_BattlePalace < PokeBattle_Battle + @@BattlePalaceUsualTable = [ + 61, 7, 32, + 20, 25, 55, + 70, 15, 15, + 38, 31, 31, + 20, 70, 10, + 30, 20, 50, + 56, 22, 22, + 25, 15, 60, + 69, 6, 25, + 35, 10, 55, + 62, 10, 28, + 58, 37, 5, + 34, 11, 55, + 35, 5, 60, + 56, 22, 22, + 35, 45, 20, + 44, 50, 6, + 56, 22, 22, + 30, 58, 12, + 30, 13, 57, + 40, 50, 10, + 18, 70, 12, + 88, 6, 6, + 42, 50, 8, + 56, 22, 22 + ] + @@BattlePalacePinchTable = [ + 61, 7, 32, + 84, 8, 8, + 32, 60, 8, + 70, 15, 15, + 70, 22, 8, + 32, 58, 10, + 56, 22, 22, + 75, 15, 10, + 28, 55, 17, + 29, 6, 65, + 30, 20, 50, + 88, 6, 6, + 29, 11, 60, + 35, 60, 5, + 56, 22, 22, + 34, 60, 6, + 34, 6, 60, + 56, 22, 22, + 30, 58, 12, + 27, 6, 67, + 25, 62, 13, + 90, 5, 5, + 22, 20, 58, + 42, 5, 53, + 56, 22, 22 + ] + + def initialize(*arg) + super + @justswitched = [false,false,false,false] + @battleAI.battlePalace = true + end + + def pbMoveCategory(move) + if move.target==PBTargets::User || move.function=="0D4" # Bide + return 1 + elsif move.statusMove? || + move.function=="071" || move.function=="072" # Counter, Mirror Coat + return 2 + else + return 0 + end + end + + # Different implementation of pbCanChooseMove, ignores Imprison/Torment/Taunt/Disable/Encore + def pbCanChooseMovePartial?(idxPokemon,idxMove) + thispkmn = @battlers[idxPokemon] + thismove = thispkmn.moves[idxMove] + return false if !thismove || thismove.id==0 + return false if thismove.pp<=0 + if thispkmn.effects[PBEffects::ChoiceBand]>=0 && + thismove.id!=thispkmn.effects[PBEffects::ChoiceBand] && + thispkmn.hasActiveItem?(:CHOICEBAND) + return false + end + # though incorrect, just for convenience (actually checks Torment later) + if thispkmn.effects[PBEffects::Torment] + return false if thismove.id==thispkmn.lastMoveUsed + end + return true + end + + def pbPinchChange(idxPokemon) + thispkmn = @battlers[idxPokemon] + if !thispkmn.effects[PBEffects::Pinch] && thispkmn.status!=PBStatuses::SLEEP && + thispkmn.hp<=thispkmn.totalhp/2 + nature = thispkmn.nature + thispkmn.effects[PBEffects::Pinch] = true + if nature==PBNatures::QUIET || + nature==PBNatures::BASHFUL || + nature==PBNatures::NAIVE || + nature==PBNatures::QUIRKY || + nature==PBNatures::HARDY || + nature==PBNatures::DOCILE || + nature==PBNatures::SERIOUS + pbDisplay(_INTL("{1} is eager for more!",thispkmn.pbThis)) + end + if nature==PBNatures::CAREFUL || + nature==PBNatures::RASH || + nature==PBNatures::LAX || + nature==PBNatures::SASSY || + nature==PBNatures::MILD || + nature==PBNatures::TIMID + pbDisplay(_INTL("{1} began growling deeply!",thispkmn.pbThis)) + end + if nature==PBNatures::GENTLE || + nature==PBNatures::ADAMANT || + nature==PBNatures::HASTY || + nature==PBNatures::LONELY || + nature==PBNatures::RELAXED || + nature==PBNatures::NAUGHTY + pbDisplay(_INTL("A glint appears in {1}'s eyes!",thispkmn.pbThis(true))) + end + if nature==PBNatures::JOLLY || + nature==PBNatures::BOLD || + nature==PBNatures::BRAVE || + nature==PBNatures::CALM || + nature==PBNatures::IMPISH || + nature==PBNatures::MODEST + pbDisplay(_INTL("{1} is getting into position!",thispkmn.pbThis)) + end + end + end + + def pbRegisterMove(idxBattler,idxMove,showMessages=true) + thispkmn = @battlers[idxBattler] + if idxMove==-2 + @choices[idxPokemon][0] = :UseMove # Move + @choices[idxPokemon][1] = -2 # "Incapable of using its power..." + @choices[idxPokemon][2] = @struggle + @choices[idxPokemon][3] = -1 + else + @choices[idxPokemon][0] = :UseMove # Move + @choices[idxPokemon][1] = idxMove # Index of move + @choices[idxPokemon][2] = thispkmn.moves[idxMove] # Move object + @choices[idxPokemon][3] = -1 # No target chosen + end + end + + def pbAutoFightMenu(idxBattler) + thispkmn = @battlers[idxBattler] + nature = thispkmn.nature + randnum = @battleAI.pbAIRandom(100) + category = 0 + atkpercent = 0 + defpercent = 0 + if thispkmn.effects[PBEffects::Pinch] + atkpercent = @@BattlePalacePinchTable[nature*3] + defpercent = atkpercent+@@BattlePalacePinchTable[nature*3+1] + else + atkpercent = @@BattlePalaceUsualTable[nature*3] + defpercent = atkpercent+@@BattlePalaceUsualTable[nature*3+1] + end + if randnum5 + shouldswitch = true + else + hppercent = thispkmn.hp*100/thispkmn.totalhp + percents = [] + maxindex = -1 + maxpercent = 0 + factor = 0 + @battle.pbParty(idxBattler).each_with_index do |pkmn,i| + if @battle.pbCanSwitch?(idxBattler,i) + percents[i] = 100*pkmn.hp/pkmn.totalhp + if percents[i]>maxpercent + maxindex = i + maxpercent = percents[i] + end + else + percents[i] = 0 + end + end + if hppercent<50 + factor = (maxpercent=0 + @battle.pbRegisterSwitch(idxBattler,maxindex) + return true + end + end + @justswitched[idxBattler] = shouldswitch + if shouldswitch + @battle.pbParty(idxBattler).each_with_index do |pkmn,i| + next if !@battle.pbCanSwitch?(idxBattler,i) + @battle.pbRegisterSwitch(idxBattler,i) + return true + end + end + return false + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/006_Other battle types/005_PokeBattle_BattleArena.rb b/Data/Scripts/011_Battle/006_Other battle types/005_PokeBattle_BattleArena.rb new file mode 100644 index 000000000..0a450e5f9 --- /dev/null +++ b/Data/Scripts/011_Battle/006_Other battle types/005_PokeBattle_BattleArena.rb @@ -0,0 +1,313 @@ +#=============================================================================== +# +#=============================================================================== +class PokeBattle_BattleArena < PokeBattle_Battle + def initialize(*arg) + super + @battlersChanged = true + @mind = [0,0] + @skill = [0,0] + @starthp = [0,0] + @count = 0 + @partyindexes = [0,0] + @battleAI.battleArena = true + end + + def pbCanSwitchLax?(idxBattler,idxParty,partyScene=nil) + if partyScene + partyScene.pbDisplay(_INTL("{1} can't be switched out!",@battlers[idxBattler].pbThis)) + end + return false + end + + def pbEORSwitch(favorDraws=false) + return if favorDraws && @decision==5 + return if !favorDraws && @decision>0 + pbJudge + return if @decision>0 + for side in 0...2 + next if !@battlers[side].fainted? + next if @partyindexes[side]+1>=self.pbParty(side).length + @partyindexes[side] += 1 + newpoke = @partyindexes[side] + pbMessagesOnReplace(side,newpoke) + pbReplace(side,newpoke) + pbOnActiveOne(@battlers[side]) + @battlers[side].pbEffectsOnSwitchIn(true) + end + end + + def pbOnActiveAll + @battlersChanged = true + for side in 0...2 + @mind[side] = 0 + @skill[side] = 0 + @starthp[side] = battlers[side].hp + end + @count = 0 + return super + end + + def pbOnActiveOne(*arg) + @battlersChanged = true + for side in 0...2 + @mind[side] = 0 + @skill[side] = 0 + @starthp[side] = battlers[side].hp + end + @count = 0 + return super + end + + def pbMindScore(move) + if move.function=="0AA" || # Detect/Protect + move.function=="0E8" || # Endure + move.function=="012" # Fake Out + return -1 + end + if move.function=="071" || # Counter + move.function=="072" || # Mirror Coat + move.function=="0D4" # Bide + return 0 + end + return 0 if move.statusMove? + return 1 + end + + def pbCommandPhase + if @battlersChanged + @scene.pbBattleArenaBattlers(@battlers[0],@battlers[1]) + @battlersChanged = false + @count = 0 + end + super + return if @decision!=0 + # Update mind rating (asserting that a move was chosen) + # TODO: Actually done at Pokémon's turn + for side in 0...2 + if @choices[side][2] && @choices[side][0]==:UseMove + @mind[side] += pbMindScore(@choices[side][2]) + end + end + end + + def pbEndOfRoundPhase + super + return if @decision!=0 + @count += 1 + # Update skill rating + for side in 0...2 + @skill[side] += self.successStates[side].skill + end +# PBDebug.log("[Mind: #{@mind.inspect}, Skill: #{@skill.inspect}]") + if @count==3 + points = [0,0] + @battlers[0].pbCancelMoves + @battlers[1].pbCancelMoves + ratings1 = [0,0,0] + ratings2 = [0,0,0] + if @mind[0]==@mind[1] + ratings1[0] = 1 + ratings2[0] = 1 + elsif @mind[0]>@mind[1] + ratings1[0] = 2 + else + ratings2[0] = 2 + end + if @skill[0]==@skill[1] + ratings1[1] = 1 + ratings2[1] = 1 + elsif @skill[0]>@skill[1] + ratings1[1] = 2 + else + ratings2[1] = 2 + end + body = [0,0] + body[0] = ((@battlers[0].hp*100)/[@starthp[0],1].max).floor + body[1] = ((@battlers[1].hp*100)/[@starthp[1],1].max).floor + if body[0]==body[1] + ratings1[2] = 1 + ratings2[2] = 1 + elsif body[0]>body[1] + ratings1[2] = 2 + else + ratings2[2] = 2 + end + @scene.pbBattleArenaJudgment(@battlers[0],@battlers[1],ratings1.clone,ratings2.clone) + points = [0,0] + for i in 0...ratings1.length + points[0] += ratings1[i] + points[1] += ratings2[i] + end + if points[0]==points[1] + pbDisplay(_INTL("{1} tied the opponent\n{2} in a referee's decision!", + @battlers[0].name,@battlers[1].name)) + # NOTE: Pokémon doesn't really lose HP, but the effect is mostly the + # same. + @battlers[0].hp = 0 + @battlers[0].pbFaint(false) + @battlers[1].hp = 0 + @battlers[1].pbFaint(false) + elsif points[0]>points[1] + pbDisplay(_INTL("{1} defeated the opponent\n{2} in a referee's decision!", + @battlers[0].name,@battlers[1].name)) + @battlers[1].hp = 0 + @battlers[1].pbFaint(false) + else + pbDisplay(_INTL("{1} lost to the opponent\n{2} in a referee's decision!", + @battlers[0].name,@battlers[1].name)) + @battlers[0].hp = 0 + @battlers[0].pbFaint(false) + end + pbGainExp + pbEORSwitch + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +class PokeBattle_AI + attr_accessor :battleArena + + alias _battleArena_pbEnemyShouldWithdraw? pbEnemyShouldWithdraw? + + def pbEnemyShouldWithdraw?(idxBattler) + return _battleArena_pbEnemyShouldWithdraw?(idxBattler) if !@battleArena + return false + end +end + + + +#=============================================================================== +# +#=============================================================================== +class PokeBattle_Scene + def pbBattleArenaUpdate + pbGraphicsUpdate + end + + def updateJudgment(window,phase,battler1,battler2,ratings1,ratings2) + total1 = 0 + total2 = 0 + for i in 0...phase + total1 += ratings1[i] + total2 += ratings2[i] + end + window.contents.clear + pbSetSystemFont(window.contents) + textpos = [ + [battler1.name,64,0,2,Color.new(248,0,0),Color.new(208,208,200)], + [_INTL("VS"),144,0,2,Color.new(72,72,72),Color.new(208,208,200)], + [battler2.name,224,0,2,Color.new(72,72,72),Color.new(208,208,200)], + [_INTL("Mind"),144,48,2,Color.new(72,72,72),Color.new(208,208,200)], + [_INTL("Skill"),144,80,2,Color.new(72,72,72),Color.new(208,208,200)], + [_INTL("Body"),144,112,2,Color.new(72,72,72),Color.new(208,208,200)], + [sprintf("%d",total1),64,160,2,Color.new(72,72,72),Color.new(208,208,200)], + [_INTL("Judgment"),144,160,2,Color.new(72,72,72),Color.new(208,208,200)], + [sprintf("%d",total2),224,160,2,Color.new(72,72,72),Color.new(208,208,200)] + ] + pbDrawTextPositions(window.contents,textpos) + images = [] + for i in 0...phase + y = [48,80,112][i] + x = (ratings1[i]==ratings2[i]) ? 64 : ((ratings1[i]>ratings2[i]) ? 0 : 32) + images.push(["Graphics/Pictures/judgment",64-16,y,x,0,32,32]) + x = (ratings1[i]==ratings2[i]) ? 64 : ((ratings1[i]total2 + pbMessageDisplay(msgwindow, + _INTL("REFEREE: Judgment: {1} to {2}!\nThe winner is {3}'s {4}!\\wtnp[40]", + total1,total2,@battle.pbGetOwnerName(battler1.index),battler1.name)) { + pbBattleArenaUpdate; dimmingvp.update; infowindow.update } + else + pbMessageDisplay(msgwindow, + _INTL("REFEREE: Judgment: {1} to {2}!\nThe winner is {3}!\\wtnp[40]", + total1,total2,battler2.name)) { + pbBattleArenaUpdate; dimmingvp.update; infowindow.update } + end + infowindow.visible = false + msgwindow.visible = false + for i in 0..10 + pbGraphicsUpdate + pbInputUpdate + msgwindow.update + dimmingvp.update + dimmingvp.color = Color.new(0,0,0,(10-i)*128/10) + end + ensure + pbDisposeMessageWindow(msgwindow) + dimmingvp.dispose + infowindow.contents.dispose + infowindow.dispose + end + end +end diff --git a/Data/Scripts/011_Battle/006_Other battle types/006_PokeBattle_BattleRecord.rb b/Data/Scripts/011_Battle/006_Other battle types/006_PokeBattle_BattleRecord.rb new file mode 100644 index 000000000..bfe491738 --- /dev/null +++ b/Data/Scripts/011_Battle/006_Other battle types/006_PokeBattle_BattleRecord.rb @@ -0,0 +1,297 @@ +#=============================================================================== +# +#=============================================================================== +module PokeBattle_RecordedBattleModule + attr_reader :randomnums + attr_reader :rounds + + module Commands + Fight = 0 + Bag = 1 + Pokemon = 2 + Run = 3 + end + + def initialize(*arg) + @randomnumbers = [] + @rounds = [] + @switches = [] + @roundindex = -1 + @properties = {} + super(*arg) + end + + def pbGetBattleType + return 0 # Battle Tower + end + + def pbGetTrainerInfo(trainer) + return nil if !trainer + if trainer.is_a?(Array) + ret = [] + for i in 0...trainer.length + ret.push([trainer[i].trainertype,trainer[i].name.clone,trainer[i].id,trainer[i].badges.clone]) + end + return ret + else + return [ + [trainer.trainertype,trainer.name.clone,trainer.id,trainer.badges.clone] + ] + end + end + + def pbStartBattle + @properties = {} + @properties["internalBattle"] = @internalBattle + @properties["player"] = pbGetTrainerInfo(@player) + @properties["opponent"] = pbGetTrainerInfo(@opponent) + @properties["party1"] = Marshal.dump(@party1) + @properties["party2"] = Marshal.dump(@party2) + @properties["party1starts"] = Marshal.dump(@party1starts) + @properties["party2starts"] = Marshal.dump(@party2starts) + @properties["endSpeeches"] = (@endSpeeches) ? @endSpeeches.clone : "" + @properties["endSpeechesWin"] = (@endSpeechesWin) ? @endSpeechesWin.clone : "" + @properties["weather"] = @field.weather + @properties["weatherDuration"] = @field.weatherDuration + @properties["canRun"] = @canRun + @properties["switchStyle"] = @switchStyle + @properties["showAnims"] = @showAnims + @properties["items"] = Marshal.dump(@items) + @properties["environment"] = @environment + @properties["rules"] = Marshal.dump(@rules) + super + end + + def pbDumpRecord + return Marshal.dump([pbGetBattleType,@properties,@rounds,@randomnumbers,@switches]) + end + + def pbSwitchInBetween(idxBattler,checkLaxOnly=false,canCancel=false) + ret = super + @switches.push(ret) + return ret + end + + def pbRegisterMove(idxBattler,idxMove,showMessages=true) + if super + @rounds[@roundindex][idxBattler] = [Commands::Fight,idxMove] + return true + end + return false + end + + def pbRegisterTarget(idxBattler,idxTarget) + super + @rounds[@roundindex][idxBattler][2] = idxTarget + end + + def pbRun(idxBattler,duringBattle=false) + ret = super + @rounds[@roundindex][idxBattler] = [Commands::Run,@decision] + return ret + end + + def pbAutoChooseMove(idxBattler,showMessages=true) + ret = super + @rounds[@roundindex][idxBattler] = [Commands::Fight,-1] + return ret + end + + def pbRegisterSwitch(idxBattler,idxParty) + if super + @rounds[@roundindex][idxBattler] = [Commands::Pokemon,idxParty] + return true + end + return false + end + + def pbRegisterItem(idxBattler,item,idxTarget=nil,idxMove=nil) + if super + @rounds[@roundindex][idxBattler] = [Commands::Bag,item,idxTarget,idxMove] + return true + end + return false + end + + def pbCommandPhase + @roundindex += 1 + @rounds[@roundindex] = [[],[],[],[]] + super + end + + def pbStorePokemon(pkmn); end + + def pbRandom(num) + ret = super(num) + @randomnumbers.push(ret) + return ret + end +end + + + +#=============================================================================== +# +#=============================================================================== +module BattlePlayerHelper + def self.pbGetOpponent(battle) + return self.pbCreateTrainerInfo(battle[1]["opponent"]) + end + + def self.pbGetBattleBGM(battle) + return self.pbGetTrainerBattleBGM(self.pbGetOpponent(battle)) + end + + def self.pbCreateTrainerInfo(trainer) + return nil if !trainer + if trainer.length>1 + ret = [] + ret[0]=PokeBattle_Trainer.new(trainer[0][1],trainer[0][0]) + ret[0].id = trainer[0][2] + ret[0].badges = trainer[0][3] + ret[1] = PokeBattle_Trainer.new(trainer[1][1],trainer[1][0]) + ret[1].id = trainer[1][2] + ret[1].badges = trainer[1][3] + return ret + else + ret = PokeBattle_Trainer.new(trainer[0][1],trainer[0][0]) + ret.id = trainer[0][2] + ret.badges = trainer[0][3] + return ret + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +module PokeBattle_BattlePlayerModule + module Commands + Fight = 0 + Bag = 1 + Pokemon = 2 + Run = 3 + end + + def initialize(scene,battle) + @battletype = battle[0] + @properties = battle[1] + @rounds = battle[2] + @randomnums = battle[3] + @switches = battle[4] + @roundindex = -1 + @randomindex = 0 + @switchindex = 0 + super(scene, + Marshal.restore(StringInput.new(@properties["party1"])), + Marshal.restore(StringInput.new(@properties["party2"])), + BattlePlayerHelper.pbCreateTrainerInfo(@properties["player"]), + BattlePlayerHelper.pbCreateTrainerInfo(@properties["opponent"]) + ) + end + + def pbStartBattle + @party1starts = @properties["party1starts"] + @party2starts = @properties["party2starts"] + @internalBattle = @properties["internalBattle"] + @endSpeeches = @properties["endSpeeches"] + @endSpeechesWin = @properties["endSpeechesWin"] + @field.weather = @properties["weather"] + @field.weatherDuration = @properties["weatherDuration"] + @canRun = @properties["canRun"] + @switchStyle = @properties["switchStyle"] + @showAnims = @properties["showAnims"] + @environment = @properties["environment"] + @items = Marshal.restore(StringInput.new(@properties["items"])) + @rules = Marshal.restore(StringInput.new(@properties["rules"])) + super + end + + def pbSwitchInBetween(idxBattler,checkLaxOnly=false,canCancel=false) + ret = @switches[@switchindex] + @switchindex += 1 + return ret + end + + def pbRandom(num) + ret = @randomnums[@randomindex] + @randomindex += 1 + return ret + end + + def pbDisplayPaused(str) + pbDisplay(str) + end + + def pbCommandPhaseCore + @roundindex += 1 + for i in 0...4 + next if @rounds[@roundindex][i].length==0 + pbClearChoice(i) + case @rounds[@roundindex][i][0] + when Commands::Fight + if @rounds[@roundindex][i][1]==-1 + pbAutoChooseMove(i,false) + else + pbRegisterMove(i,@rounds[@roundindex][i][1]) + end + if @rounds[@roundindex][i][2] + pbRegisterTarget(i,@rounds[@roundindex][i][2]) + end + when Commands::Bag + pbRegisterItem(i,@rounds[@roundindex][i][1],@rounds[@roundindex][i][2],@rounds[@roundindex][i][3]) + when Commands::Pokemon + pbRegisterSwitch(i,@rounds[@roundindex][i][1]) + when Commands::Run + @decision = @rounds[@roundindex][i][1] + end + end + end +end + + + +#=============================================================================== +# +#=============================================================================== +class PokeBattle_RecordedBattle < PokeBattle_Battle + include PokeBattle_RecordedBattleModule + + def pbGetBattleType; return 0; end +end + + + +class PokeBattle_RecordedBattlePalace < PokeBattle_BattlePalace + include PokeBattle_RecordedBattleModule + + def pbGetBattleType; return 1; end +end + + + +class PokeBattle_RecordedBattleArena < PokeBattle_BattleArena + include PokeBattle_RecordedBattleModule + + def pbGetBattleType; return 2; end +end + + + +class PokeBattle_BattlePlayer < PokeBattle_Battle + include PokeBattle_BattlePlayerModule +end + + + +class PokeBattle_BattlePalacePlayer < PokeBattle_BattlePalace + include PokeBattle_BattlePlayerModule +end + + + +class PokeBattle_BattleArenaPlayer < PokeBattle_BattleArena + include PokeBattle_BattlePlayerModule +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/006_Other battle types/007_PokeBattle_DebugScene.rb b/Data/Scripts/011_Battle/006_Other battle types/007_PokeBattle_DebugScene.rb new file mode 100644 index 000000000..f09dfba1b --- /dev/null +++ b/Data/Scripts/011_Battle/006_Other battle types/007_PokeBattle_DebugScene.rb @@ -0,0 +1,85 @@ +#=============================================================================== +# Used when generating new trainers for battle challenges +#=============================================================================== +class PokeBattle_DebugSceneNoLogging + def initialize + @battle = nil + @lastCmd = [0,0,0,0] + @lastMove = [0,0,0,0] + end + + # Called whenever the battle begins. + def pbStartBattle(battle) + @battle = battle + @lastCmd = [0,0,0,0] + @lastMove = [0,0,0,0] + end + + def pbBlitz(keys) + return rand(30) + end + + # Called whenever a new round begins. + def pbBeginCommandPhase; end + def pbBeginAttackPhase; end + def pbShowOpponent(idxTrainer); end + def pbDamageAnimation(battler,effectiveness=0); end + def pbCommonAnimation(animName,user=nil,target=nil,hitNum=0); end + def pbAnimation(moveID,user,targets,hitNum=0); end + def pbEndBattle(result); end + def pbWildBattleSuccess; end + def pbTrainerBattleSuccess; end + def pbBattleArenaJudgment(b1,b2,r1,r2); end + def pbBattleArenaBattlers(b1,b2); end + + def pbRefresh; end + + def pbDisplayMessage(msg,brief=false); end + def pbDisplayPausedMessage(msg); end + def pbDisplayConfirmMessage(msg); return true; end + def pbShowCommands(msg,commands,defaultValue); return 0; end + + def pbSendOutBattlers(sendOuts,startBattle=false); end + def pbRecall(idxBattler); end + def pbItemMenu(idxBattler,firstAction); return -1; end + def pbResetMoveIndex(idxBattler); end + + def pbChatter(user,target); end + def pbHPChanged(battler,oldHP,showAnim=false); end + def pbFaintBattler(battler); end + def pbEXPBar(battler,startExp,endExp,tempExp1,tempExp2); end + def pbLevelUp(pkmn,battler,oldTotalHP,oldAttack,oldDefense, + oldSpAtk,oldSpDef,oldSpeed); end + def pbForgetMove(pkmn,moveToLearn); return 0; end # Always forget first move + + def pbCommandMenu(idxBattler,firstAction) + return 1 if rand(15)==0 # Bag + return 4 if rand(10)==0 # Call + return 0 # Fight + end + + def pbFightMenu(idxBattler,megaEvoPossible=false) + battler = @battle.battlers[idxBattler] + 50.times do + break if yield rand(battler.move.length) + end + end + + def pbChooseTarget(idxBattler,targetType,visibleSprites=nil) + targets = [] + @battle.eachOtherSideBattler(idxBattler) { |b| targets.push(b.index) } + return -1 if targets.length==0 + return targets[rand(targets.length)] + end + + def pbPartyScreen(idxBattler,canCancel=false) + replacements = [] + @battle.eachInTeamFromBattlerIndex(idxBattler) do |b,idxParty| + replacements.push(idxParty) if !@battle.pbFindBattler(idxParty,idxBattler) + end + return if replacements.length==0 + 50.times do + break if yield replacements[rand(replacements.length)],self + end + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/006_Other battle types/008_PokeBattle_BattlePeer.rb b/Data/Scripts/011_Battle/006_Other battle types/008_PokeBattle_BattlePeer.rb new file mode 100644 index 000000000..d20c90768 --- /dev/null +++ b/Data/Scripts/011_Battle/006_Other battle types/008_PokeBattle_BattlePeer.rb @@ -0,0 +1,65 @@ +#=============================================================================== +# +#=============================================================================== +# Unused class. +class PokeBattle_NullBattlePeer + def pbOnEnteringBattle(battle,pkmn,wild=false); end + def pbOnLeavingBattle(battle,pkmn,usedInBattle,endBattle=false); end + + def pbStorePokemon(player,pkmn) + player.party[player.party.length] = pkmn if player.party.length<6 + return -1 + end + + def pbGetStorageCreatorName; return nil; end + def pbCurrentBox; return -1; end + def pbBoxName(box); return ""; end +end + + + +#=============================================================================== +# +#=============================================================================== +class PokeBattle_RealBattlePeer + def pbStorePokemon(player,pkmn) + if player.party.length<6 + player.party[player.party.length] = pkmn + return -1 + end + pkmn.heal + oldCurBox = pbCurrentBox + storedBox = $PokemonStorage.pbStoreCaught(pkmn) + if storedBox<0 + # NOTE: Poké Balls can't be used if storage is full, so you shouldn't ever + # see this message. + pbDisplayPaused(_INTL("Can't catch any more...")) + return oldCurBox + end + return storedBox + end + + def pbGetStorageCreatorName + return pbGetStorageCreator if $PokemonGlobal.seenStorageCreator + return nil + end + + def pbCurrentBox + return $PokemonStorage.currentBox + end + + def pbBoxName(box) + return (box<0) ? "" : $PokemonStorage[box].name + end +end + + + +#=============================================================================== +# +#=============================================================================== +class PokeBattle_BattlePeer + def self.create + return PokeBattle_RealBattlePeer.new + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/006_Other battle types/009_PokeBattle_Clauses.rb b/Data/Scripts/011_Battle/006_Other battle types/009_PokeBattle_Clauses.rb new file mode 100644 index 000000000..def92991d --- /dev/null +++ b/Data/Scripts/011_Battle/006_Other battle types/009_PokeBattle_Clauses.rb @@ -0,0 +1,244 @@ +#=============================================================================== +# This script modifies the battle system to implement battle rules +#=============================================================================== +class PokeBattle_Battle + unless @__clauses__aliased + alias __clauses__pbDecisionOnDraw pbDecisionOnDraw + alias __clauses__pbEndOfRoundPhase pbEndOfRoundPhase + @__clauses__aliased = true + end + + def pbDecisionOnDraw + if @rules["selfkoclause"] + if self.lastMoveUser<0 + # in extreme cases there may be no last move user + return 5 # game is a draw + elsif opposes?(self.lastMoveUser) + return 2 # loss + else + return 1 # win + end + end + return __clauses__pbDecisionOnDraw + end + + def pbJudgeCheckpoint(user,move=nil) + if pbAllFainted?(0) && pbAllFainted?(1) + if @rules["drawclause"] # NOTE: Also includes Life Orb (not implemented) + if !(move && move.function=="0DD") # Not a draw if fainting occurred due to Liquid Ooze + @decision = (user.opposes?) ? 1 : 2 # win / loss + end + elsif @rules["modifiedselfdestructclause"] + if move && move.function=="0E0" # Self-Destruct + @decision = (user.opposes?) ? 1 : 2 # win / loss + end + end + end + end + + def pbEndOfRoundPhase + __clauses__pbEndOfRoundPhase + if @rules["suddendeath"] && @decision==0 + p1able = pbAbleCount(0) + p2able = pbAbleCount(1) + if p1able>p2able; @decision = 1 # loss + elsif p1able0 + end +end + + + +class PokeBattle_Move_022 # Double Team + alias __clauses__pbMoveFailed? pbMoveFailed? + + def pbMoveFailed?(user,targets) + if !damagingMove? && @battle.rules["evasionclause"] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return __clauses__pbMoveFailed?(user,targets) + end +end + + + +class PokeBattle_Move_034 # Minimize + alias __clauses__pbMoveFailed? pbMoveFailed? + + def pbMoveFailed?(user,targets) + if !damagingMove? && @battle.rules["evasionclause"] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return __clauses__pbMoveFailed?(user,targets) + end +end + + + +class PokeBattle_Move_067 # Skill Swap + alias __clauses__pbFailsAgainstTarget? pbFailsAgainstTarget? + + def pbFailsAgainstTarget?(user,target) + if @battle.rules["skillswapclause"] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return __clauses__pbFailsAgainstTarget?(user,target) + end +end + + + +class PokeBattle_Move_06A # Sonic Boom + alias __clauses__pbFailsAgainstTarget? pbFailsAgainstTarget? + + def pbFailsAgainstTarget?(user,target) + if @battle.rules["sonicboomclause"] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return __clauses__pbFailsAgainstTarget?(user,target) + end +end + + + +class PokeBattle_Move_06B # Dragon Rage + alias __clauses__pbFailsAgainstTarget? pbFailsAgainstTarget? + + def pbFailsAgainstTarget?(user,target) + if @battle.rules["sonicboomclause"] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return __clauses__pbFailsAgainstTarget?(user,target) + end +end + + + +class PokeBattle_Move_070 # OHKO moves + alias __clauses__pbFailsAgainstTarget? pbFailsAgainstTarget? + + def pbFailsAgainstTarget?(user,target) + if @battle.rules["ohkoclause"] + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return __clauses__pbFailsAgainstTarget?(user,target) + end +end + + + +class PokeBattle_Move_0E0 # Self-Destruct + unless @__clauses__aliased + alias __clauses__pbMoveFailed? pbMoveFailed? + @__clauses__aliased = true + end + + def pbMoveFailed?(user,targets) + if @battle.rules["selfkoclause"] + # Check whether no unfainted Pokemon remain in either party + count = @battle.pbAbleNonActiveCount(user.idxOwnSide) + count += @battle.pbAbleNonActiveCount(user.idxOpposingSide) + if count==0 + @battle.pbDisplay("But it failed!") + return false + end + end + if @battle.rules["selfdestructclause"] + # Check whether no unfainted Pokemon remain in either party + count = @battle.pbAbleNonActiveCount(user.idxOwnSide) + count += @battle.pbAbleNonActiveCount(user.idxOpposingSide) + if count==0 + @battle.pbDisplay(_INTL("{1}'s team was disqualified!",user.pbThis)) + @battle.decision = (user.opposes?) ? 1 : 2 + return false + end + end + return __clauses__pbMoveFailed?(user,targets) + end +end + + + +class PokeBattle_Move_0E5 # Perish Song + alias __clauses__pbFailsAgainstTarget? pbFailsAgainstTarget? + + def pbFailsAgainstTarget?(user,target) + if @battle.rules["perishsongclause"] && + @battle.pbAbleNonActiveCount(user.idxOwnSide)==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return __clauses__pbFailsAgainstTarget?(user,target) + end +end + + + +class PokeBattle_Move_0E7 # Destiny Bond + alias __clauses__pbFailsAgainstTarget? pbFailsAgainstTarget? + + def pbFailsAgainstTarget?(user,target) + if @battle.rules["perishsongclause"] && + @battle.pbAbleNonActiveCount(user.idxOwnSide)==0 + @battle.pbDisplay(_INTL("But it failed!")) + return true + end + return __clauses__pbFailsAgainstTarget?(user,target) + end +end \ No newline at end of file diff --git a/Data/Scripts/011_Battle/007_BattleHandlers_Abilities.rb b/Data/Scripts/011_Battle/007_BattleHandlers_Abilities.rb new file mode 100644 index 000000000..687bdd2d8 --- /dev/null +++ b/Data/Scripts/011_Battle/007_BattleHandlers_Abilities.rb @@ -0,0 +1,2507 @@ +#=============================================================================== +# SpeedCalcAbility handlers +#=============================================================================== + +BattleHandlers::SpeedCalcAbility.add(:CHLOROPHYLL, + proc { |ability,battler,mult| + w = battler.battle.pbWeather + next mult*2 if w==PBWeather::Sun || w==PBWeather::HarshSun + } +) + +BattleHandlers::SpeedCalcAbility.add(:QUICKFEET, + proc { |ability,battler,mult| + next (mult*1.5).round if battler.pbHasAnyStatus? + } +) + +BattleHandlers::SpeedCalcAbility.add(:SANDRUSH, + proc { |ability,battler,mult| + w = battler.battle.pbWeather + next mult*2 if w==PBWeather::Sandstorm + } +) + +BattleHandlers::SpeedCalcAbility.add(:SLOWSTART, + proc { |ability,battler,mult| + next mult/2 if battler.turnCount<=5 + } +) + +BattleHandlers::SpeedCalcAbility.add(:SLUSHRUSH, + proc { |ability,battler,mult| + w = battler.battle.pbWeather + next mult*2 if w==PBWeather::Hail + } +) + +BattleHandlers::SpeedCalcAbility.add(:SURGESURFER, + proc { |ability,battler,mult| + next mult*2 if battler.battle.field.terrain==PBBattleTerrains::Electric + } +) + +BattleHandlers::SpeedCalcAbility.add(:SWIFTSWIM, + proc { |ability,battler,mult| + w = battler.battle.pbWeather + next mult*2 if w==PBWeather::Rain || w==PBWeather::HeavyRain + } +) + +BattleHandlers::SpeedCalcAbility.add(:UNBURDEN, + proc { |ability,battler,mult| + next mult*2 if battler.effects[PBEffects::Unburden] && battler.item==0 + } +) + +#=============================================================================== +# WeightCalcAbility handlers +#=============================================================================== + +BattleHandlers::WeightCalcAbility.add(:HEAVYMETAL, + proc { |ability,battler,w| + next w*2 + } +) + +BattleHandlers::WeightCalcAbility.add(:LIGHTMETAL, + proc { |ability,battler,w| + next [w/2,1].max + } +) + +#=============================================================================== +# AbilityOnHPDroppedBelowHalf handlers +#=============================================================================== + +BattleHandlers::AbilityOnHPDroppedBelowHalf.add(:EMERGENCYEXIT, + proc { |ability,battler,battle| + next false if battler.effects[PBEffects::SkyDrop]>=0 || battler.inTwoTurnAttack?("0CE") # Sky Drop + # In wild battles + if battle.wildBattle? + next false if battler.opposes? && battle.pbSideBattlerCount(battler.index)>1 + next false if !battle.pbCanRun?(battler.index) + battle.pbShowAbilitySplash(battler,true) + battle.pbHideAbilitySplash(battler) + battle.pbDisplay(_INTL("{1} fled from battle!",battler.pbThis)) { pbSEPlay("Battle flee") } + battle.decision = 3 # Escaped + next true + end + # In trainer battles + next false if battle.pbAllFainted?(battler.idxOpposingSide) + next false if !battle.pbCanSwitch?(battler.index) # Battler can't switch out + next false if !battle.pbCanChooseNonActive?(battler.index) # No Pokémon can switch in + battle.pbShowAbilitySplash(battler,true) + battle.pbHideAbilitySplash(battler) + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s {2} activated!",battler.pbThis,battler.abilityName)) + end + battle.pbDisplay(_INTL("{1} went back to {2}!", + battler.pbThis,battle.pbGetOwnerName(battler.index))) + if battle.endOfRound # Just switch out + battle.scene.pbRecall(battler.index) if !battler.fainted? + battler.pbAbilitiesOnSwitchOut # Inc. primordial weather check + next true + end + newPkmn = battle.pbGetReplacementPokemonIndex(battler.index) # Owner chooses + next false if newPkmn<0 # Shouldn't ever do this + battle.pbRecallAndReplace(battler.index,newPkmn) + battle.pbClearChoice(battler.index) # Replacement Pokémon does nothing this round + next true + } +) + +BattleHandlers::AbilityOnHPDroppedBelowHalf.copy(:EMERGENCYEXIT,:WIMPOUT) + +#=============================================================================== +# StatusCheckAbilityNonIgnorable handlers +#=============================================================================== + +BattleHandlers::StatusCheckAbilityNonIgnorable.add(:COMATOSE, + proc { |ability,battler,status| + next false if !isConst?(battler.species,PBSpecies,:KOMALA) + next true if status.nil? || status==PBStatuses::SLEEP + } +) + +#=============================================================================== +# StatusImmunityAbility handlers +#=============================================================================== + +BattleHandlers::StatusImmunityAbility.add(:FLOWERVEIL, + proc { |ability,battler,status| + next true if battler.pbHasType?(:GRASS) + } +) + +BattleHandlers::StatusImmunityAbility.add(:IMMUNITY, + proc { |ability,battler,status| + next true if status==PBStatuses::POISON + } +) + +BattleHandlers::StatusImmunityAbility.add(:INSOMNIA, + proc { |ability,battler,status| + next true if status==PBStatuses::SLEEP + } +) + +BattleHandlers::StatusImmunityAbility.copy(:INSOMNIA,:SWEETVEIL,:VITALSPIRIT) + +BattleHandlers::StatusImmunityAbility.add(:LEAFGUARD, + proc { |ability,battler,status| + w = battler.battle.pbWeather + next true if w==PBWeather::Sun || w==PBWeather::HarshSun + } +) + +BattleHandlers::StatusImmunityAbility.add(:LIMBER, + proc { |ability,battler,status| + next true if status==PBStatuses::PARALYSIS + } +) + +BattleHandlers::StatusImmunityAbility.add(:MAGMAARMOR, + proc { |ability,battler,status| + next true if status==PBStatuses::FROZEN + } +) + +BattleHandlers::StatusImmunityAbility.add(:WATERVEIL, + proc { |ability,battler,status| + next true if status==PBStatuses::BURN + } +) + +BattleHandlers::StatusImmunityAbility.copy(:WATERVEIL,:WATERBUBBLE) + +#=============================================================================== +# StatusImmunityAbilityNonIgnorable handlers +#=============================================================================== + +BattleHandlers::StatusImmunityAbilityNonIgnorable.add(:COMATOSE, + proc { |ability,battler,status| + next true if isConst?(battler.species,PBSpecies,:KOMALA) + } +) + +BattleHandlers::StatusImmunityAbilityNonIgnorable.add(:SHIELDSDOWN, + proc { |ability,battler,status| + next true if isConst?(battler.species,PBSpecies,:MINIOR) && battler.form<7 + } +) + +#=============================================================================== +# StatusImmunityAllyAbility handlers +#=============================================================================== + +BattleHandlers::StatusImmunityAllyAbility.add(:FLOWERVEIL, + proc { |ability,battler,status| + next true if battler.pbHasType?(:GRASS) + } +) + +BattleHandlers::StatusImmunityAbility.add(:SWEETVEIL, + proc { |ability,battler,status| + next true if status==PBStatuses::SLEEP + } +) + +#=============================================================================== +# AbilityOnStatusInflicted handlers +#=============================================================================== + +BattleHandlers::AbilityOnStatusInflicted.add(:SYNCHRONIZE, + proc { |ability,battler,user,status| + next if !user || user.index==battler.index + case status + when PBStatuses::POISON + if user.pbCanPoisonSynchronize?(battler) + battler.battle.pbShowAbilitySplash(battler) + msg = nil + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + msg = _INTL("{1}'s {2} poisoned {3}!",battler.pbThis,battler.abilityName,user.pbThis(true)) + end + user.pbPoison(nil,msg,(battler.statusCount>0)) + battler.battle.pbHideAbilitySplash(battler) + end + when PBStatuses::BURN + if user.pbCanBurnSynchronize?(battler) + battler.battle.pbShowAbilitySplash(battler) + msg = nil + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + msg = _INTL("{1}'s {2} burned {3}!",battler.pbThis,battler.abilityName,user.pbThis(true)) + end + user.pbBurn(nil,msg) + battler.battle.pbHideAbilitySplash(battler) + end + when PBStatuses::PARALYSIS + if user.pbCanParalyzeSynchronize?(battler) + battler.battle.pbShowAbilitySplash(battler) + msg = nil + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + msg = _INTL("{1}'s {2} paralyzed {3}! It may be unable to move!", + battler.pbThis,battler.abilityName,user.pbThis(true)) + end + user.pbParalyze(nil,msg) + battler.battle.pbHideAbilitySplash(battler) + end + end + } +) + +#=============================================================================== +# StatusCureAbility handlers +#=============================================================================== + +BattleHandlers::StatusCureAbility.add(:IMMUNITY, + proc { |ability,battler| + next if battler.status!=PBStatuses::POISON + battler.battle.pbShowAbilitySplash(battler) + battler.pbCureStatus(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battler.battle.pbDisplay(_INTL("{1}'s {2} cured its poisoning!",battler.pbThis,battler.abilityName)) + end + battler.battle.pbHideAbilitySplash(battler) + } +) + +BattleHandlers::StatusCureAbility.add(:INSOMNIA, + proc { |ability,battler| + next if battler.status!=PBStatuses::SLEEP + battler.battle.pbShowAbilitySplash(battler) + battler.pbCureStatus(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battler.battle.pbDisplay(_INTL("{1}'s {2} woke it up!",battler.pbThis,battler.abilityName)) + end + battler.battle.pbHideAbilitySplash(battler) + } +) + +BattleHandlers::StatusCureAbility.copy(:INSOMNIA,:VITALSPIRIT) + +BattleHandlers::StatusCureAbility.add(:LIMBER, + proc { |ability,battler| + next if battler.status!=PBStatuses::PARALYSIS + battler.battle.pbShowAbilitySplash(battler) + battler.pbCureStatus(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battler.battle.pbDisplay(_INTL("{1}'s {2} cured its paralysis!",battler.pbThis,battler.abilityName)) + end + battler.battle.pbHideAbilitySplash(battler) + } +) + +BattleHandlers::StatusCureAbility.add(:MAGMAARMOR, + proc { |ability,battler| + next if battler.status!=PBStatuses::FROZEN + battler.battle.pbShowAbilitySplash(battler) + battler.pbCureStatus(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battler.battle.pbDisplay(_INTL("{1}'s {2} defrosted it!",battler.pbThis,battler.abilityName)) + end + battler.battle.pbHideAbilitySplash(battler) + } +) + +BattleHandlers::StatusCureAbility.add(:OBLIVIOUS, + proc { |ability,battler| + next if battler.effects[PBEffects::Attract]<0 && + (battler.effects[PBEffects::Taunt]==0 || !NEWEST_BATTLE_MECHANICS) + battler.battle.pbShowAbilitySplash(battler) + if battler.effects[PBEffects::Attract]>=0 + battler.pbCureAttract + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battler.battle.pbDisplay(_INTL("{1} got over its infatuation.",battler.pbThis)) + else + battler.battle.pbDisplay(_INTL("{1}'s {2} cured its infatuation status!", + battler.pbThis,battler.abilityName)) + end + end + if battler.effects[PBEffects::Taunt]>0 && NEWEST_BATTLE_MECHANICS + battler.effects[PBEffects::Taunt] = 0 + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battler.battle.pbDisplay(_INTL("{1}'s Taunt wore off!",battler.pbThis)) + else + battler.battle.pbDisplay(_INTL("{1}'s {2} made its taunt wear off!", + battler.pbThis,battler.abilityName)) + end + end + battler.battle.pbHideAbilitySplash(battler) + } +) + +BattleHandlers::StatusCureAbility.add(:OWNTEMPO, + proc { |ability,battler| + next if battler.effects[PBEffects::Confusion]==0 + battler.battle.pbShowAbilitySplash(battler) + battler.pbCureConfusion + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battler.battle.pbDisplay(_INTL("{1} snapped out of its confusion.",battler.pbThis)) + else + battler.battle.pbDisplay(_INTL("{1}'s {2} snapped it out of its confusion!", + battler.pbThis,battler.abilityName)) + end + battler.battle.pbHideAbilitySplash(battler) + } +) + +BattleHandlers::StatusCureAbility.add(:WATERVEIL, + proc { |ability,battler| + next if battler.status!=PBStatuses::BURN + battler.battle.pbShowAbilitySplash(battler) + battler.pbCureStatus(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battler.battle.pbDisplay(_INTL("{1}'s {2} healed its burn!",battler.pbThis,battler.abilityName)) + end + battler.battle.pbHideAbilitySplash(battler) + } +) + +BattleHandlers::StatusCureAbility.copy(:WATERVEIL,:WATERBUBBLE) + +#=============================================================================== +# StatLossImmunityAbility handlers +#=============================================================================== + +BattleHandlers::StatLossImmunityAbility.add(:BIGPECKS, + proc { |ability,battler,stat,battle,showMessages| + next false if stat!=PBStats::DEFENSE + if showMessages + battle.pbShowAbilitySplash(battler) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s {2} cannot be lowered!",battler.pbThis,PBStats.getName(stat))) + else + battle.pbDisplay(_INTL("{1}'s {2} prevents {3} loss!",battler.pbThis, + battler.abilityName,PBStats.getName(stat))) + end + battle.pbHideAbilitySplash(battler) + end + next true + } +) + +BattleHandlers::StatLossImmunityAbility.add(:CLEARBODY, + proc { |ability,battler,stat,battle,showMessages| + if showMessages + battle.pbShowAbilitySplash(battler) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s stats cannot be lowered!",battler.pbThis)) + else + battle.pbDisplay(_INTL("{1}'s {2} prevents stat loss!",battler.pbThis,battler.abilityName)) + end + battle.pbHideAbilitySplash(battler) + end + next true + } +) + +BattleHandlers::StatLossImmunityAbility.copy(:CLEARBODY,:WHITESMOKE) + +BattleHandlers::StatLossImmunityAbility.add(:FLOWERVEIL, + proc { |ability,battler,stat,battle,showMessages| + next false if !battler.pbHasType?(:GRASS) + if showMessages + battle.pbShowAbilitySplash(battler) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s stats cannot be lowered!",battler.pbThis)) + else + battle.pbDisplay(_INTL("{1}'s {2} prevents stat loss!",battler.pbThis,battler.abilityName)) + end + battle.pbHideAbilitySplash(battler) + end + next true + } +) + +BattleHandlers::StatLossImmunityAbility.add(:HYPERCUTTER, + proc { |ability,battler,stat,battle,showMessages| + next false if stat!=PBStats::ATTACK + if showMessages + battle.pbShowAbilitySplash(battler) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s {2} cannot be lowered!",battler.pbThis,PBStats.getName(stat))) + else + battle.pbDisplay(_INTL("{1}'s {2} prevents {3} loss!",battler.pbThis, + battler.abilityName,PBStats.getName(stat))) + end + battle.pbHideAbilitySplash(battler) + end + next true + } +) + +BattleHandlers::StatLossImmunityAbility.add(:KEENEYE, + proc { |ability,battler,stat,battle,showMessages| + next false if stat!=PBStats::ACCURACY + if showMessages + battle.pbShowAbilitySplash(battler) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s {2} cannot be lowered!",battler.pbThis,PBStats.getName(stat))) + else + battle.pbDisplay(_INTL("{1}'s {2} prevents {3} loss!",battler.pbThis, + battler.abilityName,PBStats.getName(stat))) + end + battle.pbHideAbilitySplash(battler) + end + next true + } +) + +#=============================================================================== +# StatLossImmunityAbilityNonIgnorable handlers +#=============================================================================== + +BattleHandlers::StatLossImmunityAbilityNonIgnorable.add(:FULLMETALBODY, + proc { |ability,battler,stat,battle,showMessages| + if showMessages + battle.pbShowAbilitySplash(battler) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s stats cannot be lowered!",battler.pbThis)) + else + battle.pbDisplay(_INTL("{1}'s {2} prevents stat loss!",battler.pbThis,battler.abilityName)) + end + battle.pbHideAbilitySplash(battler) + end + next true + } +) + +#=============================================================================== +# StatLossImmunityAllyAbility handlers +#=============================================================================== + +BattleHandlers::StatLossImmunityAllyAbility.add(:FLOWERVEIL, + proc { |ability,bearer,battler,stat,battle,showMessages| + next false if !battler.pbHasType?(:GRASS) + if showMessages + battle.pbShowAbilitySplash(bearer) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s stats cannot be lowered!",battler.pbThis)) + else + battle.pbDisplay(_INTL("{1}'s {2} prevents {3}'s stat loss!", + bearer.pbThis,bearer.abilityName,battler.pbThis(true))) + end + battle.pbHideAbilitySplash(bearer) + end + next true + } +) + +#=============================================================================== +# AbilityOnStatGain handlers +#=============================================================================== + +# There aren't any! + +#=============================================================================== +# AbilityOnStatLoss handlers +#=============================================================================== + +BattleHandlers::AbilityOnStatLoss.add(:COMPETITIVE, + proc { |ability,battler,stat,user| + next if user && !user.opposes?(battler) + battler.pbRaiseStatStageByAbility(PBStats::SPATK,2,battler) + } +) + +BattleHandlers::AbilityOnStatLoss.add(:DEFIANT, + proc { |ability,battler,stat,user| + next if user && !user.opposes?(battler) + battler.pbRaiseStatStageByAbility(PBStats::ATTACK,2,battler) + } +) + +#=============================================================================== +# PriorityChangeAbility handlers +#=============================================================================== + +BattleHandlers::PriorityChangeAbility.add(:GALEWINGS, + proc { |ability,battler,move,pri| + next pri+1 if battler.hp==battler.totalhp && isConst?(move.type,PBTypes,:FLYING) + } +) + +BattleHandlers::PriorityChangeAbility.add(:PRANKSTER, + proc { |ability,battler,move,pri| + if move.statusMove? + battler.effects[PBEffects::Prankster] = true + next pri+1 + end + } +) + +BattleHandlers::PriorityChangeAbility.add(:TRIAGE, + proc { |ability,battler,move,pri| + next pri+3 if move.healingMove? + } +) + +#=============================================================================== +# PriorityBracketChangeAbility handlers +#=============================================================================== + +BattleHandlers::PriorityBracketChangeAbility.add(:STALL, + proc { |ability,battler,subPri,battle| + next -1 if subPri==0 + } +) + +#=============================================================================== +# PriorityBracketUseAbility handlers +#=============================================================================== + +# There aren't any! + +#=============================================================================== +# AbilityOnFlinch handlers +#=============================================================================== + +BattleHandlers::AbilityOnFlinch.add(:STEADFAST, + proc { |ability,battler,battle| + battler.pbRaiseStatStageByAbility(PBStats::SPEED,1,battler) + } +) + +#=============================================================================== +# MoveBlockingAbility handlers +#=============================================================================== + +BattleHandlers::MoveBlockingAbility.add(:DAZZLING, + proc { |ability,bearer,user,targets,move,battle| + next false if battle.choices[user.index][4]<=0 + next false if !bearer.opposes?(user) + ret = false + targets.each do |b| + next if !b.opposes?(user) + ret = true + end + next ret + } +) + +BattleHandlers::MoveBlockingAbility.copy(:DAZZLING,:QUEENLYMAJESTY) + +#=============================================================================== +# MoveImmunityTargetAbility handlers +#=============================================================================== + +BattleHandlers::MoveImmunityTargetAbility.add(:BULLETPROOF, + proc { |ability,user,target,move,type,battle| + next false if !move.bombMove? + battle.pbShowAbilitySplash(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true))) + else + battle.pbDisplay(_INTL("{1}'s {2} made {3} ineffective!", + target.pbThis,target.abilityName,move.name)) + end + battle.pbHideAbilitySplash(target) + next true + } +) + +BattleHandlers::MoveImmunityTargetAbility.add(:FLASHFIRE, + proc { |ability,user,target,move,type,battle| + next false if user.index==target.index + next false if !isConst?(type,PBTypes,:FIRE) + battle.pbShowAbilitySplash(target) + if !target.effects[PBEffects::FlashFire] + target.effects[PBEffects::FlashFire] = true + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("The power of {1}'s Fire-type moves rose!",target.pbThis(true))) + else + battle.pbDisplay(_INTL("The power of {1}'s Fire-type moves rose because of its {2}!", + target.pbThis(true),target.abilityName)) + end + else + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true))) + else + battle.pbDisplay(_INTL("{1}'s {2} made {3} ineffective!", + target.pbThis,target.abilityName,move.name)) + end + end + battle.pbHideAbilitySplash(target) + next true + } +) + +BattleHandlers::MoveImmunityTargetAbility.add(:LIGHTNINGROD, + proc { |ability,user,target,move,type,battle| + next pbBattleMoveImmunityStatAbility(user,target,move,type,:ELECTRIC,PBStats::SPATK,1,battle) + } +) + +BattleHandlers::MoveImmunityTargetAbility.add(:MOTORDRIVE, + proc { |ability,user,target,move,type,battle| + next pbBattleMoveImmunityStatAbility(user,target,move,type,:ELECTRIC,PBStats::SPEED,1,battle) + } +) + +BattleHandlers::MoveImmunityTargetAbility.add(:SAPSIPPER, + proc { |ability,user,target,move,type,battle| + next pbBattleMoveImmunityStatAbility(user,target,move,type,:GRASS,PBStats::ATTACK,1,battle) + } +) + +BattleHandlers::MoveImmunityTargetAbility.add(:SOUNDPROOF, + proc { |ability,user,target,move,type,battle| + next false if !move.soundMove? + battle.pbShowAbilitySplash(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true))) + else + battle.pbDisplay(_INTL("{1}'s {2} blocks {3}!",target.pbThis,target.abilityName,move.name)) + end + battle.pbHideAbilitySplash(target) + next true + + } +) + +BattleHandlers::MoveImmunityTargetAbility.add(:STORMDRAIN, + proc { |ability,user,target,move,type,battle| + next pbBattleMoveImmunityStatAbility(user,target,move,type,:WATER,PBStats::SPATK,1,battle) + } +) + +BattleHandlers::MoveImmunityTargetAbility.add(:TELEPATHY, + proc { |ability,user,target,move,type,battle| + next false if move.statusMove? + next false if user.index==target.index || target.opposes?(user) + battle.pbShowAbilitySplash(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1} avoids attacks by its ally Pokémon!",target.pbThis(true))) + else + battle.pbDisplay(_INTL("{1} avoids attacks by its ally Pokémon with {2}!", + target.pbThis,target.abilityName)) + end + battle.pbHideAbilitySplash(target) + next true + } +) + +BattleHandlers::MoveImmunityTargetAbility.add(:VOLTABSORB, + proc { |ability,user,target,move,type,battle| + next pbBattleMoveImmunityHealAbility(user,target,move,type,:ELECTRIC,battle) + } +) + +BattleHandlers::MoveImmunityTargetAbility.add(:WATERABSORB, + proc { |ability,user,target,move,type,battle| + next pbBattleMoveImmunityHealAbility(user,target,move,type,:WATER,battle) + } +) + +BattleHandlers::MoveImmunityTargetAbility.copy(:WATERABSORB,:DRYSKIN) + +BattleHandlers::MoveImmunityTargetAbility.add(:WONDERGUARD, + proc { |ability,user,target,move,type,battle| + next false if move.statusMove? + next false if type<0 || PBTypes.superEffective?(target.damageState.typeMod) + battle.pbShowAbilitySplash(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("It doesn't affect {1}...",target.pbThis(true))) + else + battle.pbDisplay(_INTL("{1} avoided damage with {2}!",target.pbThis,target.abilityName)) + end + battle.pbHideAbilitySplash(target) + next true + } +) + +#=============================================================================== +# MoveBaseTypeModifierAbility handlers +#=============================================================================== + +BattleHandlers::MoveBaseTypeModifierAbility.add(:AERILATE, + proc { |ability,user,move,type| + next if !isConst?(type,PBTypes,:NORMAL) || !hasConst?(PBTypes,:FLYING) + move.powerBoost = true + next getConst(PBTypes,:FLYING) + } +) + +BattleHandlers::MoveBaseTypeModifierAbility.add(:GALVANIZE, + proc { |ability,user,move,type| + next if !isConst?(type,PBTypes,:NORMAL) || !hasConst?(PBTypes,:ELECTRIC) + move.powerBoost = true + next getConst(PBTypes,:ELECTRIC) + } +) + +BattleHandlers::MoveBaseTypeModifierAbility.add(:LIQUIDVOICE, + proc { |ability,user,move,type| + next getConst(PBTypes,:WATER) if hasConst?(PBTypes,:WATER) && move.soundMove? + } +) + +BattleHandlers::MoveBaseTypeModifierAbility.add(:NORMALIZE, + proc { |ability,user,move,type| + next if !hasConst?(PBTypes,:NORMAL) + move.powerBoost = true if NEWEST_BATTLE_MECHANICS + next getConst(PBTypes,:NORMAL) + } +) + +BattleHandlers::MoveBaseTypeModifierAbility.add(:PIXILATE, + proc { |ability,user,move,type| + next if !isConst?(type,PBTypes,:NORMAL) || !hasConst?(PBTypes,:FAIRY) + move.powerBoost = true + next getConst(PBTypes,:FAIRY) + } +) + +BattleHandlers::MoveBaseTypeModifierAbility.add(:REFRIGERATE, + proc { |ability,user,move,type| + next if !isConst?(type,PBTypes,:NORMAL) || !hasConst?(PBTypes,:ICE) + move.powerBoost = true + next getConst(PBTypes,:ICE) + } +) + +#=============================================================================== +# AccuracyCalcUserAbility handlers +#=============================================================================== + +BattleHandlers::AccuracyCalcUserAbility.add(:COMPOUNDEYES, + proc { |ability,mods,user,target,move,type| + mods[ACC_MULT] = (mods[ACC_MULT]*1.3).round + } +) + +BattleHandlers::AccuracyCalcUserAbility.add(:HUSTLE, + proc { |ability,mods,user,target,move,type| + mods[ACC_MULT] = (mods[ACC_MULT]*0.8).round if move.physicalMove? + } +) + +BattleHandlers::AccuracyCalcUserAbility.add(:KEENEYE, + proc { |ability,mods,user,target,move,type| + mods[EVA_STAGE] = 0 if mods[EVA_STAGE]>0 && NEWEST_BATTLE_MECHANICS + } +) + +BattleHandlers::AccuracyCalcUserAbility.add(:NOGUARD, + proc { |ability,mods,user,target,move,type| + mods[BASE_ACC] = 0 + } +) + +BattleHandlers::AccuracyCalcUserAbility.add(:UNAWARE, + proc { |ability,mods,user,target,move,type| + mods[EVA_STAGE] = 0 if move.damagingMove? + } +) + +BattleHandlers::AccuracyCalcUserAbility.add(:VICTORYSTAR, + proc { |ability,mods,user,target,move,type| + mods[ACC_MULT] = (mods[ACC_MULT]*1.1).round + } +) + +#=============================================================================== +# AccuracyCalcUserAllyAbility handlers +#=============================================================================== + +BattleHandlers::AccuracyCalcUserAllyAbility.add(:VICTORYSTAR, + proc { |ability,mods,user,target,move,type| + mods[ACC_MULT] = (mods[ACC_MULT]*1.1).round + } +) + +#=============================================================================== +# AccuracyCalcTargetAbility handlers +#=============================================================================== + +BattleHandlers::AccuracyCalcTargetAbility.add(:LIGHTNINGROD, + proc { |ability,mods,user,target,move,type| + mods[BASE_ACC] = 0 if isConst?(type,PBTypes,:ELECTRIC) + } +) + +BattleHandlers::AccuracyCalcTargetAbility.add(:NOGUARD, + proc { |ability,mods,user,target,move,type| + mods[BASE_ACC] = 0 + } +) + +BattleHandlers::AccuracyCalcTargetAbility.add(:SANDVEIL, + proc { |ability,mods,user,target,move,type| + if target.battle.pbWeather==PBWeather::Sandstorm + mods[EVA_MULT] = (mods[EVA_MULT]*1.25).round + end + } +) + +BattleHandlers::AccuracyCalcTargetAbility.add(:SNOWCLOAK, + proc { |ability,mods,user,target,move,type| + if target.battle.pbWeather==PBWeather::Hail + mods[EVA_MULT] = (mods[EVA_MULT]*1.25).round + end + } +) + +BattleHandlers::AccuracyCalcTargetAbility.add(:STORMDRAIN, + proc { |ability,mods,user,target,move,type| + mods[BASE_ACC] = 0 if isConst?(type,PBTypes,:WATER) + } +) + +BattleHandlers::AccuracyCalcTargetAbility.add(:TANGLEDFEET, + proc { |ability,mods,user,target,move,type| + mods[ACC_MULT] /= 2 if target.effects[PBEffects::Confusion]>0 + } +) + +BattleHandlers::AccuracyCalcTargetAbility.add(:UNAWARE, + proc { |ability,mods,user,target,move,type| + mods[ACC_STAGE] = 0 if move.damagingMove? + } +) + +BattleHandlers::AccuracyCalcTargetAbility.add(:WONDERSKIN, + proc { |ability,mods,user,target,move,type| + if move.statusMove? && user.opposes?(target) + mods[BASE_ACC] = 0 if mods[BASE_ACC]>50 + end + } +) + +#=============================================================================== +# DamageCalcUserAbility handlers +#=============================================================================== + +BattleHandlers::DamageCalcUserAbility.add(:AERILATE, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*1.2).round if move.powerBoost + } +) + +BattleHandlers::DamageCalcUserAbility.copy(:AERILATE,:PIXILATE,:REFRIGERATE,:GALVANIZE) + +BattleHandlers::DamageCalcUserAbility.add(:ANALYTIC, + proc { |ability,user,target,move,mults,baseDmg,type| + if (target.battle.choices[target.index][0]!=:UseMove && + target.battle.choices[target.index][0]!=:Shift) || + target.movedThisRound? + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*1.3).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:BLAZE, + proc { |ability,user,target,move,mults,baseDmg,type| + if user.hp<=user.totalhp/3 && isConst?(type,PBTypes,:FIRE) + mults[ATK_MULT] = (mults[ATK_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:DEFEATIST, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[ATK_MULT] = (mults[ATK_MULT]*0.5).round if user.hp<=user.totalhp/2 + } +) + +BattleHandlers::DamageCalcUserAbility.add(:FLAREBOOST, + proc { |ability,user,target,move,mults,baseDmg,type| + if user.burned? && move.specialMove? + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:FLASHFIRE, + proc { |ability,user,target,move,mults,baseDmg,type| + if user.effects[PBEffects::FlashFire] && isConst?(type,PBTypes,:FIRE) + mults[ATK_MULT] = (mults[ATK_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:FLOWERGIFT, + proc { |ability,user,target,move,mults,baseDmg,type| + w = user.battle.pbWeather + if move.physicalMove? && (w==PBWeather::Sun || w==PBWeather::HarshSun) + mults[ATK_MULT] = (mults[ATK_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:GUTS, + proc { |ability,user,target,move,mults,baseDmg,type| + if user.pbHasAnyStatus? && move.physicalMove? + mults[ATK_MULT] = (mults[ATK_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:HUGEPOWER, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[ATK_MULT] *= 2 if move.physicalMove? + } +) + +BattleHandlers::DamageCalcUserAbility.copy(:HUGEPOWER,:PUREPOWER) + +BattleHandlers::DamageCalcUserAbility.add(:HUSTLE, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[ATK_MULT] = (mults[ATK_MULT]*1.5).round if move.physicalMove? + } +) + +BattleHandlers::DamageCalcUserAbility.add(:IRONFIST, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*1.2).round if move.punchingMove? + } +) + +BattleHandlers::DamageCalcUserAbility.add(:MEGALAUNCHER, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*1.5).round if move.pulseMove? + } +) + +BattleHandlers::DamageCalcUserAbility.add(:MINUS, + proc { |ability,user,target,move,mults,baseDmg,type| + next if !move.specialMove? + user.eachAlly do |b| + next if !b.hasActiveAbility?([:MINUS,:PLUS]) + mults[ATK_MULT] = (mults[ATK_MULT]*1.5).round + break + end + } +) + +BattleHandlers::DamageCalcUserAbility.copy(:MINUS,:PLUS) + +BattleHandlers::DamageCalcUserAbility.add(:NEUROFORCE, + proc { |ability,user,target,move,mults,baseDmg,type| + if PBTypes.superEffective?(target.damageState.typeMod) + mults[FINAL_DMG_MULT] = (mults[FINAL_DMG_MULT]*1.25).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:OVERGROW, + proc { |ability,user,target,move,mults,baseDmg,type| + if user.hp<=user.totalhp/3 && isConst?(type,PBTypes,:GRASS) + mults[ATK_MULT] = (mults[ATK_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:RECKLESS, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*1.2).round if move.recoilMove? + } +) + +BattleHandlers::DamageCalcUserAbility.add(:RIVALRY, + proc { |ability,user,target,move,mults,baseDmg,type| + if user.gender!=2 && target.gender!=2 + if user.gender==target.gender + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*1.25).round + else + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*0.75).round + end + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:SANDFORCE, + proc { |ability,user,target,move,mults,baseDmg,type| + if user.battle.pbWeather==PBWeather::Sandstorm && + (isConst?(type,PBTypes,:ROCK) || + isConst?(type,PBTypes,:GROUND) || + isConst?(type,PBTypes,:STEEL)) + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*1.3).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:SHEERFORCE, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*1.3).round if move.addlEffect>0 + } +) + +BattleHandlers::DamageCalcUserAbility.add(:SLOWSTART, + proc { |ability,user,target,move,mults,baseDmg,type| + if user.turnCount<=5 && move.physicalMove? + mults[ATK_MULT] = (mults[ATK_MULT]*0.5).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:SOLARPOWER, + proc { |ability,user,target,move,mults,baseDmg,type| + w = user.battle.pbWeather + if move.specialMove? && (w==PBWeather::Sun || w==PBWeather::HarshSun) + mults[ATK_MULT] = (mults[ATK_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:SNIPER, + proc { |ability,user,target,move,mults,baseDmg,type| + if target.damageState.critical + mults[FINAL_DMG_MULT] = (mults[FINAL_DMG_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:STAKEOUT, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[ATK_MULT] *= 2 if target.battle.choices[target.index][0]==:SwitchOut + } +) + +BattleHandlers::DamageCalcUserAbility.add(:STEELWORKER, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[ATK_MULT] = (mults[ATK_MULT]*1.5).round if isConst?(type,PBTypes,:STEEL) + } +) + +BattleHandlers::DamageCalcUserAbility.add(:STRONGJAW, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*1.5).round if move.bitingMove? + } +) + +BattleHandlers::DamageCalcUserAbility.add(:SWARM, + proc { |ability,user,target,move,mults,baseDmg,type| + if user.hp<=user.totalhp/3 && isConst?(type,PBTypes,:BUG) + mults[ATK_MULT] = (mults[ATK_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:TECHNICIAN, + proc { |ability,user,target,move,mults,baseDmg,type| + if user.index!=target.index && move.id>0 && baseDmg*mults[BASE_DMG_MULT]/0x1000<=60 + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:TINTEDLENS, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[FINAL_DMG_MULT] *= 2 if PBTypes.resistant?(target.damageState.typeMod) + } +) + +BattleHandlers::DamageCalcUserAbility.add(:TORRENT, + proc { |ability,user,target,move,mults,baseDmg,type| + if user.hp<=user.totalhp/3 && isConst?(type,PBTypes,:WATER) + mults[ATK_MULT] = (mults[ATK_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:TOUGHCLAWS, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*4/3.0).round if move.contactMove? + } +) + +BattleHandlers::DamageCalcUserAbility.add(:TOXICBOOST, + proc { |ability,user,target,move,mults,baseDmg,type| + if user.poisoned? && move.physicalMove? + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcUserAbility.add(:WATERBUBBLE, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[ATK_MULT] *= 2 if isConst?(type,PBTypes,:WATER) + } +) + +#=============================================================================== +# DamageCalcUserAllyAbility handlers +#=============================================================================== + +BattleHandlers::DamageCalcUserAllyAbility.add(:BATTERY, + proc { |ability,user,target,move,mults,baseDmg,type| + next if !move.specialMove? + mults[FINAL_DMG_MULT] = (mults[FINAL_DMG_MULT]*1.3).round + } +) + +BattleHandlers::DamageCalcUserAllyAbility.add(:FLOWERGIFT, + proc { |ability,user,target,move,mults,baseDmg,type| + w = user.battle.pbWeather + if move.physicalMove? && (w==PBWeather::Sun || w==PBWeather::HarshSun) + mults[ATK_MULT] = (mults[ATK_MULT]*1.5).round + end + } +) + +#=============================================================================== +# DamageCalcTargetAbility handlers +#=============================================================================== + +BattleHandlers::DamageCalcTargetAbility.add(:DRYSKIN, + proc { |ability,user,target,move,mults,baseDmg,type| + if isConst?(type,PBTypes,:FIRE) + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*1.25).round + end + } +) + +BattleHandlers::DamageCalcTargetAbility.add(:FILTER, + proc { |ability,user,target,move,mults,baseDmg,type| + if PBTypes.superEffective?(target.damageState.typeMod) + mults[FINAL_DMG_MULT] = (mults[FINAL_DMG_MULT]*0.75).round + end + } +) + +BattleHandlers::DamageCalcTargetAbility.copy(:FILTER,:SOLIDROCK) + +BattleHandlers::DamageCalcTargetAbility.add(:FLOWERGIFT, + proc { |ability,user,target,move,mults,baseDmg,type| + w = user.battle.pbWeather + if move.specialMove? && (w==PBWeather::Sun || w==PBWeather::HarshSun) + mults[DEF_MULT] = (mults[DEF_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcTargetAbility.add(:FLUFFY, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[FINAL_DMG_MULT] *= 2 if isConst?(move.calcType,PBTypes,:FIRE) + mults[FINAL_DMG_MULT] = (mults[FINAL_DMG_MULT]*0.5).round if move.contactMove? + } +) + +BattleHandlers::DamageCalcTargetAbility.add(:FURCOAT, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[DEF_MULT] *= 2 if move.physicalMove? || move.function=="122" # Psyshock + } +) + +BattleHandlers::DamageCalcTargetAbility.add(:GRASSPELT, + proc { |ability,user,target,move,mults,baseDmg,type| + if user.battle.field.terrain==PBBattleTerrains::Grassy + mults[DEF_MULT] = (mults[DEF_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcTargetAbility.add(:HEATPROOF, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*0.5).round if isConst?(type,PBTypes,:FIRE) + } +) + +BattleHandlers::DamageCalcTargetAbility.add(:MARVELSCALE, + proc { |ability,user,target,move,mults,baseDmg,type| + if target.pbHasAnyStatus? && move.physicalMove? + mults[DEF_MULT] = (mults[DEF_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcTargetAbility.add(:MULTISCALE, + proc { |ability,user,target,move,mults,baseDmg,type| + if target.hp==target.totalhp + mults[FINAL_DMG_MULT] = (mults[FINAL_DMG_MULT]*0.5).round + end + } +) + +BattleHandlers::DamageCalcTargetAbility.add(:THICKFAT, + proc { |ability,user,target,move,mults,baseDmg,type| + if isConst?(type,PBTypes,:FIRE) || isConst?(type,PBTypes,:ICE) + mults[BASE_DMG_MULT] = (mults[BASE_DMG_MULT]*0.5).round + end + } +) + +BattleHandlers::DamageCalcTargetAbility.add(:WATERBUBBLE, + proc { |ability,user,target,move,mults,baseDmg,type| + if isConst?(type,PBTypes,:FIRE) + mults[FINAL_DMG_MULT] = (mults[FINAL_DMG_MULT]*0.5).round + end + } +) + +#=============================================================================== +# DamageCalcTargetAbilityNonIgnorable handlers +#=============================================================================== + +BattleHandlers::DamageCalcTargetAbilityNonIgnorable.add(:PRISMARMOR, + proc { |ability,user,target,move,mults,baseDmg,type| + if PBTypes.superEffective?(target.damageState.typeMod) + mults[FINAL_DMG_MULT] = (mults[FINAL_DMG_MULT]*0.75).round + end + } +) + +BattleHandlers::DamageCalcTargetAbilityNonIgnorable.add(:SHADOWSHIELD, + proc { |ability,user,target,move,mults,baseDmg,type| + if target.hp==target.totalhp + mults[FINAL_DMG_MULT] = (mults[FINAL_DMG_MULT]*0.5).round + end + } +) + +#=============================================================================== +# DamageCalcTargetAllyAbility handlers +#=============================================================================== + +BattleHandlers::DamageCalcTargetAllyAbility.add(:FLOWERGIFT, + proc { |ability,user,target,move,mults,baseDmg,type| + w = user.battle.pbWeather + if move.specialMove? && (w==PBWeather::Sun || w==PBWeather::HarshSun) + mults[DEF_MULT] = (mults[DEF_MULT]*1.5).round + end + } +) + +BattleHandlers::DamageCalcTargetAllyAbility.add(:FRIENDGUARD, + proc { |ability,user,target,move,mults,baseDmg,type| + mults[FINAL_DMG_MULT] = (mults[FINAL_DMG_MULT]*0.75).round + } +) + +#=============================================================================== +# CriticalCalcUserAbility handlers +#=============================================================================== + +BattleHandlers::CriticalCalcUserAbility.add(:MERCILESS, + proc { |ability,user,target,c| + next 99 if target.poisoned? + } +) + +BattleHandlers::CriticalCalcUserAbility.add(:SUPERLUCK, + proc { |ability,user,target,c| + next c+1 + } +) + +#=============================================================================== +# CriticalCalcTargetAbility handlers +#=============================================================================== + +BattleHandlers::CriticalCalcTargetAbility.add(:BATTLEARMOR, + proc { |ability,user,target,c| + next -1 + } +) + +BattleHandlers::CriticalCalcTargetAbility.copy(:BATTLEARMOR,:SHELLARMOR) + +#=============================================================================== +# TargetAbilityOnHit handlers +#=============================================================================== + +BattleHandlers::TargetAbilityOnHit.add(:AFTERMATH, + proc { |ability,user,target,move,battle| + next if !target.fainted? + next if !move.pbContactMove?(user) + battle.pbShowAbilitySplash(target) + if !battle.moldBreaker + dampBattler = battle.pbCheckGlobalAbility(:DAMP) + if dampBattler + battle.pbShowAbilitySplash(dampBattler) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1} cannot use {2}!",target.pbThis,target.abilityName)) + else + battle.pbDisplay(_INTL("{1} cannot use {2} because of {3}'s {4}!", + target.pbThis,target.abilityName,dampBattler.pbThis(true),dampBattler.abilityName)) + end + battle.pbHideAbilitySplash(dampBattler) + battle.pbHideAbilitySplash(target) + next + end + end + if user.takesIndirectDamage?(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) && + user.affectedByContactEffect?(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + battle.scene.pbDamageAnimation(user) + user.pbReduceHP(user.totalhp/4,false) + battle.pbDisplay(_INTL("{1} was caught in the aftermath!",user.pbThis)) + end + battle.pbHideAbilitySplash(target) + } +) + +BattleHandlers::TargetAbilityOnHit.add(:ANGERPOINT, + proc { |ability,user,target,move,battle| + next if !target.damageState.critical + next if !target.pbCanRaiseStatStage?(PBStats::ATTACK,target) + battle.pbShowAbilitySplash(target) + target.stages[PBStats::ATTACK] = 6 + battle.pbCommonAnimation("StatUp",target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1} maxed its {2}!",target.pbThis,PBStats.getName(PBStats::ATTACK))) + else + battle.pbDisplay(_INTL("{1}'s {2} maxed its {3}!", + target.pbThis,target.abilityName,PBStats.getName(PBStats::ATTACK))) + end + battle.pbHideAbilitySplash(target) + } +) + +BattleHandlers::TargetAbilityOnHit.add(:CURSEDBODY, + proc { |ability,user,target,move,battle| + next if user.fainted? + next if user.effects[PBEffects::Disable]>0 + regularMove = nil + user.eachMove do |m| + next if m.id!=user.lastRegularMoveUsed + regularMove = m + break + end + next if !regularMove || (regularMove.pp==0 && regularMove.totalpp>0) + next if battle.pbRandom(100)>=30 + battle.pbShowAbilitySplash(target) + if !move.pbMoveFailedAromaVeil?(target,user,PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + user.effects[PBEffects::Disable] = 3 + user.effects[PBEffects::DisableMove] = regularMove.id + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s {2} was disabled!",user.pbThis,regularMove.name)) + else + battle.pbDisplay(_INTL("{1}'s {2} was disabled by {3}'s {4}!", + user.pbThis,regularMove.name,target.pbThis(true),target.abilityName)) + end + battle.pbHideAbilitySplash(target) + user.pbItemStatusCureCheck + end + battle.pbHideAbilitySplash(target) + } +) + +BattleHandlers::TargetAbilityOnHit.add(:CUTECHARM, + proc { |ability,user,target,move,battle| + next if target.fainted? + next if !move.pbContactMove?(user) + next if battle.pbRandom(100)>=30 + battle.pbShowAbilitySplash(target) + if user.pbCanAttract?(target,PokeBattle_SceneConstants::USE_ABILITY_SPLASH) && + user.affectedByContactEffect?(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + msg = nil + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + msg = _INTL("{1}'s {2} made {3} fall in love!",target.pbThis, + target.abilityName,user.pbThis(true)) + end + user.pbAttract(target,msg) + end + battle.pbHideAbilitySplash(target) + } +) + +BattleHandlers::TargetAbilityOnHit.add(:EFFECTSPORE, + proc { |ability,user,target,move,battle| + # NOTE: This ability has a 30% chance of triggering, not a 30% chance of + # inflicting a status condition. It can try (and fail) to inflict a + # status condition that the user is immune to. + next if !move.pbContactMove?(user) + next if battle.pbRandom(100)>=30 + r = battle.pbRandom(3) + next if r==0 && user.asleep? + next if r==1 && user.poisoned? + next if r==2 && user.paralyzed? + battle.pbShowAbilitySplash(target) + if user.affectedByPowder?(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) && + user.affectedByContactEffect?(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + case r + when 0 + if user.pbCanSleep?(target,PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + msg = nil + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + msg = _INTL("{1}'s {2} made {3} fall asleep!",target.pbThis, + target.abilityName,user.pbThis(true)) + end + user.pbSleep(msg) + end + when 1 + if user.pbCanPoison?(target,PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + msg = nil + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + msg = _INTL("{1}'s {2} poisoned {3}!",target.pbThis, + target.abilityName,user.pbThis(true)) + end + user.pbPoison(target,msg) + end + when 2 + if user.pbCanParalyze?(target,PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + msg = nil + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + msg = _INTL("{1}'s {2} paralyzed {3}! It may be unable to move!", + target.pbThis,target.abilityName,user.pbThis(true)) + end + user.pbParalyze(target,msg) + end + end + end + battle.pbHideAbilitySplash(target) + } +) + +BattleHandlers::TargetAbilityOnHit.add(:FLAMEBODY, + proc { |ability,user,target,move,battle| + next if !move.pbContactMove?(user) + next if user.burned? || battle.pbRandom(100)>=30 + battle.pbShowAbilitySplash(target) + if user.pbCanBurn?(target,PokeBattle_SceneConstants::USE_ABILITY_SPLASH) && + user.affectedByContactEffect?(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + msg = nil + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + msg = _INTL("{1}'s {2} burned {3}!",target.pbThis,target.abilityName,user.pbThis(true)) + end + user.pbBurn(target,msg) + end + battle.pbHideAbilitySplash(target) + } +) + +BattleHandlers::TargetAbilityOnHit.add(:GOOEY, + proc { |ability,user,target,move,battle| + next if !move.pbContactMove?(user) + user.pbLowerStatStageByAbility(PBStats::SPEED,1,target,true,true) + } +) + +BattleHandlers::TargetAbilityOnHit.copy(:GOOEY,:TANGLINGHAIR) + +BattleHandlers::TargetAbilityOnHit.add(:ILLUSION, + proc { |ability,user,target,move,battle| + # NOTE: This intentionally doesn't show the ability splash. + next if !target.effects[PBEffects::Illusion] + target.effects[PBEffects::Illusion] = nil + battle.scene.pbChangePokemon(target,target.pokemon) + battle.pbDisplay(_INTL("{1}'s illusion wore off!",target.pbThis)) + battle.pbSetSeen(target) + } +) + +BattleHandlers::TargetAbilityOnHit.add(:INNARDSOUT, + proc { |ability,user,target,move,battle| + next if !target.fainted? || user.dummy + battle.pbShowAbilitySplash(target) + if user.takesIndirectDamage?(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + battle.scene.pbDamageAnimation(user) + user.pbReduceHP(target.damageState.hpLost,false) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1} is hurt!",user.pbThis)) + else + battle.pbDisplay(_INTL("{1} is hurt by {2}'s {3}!",user.pbThis, + target.pbThis(true),target.abilityName)) + end + end + battle.pbHideAbilitySplash(target) + } +) + +BattleHandlers::TargetAbilityOnHit.add(:IRONBARBS, + proc { |ability,user,target,move,battle| + next if !move.pbContactMove?(user) + battle.pbShowAbilitySplash(target) + if user.takesIndirectDamage?(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) && + user.affectedByContactEffect?(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + battle.scene.pbDamageAnimation(user) + user.pbReduceHP(user.totalhp/8,false) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1} is hurt!",user.pbThis)) + else + battle.pbDisplay(_INTL("{1} is hurt by {2}'s {3}!",user.pbThis, + target.pbThis(true),target.abilityName)) + end + end + battle.pbHideAbilitySplash(target) + } +) + +BattleHandlers::TargetAbilityOnHit.copy(:IRONBARBS,:ROUGHSKIN) + +BattleHandlers::TargetAbilityOnHit.add(:JUSTIFIED, + proc { |ability,user,target,move,battle| + next if !isConst?(move.calcType,PBTypes,:DARK) + target.pbRaiseStatStageByAbility(PBStats::ATTACK,1,target) + } +) + +BattleHandlers::TargetAbilityOnHit.add(:MUMMY, + proc { |ability,user,target,move,battle| + next if !move.pbContactMove?(user) + next if user.fainted? + abilityBlacklist = [ + # This ability + :MUMMY, + # Form-changing abilities + :BATTLEBOND, + :DISGUISE, +# :FLOWERGIFT, # This can be replaced +# :FORECAST, # This can be replaced + :MULTITYPE, + :POWERCONSTRUCT, + :SCHOOLING, + :SHIELDSDOWN, + :STANCECHANGE, + :ZENMODE, + # Abilities intended to be inherent properties of a certain species + :COMATOSE, + :RKSSYSTEM, + ] + failed = false + abilityBlacklist.each do |abil| + next if !isConst?(user.ability,PBAbilities,abil) + failed = true + break + end + next if failed + oldAbil = -1 + battle.pbShowAbilitySplash(target) if user.opposes?(target) + if user.affectedByContactEffect?(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + oldAbil = user.ability + battle.pbShowAbilitySplash(user,true,false) if user.opposes?(target) + user.ability = getConst(PBAbilities,:MUMMY) + battle.pbReplaceAbilitySplash(user) if user.opposes?(target) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s Ability became {2}!",user.pbThis,user.abilityName)) + else + battle.pbDisplay(_INTL("{1}'s Ability became {2} because of {3}!", + user.pbThis,user.abilityName,target.pbThis(true))) + end + battle.pbHideAbilitySplash(user) if user.opposes?(target) + end + battle.pbHideAbilitySplash(target) if user.opposes?(target) + user.pbOnAbilityChanged(oldAbil) if oldAbil>=0 + } +) + +BattleHandlers::TargetAbilityOnHit.add(:POISONPOINT, + proc { |ability,user,target,move,battle| + next if !move.pbContactMove?(user) + next if user.poisoned? || battle.pbRandom(100)>=30 + battle.pbShowAbilitySplash(target) + if user.pbCanPoison?(target,PokeBattle_SceneConstants::USE_ABILITY_SPLASH) && + user.affectedByContactEffect?(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + msg = nil + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + msg = _INTL("{1}'s {2} poisoned {3}!",target.pbThis,target.abilityName,user.pbThis(true)) + end + user.pbPoison(target,msg) + end + battle.pbHideAbilitySplash(target) + } +) + +BattleHandlers::TargetAbilityOnHit.add(:RATTLED, + proc { |ability,user,target,move,battle| + next if !isConst?(move.calcType,PBTypes,:BUG) && + !isConst?(move.calcType,PBTypes,:DARK) && + !isConst?(move.calcType,PBTypes,:GHOST) + target.pbRaiseStatStageByAbility(PBStats::SPEED,1,target) + } +) + +BattleHandlers::TargetAbilityOnHit.add(:STAMINA, + proc { |ability,user,target,move,battle| + target.pbRaiseStatStageByAbility(PBStats::DEFENSE,1,target) + } +) + +BattleHandlers::TargetAbilityOnHit.add(:STATIC, + proc { |ability,user,target,move,battle| + next if !move.pbContactMove?(user) + next if user.paralyzed? || battle.pbRandom(100)>=30 + battle.pbShowAbilitySplash(target) + if user.pbCanParalyze?(target,PokeBattle_SceneConstants::USE_ABILITY_SPLASH) && + user.affectedByContactEffect?(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + msg = nil + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + msg = _INTL("{1}'s {2} paralyzed {3}! It may be unable to move!", + target.pbThis,target.abilityName,user.pbThis(true)) + end + user.pbParalyze(target,msg) + end + battle.pbHideAbilitySplash(target) + } +) + +BattleHandlers::TargetAbilityOnHit.add(:WATERCOMPACTION, + proc { |ability,user,target,move,battle| + next if !isConst?(move.calcType,PBTypes,:WATER) + target.pbRaiseStatStageByAbility(PBStats::DEFENSE,2,target) + } +) + +BattleHandlers::TargetAbilityOnHit.add(:WEAKARMOR, + proc { |ability,user,target,move,battle| + next if !move.physicalMove? + next if !target.pbCanLowerStatStage?(PBStats::DEFENSE,target) && + !target.pbCanRaiseStatStage?(PBStats::SPEED,target) + battle.pbShowAbilitySplash(target) + target.pbLowerStatStageByAbility(PBStats::DEFENSE,1,target,false) + target.pbRaiseStatStageByAbility(PBStats::SPEED, + (NEWEST_BATTLE_MECHANICS) ? 2 : 1,target,false) + battle.pbHideAbilitySplash(target) + } +) + +#=============================================================================== +# UserAbilityOnHit handlers +#=============================================================================== + +BattleHandlers::UserAbilityOnHit.add(:POISONTOUCH, + proc { |ability,user,target,move,battle| + next if !move.contactMove? + next if battle.pbRandom(100)>=30 + battle.pbShowAbilitySplash(user) + if target.hasActiveAbility?(:SHIELDDUST) && !battle.moldBreaker + battle.pbShowAbilitySplash(target) + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1} is unaffected!",target.pbThis)) + end + battle.pbHideAbilitySplash(target) + elsif target.pbCanPoison?(user,PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + msg = nil + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + msg = _INTL("{1}'s {2} poisoned {3}!",user.pbThis,user.abilityName,target.pbThis(true)) + end + target.pbPoison(user,msg) + end + battle.pbHideAbilitySplash(user) + } +) + +#=============================================================================== +# UserAbilityEndOfMove handlers +#=============================================================================== + +BattleHandlers::UserAbilityEndOfMove.add(:BEASTBOOST, + proc { |ability,user,targets,move,battle| + next if battle.pbAllFainted?(user.idxOpposingSide) + numFainted = 0 + targets.each { |b| numFainted += 1 if b.damageState.fainted } + next if numFainted==0 + userStats = user.plainStats + highestStatValue = userStats.max + PBStats.eachMainBattleStat do |s| + next if userStats[s]0 + next if battle.wildBattle? && user.opposes? + targets.each do |b| + next if b.damageState.unaffected || b.damageState.substitute + next if b.item==0 + next if b.unlosableItem?(b.item) || user.unlosableItem?(b.item) + battle.pbShowAbilitySplash(user) + if b.hasActiveAbility?(:STICKYHOLD) + battle.pbShowAbilitySplash(b) if user.opposes?(b) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s item cannot be stolen!",b.pbThis)) + end + battle.pbHideAbilitySplash(b) if user.opposes?(b) + next + end + user.item = b.item + b.item = 0 + b.effects[PBEffects::Unburden] = true + if battle.wildBattle? && user.initialItem==0 && b.initialItem==user.item + user.setInitialItem(user.item) + b.setInitialItem(0) + end + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1} stole {2}'s {3}!",user.pbThis, + b.pbThis(true),user.itemName)) + else + battle.pbDisplay(_INTL("{1} stole {2}'s {3} with {4}!",user.pbThis, + b.pbThis(true),user.itemName,user.abilityName)) + end + battle.pbHideAbilitySplash(user) + user.pbHeldItemTriggerCheck + break + end + } +) + +BattleHandlers::UserAbilityEndOfMove.add(:MOXIE, + proc { |ability,user,targets,move,battle| + next if battle.pbAllFainted?(user.idxOpposingSide) + numFainted = 0 + targets.each { |b| numFainted += 1 if b.damageState.fainted } + next if numFainted==0 || !user.pbCanRaiseStatStage?(PBStats::ATTACK,user) + user.pbRaiseStatStageByAbility(PBStats::ATTACK,numFainted,user) + } +) + +#=============================================================================== +# TargetAbilityAfterMoveUse handlers +#=============================================================================== + +BattleHandlers::TargetAbilityAfterMoveUse.add(:BERSERK, + proc { |ability,target,user,move,switched,battle| + next if !move.damagingMove? + next if target.damageState.initialHP=target.totalhp/2 + next if !target.pbCanRaiseStatStage?(PBStats::SPATK,target) + target.pbRaiseStatStageByAbility(PBStats::SPATK,1,target) + } +) + +BattleHandlers::TargetAbilityAfterMoveUse.add(:COLORCHANGE, + proc { |ability,target,user,move,switched,battle| + next if target.damageState.calcDamage==0 || target.damageState.substitute + next if move.calcType<0 || PBTypes.isPseudoType?(move.calcType) + next if target.pbHasType?(move.calcType) && !target.pbHasOtherType?(move.calcType) + typeName = PBTypes.getName(move.calcType) + battle.pbShowAbilitySplash(target) + target.pbChangeTypes(move.calcType) + battle.pbDisplay(_INTL("{1}'s {2} made it the {3} type!",target.pbThis, + target.abilityName,typeName)) + battle.pbHideAbilitySplash(target) + } +) + +BattleHandlers::TargetAbilityAfterMoveUse.add(:PICKPOCKET, + proc { |ability,target,user,move,switched,battle| + # NOTE: According to Bulbapedia, this can still trigger to steal the user's + # item even if it was switched out by a Red Card. This doesn't make + # sense, so this code doesn't do it. + next if battle.wildBattle? && target.opposes? + next if !move.contactMove? + next if switched.include?(user.index) + next if user.effects[PBEffects::Substitute]>0 || target.damageState.substitute + next if target.item>0 || user.item==0 + next if user.unlosableItem?(user.item) || target.unlosableItem?(user.item) + battle.pbShowAbilitySplash(target) + if user.hasActiveAbility?(:STICKYHOLD) + battle.pbShowAbilitySplash(user) if target.opposes?(user) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s item cannot be stolen!",user.pbThis)) + end + battle.pbHideAbilitySplash(user) if target.opposes?(user) + battle.pbHideAbilitySplash(target) + next + end + target.item = user.item + user.item = 0 + user.effects[PBEffects::Unburden] = true + if battle.wildBattle? && target.initialItem==0 && user.initialItem==target.item + target.setInitialItem(target.item) + user.setInitialItem(0) + end + battle.pbDisplay(_INTL("{1} pickpocketed {2}'s {3}!",target.pbThis, + user.pbThis(true),target.itemName)) + battle.pbHideAbilitySplash(target) + target.pbHeldItemTriggerCheck + } +) + +#=============================================================================== +# EORWeatherAbility handlers +#=============================================================================== + +BattleHandlers::EORWeatherAbility.add(:DRYSKIN, + proc { |ability,weather,battler,battle| + case weather + when PBWeather::Sun, PBWeather::HarshSun + battle.pbShowAbilitySplash(battler) + battle.scene.pbDamageAnimation(battler) + battler.pbReduceHP(battler.totalhp/8,false) + battle.pbDisplay(_INTL("{1} was hurt by the sunlight!",battler.pbThis)) + battle.pbHideAbilitySplash(battler) + battler.pbItemHPHealCheck + when PBWeather::Rain, PBWeather::HeavyRain + next if !battler.canHeal? + battle.pbShowAbilitySplash(battler) + battler.pbRecoverHP(battler.totalhp/8) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s HP was restored.",battler.pbThis)) + else + battle.pbDisplay(_INTL("{1}'s {2} restored its HP.",battler.pbThis,battler.abilityName)) + end + battle.pbHideAbilitySplash(battler) + end + } +) + +BattleHandlers::EORWeatherAbility.add(:ICEBODY, + proc { |ability,weather,battler,battle| + next unless weather==PBWeather::Hail + next if !battler.canHeal? + battle.pbShowAbilitySplash(battler) + battler.pbRecoverHP(battler.totalhp/16) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s HP was restored.",battler.pbThis)) + else + battle.pbDisplay(_INTL("{1}'s {2} restored its HP.",battler.pbThis,battler.abilityName)) + end + battle.pbHideAbilitySplash(battler) + } +) + +BattleHandlers::EORWeatherAbility.add(:RAINDISH, + proc { |ability,weather,battler,battle| + next unless weather==PBWeather::Rain || weather==PBWeather::HeavyRain + next if !battler.canHeal? + battle.pbShowAbilitySplash(battler) + battler.pbRecoverHP(battler.totalhp/16) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1}'s HP was restored.",battler.pbThis)) + else + battle.pbDisplay(_INTL("{1}'s {2} restored its HP.",battler.pbThis,battler.abilityName)) + end + battle.pbHideAbilitySplash(battler) + } +) + +BattleHandlers::EORWeatherAbility.add(:SOLARPOWER, + proc { |ability,weather,battler,battle| + next unless weather==PBWeather::Sun || weather==PBWeather::HarshSun + battle.pbShowAbilitySplash(battler) + battle.scene.pbDamageAnimation(battler) + battler.pbReduceHP(battler.totalhp/8,false) + battle.pbDisplay(_INTL("{1} was hurt by the sunlight!",battler.pbThis)) + battle.pbHideAbilitySplash(battler) + battler.pbItemHPHealCheck + } +) + +#=============================================================================== +# EORHealingAbility handlers +#=============================================================================== + +BattleHandlers::EORHealingAbility.add(:HEALER, + proc { |ability,battler,battle| + next unless battle.pbRandom(100)<30 + battler.eachAlly do |b| + next if b.status==PBStatuses::NONE + battle.pbShowAbilitySplash(battler) + oldStatus = b.status + b.pbCureStatus(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + case oldStatus + when PBStatuses::SLEEP + battle.pbDisplay(_INTL("{1}'s {2} woke its partner up!",battler.pbThis,battler.abilityName)) + when PBStatuses::POISON + battle.pbDisplay(_INTL("{1}'s {2} cured its partner's poison!",battler.pbThis,battler.abilityName)) + when PBStatuses::BURN + battle.pbDisplay(_INTL("{1}'s {2} healed its partner's burn!",battler.pbThis,battler.abilityName)) + when PBStatuses::PARALYSIS + battle.pbDisplay(_INTL("{1}'s {2} cured its partner's paralysis!",battler.pbThis,battler.abilityName)) + when PBStatuses::FROZEN + battle.pbDisplay(_INTL("{1}'s {2} defrosted its partner!",battler.pbThis,battler.abilityName)) + end + end + battle.pbHideAbilitySplash(battler) + end + } +) + +BattleHandlers::EORHealingAbility.add(:HYDRATION, + proc { |ability,battler,battle| + next if battler.status==PBStatuses::NONE + curWeather = battle.pbWeather + next if curWeather!=PBWeather::Rain && curWeather!=PBWeather::HeavyRain + battle.pbShowAbilitySplash(battler) + oldStatus = battler.status + battler.pbCureStatus(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + case oldStatus + when PBStatuses::SLEEP + battle.pbDisplay(_INTL("{1}'s {2} woke it up!",battler.pbThis,battler.abilityName)) + when PBStatuses::POISON + battle.pbDisplay(_INTL("{1}'s {2} cured its poison!",battler.pbThis,battler.abilityName)) + when PBStatuses::BURN + battle.pbDisplay(_INTL("{1}'s {2} healed its burn!",battler.pbThis,battler.abilityName)) + when PBStatuses::PARALYSIS + battle.pbDisplay(_INTL("{1}'s {2} cured its paralysis!",battler.pbThis,battler.abilityName)) + when PBStatuses::FROZEN + battle.pbDisplay(_INTL("{1}'s {2} defrosted it!",battler.pbThis,battler.abilityName)) + end + end + battle.pbHideAbilitySplash(battler) + } +) + +BattleHandlers::EORHealingAbility.add(:SHEDSKIN, + proc { |ability,battler,battle| + next if battler.status==PBStatuses::NONE + next unless battle.pbRandom(100)<30 + battle.pbShowAbilitySplash(battler) + oldStatus = battler.status + battler.pbCureStatus(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + case oldStatus + when PBStatuses::SLEEP + battle.pbDisplay(_INTL("{1}'s {2} woke it up!",battler.pbThis,battler.abilityName)) + when PBStatuses::POISON + battle.pbDisplay(_INTL("{1}'s {2} cured its poison!",battler.pbThis,battler.abilityName)) + when PBStatuses::BURN + battle.pbDisplay(_INTL("{1}'s {2} healed its burn!",battler.pbThis,battler.abilityName)) + when PBStatuses::PARALYSIS + battle.pbDisplay(_INTL("{1}'s {2} cured its paralysis!",battler.pbThis,battler.abilityName)) + when PBStatuses::FROZEN + battle.pbDisplay(_INTL("{1}'s {2} defrosted it!",battler.pbThis,battler.abilityName)) + end + end + battle.pbHideAbilitySplash(battler) + } +) + +#=============================================================================== +# EOREffectAbility handlers +#=============================================================================== + +BattleHandlers::EOREffectAbility.add(:BADDREAMS, + proc { |ability,battler,battle| + battle.eachOtherSideBattler(battler.index) do |b| + next if !b.near?(battler) || !b.asleep? + battle.pbShowAbilitySplash(battler) + next if !b.takesIndirectDamage?(PokeBattle_SceneConstants::USE_ABILITY_SPLASH) + oldHP = b.hp + b.pbReduceHP(b.totalhp/8) + if PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1} is tormented!",b.pbThis)) + else + battle.pbDisplay(_INTL("{1} is tormented by {2}'s {3}!",b.pbThis, + battler.pbThis(true),battler.abilityName)) + end + battle.pbHideAbilitySplash(battler) + b.pbItemHPHealCheck + b.pbAbilitiesOnDamageTaken(oldHP) + b.pbFaint if b.fainted? + end + } +) + +BattleHandlers::EOREffectAbility.add(:MOODY, + proc { |ability,battler,battle| + randomUp = []; randomDown = [] + PBStats.eachBattleStat do |s| + randomUp.push(s) if battler.pbCanRaiseStatStage?(s,battler) + randomDown.push(s) if battler.pbCanLowerStatStage?(s,battler) + end + next if randomUp.length==0 && randomDown.length==0 + battle.pbShowAbilitySplash(battler) + if randomUp.length>0 + r = battle.pbRandom(randomUp.length) + battler.pbRaiseStatStageByAbility(randomUp[r],2,battler,false) + randomDown.delete(randomUp[r]) + end + if randomDown.length>0 + r = battle.pbRandom(randomDown.length) + battler.pbLowerStatStageByAbility(randomDown[r],1,battler,false) + end + battle.pbHideAbilitySplash(battler) + battler.pbItemStatRestoreCheck if randomDown.length>0 + } +) + +BattleHandlers::EOREffectAbility.add(:SPEEDBOOST, + proc { |ability,battler,battle| + # A Pokémon's turnCount is 0 if it became active after the beginning of a + # round + if battler.turnCount>0 && battler.pbCanRaiseStatStage?(PBStats::SPEED,battler) + battler.pbRaiseStatStageByAbility(PBStats::SPEED,1,battler) + end + } +) + +#=============================================================================== +# EORGainItemAbility handlers +#=============================================================================== + +BattleHandlers::EORGainItemAbility.add(:HARVEST, + proc { |ability,battler,battle| + next if battler.item>0 + next if battler.recycleItem<=0 || !pbIsBerry?(battler.recycleItem) + curWeather = battle.pbWeather + if curWeather!=PBWeather::Sun && curWeather!=PBWeather::HarshSun + next unless battle.pbRandom(100)<50 + end + battle.pbShowAbilitySplash(battler) + battler.item = battler.recycleItem + battler.setRecycleItem(0) + battler.setInitialItem(battler.item) if battler.initialItem==0 + battle.pbDisplay(_INTL("{1} harvested one {2}!",battler.pbThis,battler.itemName)) + battle.pbHideAbilitySplash(battler) + battler.pbHeldItemTriggerCheck + } +) + +BattleHandlers::EORGainItemAbility.add(:PICKUP, + proc { |ability,battler,battle| + next if battler.item>0 + foundItem = 0; fromBattler = nil; use = 0 + battle.eachBattler do |b| + next if b.index==battler.index + next if b.effects[PBEffects::PickupUse]<=use + foundItem = b.effects[PBEffects::PickupItem] + fromBattler = b + use = b.effects[PBEffects::PickupUse] + end + next if foundItem<=0 + battle.pbShowAbilitySplash(battler) + battler.item = foundItem + fromBattler.effects[PBEffects::PickupItem] = 0 + fromBattler.effects[PBEffects::PickupUse] = 0 + fromBattler.setRecycleItem(0) if fromBattler.recycleItem==foundItem + if battle.wildBattle? && battler.initialItem==0 && fromBattler.initialItem==foundItem + battler.setInitialItem(foundItem) + fromBattler.setInitialItem(0) + end + battle.pbDisplay(_INTL("{1} found one {2}!",battler.pbThis,battler.itemName)) + battle.pbHideAbilitySplash(battler) + battler.pbHeldItemTriggerCheck + } +) + +#=============================================================================== +# CertainSwitchingUserAbility handlers +#=============================================================================== + +# There aren't any! + +#=============================================================================== +# TrappingTargetAbility handlers +#=============================================================================== + +BattleHandlers::TrappingTargetAbility.add(:ARENATRAP, + proc { |ability,switcher,bearer,battle| + next true if !switcher.airborne? + } +) + +BattleHandlers::TrappingTargetAbility.add(:MAGNETPULL, + proc { |ability,switcher,bearer,battle| + next true if switcher.pbHasType?(:STEEL) + } +) + +BattleHandlers::TrappingTargetAbility.add(:SHADOWTAG, + proc { |ability,switcher,bearer,battle| + next true if !switcher.hasActiveAbility?(:SHADOWTAG) + } +) + +#=============================================================================== +# AbilityOnSwitchIn handlers +#=============================================================================== + +BattleHandlers::AbilityOnSwitchIn.add(:AIRLOCK, + proc { |ability,battler,battle| + battle.pbShowAbilitySplash(battler) + if !PokeBattle_SceneConstants::USE_ABILITY_SPLASH + battle.pbDisplay(_INTL("{1} has {2}!",battler.pbThis,battler.abilityName)) + end + battle.pbDisplay(_INTL("The effects of the weather disappeared.")) + battle.pbHideAbilitySplash(battler) + } +) + +BattleHandlers::AbilityOnSwitchIn.copy(:AIRLOCK,:CLOUDNINE) + +BattleHandlers::AbilityOnSwitchIn.add(:ANTICIPATION, + proc { |ability,battler,battle| + next if !battler.pbOwnedByPlayer? + battlerTypes = battler.pbTypes(true) + type1 = (battlerTypes.length>0) ? battlerTypes[0] : nil + type2 = (battlerTypes.length>1) ? battlerTypes[1] : type1 + type3 = (battlerTypes.length>2) ? battlerTypes[2] : type2 + found = false + battle.eachOtherSideBattler(battler.index) do |b| + b.eachMove do |m| + next if m.statusMove? + moveData = pbGetMoveData(m.id) + if type1 + moveType = moveData[MOVE_TYPE] + if NEWEST_BATTLE_MECHANICS && isConst?(m.id,PBMoves,:HIDDENPOWER) + moveType = pbHiddenPower(b.pokemon)[0] + end + eff = PBTypes.getCombinedEffectiveness(moveData[MOVE_TYPE],type1,type2,type3) + next if PBTypes.ineffective?(eff) + next if !PBTypes.superEffective?(eff) && moveData[MOVE_FUNCTION_CODE]!="070" # OHKO + else + next if moveData[MOVE_FUNCTION_CODE]!="070" # OHKO + end + found = true + break + end + break if found + end + if found + battle.pbShowAbilitySplash(battler) + battle.pbDisplay(_INTL("{1} shuddered with anticipation!",battler.pbThis)) + battle.pbHideAbilitySplash(battler) + end + } +) + +BattleHandlers::AbilityOnSwitchIn.add(:AURABREAK, + proc { |ability,battler,battle| + battle.pbShowAbilitySplash(battler) + battle.pbDisplay(_INTL("{1} reversed all other Pokémon's auras!",battler.pbThis)) + battle.pbHideAbilitySplash(battler) + } +) + +BattleHandlers::AbilityOnSwitchIn.add(:COMATOSE, + proc { |ability,battler,battle| + battle.pbShowAbilitySplash(battler) + battle.pbDisplay(_INTL("{1} is drowsing!",battler.pbThis)) + battle.pbHideAbilitySplash(battler) + } +) + +BattleHandlers::AbilityOnSwitchIn.add(:DARKAURA, + proc { |ability,battler,battle| + battle.pbShowAbilitySplash(battler) + battle.pbDisplay(_INTL("{1} is radiating a dark aura!",battler.pbThis)) + battle.pbHideAbilitySplash(battler) + } +) + +BattleHandlers::AbilityOnSwitchIn.add(:DELTASTREAM, + proc { |ability,battler,battle| + pbBattleWeatherAbility(PBWeather::StrongWinds,battler,battle,true) + } +) + +BattleHandlers::AbilityOnSwitchIn.add(:DESOLATELAND, + proc { |ability,battler,battle| + pbBattleWeatherAbility(PBWeather::HarshSun,battler,battle,true) + } +) + +BattleHandlers::AbilityOnSwitchIn.add(:DOWNLOAD, + proc { |ability,battler,battle| + oDef = oSpDef = 0 + battle.eachOtherSideBattler(battler.index) do |b| + oDef += b.defense + oSpDef += b.spdef + end + stat = (oDef