18 Commits

Author SHA1 Message Date
chardub
e11e62272c possible missing files 2025-09-28 16:18:37 -04:00
chardub
51f98b9cc7 sprites credits 2025-09-28 16:14:12 -04:00
chardub
318ff90d8d update 6.7 2025-09-28 15:53:01 -04:00
chardub
ef5e023ae0 6.6.2 2025-06-28 15:18:40 -04:00
chardub
d383af130b koga necrozma fix 2025-06-11 15:46:36 -04:00
chardub
0c50a74a13 fixes typo causing crash 2025-06-10 16:10:31 -04:00
chardub
3e6e955070 6.6.1 patch 2025-06-10 12:45:27 -04:00
chardub
4ba51c7473 Fixes necrozma quest crash 2025-06-08 01:08:32 -04:00
chardub
aa795459b4 game.exe (needed for launcher) 2025-06-08 01:00:33 -04:00
chardub
fd3429c3b5 6.6 release fixes 2025-06-07 14:42:44 -04:00
chardub
a50151c4af fixes oran berry hat 2025-06-07 13:07:41 -04:00
chardub
3622cb9bed fixes oran berry hat 2025-06-07 12:47:25 -04:00
chardub
23de3cb6e4 updates gitignore 2025-06-07 12:00:11 -04:00
chardub
451490edc7 spritesheets cache 2025-06-07 11:25:44 -04:00
chardub
30db0a406c gitkeep 2025-06-07 11:18:41 -04:00
chardub
937527828f removes temp folder 2025-06-07 10:40:53 -04:00
chardub
b2f6901c6b fixes alt sprites option being on by default 2025-06-07 10:22:50 -04:00
chardub
a393ba1137 6.6 update 2025-06-07 08:16:50 -04:00
662 changed files with 386766 additions and 22992 deletions

3
.gitignore vendored
View File

@@ -1,6 +1,6 @@
Graphics/CustomBattlers/local_sprites/*
Graphics/Battlers/Shiny/*
Graphics/Pokemon/FusionIcons/*
Graphics/CustomBattlers/spritesheets/*
Graphics/CustomBattlers/*
Data/sprites/*
@@ -14,5 +14,4 @@ Data/sprites/sprites_rate_limit.log
Game.rxproj
.DS_Store
PBS/*
Game.exe
rpgmaker.sh

3
.gitmodules vendored
View File

@@ -1,3 +0,0 @@
[submodule "Data/Scripts"]
path = Data/Scripts
url = https://github.com/infinitefusion/scripts.git

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,5 +1,5 @@
########## CREDITS ###############
Pok?mon Infinite Fusion & Pok?mon Infinite Fusion: Hoenn
Pok?mon Infinite Fusion: Hoenn
Game made by Chardub
*This is NOT an official Pokemon game. The author of this game is not
@@ -107,9 +107,9 @@ casinoluck<s>ymirbot
###################################
### Custom Pok?dex entries ###
### Custom Pokedex entries ###
###################################
### Pok?dex entries quality control (Unown)
### Pokedex entries quality control (Unown)
luvischlo<s>char_latte3412<s>strawbearycandy
bobosmith01<s>griddle<s>.izik
knilk<s>lordjoostmeister<s>.realthree
@@ -127,6 +127,10 @@ Maruno (http://pokemonessentials.wikia.com/wiki/Pok%C3%A9mon_Essentials_Wiki)
###########################################################
Playtesting was done by various members of the Discord channel.
Special thanks to all of you!
Elite 4 rematch teams
duskrd<s>anaconja
###########################################################
### Graphics #
###########################################################
@@ -139,13 +143,14 @@ Gen 4 Legendary trio eggs by calicorn
Nintendo
GameFreak
###########################################################
### French translation #
###########################################################
########################
### Translations #
########################
French translation:
anthonygourmand
locpic_
blood.wolf58 (Willi)
ragzzh
#######################################################################
The following ressources were also used
@@ -165,7 +170,6 @@ with their respective authors' consent
zender1752 (Sabrina OW sprite) https://www.deviantart.com/zender1752/art/Pokemon-Anime-Delia-Ketchum-Overworld-sprite-840038766
wesleyfg (Hoenn overworld NPCs) https://www.deviantart.com/wesleyfg/art/Hoenn-People-OW-in-BW-style-274475232
Wergan https://www.deviantart.com/wergan/art/Pokemon-Random-characters-002-959395621
TooManyLuigis, Mortedes, Billla,WolfPP: Golurk overworld sprite https://www.deviantart.com/wolfang62/art/Golurk-Sprite-Overworld-834611476
Battle sprites:
Custom graphics:
Kiwikelly, UnworthyPie
@@ -240,7 +244,12 @@ Move animations:
### RPG Maker Scripts ###
###########################################################
andracass (Pok<6F>mon Reborn) Compiler optimization
#Custom Infinite fusion scripts:
Improved Shinies: anthonygourmand
andracass (Pok?on Reborn) Compiler optimization (legacy)
#Public use scripts:
Luka S.J. Elite Battle System
Animated Title Screen (modified):
shiney570 BW2 Summary script (+ graphics) http://reliccastle.com/forums/showthread.php?tid=1090

BIN
Data/.DS_Store vendored

Binary file not shown.

8
Data/.idea/.gitignore generated vendored
View File

@@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/../../../../../../../../../../../:\Users\charl\Documents\Jeux\rpgmaker\infinitefusion\dev\infinitefusion-e18\Data\.idea/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
Data/Map205.rxdata Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Submodule Data/Scripts deleted from 52c7e25e82

View File

@@ -0,0 +1,664 @@
#==============================================================================#
# Pokémon Essentials #
# Version 19.1.dev #
# https://github.com/Maruno17/pokemon-essentials #
#==============================================================================#
module Settings
# The version of your game. It has to adhere to the MAJOR.MINOR.PATCH format.
GAME_VERSION = '6.7.0'
GAME_VERSION_NUMBER = "6.7.0"
LATEST_GAME_RELEASE = "6.6"
KANTO = GAME_ID == :IF_KANTO
HOENN = GAME_ID == :IF_HOENN
POKERADAR_LIGHT_ANIMATION_RED_ID = 17
POKERADAR_LIGHT_ANIMATION_GREEN_ID = 18
POKERADAR_HIDDEN_ABILITY_POKE_CHANCE = 32
POKERADAR_BATTERY_STEPS = 0
LEADER_VICTORY_MUSIC="Battle victory leader"
TRAINER_VICTORY_MUSIC="trainer-victory"
WILD_VICTORY_MUSIC="wild-victory"
#getRandomCustomFusionForIntro
FUSION_ICON_SPRITE_OFFSET = 10
ANIMATE_REFLECTIONS= false#GAME_ID == :IF_HOENN #true
USE_REFLECTIONS = false
#Infinite fusion settings
NB_POKEMON = Settings::GAME_ID == :IF_HOENN ? 565 : 501
CUSTOM_BASE_SPRITES_FOLDER = "Graphics/CustomBattlers/local_sprites/BaseSprites/"
CUSTOM_BATTLERS_FOLDER = "Graphics/CustomBattlers/"
CUSTOM_SPRITES_TO_IMPORT_FOLDER = "Graphics/CustomBattlers/Sprites to import/"
CUSTOM_BATTLERS_FOLDER_INDEXED = "Graphics/CustomBattlers/local_sprites/indexed/"
CUSTOM_BASE_SPRITE_FOLDER = "Graphics/CustomBattlers/local_sprites/BaseSprites/"
BATTLERS_FOLDER = "Graphics/Battlers/Autogens/"
DOWNLOADED_SPRITES_FOLDER = "Graphics/temp/"
DEFAULT_SPRITE_PATH = "Graphics/Battlers/Special/000.png"
CREDITS_FILE_PATH = "Data/sprites/Sprite_Credits.csv"
VERSION_FILE_PATH = "Data/VERSION"
CUSTOM_SPRITES_FILE_PATH = "Data/sprites/CUSTOM_SPRITES"
BASE_SPRITES_FILE_PATH = "Data/sprites/BASE_SPRITES"
CUSTOM_DEX_ENTRIES_PATH = "Data/pokedex/dex.json"
AI_DEX_ENTRIES_PATH = "Data/pokedex/generated_entries.json"
POKEDEX_ENTRIES_PATH = "Data/pokedex/all_entries.json"
UPDATED_SPRITESHEETS_CACHE = "Data/sprites/updated_spritesheets_cache"
BACK_ITEM_ICON_PATH = "Graphics/Items/back.png"
PLAYER_GRAPHICS_FOLDER = "Graphics/Characters/player/"
PLAYER_HAT_FOLDER = 'hat'
PLAYER_HAIR_FOLDER = 'hair'
PLAYER_CLOTHES_FOLDER = 'clothes'
PLAYER_BALL_FOLDER = 'balls'
PLAYER_TEMP_OUTFIT_FALLBACK = 'temp'
HATS_DATA_PATH = "Data/outfits/hats_data.json"
HAIRSTYLE_DATA_PATH = "Data/outfits/hairstyles_data.json"
CLOTHES_DATA_PATH = "Data/outfits/clothes_data.json"
PLAYER_SURFBASE_FOLDER = 'surf_base/'
OW_SHINE_ANIMATION_ID=25
HTTP_CONFIGS_FILE_URL = "https://raw.githubusercontent.com/infinitefusion/pif-downloadables/refs/heads/master/Settings.rb"
HTTP_CONFIGS_FILE_PATH = "Data/Scripts/DownloadedSettings.rb"
SPRITES_FILE_URL = "https://raw.githubusercontent.com/infinitefusion/infinitefusion-e18/main/Data/sprites/CUSTOM_SPRITES"
BASE_SPRITES_FILE_URL = "https://raw.githubusercontent.com/infinitefusion/infinitefusion-e18/main/Data/sprites/BASE_SPRITES"
CREDITS_FILE_URL = "https://infinitefusion.net/customsprites/Sprite_Credits.csv"
CUSTOM_DEX_FILE_URL = "https://raw.githubusercontent.com/infinitefusion/pif-downloadables/refs/heads/master/dex.json"
SECRETBASE_UPLOAD_URL = "http://secretbases-upload.pkmninfinitefusion.workers.dev"
SECRETBASE_DOWNLOAD_URL = "https://secretbase-download.pkmninfinitefusion.workers.dev"
STARTUP_MESSAGES = ""
LEVEL_CAPS=[12,22,26,35,38,45,51,54,62,62,63,64,64,65,67,68]
CUSTOM_ENTRIES_NAME_PLACEHOLDER = "POKENAME"
DEFAULT_SPEED_UP_SPEED=2
FRONTSPRITE_POSITION_OFFSET = 20
FRONTSPRITE_SCALE = 0.6666666666666666
BACKRPSPRITE_SCALE = 1
EGGSPRITE_SCALE = 1
BACKSPRITE_POSITION_OFFSET = 20
FRONTSPRITE_POSITION = 200
SHINY_HUE_OFFSET = 75 #no longer used
NO_LEVEL_MODE_LEVEL_INCR = 5.8
NO_LEVEL_MODE_LEVEL_BASE = 6
SAVEFILE_NB_BACKUPS=10
DISCORD_URL = "https://discord.com/invite/infinitefusion"
WIKI_URL = "https://infinitefusion.fandom.com/"
AI_ENTRIES_URL = "https://ai-entries.pkmninfinitefusion.workers.dev/"
AI_ENTRIES_RATE_MAX_NB_REQUESTS = 10 #Nb. requests allowed in each time window
AI_ENTRIES_RATE_TIME_WINDOW = 120 # In seconds
AI_ENTRIES_RATE_LOG_FILE = 'Data/pokedex/dex_rate_limit.log' # Path to the log file
CUSTOMSPRITES_RATE_MAX_NB_REQUESTS = 15 #Nb. requests allowed in each time window
CUSTOMSPRITES_ENTRIES_RATE_TIME_WINDOW = 120 # In seconds
CUSTOMSPRITES_RATE_LOG_FILE = 'Data/sprites/sprites_rate_limit.log' # Path to the log file
MAX_NB_SPRITES_TO_DOWNLOAD_AT_ONCE=5
CUSTOM_SPRITES_REPO_URL = "https://bitbucket.org/infinitefusionsprites/customsprites/raw/main/CustomBattlers/"
CUSTOM_SPRITES_NEW_URL = "https://infinitefusion.net/CustomBattlers/"
BASE_POKEMON_ALT_SPRITES_REPO_URL = "https://bitbucket.org/infinitefusionsprites/customsprites/raw/main/Other/BaseSprites/"
BASE_POKEMON_ALT_SPRITES_NEW_URL = "https://infinitefusion.net/Other/BaseSprites/"
BASE_POKEMON_SPRITESHEET_URL = "https://infinitefusion.net/spritesheets/spritesheets_base/"
CUSTOM_FUSIONS_SPRITESHEET_URL = "https://infinitefusion.net/spritesheets/spritesheets_custom/"
BASE_POKEMON_SPRITESHEET_TRUE_SIZE_URL = ""
CUSTOM_FUSIONS_SPRITESHEET_TRUE_SIZE_URL = ""
RIVAL_STARTER_PLACEHOLDER_SPECIES = :MEW #(MEW)
VAR_1_PLACEHOLDER_SPECIES = :DIALGA
VAR_2_PLACEHOLDER_SPECIES = :PALKIA
VAR_3_PLACEHOLDER_SPECIES = :GIRATINA
RIVAL_STARTER_PLACEHOLDER_VARIABLE = 250
OVERRIDE_BATTLE_LEVEL_SWITCH = 785
OVERRIDE_BATTLE_LEVEL_VALUE_VAR = 240
HARD_MODE_LEVEL_MODIFIER = 1.1
ZAPMOLCUNO_NB = 999999#176821
MAPS_WITHOUT_SURF_MUSIC = [762]
WONDERTRADE_BASE_URL = "http://localhost:8080"
WONDERTRADE_PUBLIC_KEY = "http://localhost:8080"
MAX_NB_OUTFITS=99
OUTFIT_PREVIEW_PICTURE_ID=20
DEFAULT_TRAINER_CARD_BG="BLUE"
# The generation that the battle system follows. Used throughout the battle
# scripts, and also by some other settings which are used in and out of battle
# (you can of course change those settings to suit your game).
# Note that this isn't perfect. Essentials doesn't accurately replicate every
# single generation's mechanics. It's considered to be good enough. Only
# generations 5 and later are reasonably supported.
MECHANICS_GENERATION = 5
#=============================================================================
# The default screen width (at a scale of 1.0).
SCREEN_WIDTH = 512
# The default screen height (at a scale of 1.0).
SCREEN_HEIGHT = 384
# The default screen scale factor. Possible values are 0.5, 1.0, 1.5 and 2.0.
SCREEN_SCALE = 1.0
FADEOUT_SPEED = 0.2
#=============================================================================
# The maximum level Pokémon can reach.
MAXIMUM_LEVEL = 100
# The level of newly hatched Pokémon.
EGG_LEVEL = 1
# Number of badges in the game
NB_BADGES = 16
# The odds of a newly generated Pokémon being shiny (out of 65536).
SHINY_POKEMON_CHANCE =16#(MECHANICS_GENERATION >= 6) ? 16 : 8
# The odds of a wild Pokémon/bred egg having Pokérus (out of 65536).
POKERUS_CHANCE = 3
# Whether a bred baby Pokémon can inherit any TM/HM moves from its father. It
# can never inherit TM/HM moves from its mother.
BREEDING_CAN_INHERIT_MACHINE_MOVES = (MECHANICS_GENERATION <= 5)
# Whether a bred baby Pokémon can inherit egg moves from its mother. It can
# always inherit egg moves from its father.
BREEDING_CAN_INHERIT_EGG_MOVES_FROM_MOTHER = (MECHANICS_GENERATION >= 6)
KANTO_STARTERS = [:BULBASAUR, :CHARMANDER, :SQUIRTLE]
JOHTO_STARTERS = [:CHIKORITA, :CYNDAQUIL, :TOTODILE]
HOENN_STARTERS = [:TREECKO, :TORCHIC, :MUDKIP]
SINNOH_STARTERS = [:TURTWIG, :CHIMCHAR, :PIPLUP]
KALOS_STARTERS = [:CHESPIN, :FENNEKIN, :FROAKIE]
DEFAULT_STARTERS = Settings::GAME_ID == :IF_KANTO ? KANTO_STARTERS : HOENN_STARTERS
GRASS_STARTERS = [:BULBASAUR,:CHIKORITA,:TREECKO,:TURTWIG,:CHESPIN]
FIRE_STARTERS = [:CHARMANDER,:CYNDAQUIL, :TORCHIC, :CHIMCHAR, :FENNEKIN]
WATER_STARTERS = [:SQUIRTLE, :TOTODILE, :MUDKIP, :PIPLUP, :FROAKIE]
#=============================================================================
# The amount of money the player starts the game with.
INITIAL_MONEY = 3000
# The maximum amount of money the player can have.
MAX_MONEY = 999_999
# The maximum number of Game Corner coins the player can have.
MAX_COINS = 99_999
# The maximum number of Battle Points the player can have.
MAX_BATTLE_POINTS = 9_999
# The maximum amount of soot the player can have.
MAX_SOOT = 9_999
# The maximum length, in characters, that the player's name can be.
MAX_PLAYER_NAME_SIZE = 10
# The maximum number of Pokémon that can be in the party.
MAX_PARTY_SIZE = 6
#=============================================================================
# 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]
]
#=============================================================================
# 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.
POISON_IN_FIELD = true #(MECHANICS_GENERATION <= 4)
# Whether poisoned Pokémon will faint while walking around in the field
# (true), or survive the poisoning with 1 HP (false).
POISON_FAINT_IN_FIELD = false
# Whether planted berries grow according to Gen 4 mechanics (true) or Gen 3
# mechanics (false).
NEW_BERRY_PLANTS = true
# Whether fishing automatically hooks the Pokémon (true), or whether there is
# a reaction test first (false).
FISHING_AUTO_HOOK = false
# The ID of the common event that runs when the player starts fishing (runs
# instead of showing the casting animation).
FISHING_BEGIN_COMMON_EVENT = -1
# The ID of the common event that runs when the player stops fishing (runs
# instead of showing the reeling in animation).
FISHING_END_COMMON_EVENT = -1
#=============================================================================
# The number of steps allowed before a Safari Zone game is over (0=infinite).
SAFARI_STEPS = 600
# The number of seconds a Bug Catching Contest lasts for (0=infinite).
BUG_CONTEST_TIME = 20 * 60 # 20 minutes
#=============================================================================
# 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 = []
#=============================================================================
# Whether you need at least a certain number of badges to use some hidden
# moves in the field (true), or whether you need one specific badge to use
# them (false). The amounts/specific badges are defined below.
FIELD_MOVES_COUNT_BADGES = true
# Depending on FIELD_MOVES_COUNT_BADGES, either the number of badges required
# to use each hidden move in the field, 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.
BADGE_FOR_CUT = 1
BADGE_FOR_FLASH = 1
BADGE_FOR_ROCKSMASH = 0
BADGE_FOR_SURF = 6
BADGE_FOR_FLY = 3
BADGE_FOR_STRENGTH = 5
BADGE_FOR_DIVE = 9
BADGE_FOR_WATERFALL = 9
BADGE_FOR_TELEPORT = 3
BADGE_FOR_BOUNCE = 8
BADGE_FOR_ROCKCLIMB = 16
#=============================================================================
# If a move taught by a TM/HM/TR replaces another move, this setting is
# whether the machine's move retains the replaced move's PP (true), or whether
# the machine's move has full PP (false).
TAUGHT_MACHINES_KEEP_OLD_PP = (MECHANICS_GENERATION == 5)
# Whether the Black/White Flutes will raise/lower the levels of wild Pokémon
# respectively (true), or will lower/raise the wild encounter rate
# respectively (false).
FLUTES_CHANGE_WILD_ENCOUNTER_LEVELS = (MECHANICS_GENERATION >= 6)
# Whether Repel uses the level of the first Pokémon in the party regardless of
# its HP (true), or it uses the level of the first unfainted Pokémon (false).
REPEL_COUNTS_FAINTED_POKEMON = (MECHANICS_GENERATION >= 6)
# Whether Rage Candy Bar acts as a Full Heal (true) or a Potion (false).
RAGE_CANDY_BAR_CURES_STATUS_PROBLEMS = (MECHANICS_GENERATION >= 7)
#=============================================================================
# The name of the person who created the Pokémon storage system.
def self.storage_creator_name
return _INTL("Bill")
end
# The number of boxes in Pokémon storage.
NUM_STORAGE_BOXES = 40
#=============================================================================
# The names of each pocket of the Bag. Ignore the first entry ("").
def self.bag_pocket_names
return ["",
_INTL("Items"),
_INTL("Medicine"),
_INTL("Poké Balls"),
_INTL("TMs & HMs"),
_INTL("Berries"),
_INTL("Mail"),
_INTL("Battle Items"),
_INTL("Key Items")
]
end
# The maximum number of slots per pocket (-1 means infinite number). Ignore
# the first number (0).
BAG_MAX_POCKET_SIZE = [0, -1, -1, -1, -1, -1, -1, -1, -1]
# The maximum number of items each slot in the Bag can hold.
BAG_MAX_PER_SLOT = 999
# Whether each pocket in turn auto-sorts itself by item ID number. Ignore the
# first entry (the 0).
BAG_POCKET_AUTO_SORT = [0, false, false, false, true, true, false, false, false]
#=============================================================================
# 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).
USE_CURRENT_REGION_DEX = false
# The names of the Pokédex lists, in the order they are defined in the PBS
# file "regionaldexes.txt". The last name is for the National Dex and is added
# onto the end of this array (remember that you don't need to use it). This
# array's order is also the order of $Trainer.pokedex.unlocked_dexes, which
# records which Dexes have been unlocked (the first is unlocked by default).
# If an entry is just a name, then the region map shown in the Area page while
# viewing that Dex list will be the region map of the region the player is
# currently in. The National Dex entry should always behave like this.
# If an entry is of the form [name, number], then the number is a region
# number. That region's map will appear in the Area page while viewing that
# Dex list, no matter which region the player is currently in.
def self.pokedex_names
return [
# ["Kanto Pokédex", 0]
]
end
# 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).
DEX_SHOWS_ALL_FORMS = false
# An array of numbers, where each number is that of a Dex list (in the same
# order as above, except the National Dex is -1). All Dex lists included here
# will begin their numbering at 0 rather than 1 (e.g. Victini in Unova's Dex).
DEXES_WITH_OFFSETS = []
#=============================================================================
# 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.
# * Game 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]
]
TRIPLE_TYPES = [:QMARKS,:ICEFIREELECTRIC,:FIREWATERELECTRIC,:WATERGROUNDFLYING,:GHOSTSTEELWATER,
:FIREWATERGRASS,:GRASSSTEEL,:BUGSTEELPSYCHIC,:ICEROCKSTEEL]
#=============================================================================
# A list of maps used by roaming Pokémon. Each map has an array of other maps
# it can lead to.
ROAMING_AREAS = {
262 => [261,311],
311 => [262,312],
312 => [311],
261 => [262,288,267],
288 => [261,267,285],
267 => [261,288,300,254],
284 => [288,266,285],
300 => [267,254],
254 => [300,265],
266 => [284,265],
265 => [266,254],
285 => [284,288]}
SEVII_ROAMING = {
528 => [526], #Treasure beach
526 => [528,559], #Knot Island
559 => [526,561,564], #Kindle Road
561 => [559], #Mt. Ember
564 => [559,562,563,594], #brine road
562 => [564], #boon island
563 => [564,600] , #kin island
594 => [564,566,603], #water labyrinth
600 => [563,619], #bond bridge
619 => [600] , #Berry forest
566 => [594,603], #Resort gorgeous
603 => [566,594], #Chrono Island
}
# A set of arrays, each containing the details of a roaming Pokémon. The
# information within each array is as follows:
# * Species.
# * Level.
# * Game 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 the bottom of PField_RoamingPokemon for lists.
# * Name of BGM to play for that encounter (optional).
# * Roaming areas specifically for this Pokémon (optional).
ROAMING_SPECIES = [
[:ENTEI, 50, 350, 1, "Legendary Birds",ROAMING_AREAS,:Sunny],
[:B245H243, 50, 341, 1, "Legendary Birds",ROAMING_AREAS,:Storm],
[:B379H378, 50, 602, 0, "Legendary Birds",SEVII_ROAMING,:StrongWinds],
[:B378H379, 50, 602, 0, "Legendary Birds",SEVII_ROAMING,:StrongWinds],
[:FEEBAS, 15, 4, 3, "Pokemon HeartGold and SoulSilver - Wild Pokemon Battle (Kanto)",SEVII_ROAMING,:Rain]
]
PINKAN_ISLAND_MAPS=[51,46,428,531]
#=============================================================================
# A set of arrays, each containing the details of a wild encounter that can
# only occur via using the Poké Radar. The information within each array 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 = [
[78, 50, :FLETCHLING,2,5], #Rt. 1
[86, 50, :FLETCHLING,2,5], #Rt. 2
[90, 50, :FLETCHLING,2,5], #Rt. 2
[491, 50, :SHROOMISH,2,5], #Viridian Forest
[490, 50, :BUDEW,4,9], #Rt. 3
[106, 50, :NINCADA,8,10], #Rt. 4
[12, 50, :TOGEPI,10,10], #Rt. 5
[16, 50, :SLAKOTH,12,15], #Rt. 6
[413, 50, :DRIFLOON,17,20], #Rt. 7
[409, 50, :SHINX,17,18], #Rt. 8
[495, 50, :ARON,12,15], #Rt. 9
[351, 50, :ARON,12,15], #Rt. 9
[154, 50, :KLINK,14,17], #Rt. 10
[155, 50, :NINCADA,12,15], #Rt. 11
[159, 50, :COTTONEE,22,25], #Rt. 12
[437, 50, :COTTONEE,22,25], #Rt. 13
[437, 50, :JOLTIK,22,25], #Rt. 13
[440, 50, :JOLTIK,22,25], #Rt. 14
[444, 50, :SOLOSIS,22,25], #Rt. 15
[438, 50, :NATU,22,25], #Rt. 16
[146, 50, :KLEFKI,22,25], #Rt. 17
[517, 50, :FERROSEED,22,25], #Rt. 18
[445, 50, :BAGON,20,20], #Safari zone 1
[484, 50, :AXEW,20,20], #Safari zone 2
[485, 50, :DEINO,20,20], #Safari zone 3
[486, 50, :LARVITAR,20,20], #Safari zone 4
[487, 50, :JANGMOO,20,20], #Safari zone 5
[59, 50, :DUNSPARCE,25,30], #Rt. 21
[171, 50, :BIDOOF,2,5], #Rt. 22
[143, 50, :RIOLU,25,25], #Rt. 23
[8, 50, :BUNEARY,12,13], #Rt. 24
[145, 50, :ABSOL,30,35], #Rt. 26
[147, 50, :ABSOL,30,35], #Rt. 27
[311, 50, :BIDOOF,5,5], #Rt. 29
[284, 50, :LUXIO,40,45], #Rt. 33
[288, 50, :VIGOROTH,40,45], #Rt. 32
[342, 50, :GOLETT,40,45], #Ruins of Alph
[261, 50, :BELLOSSOM,45,50], #Rt. 31
[262, 50, :BIBAREL,45,50], #Rt. 30
[265, 50, :KIRLIA,25,30], #Rt. 34
[254, 50, :SMEARGLE,25,30], #Rt. 35
[267, 50, :SUDOWOODO,25,30], #Rt. 36
[500, 50, :FOMANTIS,30,30], #National Park
[266, 50, :BRELOOM,30,30], #Ilex Forest
[670, 50, :WEAVILE,50,50], #Ice mountains
[528, 50, :PYUKUMUKU,20,20], #Treasure Beach
[690, 50, :OCTILLERY,32,45], #Deep Ocean
[561, 50, :FLETCHINDER,32,45], #Mt. Ember
[562, 50, :NINJASK,45,50], #Boon Island
[603, 50, :KECLEON,45,50], #Chrono Island
[654, 50, :WHIMSICOTT,32,45], #Brine Road
[559, 50, :SCRAGGY,32,45] #Kindle Road
]
#=============================================================================
# The Game Switch that is set to ON when the player blacks out.
STARTING_OVER_SWITCH = 1
# The Game 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).
SEEN_POKERUS_SWITCH = 2
# The Game Switch which, while ON, makes all wild Pokémon created be shiny.
SHINY_WILD_POKEMON_SWITCH = 31
# The Game Switch which, while ON, makes all Pokémon created considered to be
# met via a fateful encounter.
FATEFUL_ENCOUNTER_SWITCH = 32
#=============================================================================
# ID of the animation played when the player steps on grass (grass rustling).
GRASS_ANIMATION_ID = 1
# ID of the animation played when the player lands on the ground after hopping
# over a ledge (shows a dust impact).
DUST_ANIMATION_ID = 2
# ID of the animation played when a trainer notices the player (an exclamation
# bubble).
EXCLAMATION_ANIMATION_ID = 3
# ID of the animation played when a patch of grass rustles due to using the
# Poké Radar.
RUSTLE_NORMAL_ANIMATION_ID = 1
# ID of the animation played when a patch of grass rustles vigorously due to
# using the Poké Radar. (Rarer species)
RUSTLE_VIGOROUS_ANIMATION_ID = 5
# ID of the animation played when a patch of grass rustles and shines due to
# using the Poké Radar. (Shiny encounter)
RUSTLE_SHINY_ANIMATION_ID = 6
# 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).
PLANT_SPARKLE_ANIMATION_ID = 7
SPARKLE_SHORT_ANIMATION_ID = 25
SPARKLE_SUBTLE_ANIMATION_ID = 29
SLEEP_ANIMATION_ID = 26
CUT_TREE_ANIMATION_ID = 19
ROCK_SMASH_ANIMATION_ID = 20
#=============================================================================
# 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"],
["Français", "french.dat"]
]
#Experimental
REMOTE_BATTLES_CONTROL = false
REMOTE_NPC_DIALOG = false
REMOTE_BATTLE_CONTROL_SERVER_URL = "http://127.0.0.1:5000/choose_move"
REMOTE_NPC_DIALOG_SERVER_URL = "http://127.0.0.1:5000"
#Technical
SPRITE_CACHE_MAX_NB=100
NEWEST_SPRITEPACK_MONTH = 12
NEWEST_SPRITEPACK_YEAR = 2020
#=============================================================================
# Available speech frames. These are graphic files in "Graphics/Windowskins/".
SPEECH_WINDOWSKINS = [
"speech hgss 1",
"speech hgss 2",
"speech hgss 3",
"speech hgss 4",
"speech hgss 5",
"speech hgss 6",
"speech hgss 7",
"speech hgss 8",
"speech hgss 9",
"speech hgss 10",
"speech hgss 11",
"speech hgss 12",
"speech hgss 13",
"speech hgss 14",
"speech hgss 15",
"speech hgss 16",
"speech hgss 17",
"speech hgss 18",
"speech hgss 19",
"speech hgss 20",
"speech pl 18",
]
# Available menu frames. These are graphic files in "Graphics/Windowskins/".
MENU_WINDOWSKINS = [
"default_transparent",
"default_opaque",
"choice 2",
"choice 3",
"choice 4",
"choice 5",
"choice 6",
"choice 7",
"choice 8",
"choice 9",
"choice 10",
"choice 11",
"choice 12",
"choice 13",
"choice 14",
"choice 15",
"choice 16",
"choice 17",
"choice 18",
"choice 19",
"choice 20",
"choice 21",
"choice 22",
"choice 23",
"choice 24",
"choice 25",
"choice 26",
"choice 27",
"choice 28"
]
RANDOMIZED_GYM_TYPE_TM=
{
:NORMAL => [:TM32,:TM49,:TM42,:TM98], #DOUBLETEAM ECHOEDVOICE FACADE BATONPASS
:FIGHTING => [:TM83,:TM115,:TM52,:TM112], #WORKUP POWERUPPUNCH FOCUSBLAST FOCUSPUNCH
:FLYING => [:TM62,:TM58,:TM108,:TM100], #ACROBATICS SKYDROP SKYATTACK DEFOG
:POISON => [:TM84,:TM06,:TM36,:TM34], #POISONJAB TOXIC SLUDGEBOMB SLUDGEWAVE
:GROUND => [:TM28,:TM78,:TM26,:TM119], #DIG BULLDOZE EARTHQUAKE STOMPINGTANTRUM
:ROCK => [:TM39,:TM80,:TM71,:TM69], #ROCKTOMB ROCKTHROW STONEDGE ROCKPOLISH
:BUG => [:TM76,:TM89,:TM113,:TM99], #STRUGGLEBUG UTURN INFESTATION QUIVERDANCE
:GHOST => [:TM85,:TM65,:TM30,:TM97], #DREAMEATER SHADOWCLAW SHADOWBALL NASTYPLOT
:STEEL => [:TM74,:TM118,:TM117,:TM75], # GYROBALL STEELWING SMARTSTRIKE SWORDDANCE
:FIRE => [:TM11,:TM43,:TM38,:TM61], #SUNNYDAY FLAMECHARGE FIREBLAST WILLOWISP
:WATER => [:TM55,:TM105,:TM121,:TM18], #WATERPULSE AQUAJET SCALD RAINDANCE
:GRASS => [:TM22,:TM53,:TM86,:TM102], # SOLARBEAM ENERGYBALL GRASSKNOT SPORE
:ELECTRIC => [:TM73,:TM116,:TM93,:TM72], #THUNDERWAVE SHOCKWAVE WILDCHARGE VOLTSWITCH
:PSYCHIC => [:TM77,:TM03,:TM29,:TM04], #PSYCHUP PSYSHOCK PSYCHIC CALMMIND
:ICE => [:TM110,:TM13,:TM14,:TM07], #AURORAVEIL ICEBEAM BLIZZARD HAIL
:DRAGON => [:TM95,:TM02,:TM82,:TM101], #SNARL DRAGONCLAW DRAGONTAIL DRAGONDANCE
:DARK => [:TM95,:TM46,:TM120,:TM97], #SNARL THIEF THROATCHOP NASTYPLOT
:FAIRY => [:TM45,:TM111,:TM96,:TM104] #ATTRACT DAZZLINGGLEAM MOONBLAST RECOVER
}
EXCLUDE_FROM_RANDOM_SHOPS=[:RARECANDY]
end
# DO NOT EDIT THESE!
module Essentials
VERSION = "19.1.dev"
ERROR_TEXT = ""
end

View File

@@ -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

View File

@@ -0,0 +1,54 @@
# To use the console, use the executable explicitly built
# with the console enabled on Windows. On Linux and macOS,
# just launch the executable directly from a terminal.
module Console
def self.setup_console
return unless $DEBUG
echoln "--------------------------------"
echoln "#{System.game_title} Output Window"
echoln "--------------------------------"
echoln "If you are seeing this window, you are running"
echoln "#{System.game_title} in Debug Mode. This means"
echoln "that you're either playing a Debug Version, or"
echoln "you are playing from within RPG Maker XP."
echoln ""
echoln "Closing this window will close the game. If"
echoln "you want to get rid of this window, run the"
echoln "program from the Shell, or download a Release"
echoln "version."
echoln ""
echoln "--------------------------------"
echoln "Debug Output:"
echoln "--------------------------------"
echoln ""
end
def self.readInput
return gets.strip
end
def self.readInput2
return self.readInput
end
def self.get_input
echo self.readInput2
end
end
module Kernel
def echo(string)
return unless $DEBUG
printf(string.is_a?(String) ? string : string.inspect)
end
def echoln(string)
caller_info = caller(1..1).first
file, line, method = caller_info.split(":")
echo "#{file}, #{line}:\t"
echo(string)
echo("\r\n")
end
end
Console.setup_console

View File

@@ -0,0 +1,93 @@
#===============================================================================
# Exceptions and critical code
#===============================================================================
class Reset < Exception
end
def pbGetExceptionMessage(e,_script="")
emessage = e.message.dup
emessage.force_encoding(Encoding::UTF_8)
if e.is_a?(Hangup)
emessage = "The script is taking too long. The game will restart."
elsif e.is_a?(Errno::ENOENT)
filename = emessage.sub("No such file or directory - ", "")
emessage = "File #{filename} not found."
end
emessage.gsub!(/Section(\d+)/) { $RGSS_SCRIPTS[$1.to_i][1] } rescue nil
return emessage
end
def pbPrintException(e)
emessage = ""
if $EVENTHANGUPMSG && $EVENTHANGUPMSG!=""
emessage = $EVENTHANGUPMSG # Message with map/event ID generated elsewhere
$EVENTHANGUPMSG = nil
else
emessage = pbGetExceptionMessage(e)
end
# begin message formatting
message = "[Infinite Fusion version #{Settings::GAME_VERSION_NUMBER}]\r\n"
if $game_switches
message += "Randomized trainers, " if $game_switches[SWITCH_RANDOM_TRAINERS]
message += "Randomized gym trainers, " if $game_switches[SWITCH_RANDOMIZE_GYMS_SEPARATELY]
message += "Randomized wild Pokemon (global), " if $game_switches[SWITCH_WILD_RANDOM_GLOBAL]
message += "Randomized wild Pokemon (area), " if $game_switches[RandomizerWildPokemonOptionsScene::RANDOM_WILD_AREA]
message += "All fused, " if $game_switches[SWITCH_RANDOM_TRAINERS]
message += "Randomized trainers, " if $game_switches[RandomizerWildPokemonOptionsScene::REGULAR_TO_FUSIONS]
end
message += "#{Essentials::ERROR_TEXT}\r\n" # For third party scripts to add to
message += "Exception: #{e.class}\r\n"
message += "Message: #{emessage}\r\n"
# show last 10/25 lines of backtrace
message += "\r\nBacktrace:\r\n"
btrace = ""
if e.backtrace
maxlength = ($INTERNAL) ? 25 : 10
e.backtrace[0, maxlength].each { |i| btrace += "#{i}\r\n" }
end
btrace.gsub!(/Section(\d+)/) { $RGSS_SCRIPTS[$1.to_i][1] } rescue nil
message += btrace
# output to log
errorlog = "errorlog.txt"
errorlog = RTP.getSaveFileName("errorlog.txt") if (Object.const_defined?(:RTP) rescue false)
File.open(errorlog, "ab") do |f|
f.write("\r\n=================\r\n\r\n[#{Time.now}]\r\n")
f.write(message)
end
# format/censor the error log directory
errorlogline = errorlog.gsub("/", "\\")
errorlogline.sub!(Dir.pwd + "\\", "")
errorlogline.sub!(pbGetUserName, "USERNAME")
errorlogline = "\r\n" + errorlogline if errorlogline.length > 20
# output message
print("#{message}\r\nThis exception was logged in #{errorlogline}.\r\nHold Ctrl when closing this message to copy it to the clipboard.")
# Give a ~500ms coyote time to start holding Control
t = System.delta
until (System.delta - t) >= 500000
Input.update
if Input.press?(Input::CTRL)
Input.clipboard = message
break
end
end
end
def pbCriticalCode
ret = 0
begin
yield
ret = 1
rescue Exception
e = $!
if e.is_a?(Reset) || e.is_a?(SystemExit)
raise
else
pbPrintException(e)
if e.is_a?(Hangup)
ret = 2
raise Reset.new
end
end
end
return ret
end

View File

@@ -0,0 +1,31 @@
# The Kernel module is extended to include the validate method.
module Kernel
private
# Used to check whether method arguments are of a given class or respond to a method.
# @param value_pairs [Hash{Object => Class, Array<Class>, Symbol}] value pairs to validate
# @example Validate a class or method
# validate foo => Integer, baz => :to_s # raises an error if foo is not an Integer or if baz doesn't implement #to_s
# @example Validate a class from an array
# validate foo => [Sprite, Bitmap, Viewport] # raises an error if foo isn't a Sprite, Bitmap or Viewport
# @raise [ArgumentError] if validation fails
def validate(value_pairs)
unless value_pairs.is_a?(Hash)
raise ArgumentError, "Non-hash argument #{value_pairs.inspect} passed into validate."
end
errors = value_pairs.map do |value, condition|
if condition.is_a?(Array)
unless condition.any? { |klass| value.is_a?(klass) }
next "Expected #{value.inspect} to be one of #{condition.inspect}, but got #{value.class.name}."
end
elsif condition.is_a?(Symbol)
next "Expected #{value.inspect} to respond to #{condition}." unless value.respond_to?(condition)
elsif !value.is_a?(condition)
next "Expected #{value.inspect} to be a #{condition.name}, but got #{value.class.name}."
end
end
errors.compact!
return if errors.empty?
raise ArgumentError, "Invalid argument passed to method.\r\n" + errors.join("\r\n")
end
end

View File

@@ -0,0 +1,53 @@
# The Deprecation module is used to warn game & plugin creators of deprecated
# methods.
module Deprecation
module_function
# Sends a warning of a deprecated method into the debug console.
# @param method_name [String] name of the deprecated method
# @param removal_version [String] version the method is removed in
# @param alternative [String] preferred alternative method
def warn_method(method_name, removal_version = nil, alternative = nil)
text = _INTL('WARN: usage of deprecated method "{1}" or its alias.', method_name)
unless removal_version.nil?
text += _INTL("\nThe method is slated to be"\
" removed in Essentials {1}.", removal_version)
end
unless alternative.nil?
text += _INTL("\nUse \"{1}\" instead.", alternative)
end
echoln text
end
end
# The Module class is extended to allow easy deprecation of instance and class methods.
class Module
private
# Creates a deprecated alias for a method.
# Using it sends a warning to the debug console.
# @param name [Symbol] name of the new alias
# @param aliased_method [Symbol] name of the aliased method
# @param removal_in [String] version the alias is removed in
# @param class_method [Boolean] whether the method is a class method
def deprecated_method_alias(name, aliased_method, removal_in: nil, class_method: false)
validate name => Symbol, aliased_method => Symbol, removal_in => [NilClass, String],
class_method => [TrueClass, FalseClass]
target = class_method ? self.class : self
class_name = self.name
unless target.method_defined?(aliased_method)
raise ArgumentError, "#{class_name} does not have method #{aliased_method} defined"
end
delimiter = class_method ? '.' : '#'
target.define_method(name) do |*args, **kvargs|
alias_name = format('%s%s%s', class_name, delimiter, name)
aliased_method_name = format('%s%s%s', class_name, delimiter, aliased_method)
Deprecation.warn_method(alias_name, removal_in, aliased_method_name)
method(aliased_method).call(*args, **kvargs)
end
end
end

View File

@@ -0,0 +1,48 @@
# Using mkxp-z v2.2.0 - https://gitlab.com/mkxp-z/mkxp-z/-/releases/v2.2.0
$VERBOSE = nil
Font.default_shadow = false if Font.respond_to?(:default_shadow)
Encoding.default_internal = Encoding::UTF_8
Encoding.default_external = Encoding::UTF_8
Graphics.frame_rate = 40
def pbSetWindowText(string)
System.set_window_title(string || System.game_title)
end
class Bitmap
attr_accessor :storedPath
alias mkxp_draw_text draw_text unless method_defined?(:mkxp_draw_text)
def draw_text(x, y, width, height, text, align = 0)
if x.is_a?(Rect)
x.y -= (@text_offset_y || 0)
# rect, string & alignment
mkxp_draw_text(x, y, width)
else
y -= (@text_offset_y || 0)
height = text_size(text).height
mkxp_draw_text(x, y, width, height, text, align)
end
end
end
module Graphics
def self.delta_s
return self.delta.to_f / 1_000_000
end
end
def pbSetResizeFactor(factor)
if !$ResizeInitialized
Graphics.resize_screen(Settings::SCREEN_WIDTH, Settings::SCREEN_HEIGHT)
$ResizeInitialized = true
end
if factor < 0 || factor == 4
Graphics.fullscreen = true if !Graphics.fullscreen
else
Graphics.fullscreen = false if Graphics.fullscreen
Graphics.scale = (factor + 1) * 0.5
Graphics.center
end
end

View File

@@ -0,0 +1,492 @@
#===============================================================================
# Reads files of certain format from a directory
#===============================================================================
class Dir
#-----------------------------------------------------------------------------
# Reads all files in a directory
#-----------------------------------------------------------------------------
def self.get(dir, filters = "*", full = true)
files = []
filters = [filters] if !filters.is_a?(Array)
self.chdir(dir) do
for filter in filters
self.glob(filter){ |f| files.push(full ? (dir + "/" + f) : f) }
end
end
return files.sort
end
#-----------------------------------------------------------------------------
# Generates entire file/folder tree from a certain directory
#-----------------------------------------------------------------------------
def self.all(dir, filters = "*", full = true)
# sets variables for starting
files = []
subfolders = []
for file in self.get(dir, filters, full)
# engages in recursion to read the entire file tree
if self.safe?(file) # Is a directory
subfolders += self.all(file, filters, full)
else # Is a file
files += [file]
end
end
# returns all found files
return files + subfolders
end
#-----------------------------------------------------------------------------
# Checks for existing directory, gets around accents
#-----------------------------------------------------------------------------
def self.safe?(dir)
return false if !FileTest.directory?(dir)
ret = false
self.chdir(dir) { ret = true } rescue nil
return ret
end
#-----------------------------------------------------------------------------
end
#===============================================================================
# extensions for file class
#===============================================================================
class File
#-----------------------------------------------------------------------------
# Checks for existing file, gets around accents
#-----------------------------------------------------------------------------
def self.safe?(file)
ret = false
self.open(file, 'rb') { ret = true } rescue nil
return ret
end
#-----------------------------------------------------------------------------
end
#===============================================================================
# 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
def pbResolveAudioSE(file)
return nil if !file
if RTP.exists?("Audio/SE/"+file,["",".wav",".mp3",".ogg"])
return RTP.getPath("Audio/SE/"+file,["",".wav",".mp3",".ogg"])
end
return nil
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 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 == ".."
if pos >= 0
ret.delete_at(pos)
pos -= 1
end
elsif x != "."
ret.push(x)
pos += 1
end
end
for i in 0...ret.length
retstr += "/" if i > 0
retstr += ret[i]
end
return retstr
end
module RTP
@rtpPaths = nil
def self.exists?(filename,extensions=[])
return false if nil_or_empty?(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", ".jpeg", ".bmp"
end
def self.getAudioPath(filename)
return self.getPath(filename,["",".mp3",".wav",".wma",".mid",".ogg",".midi"])
end
def self.getPath(filename,extensions=[])
return filename if nil_or_empty?(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.
# This function basically does nothing now, because
# the passage of time and introduction of MKXP make
# it useless, but leaving it for compatibility
# reasons
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(/[\/\\]$/,"")+"/"
end
private
def self.getSaveFileName(fileName)
File.join(getSaveFolder, fileName)
end
def self.getSaveFolder
# MKXP makes sure that this folder has been created
# once it starts. The location differs depending on
# the operating system:
# Windows: %APPDATA%
# Linux: $HOME/.local/share
# macOS (unsandboxed): $HOME/Library/Application Support
System.data_directory
end
end
module FileTest
Image_ext = ['.png', '.gif'] # '.jpg', '.jpeg', '.bmp',
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.
# Note: pbGetFileChar checks anything added in MKXP's RTP setting,
# and matching mount points added through System.mount
def pbRgssExists?(filename)
if safeExists?("./Game.rgssad")
return pbGetFileChar(filename)!=nil
else
filename = canonicalize(filename)
return safeExists?(filename)
end
end
# Opens an IO, even if the file is in an encrypted archive.
# Doesn't check RTP for the file.
# Note: load_data checks anything added in MKXP's RTP setting,
# and matching mount points added through System.mount
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")
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
str = load_data(file, true)
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)
canon_file = canonicalize(file)
if !safeExists?("./Game.rgssad")
return nil if !safeExists?(canon_file)
return nil if file.last == '/' # Is a directory
begin
File.open(canon_file, "rb") { |f| return f.read(1) } # read one byte
rescue Errno::ENOENT, Errno::EINVAL, Errno::EACCES, Errno::EISDIR
return nil
end
end
str = nil
begin
str = load_data(canon_file, true)
rescue Errno::ENOENT, Errno::EINVAL, Errno::EACCES, Errno::EISDIR, RGSSError, MKXPError
str = nil
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.
# Note: load_data will check anything added in MKXP's RTP setting,
# and matching mount points added through System.mount
def pbGetFileString(file)
file = canonicalize(file)
if !safeExists?("./Game.rgssad")
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
str = nil
begin
str = load_data(file, true)
rescue Errno::ENOENT, Errno::EINVAL, Errno::EACCES, RGSSError, MKXPError
str = nil
end
return str
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 then @pos = offset
when IO::SEEK_CUR then @pos += offset
when IO::SEEK_END then @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

View File

@@ -0,0 +1,150 @@
module FileInputMixin
def fgetb
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 << x)
x += 8
break if x == 16
end
return ret
end
def fgetdw
x = 0
ret = 0
each_byte do |i|
break if !i
ret |= (i << x)
x += 8
break if x == 32
end
return ret
end
def fgetsb
ret = fgetb
ret -= 256 if (ret & 0x80) != 0
return ret
end
def xfgetb(offset)
self.pos = offset
return fgetb
end
def xfgetw(offset)
self.pos = offset
return fgetw
end
def xfgetdw(offset)
self.pos = offset
return fgetdw
end
def getOffset(index)
self.binmode
self.pos = 0
offset = fgetdw >> 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 &= 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

View File

@@ -0,0 +1,222 @@
#############################
#
# HTTP utility functions
#
#############################
def pbPostData(url, postdata, filename=nil, depth=0)
return "" unless url =~ /^https?:\/\/([^\/]+)(.*)$/
host = $1
path = $2
path = "/" if path.empty?
userAgent = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.14) Gecko/2009082707 Firefox/3.0.14"
# Serialize as JSON
body = serialize_json(postdata)
ret = HTTPLite.post_body(
url,
body,
"application/json",
{
"Host" => host,
"Proxy-Connection" => "Close",
"Content-Length" => body.bytesize.to_s,
"Pragma" => "no-cache",
"User-Agent" => userAgent
}
) rescue ""
return "" if !ret.is_a?(Hash)
return "" if ret[:status] != 200
if filename
File.open(filename, "wb") { |f| f.write(ret[:body]) }
return ""
end
ret[:body]
end
def pbDownloadData(url, filename = nil, authorization = nil, depth = 0, &block)
return nil if !downloadAllowed?()
echoln "downloading data from #{url}"
headers = {
"Proxy-Connection" => "Close",
"Pragma" => "no-cache",
"User-Agent" => "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.0.14) Gecko/2009082707 Firefox/3.0.14"
}
headers["authorization"] = authorization if authorization
ret = HTTPLite.get(url, headers) rescue ""
return ret if !ret.is_a?(Hash)
return "" if ret[:status] != 200
return ret[:body] if !filename
File.open(filename, "wb") { |f| f.write(ret[:body]) }
return ""
end
def pbDownloadToString(url)
begin
data = pbDownloadData(url)
return data if data
return ""
rescue
return ""
end
end
def pbDownloadToFile(url, file)
begin
pbDownloadData(url,file)
rescue
end
end
def pbPostToString(url, postdata, timeout = 30)
safe_postdata = postdata.transform_values(&:to_s)
begin
data = pbPostData(url, safe_postdata)
return data || ""
rescue MKXPError => e
echoln("[Remote AI] Exception: #{e.message}")
return ""
end
end
def pbPostToFile(url, postdata, file)
begin
pbPostData(url, postdata,file)
rescue
end
end
def serialize_value_legacy(value)
if value.is_a?(Hash)
serialize_json(value)
elsif value.is_a?(String)
escaped_value = value.gsub(/\\/, '\\\\\\').gsub(/"/, '\\"').gsub(/\n/, '\\n').gsub(/\r/, '\\r')
"\"#{escaped_value}\""
else
value.to_s
end
end
def serialize_json(data)
if data.is_a?(Hash)
parts = ["{"]
data.each_with_index do |(key, value), index|
parts << "\"#{key}\":#{serialize_value(value)}"
parts << "," unless index == data.size - 1
end
parts << "}"
return parts.join
else
return serialize_value(data)
end
end
def serialize_value(value)
case value
when String
"\"#{escape_json_string(value)}\""
when Numeric
value.to_s
when TrueClass, FalseClass
value.to_s
when NilClass
"null"
when Array
"[" + value.map { |v| serialize_value(v) }.join(",") + "]"
when Hash
serialize_json(value)
else
raise "Unsupported type: #{value.class}"
end
end
def escape_json_string(str)
# Minimal escape handling
str.gsub(/["\\]/) { |m| "\\#{m}" }
.gsub("\n", "\\n")
.gsub("\t", "\\t")
.gsub("\r", "\\r")
end
def downloadAllowed?()
return $PokemonSystem.download_sprites==0
end
def clean_json_string(str)
#echoln str
#return str if $PokemonSystem.on_mobile
# Remove non-UTF-8 characters and unexpected control characters
#cleaned_str = str.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')
cleaned_str = str
# Remove literal \n, \r, \t, etc.
cleaned_str = cleaned_str.gsub(/\\n|\\r|\\t/, '')
# Remove actual newlines and carriage returns
cleaned_str = cleaned_str.gsub(/[\n\r]/, '')
# Remove leading and trailing quotes
cleaned_str = cleaned_str.gsub(/\A"|"\Z/, '')
# Replace Unicode escape sequences with corresponding characters
cleaned_str = cleaned_str.gsub(/\\u([\da-fA-F]{4})/) { |match|
[$1.to_i(16)].pack("U")
}
return cleaned_str
end
# json.rb - lightweight JSON parser for MKXP/RGSS XP
# Lightweight JSON for MKXP/RGSS XP
module JSON
module_function
# Convert Ruby object (hash/array/etc) into JSON string
def generate(obj)
case obj
when Hash
"{" + obj.map { |k, v| "\"#{k}\":#{generate(v)}" }.join(",") + "}"
when Array
"[" + obj.map { |v| generate(v) }.join(",") + "]"
when String, Symbol
"\"#{obj.to_s}\""
when TrueClass, FalseClass
obj.to_s
when NilClass
"null"
when Numeric
obj.to_s
else
raise "Unsupported type #{obj.class}"
end
end
# Simple parser (not full JSON) — optional
def parse(str)
return nil if str.nil? || str.strip.empty?
eval(str)
end
end

View File

@@ -0,0 +1,142 @@
#===============================================================================
# class Object
#===============================================================================
class Object
alias full_inspect inspect unless method_defined?(:full_inspect)
def inspect
return "#<#{self.class}>"
end
end
#===============================================================================
# class Class
#===============================================================================
class Class
def to_sym
return self.to_s.to_sym
end
end
#===============================================================================
# class String
#===============================================================================
class String
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 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
def to_word
ret = [_INTL("zero"), _INTL("one"), _INTL("two"), _INTL("three"),
_INTL("four"), _INTL("five"), _INTL("six"), _INTL("seven"),
_INTL("eight"), _INTL("nine"), _INTL("ten"), _INTL("eleven"),
_INTL("twelve"), _INTL("thirteen"), _INTL("fourteen"), _INTL("fifteen"),
_INTL("sixteen"), _INTL("seventeen"), _INTL("eighteen"), _INTL("nineteen"),
_INTL("twenty")]
return ret[self] if self.is_a?(Integer) && self >= 0 && self <= ret.length
return self.to_s
end
end
#===============================================================================
# class Array
#===============================================================================
class Array
def ^(other) # xor of two arrays
return (self|other) - (self&other)
end
def swap(val1, val2)
index1 = self.index(val1)
index2 = self.index(val2)
self[index1] = val2
self[index2] = val1
end
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

View File

@@ -0,0 +1,783 @@
def pbAddScriptTexts(items, script)
script.force_encoding(Encoding::UTF_8)
script.scan(/(?:_I)\s*\(\s*\"((?:[^\\\"]*\\\"?)*[^\"]*)\"/) do |s|
string = s[0]
string.gsub!(/\\\"/, "\"")
string.gsub!(/\\\\/, "\\")
items.push(string)
end
end
def pbAddRgssScriptTexts(items, script)
script.force_encoding(Encoding::UTF_8)
script.scan(/(?:_INTL|_ISPRINTF)\s*\(\s*\"((?:[^\\\"]*\\\"?)*[^\"]*)\"/) do |s|
string = s[0]
string.gsub!(/\\r/, "\r")
string.gsub!(/\\n/, "\n")
string.gsub!(/\\1/, "\1")
string.gsub!(/\\\"/, "\"")
string.gsub!(/\\\\/, "\\")
items.push(string)
end
end
def pbSetTextMessages
Graphics.update
begin
t = System.uptime
texts = []
# Get script texts from Scripts.rxdata
$RGSS_SCRIPTS.each do |script|
if System.uptime - t >= 5
t += 5
Graphics.update
end
scr = Zlib::Inflate.inflate(script[2])
pbAddRgssScriptTexts(texts, scr)
end
# If Scripts.rxdata only has 1 section, scripts have been extracted. Get
# script texts from .rb files in Data/Scripts
if $RGSS_SCRIPTS.length == 1
Dir.all("Data/Scripts").each do |script_file|
if System.uptime - t >= 5
t += 5
Graphics.update
end
File.open(script_file, "rb") do |f|
pbAddRgssScriptTexts(texts, f.read)
end
end
end
# Must add messages because this code is used by both game system and Editor
MessageTypes.addMessagesAsHash(MessageTypes::ScriptTexts, texts)
commonevents = load_data("Data/CommonEvents.rxdata")
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]}"
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 = pbLoadMapInfos
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.rxdata", id)
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]}"
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].ord == 0xEF && line[1].ord == 0xBB && line[2].ord == 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
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 i < section.length
if itemlength == 3
if !section[i][/^\d+$/]
raise _INTL("Expected a number in section {1}, got {2} instead", name, section[i])
end
key = section[i].to_i
i += 1
else
key = MessageTypes.denormalizeValue(section[i])
end
intlhash[key] = MessageTypes.denormalizeValue(section[i + 1])
i += 2 end
if ismap
intldat[0] = [] if !intldat[0]
intldat[0][id] = intlhash
else
intldat[id] = intlhash
end
}
ensure
file.close
end
return intldat
end
def pbCompileText
outfile = File.open("intl.dat", "wb")
begin
intldat = pbGetText("intl.txt")
Marshal.dump(intldat, outfile)
rescue
raise
ensure
outfile.close
end
end
class OrderedHash < Hash
def initialize
@keys = []
super
end
def keys
return @keys.clone
end
def inspect
str = "{"
for i in 0...@keys.length
str += ", " if i > 0
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/, "<<r>>")
ret.gsub!(/\n/, "<<n>>")
ret.gsub!(/\t/, "<<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>>/, "\r")
ret.gsub!(/<<n>>/, "\n")
ret.gsub!(/<<\[>>/, "[")
ret.gsub!(/<<\]>>/, "]")
ret.gsub!(/<<t>>/, "\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 nil_or_empty?(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 nil_or_empty?(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)
hash = OrderedHash.new if !hash
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
RibbonNames = 25
RibbonDescriptions = 26
@@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

View File

@@ -0,0 +1,33 @@
module Input
USE = C
BACK = B
ACTION = A
JUMPUP = X
JUMPDOWN = Y
SPECIAL = Z
AUX1 = L
AUX2 = R
unless defined?(update_KGC_ScreenCapture)
class << Input
alias update_KGC_ScreenCapture update
end
end
def self.update
update_KGC_ScreenCapture
if trigger?(Input::F8)
pbScreenCapture
end
end
end
module Mouse
module_function
# Returns the position of the mouse relative to the game window.
def getMousePos(catch_anywhere = false)
return nil unless Input.mouse_in_window || catch_anywhere
return Input.mouse_x, Input.mouse_y
end
end

View File

@@ -0,0 +1,712 @@
#==============================================================================#
# Plugin Manager #
# by Marin #
# support for external plugin scripts by Luka S.J. #
# tweaked by Maruno #
#------------------------------------------------------------------------------#
# Provides a simple interface that allows plugins to require dependencies #
# at specific versions, and to specify incompatibilities between plugins. #
# #
# Supports external scripts that are in .rb files in folders within the #
# Plugins folder. #
#------------------------------------------------------------------------------#
# 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 should be in the format X.Y.Z, but the number of digits #
# you use 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. #
# #
# IF there are multiple people to credit, their names should be in an array. #
# If there is only one credit, it does not need an array: #
# #
# :credits => "Marin" #
# :credits => ["Marin", "Maruno"], #
# #
# #
# #
# Dependency: #
# #
# A plugin can require another plugin to be installed in order to work. For #
# example, the "Simple Extension" plugin depends on the above "Basic Plugin" #
# like so: #
# #
# PluginManager.register({ #
# :name => "Simple Extension", #
# :version => "1.0", #
# :link => "https://reliccastle.com/link-to-the-plugin/", #
# :credits => ["Marin", "Maruno"], #
# :dependencies => ["Basic Plugin"] #
# }) #
# #
# If there are multiple dependencies, they should be listed in an array. If #
# there is only one dependency, it does not need an array: #
# #
# :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: #
# #
# :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: #
# #
# :dependencies => [ #
# [:exact, "Basic Plugin", "1.2"] #
# ] #
# #
# If your plugin can work without another plugin, but it is 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 check whether 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. #
# #
# :dependencies => [ #
# [:optional, "QoL Improvements", "1.1"] #
# ] #
# #
# The :optional_exact flag is a combination of :optional and :exact. #
# #
# #
# #
# Incompatibility: #
# #
# 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" #
# ] #
# }) #
# #
#------------------------------------------------------------------------------#
# Plugin folder: #
# #
# The Plugin folder is treated like the PBS folder, but for script files for #
# plugins. Each plugin has its own folder within the Plugin folder. Each #
# plugin must have a meta.txt file in its folder, which contains information #
# about that plugin. Folders without this meta.txt file are ignored. #
# #
# Scripts must be in .rb files. You should not put any other files into a #
# plugin's folder except for script files and meta.txt. #
# #
# When the game is compiled, scripts in these folders are read and converted #
# into a usable format, and saved in the file Data/PluginScripts.rxdata. #
# Script files are loaded in order of their name and subfolder, so it is wise #
# to name script files "001_first script.rb", "002_second script.rb", etc. to #
# ensure they are loaded in the correct order. #
# #
# When the game is compressed for distribution, the Plugin folder and all its #
# contents should be deleted (like the PBS folder), because its contents will #
# be unused (they will have been compiled into the PluginScripts.rxdata file). #
# #
# The contents of meta.txt are as follows: #
# #
# Name = Simple Extension #
# Version = 1.0 #
# Requires = Basic Plugin #
# Requires = Useful Utilities,1.1 #
# Conflicts = Complex Extension #
# Conflicts = Extended Windows #
# Link = https://reliccastle.com/link-to-the-plugin/ #
# Credits = Luka S.J.,Maruno,Marin #
# #
# These lines are related to what is described above. You can have multiple #
# "Requires" and "Conflicts" lines, each listing a single other plugin that is #
# either a dependency or a conflict respectively. #
# #
# Examples of the "Requires" line: #
# #
# Requires = Basic Plugin #
# Requires = Basic Plugin,1.1 #
# Requires = Basic Plugin,1.1,exact #
# Requires = Basic Plugin,1.1,optional #
# Exact = Basic Plugin,1.1 #
# Optional = Basic Plugin,1.1 #
# #
# The "Exact" and "Optional" lines are equivalent to the "Requires" lines #
# that contain those keywords. #
# #
# There is also a "Scripts" line, which lists one or more script files that #
# should be loaded first. You can have multiple "Scripts" lines. However, you #
# can achieve the same effect by simply naming your script files in #
# alphanumeric order to make them load in a particular order, so the "Scripts" #
# line should not be necessary. #
# #
#------------------------------------------------------------------------------#
# Please give credit when using this. #
#==============================================================================#
module PluginManager
# 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 dep_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
echoln "Plugin Error:\r\n#{msg}"
p "Plugin Error: #{msg}"
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
#-----------------------------------------------------------------------------
# formats the error message
#-----------------------------------------------------------------------------
def self.pluginErrorMsg(name, script)
# begin message formatting
message = "[Infinite Fusion version #{Settings::GAME_VERSION_NUMBER}]\r\n"
message += "#{Essentials::ERROR_TEXT}\r\n" # For third party scripts to add to
message += "Error in Plugin [#{name}]:\r\n"
message += "#{$!.class} occurred.\r\n"
# go through message content
for line in $!.message.split("\r\n")
next if nil_or_empty?(line)
n = line[/\d+/]
err = line.split(":")[-1].strip
lms = line.split(":")[0].strip
err.gsub!(n, "") if n
err = err.capitalize if err.is_a?(String) && !err.empty?
linum = n ? "Line #{n}: " : ""
message += "#{linum}#{err}: #{lms}\r\n"
end
# show last 10 lines of backtrace
message += "\r\nBacktrace:\r\n"
$!.backtrace[0, 10].each { |i| message += "#{i}\r\n" }
# output to log
errorlog = "errorlog.txt"
errorlog = RTP.getSaveFileName("errorlog.txt") if (Object.const_defined?(:RTP) rescue false)
File.open(errorlog, "ab") do |f|
f.write("\r\n=================\r\n\r\n[#{Time.now}]\r\n")
f.write(message)
end
# format/censor the error log directory
errorlogline = errorlog.gsub("/", "\\")
errorlogline.sub!(Dir.pwd + "\\", "")
errorlogline.sub!(pbGetUserName, "USERNAME")
errorlogline = "\r\n" + errorlogline if errorlogline.length > 20
# output message
print("#{message}\r\nThis exception was logged in #{errorlogline}.\r\nHold Ctrl when closing this message to copy it to the clipboard.")
# Give a ~500ms coyote time to start holding Control
t = System.delta
until (System.delta - t) >= 500000
Input.update
if Input.press?(Input::CTRL)
Input.clipboard = message
break
end
end
end
#-----------------------------------------------------------------------------
# Used to read the metadata file
#-----------------------------------------------------------------------------
def self.readMeta(dir, file)
filename = "#{dir}/#{file}"
meta = {}
# read file
Compiler.pbCompilerEachPreppedLine(filename) { |line, line_no|
# split line up into property name and values
if !line[/^\s*(\w+)\s*=\s*(.*)$/]
raise _INTL("Bad line syntax (expected syntax like XXX=YYY)\r\n{1}", FileLineData.linereport)
end
property = $~[1].upcase
data = $~[2].split(',')
data.each_with_index { |value, i| data[i] = value.strip }
# begin formatting data hash
case property
when 'REQUIRES'
meta[:dependencies] = [] if !meta[:dependencies]
if data.length < 2 # No version given, just push name of plugin dependency
meta[:dependencies].push(data[0])
next
elsif data.length == 2 # Push name and version of plugin dependency
meta[:dependencies].push([data[0], data[1]])
else # Push dependency type, name and version of plugin dependency
meta[:dependencies].push([data[2].downcase.to_sym, data[0], data[1]])
end
when 'EXACT'
next if data.length < 2 # Exact dependencies must have a version given; ignore if not
meta[:dependencies] = [] if !meta[:dependencies]
meta[:dependencies].push([:exact, data[0], data[1]])
when 'OPTIONAL'
next if data.length < 2 # Optional dependencies must have a version given; ignore if not
meta[:dependencies] = [] if !meta[:dependencies]
meta[:dependencies].push([:optional, data[0], data[1]])
when 'CONFLICTS'
meta[:incompatibilities] = [] if !meta[:incompatibilities]
data.each { |value| meta[:incompatibilities].push(value) if value && !value.empty? }
when 'SCRIPTS'
meta[:scripts] = [] if !meta[:scripts]
data.each { |scr| meta[:scripts].push(scr) }
when 'CREDITS'
meta[:credits] = data
when 'LINK', 'WEBSITE'
meta[:link] = data[0]
else
meta[property.downcase.to_sym] = data[0]
end
}
# generate a list of all script files to be loaded, in the order they are to
# be loaded (files listed in the meta file are loaded first)
meta[:scripts] = [] if !meta[:scripts]
# get all script files from plugin Dir
for fl in Dir.all(dir)
next if !fl.include?(".rb")
meta[:scripts].push(fl.gsub("#{dir}/", ""))
end
# ensure no duplicate script files are queued
meta[:scripts].uniq!
# return meta hash
return meta
end
#-----------------------------------------------------------------------------
# Get a list of all the plugin directories to inspect
#-----------------------------------------------------------------------------
def self.listAll
return []
return [] if !$DEBUG || safeExists?("Game.rgssad")
# get a list of all directories in the `Plugins/` folder
dirs = []
Dir.get("Plugins").each { |d| dirs.push(d) if Dir.safe?(d) }
# return all plugins
return dirs
end
#-----------------------------------------------------------------------------
# Catch any potential loop with dependencies and raise an error
#-----------------------------------------------------------------------------
def self.validateDependencies(name, meta, og = nil)
# exit if no registered dependency
return nil if !meta[name] || !meta[name][:dependencies]
og = [name] if !og
# go through all dependencies
for dname in meta[name][:dependencies]
# clean the name to a simple string
dname = dname[0] if dname.is_a?(Array) && dname.length == 2
dname = dname[1] if dname.is_a?(Array) && dname.length == 3
# catch looping dependency issue
self.error("Plugin '#{og[0]}' has looping dependencies which cannot be resolved automatically.") if !og.nil? && og.include?(dname)
new_og = og.clone
new_og.push(dname)
self.validateDependencies(dname, meta, new_og)
end
return name
end
#-----------------------------------------------------------------------------
# Sort load order based on dependencies (this ends up in reverse order)
#-----------------------------------------------------------------------------
def self.sortLoadOrder(order, plugins)
# go through the load order
for o in order
next if !plugins[o] || !plugins[o][:dependencies]
# go through all dependencies
for dname in plugins[o][:dependencies]
# clean the name to a simple string
dname = dname[0] if dname.is_a?(Array) && dname.length == 2
dname = dname[1] if dname.is_a?(Array) && dname.length == 3
# catch missing dependency
self.error("Plugin '#{o}' requires plugin '#{dname}' to work properly.") if !order.include?(dname)
# skip if already sorted
next if order.index(dname) > order.index(o)
# catch looping dependency issue
order.swap(o, dname)
order = self.sortLoadOrder(order, plugins)
end
end
return order
end
#-----------------------------------------------------------------------------
# Get the order in which to load plugins
#-----------------------------------------------------------------------------
def self.getPluginOrder
plugins = {}
order = []
# Find all plugin folders that have a meta.txt and add them to the list of
# plugins.
for dir in self.listAll
# skip if there is no meta file
next if !safeExists?(dir + "/meta.txt")
ndx = order.length
meta = self.readMeta(dir, "meta.txt")
meta[:dir] = dir
# raise error if no name defined for plugin
self.error("No 'Name' metadata defined for plugin located at '#{dir}'.") if !meta[:name]
# raise error if no script defined for plugin
self.error("No 'Scripts' metadata defined for plugin located at '#{dir}'.") if !meta[:scripts]
plugins[meta[:name]] = meta
# raise error if a plugin with the same name already exists
self.error("A plugin called '#{meta[:name]}' already exists in the load order.") if order.include?(meta[:name])
order.insert(ndx, meta[:name])
end
# validate all dependencies
order.each { |o| self.validateDependencies(o, plugins) }
# sort the load order
return self.sortLoadOrder(order, plugins).reverse, plugins
end
#-----------------------------------------------------------------------------
# Check if plugins need compiling
#-----------------------------------------------------------------------------
def self.needCompiling?(order, plugins)
# fixed actions
return false if !$DEBUG || safeExists?("Game.rgssad")
return true if !safeExists?("Data/PluginScripts.rxdata")
Input.update
return true if Input.press?(Input::CTRL)
# analyze whether or not to push recompile
mtime = File.mtime("Data/PluginScripts.rxdata")
for o in order
# go through all the registered plugin scripts
scr = plugins[o][:scripts]
dir = plugins[o][:dir]
for sc in scr
return true if File.mtime("#{dir}/#{sc}") > mtime
end
return true if File.mtime("#{dir}/meta.txt") > mtime
end
return false
end
#-----------------------------------------------------------------------------
# Check if plugins need compiling
#-----------------------------------------------------------------------------
def self.compilePlugins(order, plugins)
echo 'Compiling plugin scripts...'
scripts = []
# go through the entire order one by one
for o in order
# save name, metadata and scripts array
meta = plugins[o].clone
meta.delete(:scripts)
meta.delete(:dir)
dat = [o, meta, []]
# iterate through each file to deflate
for file in plugins[o][:scripts]
File.open("#{plugins[o][:dir]}/#{file}", 'rb') do |f|
dat[2].push([file, Zlib::Deflate.deflate(f.read)])
end
end
# push to the main scripts array
scripts.push(dat)
end
# save to main `PluginScripts.rxdata` file
File.open("Data/PluginScripts.rxdata", 'wb') { |f| Marshal.dump(scripts, f) }
# collect garbage
GC.start
echoln ' done.'
echoln ''
end
#-----------------------------------------------------------------------------
# Check if plugins need compiling
#-----------------------------------------------------------------------------
def self.runPlugins
# get the order of plugins to interpret
order, plugins = self.getPluginOrder
# compile if necessary
self.compilePlugins(order, plugins) if self.needCompiling?(order, plugins)
# load plugins
scripts = load_data("Data/PluginScripts.rxdata")
echoed_plugins = []
for plugin in scripts
# get the required data
name, meta, script = plugin
# register plugin
self.register(meta)
# go through each script and interpret
for scr in script
# turn code into plaintext
code = Zlib::Inflate.inflate(scr[1]).force_encoding(Encoding::UTF_8)
# get rid of tabs
code.gsub!("\t", " ")
# construct filename
sname = scr[0].gsub("\\","/").split("/")[-1]
fname = "[#{name}] #{sname}"
# try to run the code
begin
eval(code, TOPLEVEL_BINDING, fname)
echoln "Loaded plugin: #{name}" if !echoed_plugins.include?(name)
echoed_plugins.push(name)
rescue Exception # format error message to display
self.pluginErrorMsg(name, sname)
Kernel.exit! true
end
end
end
echoln '' if !echoed_plugins.empty?
end
#-----------------------------------------------------------------------------
end

View File

@@ -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 || !@@_animations.include?(animation)
16.times do
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 = []
16.times do
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
sprite_y += self.src_rect.height / 2 if position == 1
sprite_y += self.src_rect.height if position == 2
end
for i in 0..15
sprite = sprites[i]
pattern = cell_data[i, 0]
if sprite == nil || pattern == nil || 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 then sprite.z = 1
when 1 then sprite.z = sprite.y+32+15
when 2 then 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 ||
(timing.condition == 1 && hit == true) ||
(timing.condition == 2 && 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) && 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

View File

@@ -0,0 +1,78 @@
module Settings
# 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).
MOVE_CATEGORY_PER_MOVE = (MECHANICS_GENERATION >= 4)
# Whether turn order is recalculated after a Pokémon Mega Evolves.
RECALCULATE_TURN_ORDER_AFTER_MEGA_EVOLUTION = (MECHANICS_GENERATION >= 7)
# Whether turn order is recalculated after a Pokémon's Speed stat changes.
RECALCULATE_TURN_ORDER_AFTER_SPEED_CHANGES = (MECHANICS_GENERATION >= 8)
# Whether critical hits do 1.5x damage and have 4 stages (true), or they do 2x
# damage and have 5 stages as in Gen 5 (false). Also determines whether
# critical hit rate can be copied by Transform/Psych Up.
NEW_CRITICAL_HIT_RATE_MECHANICS = (MECHANICS_GENERATION >= 5)
# Whether several effects apply relating to a Pokémon's type:
# * Electric-type immunity to paralysis
# * Ghost-type immunity to being trapped
# * Grass-type immunity to powder moves and Effect Spore
# * Poison-type Pokémon can't miss when using Toxic
MORE_TYPE_EFFECTS = (MECHANICS_GENERATION >= 5)
# Whether weather caused by an ability lasts 5 rounds (true) or forever (false).
FIXED_DURATION_WEATHER_FROM_ABILITY = (MECHANICS_GENERATION >= 6)
#=============================================================================
# Whether X items (X Attack, etc.) raise their stat by 2 stages (true) or 1
# (false).
X_STAT_ITEMS_RAISE_BY_TWO_STAGES = (MECHANICS_GENERATION >= 7)
# Whether some Poké Balls have catch rate multipliers from Gen 7 (true) or
# from earlier generations (false).
NEW_POKE_BALL_CATCH_RATES = (MECHANICS_GENERATION >= 7)
# Whether Soul Dew powers up Psychic and Dragon-type moves by 20% (true) or
# raises the holder's Special Attack and Special Defense by 50% (false).
SOUL_DEW_POWERS_UP_TYPES = (MECHANICS_GENERATION >= 7)
#=============================================================================
# The minimum number of badges required to boost each stat of a player's
# Pokémon by 1.1x, in battle only.
NUM_BADGES_BOOST_ATTACK = (MECHANICS_GENERATION >= 4) ? 999 : 1
NUM_BADGES_BOOST_DEFENSE = (MECHANICS_GENERATION >= 4) ? 999 : 5
NUM_BADGES_BOOST_SPATK = (MECHANICS_GENERATION >= 4) ? 999 : 7
NUM_BADGES_BOOST_SPDEF = (MECHANICS_GENERATION >= 4) ? 999 : 7
NUM_BADGES_BOOST_SPEED = (MECHANICS_GENERATION >= 4) ? 999 : 3
#=============================================================================
# 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).
MEGA_RINGS = [:MEGARING, :MEGABRACELET, :MEGACUFF, :MEGACHARM]
# The Game Switch which, while ON, prevents all Pokémon in battle from Mega
# Evolving even if they otherwise could.
NO_MEGA_EVOLUTION = 34
#=============================================================================
# Whether the Exp gained from beating a Pokémon should be scaled depending on
# the gainer's level.
SCALED_EXP_FORMULA = (MECHANICS_GENERATION == 5 || MECHANICS_GENERATION >= 7)
# 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.
SPLIT_EXP_BETWEEN_GAINERS = (MECHANICS_GENERATION <= 5)
# Whether the critical capture mechanic applies. Note that its calculation 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.
ENABLE_CRITICAL_CAPTURES = false#(MECHANICS_GENERATION >= 5)
# Whether Pokémon gain Exp for capturing a Pokémon.
GAIN_EXP_FOR_CAPTURE = (MECHANICS_GENERATION >= 5)
# The Game Switch which, whie ON, prevents the player from losing money if
# they lose a battle (they can still gain money from trainers for winning).
NO_MONEY_LOSS = 33
# Whether party Pokémon check whether they can evolve after all battles
# regardless of the outcome (true), or only after battles the player won (false).
CHECK_EVOLUTION_AFTER_ALL_BATTLES = (MECHANICS_GENERATION >= 6)
# Whether fainted Pokémon can try to evolve after a battle.
CHECK_EVOLUTION_FOR_FAINTED_POKEMON = true
end

View File

@@ -0,0 +1,96 @@
# The SaveData module is used to manipulate save data. It contains the {Value}s
# that make up the save data and {Conversion}s for resolving incompatibilities
# between Essentials and game versions.
# @see SaveData.register
# @see SaveData.register_conversion
module SaveData
# Contains the file path of the save file.
FILE_PATH = if File.directory?(System.data_directory)
System.data_directory + '/Game.rxdata'
else
'./Game.rxdata'
end
# @return [Boolean] whether the save file exists
def self.exists?
return File.file?(FILE_PATH)
end
# Fetches the save data from the given file.
# Returns an Array in the case of a pre-v19 save file.
# @param file_path [String] path of the file to load from
# @return [Hash, Array] loaded save data
# @raise [IOError, SystemCallError] if file opening fails
def self.get_data_from_file(file_path) validate file_path => String
save_data = nil
File.open(file_path) do |file|
data = Marshal.load(file)
if data.is_a?(Hash)
save_data = data
next
end
save_data = [data]
save_data << Marshal.load(file) until file.eof?
end
return save_data
end
# Fetches save data from the given file. If it needed converting, resaves it.
# @param file_path [String] path of the file to read from
# @return [Hash] save data in Hash format
# @raise (see .get_data_from_file)
def self.read_from_file(file_path)
validate file_path => String
save_data = get_data_from_file(file_path)
save_data = to_hash_format(save_data) if save_data.is_a?(Array)
if !save_data.empty? && run_conversions(save_data)
File.open(file_path, 'wb') { |file| Marshal.dump(save_data, file) }
end
return save_data
end
# Compiles the save data and saves a marshaled version of it into
# the given file.
# @param file_path [String] path of the file to save into
# @raise [InvalidValueError] if an invalid value is being saved
def self.save_to_file(file_path)
validate file_path => String
save_data = self.compile_save_hash
File.open(file_path, 'wb') { |file| Marshal.dump(save_data, file) }
end
# Deletes the save file (and a possible .bak backup file if one exists)
# @raise [Error::ENOENT]
def self.delete_file
File.delete(FILE_PATH)
File.delete(FILE_PATH + '.bak') if File.file?(FILE_PATH + '.bak')
end
# Converts the pre-v19 format data to the new format.
# @param old_format [Array] pre-v19 format save data
# @return [Hash] save data in new format
def self.to_hash_format(old_format)
validate old_format => Array
hash = {}
@values.each do |value|
data = value.get_from_old_format(old_format)
hash[value.id] = data unless data.nil?
end
return hash
end
# Moves a save file from the old Saved Games folder to the new
# location specified by {FILE_PATH}. Does nothing if a save file
# already exists in {FILE_PATH}.
def self.move_old_windows_save
return if File.file?(FILE_PATH)
game_title = System.game_title.gsub(/[^\w ]/, '_')
home = ENV['HOME'] || ENV['HOMEPATH']
return if home.nil?
old_location = File.join(home, 'Saved Games', game_title)
return unless File.directory?(old_location)
old_file = File.join(old_location, 'Game.rxdata')
return unless File.file?(old_file)
File.move(old_file, FILE_PATH)
end
end

View File

@@ -0,0 +1,269 @@
module SaveData
# Contains Value objects for each save element.
# Populated during runtime by SaveData.register calls.
# @type [Array<Value>]
@values = []
# An error raised if an invalid save value is being saved or loaded.
class InvalidValueError < RuntimeError; end
#=============================================================================
# Represents a single value in save data.
# New values are added using {SaveData.register}.
class Value
# @return [Symbol] the value id
attr_reader :id
# @param id [Symbol] value id
def initialize(id, &block)
validate id => Symbol, block => Proc
@id = id
@loaded = false
@load_in_bootup = false
instance_eval(&block)
raise "No save_value defined for save value #{id.inspect}" if @save_proc.nil?
raise "No load_value defined for save value #{id.inspect}" if @load_proc.nil?
end
# @param value [Object] value to check
# @return [Boolean] whether the given value is valid
def valid?(value)
return true if @ensured_class.nil?
return value.is_a?(Object.const_get(@ensured_class))
end
# Calls the value's load proc with the given argument passed into it.
# @param value [Object] load proc argument
# @raise [InvalidValueError] if an invalid value is being loaded
def load(value)
validate_value(value)
@load_proc.call(value)
@loaded = true
end
# Calls the value's save proc and returns its value.
# @return [Object] save proc value
# @raise [InvalidValueError] if an invalid value is being saved
def save
value = @save_proc.call
validate_value(value)
return value
end
# @return [Boolean] whether the value has a new game value proc defined
def has_new_game_proc?
return @new_game_value_proc.is_a?(Proc)
end
# Calls the save value's load proc with the value fetched
# from the defined new game value proc.
# @raise (see #load)
def load_new_game_value
unless self.has_new_game_proc?
raise "Save value #{@id.inspect} has no new_game_value defined"
end
self.load(@new_game_value_proc.call)
end
# @return [Boolean] whether the value should be loaded during bootup
def load_in_bootup?
return @load_in_bootup
end
# @return [Boolean] whether the value has been loaded
def loaded?
return @loaded
end
# Marks value as unloaded.
def mark_as_unloaded
@loaded = false
end
# Uses the {#from_old_format} proc to select the correct data from
# +old_format+ and return it.
# Returns nil if the proc is undefined.
# @param old_format [Array] old format to load value from
# @return [Object] data from the old format
def get_from_old_format(old_format)
return nil if @old_format_get_proc.nil?
return @old_format_get_proc.call(old_format)
end
private
# Raises an {InvalidValueError} if the given value is invalid.
# @param value [Object] value to check
# @raise [InvalidValueError] if the value is invalid
def validate_value(value)
return if self.valid?(value)
raise InvalidValueError, "Save value #{@id.inspect} is not a #{@ensured_class} (#{value.class.name} given)"
end
# @!group Configuration
# If present, ensures that the value is of the given class.
# @param class_name [Symbol] class to enforce
# @see SaveData.register
def ensure_class(class_name)
validate class_name => Symbol
@ensured_class = class_name
end
# Defines how the loaded value is placed into a global variable.
# Requires a block with the loaded value as its parameter.
# @see SaveData.register
def load_value(&block)
raise ArgumentError, 'No block given to load_value' unless block_given?
@load_proc = block
end
# Defines what is saved into save data. Requires a block.
# @see SaveData.register
def save_value(&block)
raise ArgumentError, 'No block given to save_value' unless block_given?
@save_proc = block
end
# If present, defines what the value is set to at the start of a new game.
# @see SaveData.register
def new_game_value(&block)
raise ArgumentError, 'No block given to new_game_value' unless block_given?
@new_game_value_proc = block
end
# If present, sets the value to be loaded during bootup.
# @see SaveData.register
def load_in_bootup
@load_in_bootup = true
end
# If present, defines how the value should be fetched from the pre-v19
# save format. Requires a block with the old format array as its parameter.
# @see SaveData.register
def from_old_format(&block)
raise ArgumentError, 'No block given to from_old_format' unless block_given?
@old_format_get_proc = block
end
# @!endgroup
end
#=============================================================================
# Registers a {Value} to be saved into save data.
# Takes a block which defines the value's saving ({Value#save_value})
# and loading ({Value#load_value}) procedures.
#
# It is also possible to provide a proc for fetching the value
# from the pre-v19 format ({Value#from_old_format}), define
# a value to be set upon starting a new game with {Value#new_game_value}
# and ensure that the saved and loaded value is of the correct
# class with {Value#ensure_class}.
#
# Values can be registered to be loaded on bootup with
# {Value#load_in_bootup}. If a new_game_value proc is defined, it
# will be called when the game is launched for the first time,
# or if the save data does not contain the value in question.
#
# @example Registering a new value
# SaveData.register(:foo) do
# ensure_class :Foo
# save_value { $foo }
# load_value { |value| $foo = value }
# new_game_value { Foo.new }
# from_old_format { |old_format| old_format[16] if old_format[16].is_a?(Foo) }
# end
# @example Registering a value to be loaded on bootup
# SaveData.register(:bar) do
# load_in_bootup
# save_value { $bar }
# load_value { |value| $bar = value }
# new_game_value { Bar.new }
# end
# @param id [Symbol] value id
# @yieldself [Value]
def self.register(id, &block)
validate id => Symbol
unless block_given?
raise ArgumentError, 'No block given to SaveData.register'
end
@values << Value.new(id, &block)
end
# @param save_data [Hash] save data to validate
# @return [Boolean] whether the given save data is valid
def self.valid?(save_data)
validate save_data => Hash
return @values.all? { |value| value.valid?(save_data[value.id]) }
end
# Loads values from the given save data.
# An optional condition can be passed.
# @param save_data [Hash] save data to load from
# @param condition_block [Proc] optional condition
# @api private
def self.load_values(save_data, &condition_block)
@values.each do |value|
next if block_given? && !condition_block.call(value)
if save_data.has_key?(value.id)
value.load(save_data[value.id])
elsif value.has_new_game_proc?
value.load_new_game_value
end
end
end
# Loads the values from the given save data by
# calling each {Value} object's {Value#load_value} proc.
# Values that are already loaded are skipped.
# If a value does not exist in the save data and has
# a {Value#new_game_value} proc defined, that value
# is loaded instead.
# @param save_data [Hash] save data to load
# @raise [InvalidValueError] if an invalid value is being loaded
def self.load_all_values(save_data)
validate save_data => Hash
load_values(save_data) { |value| !value.loaded? }
end
# Marks all values that aren't loaded on bootup as unloaded.
def self.mark_values_as_unloaded
@values.each do |value|
value.mark_as_unloaded unless value.load_in_bootup?
end
end
# Loads each value from the given save data that has
# been set to be loaded during bootup. Done when a save file exists.
# @param save_data [Hash] save data to load
# @raise [InvalidValueError] if an invalid value is being loaded
def self.load_bootup_values(save_data)
validate save_data => Hash
load_values(save_data) { |value| !value.loaded? && value.load_in_bootup? }
end
# Goes through each value with {Value#load_in_bootup} enabled and loads their
# new game value, if one is defined. Done when no save file exists.
def self.initialize_bootup_values
@values.each do |value|
next unless value.load_in_bootup?
value.load_new_game_value if value.has_new_game_proc? && !value.loaded?
end
end
# Loads each {Value}'s new game value, if one is defined. Done when starting a
# new game.
def self.load_new_game_values
@values.each do |value|
value.load_new_game_value if value.has_new_game_proc? && !value.loaded?
end
end
# @return [Hash{Symbol => Object}] a hash representation of the save data
# @raise [InvalidValueError] if an invalid value is being saved
def self.compile_save_hash
save_data = {}
@values.each { |value| save_data[value.id] = value.save }
return save_data
end
end

View File

@@ -0,0 +1,221 @@
module SaveData
# Contains Conversion objects for each defined conversion:
# {
# :essentials => {
# '19' => [<Conversion>, ...],
# '19.1' => [<Conversion>, ...],
# ...
# },
# :game => {
# '1.1.0' => [<Conversion>, ...],
# '1.2.0' => [<Conversion>, ...],
# ...
# }
# }
# Populated during runtime by SaveData.register_conversion calls.
@conversions = {
essentials: {},
game: {}
}
#=============================================================================
# Represents a conversion made to save data.
# New conversions are added using {SaveData.register_conversion}.
class Conversion
# @return [Symbol] conversion ID
attr_reader :id
# @return [String] conversion title
attr_reader :title
# @return [Symbol] trigger type of the conversion (+:essentials+ or +:game+)
attr_reader :trigger_type
# @return [String] trigger version of the conversion
attr_reader :version
# @param id [String] conversion ID
def initialize(id, &block)
@id = id
@value_procs = {}
@all_proc = nil
@title = "Running conversion #{@id}"
@trigger_type = nil
@version = nil
instance_eval(&block)
if @trigger_type.nil? || @version.nil?
raise "Conversion #{@id} is missing a condition"
end
end
# Returns whether the conversion should be run with the given version.
# @param version [String] version to check
# @return [Boolean] whether the conversion should be run
def should_run?(version)
return PluginManager.compare_versions(version, @version) < 0
end
# Runs the conversion on the given save data.
# @param save_data [Hash]
def run(save_data)
@value_procs.each do |value_id, proc|
unless save_data.has_key?(value_id)
raise "Save data does not have value #{value_id.inspect}"
end
proc.call(save_data[value_id])
end
@all_proc.call(save_data) if @all_proc.is_a?(Proc)
end
# Runs the conversion on the given object.
# @param object
# @param key [Symbol]
def run_single(object, key)
@value_procs[key].call(object) if @value_procs[key].is_a?(Proc)
end
private
# @!group Configuration
# Sets the conversion's title.
# @param new_title [String] conversion title
# @note Since conversions are run before loading the player's chosen language,
# conversion titles can not be localized.
# @see SaveData.register_conversion
def display_title(new_title)
validate new_title => String
@title = new_title
end
# Sets the conversion to trigger for save files created below
# the given Essentials version.
# @param version [Numeric, String]
# @see SaveData.register_conversion
def essentials_version(version)
validate version => [Numeric, String]
raise "Multiple conditions in conversion #{@id}" unless @version.nil?
@trigger_type = :essentials
@version = version.to_s
end
# Sets the conversion to trigger for save files created below
# the given game version.
# @param version [Numeric, String]
# @see SaveData.register_conversion
def game_version(version)
validate version => [Numeric, String]
raise "Multiple conditions in conversion #{@id}" unless @version.nil?
@trigger_type = :game
@version = version.to_s
end
# Defines a conversion to the given save value.
# @param value_id [Symbol] save value ID
# @see SaveData.register_conversion
def to_value(value_id, &block)
validate value_id => Symbol
raise ArgumentError, 'No block given to to_value' unless block_given?
if @value_procs[value_id].is_a?(Proc)
raise "Multiple to_value definitions in conversion #{@id} for #{value_id}"
end
@value_procs[value_id] = block
end
# Defines a conversion to the entire save data.
# @see SaveData.register_conversion
def to_all(&block)
raise ArgumentError, 'No block given to to_all' unless block_given?
if @all_proc.is_a?(Proc)
raise "Multiple to_all definitions in conversion #{@id}"
end
@all_proc = block
end
# @!endgroup
end
#=============================================================================
# Registers a {Conversion} to occur for save data that meets the given criteria.
# Two types of criteria can be defined: {Conversion#essentials_version} and
# {Conversion#game_version}. The conversion is automatically run on save data
# that contains an older version number.
#
# A single value can be modified with {Conversion#to_value}. The entire save data
# is accessed with {Conversion#to_all}, and a conversion title can be specified
# with {Conversion#display_title}.
# @example Registering a new conversion
# SaveData.register_conversion(:my_conversion) do
# game_version '1.1.0'
# display_title 'Converting some stuff'
# to_value :player do |player|
# # code that modifies the :player value
# end
# to_all do |save_data|
# save_data[:new_value] = Foo.new
# end
# end
# @yield self [Conversion]
def self.register_conversion(id, &block)
validate id => Symbol
unless block_given?
raise ArgumentError, 'No block given to SaveData.register_conversion'
end
conversion = Conversion.new(id, &block)
@conversions[conversion.trigger_type][conversion.version] ||= []
@conversions[conversion.trigger_type][conversion.version] << conversion
end
# @param save_data [Hash] save data to get conversions for
# @return [Array<Conversion>] all conversions that should be run on the data
def self.get_conversions(save_data)
conversions_to_run = []
versions = {
essentials: save_data[:essentials_version] || '18.1',
game: save_data[:game_version] || '0.0.0'
}
[:essentials, :game].each do |trigger_type|
# Ensure the versions are sorted from lowest to highest
sorted_versions = @conversions[trigger_type].keys.sort do |v1, v2|
PluginManager.compare_versions(v1, v2)
end
sorted_versions.each do |version|
@conversions[trigger_type][version].each do |conversion|
next unless conversion.should_run?(versions[trigger_type])
conversions_to_run << conversion
end
end
end
return conversions_to_run
end
# Runs all possible conversions on the given save data.
# Saves a backup before running conversions.
# @param save_data [Hash] save data to run conversions on
# @return [Boolean] whether conversions were run
def self.run_conversions(save_data)
validate save_data => Hash
conversions_to_run = self.get_conversions(save_data)
return false if conversions_to_run.none?
File.open(SaveData::FILE_PATH + '.bak', 'wb') { |f| Marshal.dump(save_data, f) }
echoln "Running #{conversions_to_run.length} conversions..."
conversions_to_run.each do |conversion|
echo "#{conversion.title}..."
conversion.run(save_data)
echoln ' done.'
end
echoln '' if conversions_to_run.length > 0
save_data[:essentials_version] = Essentials::VERSION
save_data[:game_version] = Settings::GAME_VERSION
return true
end
# Runs all possible conversions on the given object.
# @param object [Hash] object to run conversions on
# @param key [Hash] object's key in save data
# @param save_data [Hash] save data to run conversions on
def self.run_single_conversions(object, key, save_data)
validate key => Symbol
conversions_to_run = self.get_conversions(save_data)
conversions_to_run.each do |conversion|
conversion.run_single(object, key)
end
end
end

View File

@@ -0,0 +1,135 @@
# Contains the save values defined in Essentials by default.
SaveData.register(:player) do
ensure_class :Player
save_value { $Trainer }
load_value { |value| $Trainer = value }
new_game_value {
trainer_type = nil # Get the first defined trainer type as a placeholder
GameData::TrainerType.each { |t| trainer_type = t.id; break }
Player.new("Unnamed", trainer_type)
}
from_old_format { |old_format| old_format[0] }
end
SaveData.register(:frame_count) do
ensure_class :Integer
save_value { Graphics.frame_count }
load_value { |value| Graphics.frame_count = value }
new_game_value { 0 }
from_old_format { |old_format| old_format[1] }
end
SaveData.register(:game_system) do
load_in_bootup
ensure_class :Game_System
save_value { $game_system }
load_value { |value| $game_system = value }
new_game_value { Game_System.new }
from_old_format { |old_format| old_format[2] }
end
SaveData.register(:pokemon_system) do
load_in_bootup
ensure_class :PokemonSystem
save_value { $PokemonSystem }
load_value { |value| $PokemonSystem = value }
new_game_value { PokemonSystem.new }
from_old_format { |old_format| old_format[3] }
end
SaveData.register(:switches) do
ensure_class :Game_Switches
save_value { $game_switches }
load_value { |value| $game_switches = value }
new_game_value { Game_Switches.new }
from_old_format { |old_format| old_format[5] }
end
SaveData.register(:variables) do
ensure_class :Game_Variables
save_value { $game_variables }
load_value { |value| $game_variables = value }
new_game_value { Game_Variables.new }
from_old_format { |old_format| old_format[6] }
end
SaveData.register(:self_switches) do
ensure_class :Game_SelfSwitches
save_value { $game_self_switches }
load_value { |value| $game_self_switches = value }
new_game_value { Game_SelfSwitches.new }
from_old_format { |old_format| old_format[7] }
end
SaveData.register(:game_screen) do
ensure_class :Game_Screen
save_value { $game_screen }
load_value { |value| $game_screen = value }
new_game_value { Game_Screen.new }
from_old_format { |old_format| old_format[8] }
end
SaveData.register(:map_factory) do
ensure_class :PokemonMapFactory
save_value { $MapFactory }
load_value { |value| $MapFactory = value }
from_old_format { |old_format| old_format[9] }
end
SaveData.register(:game_player) do
ensure_class :Game_Player
save_value { $game_player }
load_value { |value| $game_player = value }
new_game_value { Game_Player.new }
from_old_format { |old_format| old_format[10] }
end
SaveData.register(:global_metadata) do
ensure_class :PokemonGlobalMetadata
save_value { $PokemonGlobal }
load_value { |value| $PokemonGlobal = value }
new_game_value { PokemonGlobalMetadata.new }
from_old_format { |old_format| old_format[11] }
end
SaveData.register(:map_metadata) do
ensure_class :PokemonMapMetadata
save_value { $PokemonMap }
load_value { |value| $PokemonMap = value }
new_game_value { PokemonMapMetadata.new }
from_old_format { |old_format| old_format[12] }
end
SaveData.register(:bag) do
ensure_class :PokemonBag
save_value { $PokemonBag }
load_value { |value| $PokemonBag = value }
new_game_value { PokemonBag.new }
from_old_format { |old_format| old_format[13] }
end
SaveData.register(:storage_system) do
ensure_class :PokemonStorage
save_value { $PokemonStorage }
load_value { |value| $PokemonStorage = value }
new_game_value { PokemonStorage.new }
from_old_format { |old_format| old_format[14] }
end
SaveData.register(:essentials_version) do
load_in_bootup
ensure_class :String
save_value { Essentials::VERSION }
load_value { |value| $SaveVersion = value }
new_game_value { Essentials::VERSION }
from_old_format { |old_format| old_format[15] }
end
SaveData.register(:game_version) do
load_in_bootup
ensure_class :String
save_value { Settings::GAME_VERSION }
load_value { |value| $game_version = value }
new_game_value { Settings::GAME_VERSION }
end

View File

@@ -0,0 +1,242 @@
# Contains conversions defined in Essentials by default.
SaveData.register_conversion(:v19_define_versions) do
essentials_version 19
display_title 'Adding game version and Essentials version to save data'
to_all do |save_data|
unless save_data.has_key?(:essentials_version)
save_data[:essentials_version] = Essentials::VERSION
end
unless save_data.has_key?(:game_version)
save_data[:game_version] = Settings::GAME_VERSION
end
end
end
SaveData.register_conversion(:v19_convert_PokemonSystem) do
essentials_version 19
display_title 'Updating PokemonSystem class'
to_all do |save_data|
new_system = PokemonSystem.new
new_system.textspeed = save_data[:pokemon_system].textspeed || new_system.textspeed
new_system.battlescene = save_data[:pokemon_system].battlescene || new_system.battlescene
new_system.battlestyle = save_data[:pokemon_system].battlestyle || new_system.battlestyle
new_system.frame = save_data[:pokemon_system].frame || new_system.frame
new_system.textskin = save_data[:pokemon_system].textskin || new_system.textskin
new_system.screensize = save_data[:pokemon_system].screensize || new_system.screensize
new_system.language = save_data[:pokemon_system].language || new_system.language
new_system.runstyle = save_data[:pokemon_system].runstyle || new_system.runstyle
new_system.bgmvolume = save_data[:pokemon_system].bgmvolume || new_system.bgmvolume
new_system.sevolume = save_data[:pokemon_system].sevolume || new_system.sevolume
new_system.textinput = save_data[:pokemon_system].textinput || new_system.textinput
save_data[:pokemon_system] = new_system
end
end
SaveData.register_conversion(:v19_convert_player) do
essentials_version 19
display_title 'Converting player trainer class'
to_all do |save_data|
next if save_data[:player].is_a?(Player)
# Conversion of the party is handled in PokeBattle_Trainer.convert
save_data[:player] = PokeBattle_Trainer.convert(save_data[:player])
end
end
SaveData.register_conversion(:v19_move_global_data_to_player) do
essentials_version 19
display_title 'Moving some global metadata data to player'
to_all do |save_data|
global = save_data[:global_metadata]
player = save_data[:player]
player.character_ID = global.playerID
global.playerID = nil
global.pokedexUnlocked.each_with_index do |value, i|
if value
player.pokedex.unlock(i)
else
player.pokedex.lock(i)
end
end
player.coins = global.coins
global.coins = nil
player.soot = global.sootsack
global.sootsack = nil
player.has_running_shoes = global.runningShoes
global.runningShoes = nil
player.seen_storage_creator = global.seenStorageCreator
global.seenStorageCreator = nil
player.has_snag_machine = global.snagMachine
global.snagMachine = nil
player.seen_purify_chamber = global.seenPurifyChamber
global.seenPurifyChamber = nil
end
end
SaveData.register_conversion(:v19_convert_global_metadata) do
essentials_version 19
display_title 'Adding encounter version variable to global metadata'
to_value :global_metadata do |global|
global.bridge ||= 0
global.encounter_version ||= 0
if global.pcItemStorage
global.pcItemStorage.items.each_with_index do |slot, i|
item_data = GameData::Item.try_get(slot[0])
if item_data
slot[0] = item_data.id
else
global.pcItemStorage.items[i] = nil
end
end
global.pcItemStorage.items.compact!
end
if global.mailbox
global.mailbox.each_with_index do |mail, i|
global.mailbox[i] = PokemonMail.convert(mail) if mail
end
end
global.phoneNumbers.each do |contact|
contact[1] = GameData::TrainerType.get(contact[1]).id if contact && contact.length == 8
end
if global.partner
global.partner[0] = GameData::TrainerType.get(global.partner[0]).id
global.partner[3].each_with_index do |pkmn, i|
global.partner[3][i] = PokeBattle_Pokemon.convert(pkmn) if pkmn
end
end
if global.daycare
global.daycare.each do |slot|
slot[0] = PokeBattle_Pokemon.convert(slot[0]) if slot && slot[0]
end
end
if global.roamPokemon
global.roamPokemon.each_with_index do |pkmn, i|
global.roamPokemon[i] = PokeBattle_Pokemon.convert(pkmn) if pkmn && pkmn != true
end
end
global.purifyChamber.sets.each do |set|
set.shadow = PokeBattle_Pokemon.convert(set.shadow) if set.shadow
set.list.each_with_index do |pkmn, i|
set.list[i] = PokeBattle_Pokemon.convert(pkmn) if pkmn
end
end
if global.hallOfFame
global.hallOfFame.each do |team|
next if !team
team.each_with_index do |pkmn, i|
team[i] = PokeBattle_Pokemon.convert(pkmn) if pkmn
end
end
end
if global.triads
global.triads.items.each do |card|
card[0] = GameData::Species.get(card[0]).id if card && card[0] && card[0] != 0
end
end
end
end
SaveData.register_conversion(:v19_1_fix_phone_contacts) do
essentials_version 19.1
display_title 'Fixing phone contacts data'
to_value :global_metadata do |global|
global.phoneNumbers.each do |contact|
contact[1] = GameData::TrainerType.get(contact[1]).id if contact && contact.length == 8
end
end
end
SaveData.register_conversion(:v19_convert_bag) do
essentials_version 19
display_title 'Converting item IDs in Bag'
to_value :bag do |bag|
bag.instance_eval do
for pocket in self.pockets
pocket.each_with_index do |item, i|
next if !item || !item[0] || item[0] == 0
item_data = GameData::Item.try_get(item[0])
if item_data
item[0] = item_data.id
else
pocket[i] = nil
end
end
pocket.compact!
end
self.registeredIndex # Just to ensure this data exists
self.registeredItems.each_with_index do |item, i|
next if !item
if item == 0
self.registeredItems[i] = nil
else
item_data = GameData::Item.try_get(item)
if item_data
self.registeredItems[i] = item_data.id
else
self.registeredItems[i] = nil
end
end
end
self.registeredItems.compact!
end # bag.instance_eval
end # to_value
end
SaveData.register_conversion(:v19_convert_game_variables) do
essentials_version 19
display_title 'Converting classes of things in Game Variables'
to_all do |save_data|
variables = save_data[:variables]
for i in 0..5000
value = variables[i]
next if value.nil?
if value.is_a?(Array)
value.each_with_index do |value2, j|
if value2.is_a?(PokeBattle_Pokemon)
value[j] = PokeBattle_Pokemon.convert(value2)
end
end
elsif value.is_a?(PokeBattle_Pokemon)
variables[i] = PokeBattle_Pokemon.convert(value)
elsif value.is_a?(PokemonBag)
SaveData.run_single_conversions(value, :bag, save_data)
end
end
end
end
SaveData.register_conversion(:v19_convert_storage) do
essentials_version 19
display_title 'Converting classes of Pokémon in storage'
to_value :storage_system do |storage|
storage.instance_eval do
for box in 0...self.maxBoxes
for i in 0...self.maxPokemon(box)
self[box, i] = PokeBattle_Pokemon.convert(self[box, i]) if self[box, i]
end
end
self.unlockedWallpapers # Just to ensure this data exists
end # storage.instance_eval
end # to_value
end
SaveData.register_conversion(:v19_convert_game_player) do
essentials_version 19
display_title 'Converting game player character'
to_value :game_player do |game_player|
game_player.width = 1
game_player.height = 1
game_player.sprite_size = [Game_Map::TILE_WIDTH, Game_Map::TILE_HEIGHT]
game_player.pattern_surf ||= 0
game_player.lock_pattern ||= false
game_player.move_speed = game_player.move_speed
end
end
SaveData.register_conversion(:v19_convert_game_screen) do
essentials_version 19
display_title 'Converting game screen'
to_value :game_screen do |game_screen|
game_screen.weather(game_screen.weather_type, game_screen.weather_max, 0)
end
end

View File

@@ -0,0 +1,266 @@
# The Game module contains methods for saving and loading the game.
module Game
# Initializes various global variables and loads the game data.
def self.initialize
$PokemonTemp = PokemonTemp.new
$game_temp = Game_Temp.new
$game_system = Game_System.new
$data_animations = load_data('Data/Animations.rxdata')
$data_tilesets = load_data('Data/Tilesets.rxdata')
$data_common_events = load_data('Data/CommonEvents.rxdata')
$data_system = load_data('Data/System.rxdata')
pbLoadBattleAnimations
load_sprites_list_caches()
$updated_spritesheets = load_updated_spritesheets()
GameData.load_all
map_file = format('Data/Map%03d.rxdata', $data_system.start_map_id)
if $data_system.start_map_id == 0 || !pbRgssExists?(map_file)
raise _INTL('No starting position was set in the map editor.')
end
end
def self.load_updated_spritesheets
updated_spritesheets_file = Settings::UPDATED_SPRITESHEETS_CACHE
updated_spritesheets = []
if File.exist?(updated_spritesheets_file)
File.open(updated_spritesheets_file, "r") do |file|
file.each_line { |line| updated_spritesheets << line.chomp }
end
end
return updated_spritesheets
end
def self.load_sprites_list_caches()
self.load_custom_sprites_list_cache() if File.exists?(Settings::CUSTOM_SPRITES_FILE_PATH)
self.load_base_sprites_list_cache() if File.exists?(Settings::BASE_SPRITES_FILE_PATH)
end
def self.load_custom_sprites_list_cache()
return if !$game_temp.custom_sprites_list.keys.empty? #only load once at loadup
echoln "loading custom sprites cache"
sprite_index = {}
File.foreach(Settings::CUSTOM_SPRITES_FILE_PATH) do |line|
filename = line.strip
next unless filename =~ /^(\d+)\.(\d+)([a-zA-Z]*)\.png$/ # Regex: Captures the numbers and any trailing letters
# Match groups
head_number = $1.to_i # Head (e.g., "1" in "1.2.png")
body_number = $2.to_i # Body (e.g., "2" in "1.2.png")
letters = $3 # Letters after the second number (e.g., "a", "b", etc.)
key = "B#{body_number}H#{head_number}".to_sym
sprite_index[key] ||= []
if letters.empty?
sprite_index[key] << ""
else
sprite_index[key] << letters
end
end
$game_temp.custom_sprites_list = sprite_index
echoln "custom sprites loaded"
end
#
# {1 => ["","a","b"]
#etc.
#
def self.load_base_sprites_list_cache()
return if !$game_temp.base_sprites_list.keys.empty? #only load once at loadup
echoln "loading base sprites cache"
sprite_index = {}
File.foreach(Settings::BASE_SPRITES_FILE_PATH) do |line|
filename = line.strip
next unless filename =~ /^(\d+)([a-zA-Z]*)\.png$/ # Regex: Captures the numbers and any trailing letters
# Match groups
dex_number = $1.to_i # Head (e.g., "1" in "1.2.png")
letters = $2 # Letters after the second number (e.g., "a", "b", etc.)
key = dex_number
sprite_index[key] ||= []
if letters.empty?
sprite_index[key] << ""
else
sprite_index[key] << letters
end
end
$game_temp.base_sprites_list = sprite_index
echoln "custom sprites loaded"
end
#
# {:B10H10 => ["","a","b"]
#etc.
#
def self.set_up_system
SaveData.move_old_windows_save if System.platform[/Windows/]
save_data = (SaveData.exists?) ? SaveData.read_from_file(SaveData::FILE_PATH) : {}
if save_data.empty?
SaveData.initialize_bootup_values
else
SaveData.load_bootup_values(save_data)
end
# Set resize factor
pbSetResizeFactor([$PokemonSystem.screensize, 4].min)
# Set language (and choose language if there is no save file)
if Settings::LANGUAGES.length >= 2
$PokemonSystem.language = pbChooseLanguage if save_data.empty?
pbLoadMessages('Data/' + Settings::LANGUAGES[$PokemonSystem.language][1])
end
end
#For new game plus - resets everything in boxes/party to level 5 and 1st stage
def self.ngp_clean_pc_data(old_storage, old_party)
new_storage = old_storage
for pokemon in old_party
new_storage.pbStoreCaught(pokemon)
end
for box in new_storage.boxes
for pokemon in box.pokemon
if pokemon != nil
if !pokemon.egg?
pokemon.exp_when_fused_head=nil
pokemon.exp_when_fused_body=nil
pokemon.exp_gained_since_fused=nil
pokemon.level = 5
echoln pokemon.owner.id
pokemon.owner.id = $Trainer.id
pokemon.ot=$Trainer.name
pokemon.obtain_method = 0
pokemon.species = GameData::Species.get(pokemon.species).get_baby_species(false)
$Trainer.pokedex.set_seen(pokemon.species)
$Trainer.pokedex.set_owned(pokemon.species)
pokemon.reset_moves
pokemon.calc_stats
end
end
end
end
return new_storage
end
#For new game plus - removes key items
def self.ngp_clean_item_data(old_bag)
new_storage = old_bag
new_storage.clear
for pocket in old_bag.pockets
for bagElement in pocket
item_id = bagElement[0]
item_qt = bagElement[1]
item = GameData::Item.get(item_id)
if !item.is_key_item? && !item.is_HM?
new_storage.pbStoreItem(item, 1)
end
end
end
return new_storage
end
# Called when starting a new game. Initializes global variables
# and transfers the player into the map scene.
def self.start_new(ngp_bag = nil, ngp_storage = nil, ngp_trainer = nil)
if $game_map && $game_map.events
$game_map.events.each_value { |event| event.clear_starting }
end
$game_temp.common_event_id = 0 if $game_temp
$PokemonTemp.begunNewGame = true
$scene = Scene_Map.new
SaveData.load_new_game_values
$MapFactory = PokemonMapFactory.new($data_system.start_map_id)
$game_player.moveto($data_system.start_x, $data_system.start_y)
$game_player.refresh
$PokemonEncounters = PokemonEncounters.new
$PokemonEncounters.setup($game_map.map_id)
$game_map.autoplay
$game_map.update
#
# if ngp_bag != nil
# $PokemonBag = ngp_clean_item_data(ngp_bag)
# end
if ngp_storage != nil
$PokemonStorage = ngp_clean_pc_data(ngp_storage, ngp_trainer.party)
end
onStartingNewGame()
end
# Loads the game from the given save data and starts the map scene.
# @param save_data [Hash] hash containing the save data
# @raise [SaveData::InvalidValueError] if an invalid value is being loaded
def self.load(save_data)
validate save_data => Hash
SaveData.load_all_values(save_data)
self.load_map
pbAutoplayOnSave
$game_map.update
$PokemonMap.updateMap
$scene = Scene_Map.new
onLoadExistingGame()
end
# Loads and validates the map. Called when loading a saved game.
def self.load_map
$game_map = $MapFactory.map
magic_number_matches = ($game_system.magic_number == $data_system.magic_number)
if !magic_number_matches || $PokemonGlobal.safesave
if pbMapInterpreterRunning?
pbMapInterpreter.setup(nil, 0)
end
begin
$MapFactory.setup($game_map.map_id)
rescue Errno::ENOENT
if $DEBUG
pbMessage(_INTL('Map {1} was not found.', $game_map.map_id))
map = pbWarpToMapList
exit unless map
$MapFactory.setup(map[0])
$game_player.moveto(map[1], map[2])
else
raise _INTL('The map was not found. The game cannot continue.')
end
end
$game_player.center($game_player.x, $game_player.y)
else
$MapFactory.setMapChanged($game_map.map_id)
end
if $game_map.events.nil?
raise _INTL('The map is corrupt. The game cannot continue.')
end
$PokemonEncounters = PokemonEncounters.new
$PokemonEncounters.setup($game_map.map_id)
self.load_secret_bases if Settings::HOENN
pbUpdateVehicle
end
def self.load_secret_bases
loader = SecretBaseLoader.new
loader.load_visitor_bases
end
# Saves the game. Returns whether the operation was successful.
# @param save_file [String] the save file path
# @param safe [Boolean] whether $PokemonGlobal.safesave should be set to true
# @return [Boolean] whether the operation was successful
# @raise [SaveData::InvalidValueError] if an invalid value is being saved
def self.save(save_file = SaveData::FILE_PATH, safe: false)
validate save_file => String, safe => [TrueClass, FalseClass]
$PokemonGlobal.safesave = safe
$game_system.save_count += 1
$game_system.magic_number = $data_system.magic_number
begin
SaveData.save_to_file(save_file)
Graphics.frame_reset
rescue IOError, SystemCallError
$game_system.save_count -= 1
return false
end
return true
end
end

View File

@@ -0,0 +1,298 @@
#===============================================================================
# ** Modified Scene_Map class for Pokémon.
#-------------------------------------------------------------------------------
#
#===============================================================================
class Scene_Map
attr_reader :spritesetGlobal
attr_reader :map_renderer
attr_accessor :spritesets
def spriteset
for i in @spritesets.values
return i if i.map == $game_map
end
return @spritesets.values[0]
end
def createSpritesets
@map_renderer = TilemapRenderer.new(Spriteset_Map.viewport) if !@map_renderer || @map_renderer.disposed?
@spritesetGlobal = Spriteset_Global.new if !@spritesetGlobal
@spritesets = {}
for map in $MapFactory.maps
@spritesets[map.map_id] = Spriteset_Map.new(map)
end
$MapFactory.setSceneStarted(self)
updateSpritesets(true)
end
def createSingleSpriteset(map)
temp = $scene.spriteset.getAnimations
@spritesets[map] = Spriteset_Map.new($MapFactory.maps[map])
$scene.spriteset.restoreAnimations(temp)
$MapFactory.setSceneStarted(self)
updateSpritesets(true)
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 = {}
end
def dispose
disposeSpritesets
@map_renderer.dispose
@map_renderer = nil
@spritesetGlobal.dispose
@spritesetGlobal = nil
end
def autofade(mapid)
playingBGM = $game_system.playing_bgm
playingBGS = $game_system.playing_bgs
return if playingBGM && playingBGM.name == "ultra_metropolis" && darknessEffectOnMap(mapid)
return if !playingBGM && !playingBGS
map = load_data(sprintf("Data/Map%03d.rxdata", 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
#todo
def cacheNeedsClearing
return RPG::Cache.size >= 100
end
def reset_switches_for_map_transfer
$game_switches[SWITCH_ILEX_FOREST_SPOOKED_POKEMON] = false
end
def clear_quest_icons()
for sprite in $scene.spriteset.character_sprites
if sprite.is_a?(Sprite_Character) && sprite.questIcon
sprite.removeQuestIcon
end
end
end
def transfer_player(cancelVehicles = true)
reset_switches_for_map_transfer()
$game_temp.player_transferring = false
pbCancelVehicles($game_temp.player_new_map_id) if cancelVehicles
autofade($game_temp.player_new_map_id)
pbBridgeOff
@spritesetGlobal.playersprite.clearShadows
clear_quest_icons()
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 then
$game_player.turn_down
when 4 then
$game_player.turn_left
when 6 then
$game_player.turn_right
when 8 then
$game_player.turn_up
end
$game_player.straighten
$game_map.update
disposeSpritesets
if RPG::Cache.need_clearing
RPG::Cache.clear
end
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_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
$game_player.update
updateMaps
$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(refresh = false)
@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
pbDayNightTint(@map_renderer)
@map_renderer.refresh if refresh
@map_renderer.update
Events.onMapUpdate.trigger(self)
end
def update
loop do
pbMapInterpreter.update
$game_player.update
updateMaps
$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
$game_temp.to_title = false
SaveData.mark_values_as_unloaded
$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 $game_temp.moving_furniture
placeFurnitureMenu() if Input.trigger?(Input::USE)
rotate__held_furniture_left if Input.trigger?(Input::JUMPDOWN)
rotate_held_furniture_right if Input.trigger?(Input::JUMPUP)
end
if Input.trigger?(Input::USE)
$PokemonTemp.hiddenMoveEventCalling = true
elsif Input.trigger?(Input::BACK)
unless $game_system.menu_disabled || $game_player.moving?
$game_temp.menu_calling = true
$game_temp.menu_beep = true
dayOfWeek = getDayOfTheWeek().to_s
$scene.spriteset.addUserSprite(LocationWindow.new($game_map.name+ "\n"+ pbGetTimeNow.strftime(_INTL("%I:%M %p")) + "\n" + dayOfWeek))
end
elsif Input.trigger?(Input::SPECIAL)
unless $game_system.menu_disabled || $game_player.moving?
$PokemonTemp.keyItemCalling = true
end
elsif Input.press?(Input::F9)
$game_temp.debug_calling = true if $DEBUG
end
end
unless $game_player.moving?
if $game_temp.menu_calling
call_menu
elsif $game_temp.debug_calling
call_debug
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 reset_player_sprite
@spritesetGlobal.playersprite.updateBitmap
end
def reset_map(fadeout = false,reset_music=true)
$MapFactory.setup($game_map.map_id)
$game_player.moveto($game_player.x, $game_player.y)
$game_player.straighten
$game_map.update
disposeSpritesets
GC.start
createSpritesets
if fadeout
$game_temp.transition_processing = false
Graphics.transition(20)
end
$game_map.autoplay if reset_music
Graphics.frame_reset
Input.update
end
def main
createSpritesets
Graphics.transition(20)
loop do
Graphics.update
Input.update
update
break if $scene != self
end
Graphics.freeze
dispose
if $game_temp.to_title
Graphics.transition(20)
Graphics.freeze
end
end
end

View File

@@ -0,0 +1,457 @@
#===============================================================================
# ** 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
if depth > 100
print("Common event call has exceeded maximum limit.")
exit
end
clear
end
def inspect
str = super.chop
str << format(' @event_id: %d>', @event_id)
return str
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
@wait_count = 0 # wait count
@child_interpreter = nil # child interpreter
@branch = {} # branch data
@buttonInput = false
end
#-----------------------------------------------------------------------------
# * Event Setup
# list : list of event commands
# event_id : event ID
#-----------------------------------------------------------------------------
def setup(list, event_id, map_id = nil)
clear
@map_id = map_id || $game_map.map_id
@event_id = event_id
@list = list
@index = 0
@branch.clear
end
def setup_starting_event
$game_map.refresh if $game_map.need_refresh
# Set up common event if one wants to start
if $game_temp.common_event_id > 0
setup($data_common_events[$game_temp.common_event_id].list, 0)
$game_temp.common_event_id = 0
return
end
# Check all map events for one that wants to start, and set it up
for event in $game_map.events.values
next if !event.starting
if event.trigger < 3 # Isn't autorun or parallel processing
event.lock
event.clear_starting
end
setup(event.list, event.id, event.map.map_id)
return
end
# Check all common events for one that is autorun, and set it up
for common_event in $data_common_events.compact
next if common_event.trigger != 1 || !$game_switches[common_event.switch_id]
setup(common_event.list, 0)
return
end
end
def running?
return @list != nil
end
#-----------------------------------------------------------------------------
# * Frame Update
#-----------------------------------------------------------------------------
def update
@loop_count = 0
loop do
@loop_count += 1
if @loop_count > 100 # Call Graphics.update for freeze prevention
Graphics.update
@loop_count = 0
end
# If this interpreter's map isn't the current map or connected to it,
# forget this interpreter's event ID
if $game_map.map_id != @map_id && !$MapFactory.areConnected?($game_map.map_id, @map_id)
@event_id = 0
end
# Update child interpreter if one exists
if @child_interpreter
@child_interpreter.update
@child_interpreter = nil if !@child_interpreter.running?
return if @child_interpreter
end
# Do nothing if a message is being shown
return if @message_waiting
# Do nothing if any event or the player is in the middle of a move route
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
# Do nothing while waiting
if @wait_count > 0
@wait_count -= 1
return
end
# Do nothing if the pause menu is going to open
return if $game_temp.menu_calling
# If there are no commands in the list, try to find something that wants to run
if @list.nil?
setup_starting_event if @main
return if @list.nil? # Couldn't find anything that wants to run
end
# Execute the next command
return if execute_command == false
# Move to the next @index
@index += 1
end
end
#-----------------------------------------------------------------------------
# * Execute script
#-----------------------------------------------------------------------------
def execute_script(script)
begin
result = eval(script)
return result
rescue Exception
e = $!
raise if e.is_a?(SystemExit) || "#{e.class}" == "Reset"
event = get_self
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}' 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
if line[/\:\:\s*$/]
message += "\r\n***Line '#{line}' can't end with '::'. Try putting\r\n"
message += "the next word on the same line, e.g. 'PBSpecies:" + ":MEW'"
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
map_name = ($game_map.name rescue nil) || "???"
err = "Script error in event #{event.id} (coords #{event.x},#{event.y}), map #{$game_map.map_id} (#{map_name}):\r\n"
err += "#{message}\r\n#{s}"
if e.is_a?(Hangup)
$EVENTHANGUPMSG = err
raise
end
elsif $game_map
map_name = ($game_map.name rescue nil) || "???"
err = "Script error in map #{$game_map.map_id} (#{map_name}):\r\n"
err += "#{message}\r\n#{s}"
if e.is_a?(Hangup)
$EVENTHANGUPMSG = err
raise
end
else
err = "Script error in interpreter:\r\n#{message}\r\n#{s}"
if e.is_a?(Hangup)
$EVENTHANGUPMSG = err
raise
end
end
raise err
end
end
#-----------------------------------------------------------------------------
# * Get Character
# parameter : parameter
#-----------------------------------------------------------------------------
def get_character(parameter = 0)
case parameter
when -1 # player
return $game_player
when 0 # this event
events = $game_map.events
return (events) ? events[@event_id] : nil
else # specific event
events = $game_map.events
return (events) ? events[parameter] : nil
end
end
def get_player
return get_character(-1)
end
def get_self
return get_character(0)
end
def get_event(parameter)
return get_character(parameter)
end
#-----------------------------------------------------------------------------
# * Freezes all events on the map (for use at the beginning of common events)
#-----------------------------------------------------------------------------
def pbGlobalLock
$game_map.events.values.each { |event| event.minilock }
end
#-----------------------------------------------------------------------------
# * Unfreezes all events on the map (for use at the end of common events)
#-----------------------------------------------------------------------------
def pbGlobalUnlock
$game_map.events.values.each { |event| event.unlock }
end
#-----------------------------------------------------------------------------
# * Gets the next index in the interpreter, ignoring certain commands 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
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
loop do
temp_index += 1
return index + 1 if temp_index >= @list.size - 1
return temp_index + 1 if @list[temp_index].code == 413 &&
@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 &&
@list[temp_index].parameters[0] == label_name
temp_index += 1
end
end
#-----------------------------------------------------------------------------
# * Various methods to be used in a script event command.
#-----------------------------------------------------------------------------
# Helper function that shows a picture in a script.
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.
def pbEraseThisEvent
if $game_map.events[@event_id]
if $game_map.events[@event_id].name[/cuttree/i]
pbSmashThisEvent()
end
$game_map.events[@event_id].erase
$PokemonMap.addErasedEvent(@event_id) if $PokemonMap
end
@index += 1
return true
end
# Runs a common event.
def pbCommonEvent(id)
common_event = $data_common_events[id]
return if !common_event
if $game_temp.in_battle
$game_system.battle_interpreter.setup(common_event.list, 0)
else
interp = Interpreter.new
interp.setup(common_event.list, 0)
loop do
Graphics.update
Input.update
interp.update
pbUpdateSceneMap
break if !interp.running?
end
end
end
# Sets another event's self switch (eg. pbSetSelfSwitch(20, "A", true) ).
def pbSetSelfSwitch(eventid, switch_name, value, mapid = -1)
mapid = @map_id if mapid < 0
old_value = $game_self_switches[[mapid, eventid, switch_name]]
$game_self_switches[[mapid, eventid, switch_name]] = value
if value != old_value && $MapFactory.hasMap?(mapid)
$MapFactory.getMap(mapid, false).need_refresh = true
end
end
def tsOff?(c)
return get_self.tsOff?(c)
end
alias isTempSwitchOff? tsOff?
def tsOn?(c)
return get_self.tsOn?(c)
end
alias isTempSwitchOn? tsOn?
def setTempSwitchOn(c)
get_self.setTempSwitchOn(c)
end
def setTempSwitchOff(c)
get_self.setTempSwitchOff(c)
end
def getVariable(*arg)
if arg.length == 0
return nil if !$PokemonGlobal.eventvars
return $PokemonGlobal.eventvars[[@map_id, @event_id]]
else
return $game_variables[arg[0]]
end
end
def setVariable(*arg)
if arg.length == 1
$PokemonGlobal.eventvars = {} if !$PokemonGlobal.eventvars
$PokemonGlobal.eventvars[[@map_id, @event_id]] = arg[0]
else
$game_variables[arg[0]] = arg[1]
$game_map.need_refresh = true
end
end
def pbGetPokemon(id)
return $Trainer.party[pbGet(id)]
end
def pbSetEventTime(*arg)
$PokemonGlobal.eventvars = {} if !$PokemonGlobal.eventvars
time = pbGetTimeNow
time = time.to_i
pbSetSelfSwitch(@event_id, "A", true)
$PokemonGlobal.eventvars[[@map_id, @event_id]] = time
for otherevt in arg
pbSetSelfSwitch(otherevt, "A", true)
$PokemonGlobal.eventvars[[@map_id, otherevt]] = time
end
end
# Used in boulder events. Allows an event to be pushed.
def pbPushThisEvent
event = get_self
old_x = event.x
old_y = event.y
# Apply strict version of passable, which treats tiles that are passable
# only from certain directions as fully impassible
# ^why?? - no
return if !event.can_move_in_direction?($game_player.direction, false)
case $game_player.direction
when 2 then event.move_down
when 4 then event.move_left
when 6 then event.move_right
when 8 then event.move_up
end
if old_x != event.x || old_y != event.y
$game_player.lock
loop do
Graphics.update
Input.update
pbUpdateSceneMap
break if !event.moving?
end
$game_player.unlock
end
end
def pbPushThisBoulder
pbPushThisEvent if $PokemonMap.strengthUsed
return true
end
def pbSmashThisEvent
event = get_self
pbSmashEvent(event) if event
@index += 1
return true
end
def pbTrainerIntro(symbol)
return true if $DEBUG && !GameData::TrainerType.exists?(symbol)
tr_type = GameData::TrainerType.get(symbol).id
pbGlobalLock
pbPlayTrainerIntroME(tr_type)
return true
end
def pbTrainerEnd
pbGlobalUnlock
event = get_self
event.erase_route if event
end
def setPrice(item, buy_price = -1, sell_price = -1)
item = GameData::Item.get(item).id
$game_temp.mart_prices[item] = [-1, -1] if !$game_temp.mart_prices[item]
$game_temp.mart_prices[item][0] = buy_price if buy_price > 0
if sell_price >= 0 # 0=can't sell
$game_temp.mart_prices[item][1] = sell_price * 2
else
$game_temp.mart_prices[item][1] = buy_price if buy_price > 0
end
end
def setSellPrice(item, sell_price)
setPrice(item, -1, sell_price)
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,275 @@
#===============================================================================
# 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(conditionProc,handler=nil,&handlerBlock)
if ![Proc,Hash].include?(handler.class) && !block_given?
raise ArgumentError, "addIf call for #{self.class.name} has no valid handler (#{handler.inspect} was given)"
end
@addIfs.push([conditionProc,handler || handlerBlock])
end
def add(sym,handler=nil,&handlerBlock) # 'sym' can be an ID or symbol
if ![Proc,Hash].include?(handler.class) && !block_given?
raise ArgumentError, "#{self.class.name} for #{sym.inspect} has no valid handler (#{handler.inspect} was given)"
end
id = fromSymbol(sym)
@hash[id] = handler || handlerBlock if id
symbol = toSymbol(sym)
@hash[symbol] = handler || handlerBlock 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
#===============================================================================
# A stripped-down version of class HandlerHash which only deals with symbols and
# doesn't care about whether those symbols actually relate to a defined thing.
#===============================================================================
class HandlerHash2
def initialize
@hash = {}
@add_ifs = []
end
def [](sym)
sym = sym.id if !sym.is_a?(Symbol) && sym.respond_to?("id")
return @hash[sym] if sym && @hash[sym]
for add_if in @add_ifs
return add_if[1] if add_if[0].call(sym)
end
return nil
end
def addIf(conditionProc, handler = nil, &handlerBlock)
if ![Proc, Hash].include?(handler.class) && !block_given?
raise ArgumentError, "addIf call for #{self.class.name} has no valid handler (#{handler.inspect} was given)"
end
@add_ifs.push([conditionProc, handler || handlerBlock])
end
def add(sym, handler = nil, &handlerBlock)
if ![Proc, Hash].include?(handler.class) && !block_given?
raise ArgumentError, "#{self.class.name} for #{sym.inspect} has no valid handler (#{handler.inspect} was given)"
end
@hash[sym] = handler || handlerBlock if sym
end
def copy(src, *dests)
handler = self[src]
return if !handler
for dest in dests
self.add(dest, handler)
end
end
def clear
@hash.clear
end
def trigger(sym, *args)
sym = sym.id if !sym.is_a?(Symbol) && sym.respond_to?("id")
handler = self[sym]
return (handler) ? handler.call(sym, *args) : nil
end
end
#===============================================================================
# An even more stripped down version of class HandlerHash which just takes
# hashes with keys, no matter what the keys are.
#===============================================================================
class HandlerHashBasic
def initialize
@ordered_keys = []
@hash = {}
@addIfs = []
end
def [](entry)
ret = nil
ret = @hash[entry] if entry && @hash[entry]
unless ret
for addif in @addIfs
return addif[1] if addif[0].call(entry)
end
end
return ret
end
def each
@ordered_keys.each { |key| yield key, @hash[key] }
end
def add(entry, handler = nil, &handlerBlock)
if ![Proc,Hash].include?(handler.class) && !block_given?
raise ArgumentError, "#{self.class.name} for #{entry.inspect} has no valid handler (#{handler.inspect} was given)"
end
return if !entry || entry.empty?
@ordered_keys.push(entry) if !@ordered_keys.include?(entry)
@hash[entry] = handler || handlerBlock
end
def addIf(conditionProc, handler = nil, &handlerBlock)
if ![Proc, Hash].include?(handler.class) && !block_given?
raise ArgumentError, "addIf call for #{self.class.name} has no valid handler (#{handler.inspect} was given)"
end
@addIfs.push([conditionProc, handler || handlerBlock])
end
def copy(src, *dests)
handler = self[src]
return if !handler
dests.each { |dest| self.add(dest, handler) }
end
def clear
@hash.clear
@ordered_keys.clear
end
def trigger(entry, *args)
handler = self[entry]
return (handler) ? handler.call(*args) : nil
end
end
#===============================================================================
#
#===============================================================================
class SpeciesHandlerHash < HandlerHash2
end
class AbilityHandlerHash < HandlerHash2
end
class ItemHandlerHash < HandlerHash2
end
class MoveHandlerHash < HandlerHash2
end

View File

@@ -0,0 +1,172 @@
#===============================================================================
# This module stores events that can happen during the game. A procedure can
# subscribe to an event by adding itself to the event. It will then be called
# whenever the event occurs.
#===============================================================================
module Events
@@OnMapCreate = Event.new
@@OnMapUpdate = Event.new
@@OnMapChange = Event.new
@@OnMapChanging = Event.new
@@OnMapSceneChange = Event.new
@@OnSpritesetCreate = Event.new
@@OnAction = Event.new
@@OnStepTaken = Event.new
@@OnLeaveTile = Event.new
@@OnStepTakenFieldMovement = Event.new
@@OnStepTakenTransferPossible = Event.new
@@OnStartBattle = Event.new
@@OnEndBattle = Event.new
@@OnWildPokemonCreate = Event.new
@@OnWildBattleOverride = Event.new
@@OnWildBattleEnd = Event.new
@@OnTrainerPartyLoad = Event.new
@@OnChangeDirection = Event.new
# Fires whenever a map is created. Event handler receives two parameters: the
# map (RPG::Map) and the tileset (RPG::Tileset)
def self.onMapCreate; @@OnMapCreate; end
def self.onMapCreate=(v); @@OnMapCreate = v; end
# Fires each frame during a map update.
def self.onMapUpdate; @@OnMapUpdate; end
def self.onMapUpdate=(v); @@OnMapUpdate = v; end
# Fires whenever one map is about to change to a different one. Event handler
# receives the new map ID and the Game_Map object representing the new map.
# When the event handler is called, $game_map still refers to the old map.
def self.onMapChanging; @@OnMapChanging; end
def self.onMapChanging=(v); @@OnMapChanging = v; end
# Fires whenever the player moves to a new map. Event handler receives the old
# map ID or 0 if none. Also fires when the first map of the game is loaded
def self.onMapChange; @@OnMapChange; end
def self.onMapChange=(v); @@OnMapChange = v; end
# Fires whenever the map scene is regenerated and soon after the player moves
# to a new map.
# Parameters:
# e[0] - Scene_Map object.
# e[1] - Whether the player just moved to a new map (either true or false). If
# false, some other code had called $scene.createSpritesets to
# regenerate the map scene without transferring the player elsewhere
def self.onMapSceneChange; @@OnMapSceneChange; end
def self.onMapSceneChange=(v); @@OnMapSceneChange = v; end
# Fires whenever a spriteset is created.
# Parameters:
# e[0] - Spriteset being created. e[0].map is the map associated with the
# spriteset (not necessarily the current map).
# e[1] - Viewport used for tilemap and characters
def self.onSpritesetCreate; @@OnSpritesetCreate; end
def self.onSpritesetCreate=(v); @@OnSpritesetCreate = v; end
# Triggers when the player presses the Action button on the map.
def self.onAction; @@OnAction; end
def self.onAction=(v); @@OnAction = v; end
# Fires whenever the player takes a step.
def self.onStepTaken; @@OnStepTaken; end
def self.onStepTaken=(v); @@OnStepTaken = v; end
# Fires whenever the player or another event leaves a tile.
# Parameters:
# e[0] - Event that just left the tile.
# e[1] - Map ID where the tile is located (not necessarily
# the current map). Use "$MapFactory.getMap(e[1])" to
# get the Game_Map object corresponding to that map.
# e[2] - X-coordinate of the tile
# e[3] - Y-coordinate of the tile
def self.onLeaveTile; @@OnLeaveTile; end
def self.onLeaveTile=(v); @@OnLeaveTile = v; end
# Fires whenever the player or another event enters a tile.
# Parameters:
# e[0] - Event that just entered a tile.
def self.onStepTakenFieldMovement; @@OnStepTakenFieldMovement; end
def self.onStepTakenFieldMovement=(v); @@OnStepTakenFieldMovement = v; end
# Fires whenever the player takes a step. The event handler may possibly move
# the player elsewhere.
# Parameters:
# e[0] - Array that contains a single boolean value. If an event handler moves
# the player to a new map, it should set this value to true. Other
# event handlers should check this parameter's value.
def self.onStepTakenTransferPossible; @@OnStepTakenTransferPossible; end
def self.onStepTakenTransferPossible=(v); @@OnStepTakenTransferPossible = v; end
def self.onStartBattle; @@OnStartBattle; end
def self.onStartBattle=(v); @@OnStartBattle = v; end
def self.onEndBattle; @@OnEndBattle; end
def self.onEndBattle=(v); @@OnEndBattle = v; end
# Triggers whenever a wild Pokémon is created
# Parameters:
# e[0] - Pokémon being created
def self.onWildPokemonCreate; @@OnWildPokemonCreate; end
def self.onWildPokemonCreate=(v); @@OnWildPokemonCreate = v; end
# Triggers at the start of a wild battle. Event handlers can provide their
# own wild battle routines to override the default behavior.
def self.onWildBattleOverride; @@OnWildBattleOverride; end
def self.onWildBattleOverride=(v); @@OnWildBattleOverride = v; end
# Triggers whenever a wild Pokémon battle ends
# Parameters:
# e[0] - Pokémon species
# e[1] - Pokémon level
# e[2] - Battle result (1-win, 2-loss, 3-escaped, 4-caught, 5-draw)
def self.onWildBattleEnd; @@OnWildBattleEnd; end
def self.onWildBattleEnd=(v); @@OnWildBattleEnd = v; end
# Triggers whenever an NPC trainer's Pokémon party is loaded
# Parameters:
# e[0] - Trainer
# e[1] - Items possessed by the trainer
# e[2] - Party
def self.onTrainerPartyLoad; @@OnTrainerPartyLoad; end
def self.onTrainerPartyLoad=(v); @@OnTrainerPartyLoad = v; end
# Fires whenever the player changes direction.
def self.onChangeDirection; @@OnChangeDirection; end
def self.onChangeDirection=(v); @@OnChangeDirection = v; end
end
#===============================================================================
#
#===============================================================================
def pbOnSpritesetCreate(spriteset,viewport)
Events.onSpritesetCreate.trigger(nil,spriteset,viewport)
end
#===============================================================================
# This module stores encounter-modifying events that can happen during the game.
# A procedure can subscribe to an event by adding itself to the event. It will
# then be called whenever the event occurs.
#===============================================================================
module EncounterModifier
@@procs = []
@@procsEnd = []
def self.register(p)
@@procs.push(p)
end
def self.registerEncounterEnd(p)
@@procsEnd.push(p)
end
def self.trigger(encounter)
for prc in @@procs
encounter = prc.call(encounter)
end
return encounter
end
def self.triggerEncounterEnd()
for prc in @@procsEnd
prc.call()
end
end
end

View File

@@ -0,0 +1,157 @@
#===============================================================================
# ** 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
attr_accessor :weather_duration # ticks in which the weather should fade in
attr_accessor :weather_power
#-----------------------------------------------------------------------------
# * 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_duration = 0
@weather_power = 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 = GameData::Weather.get(type).id
@weather_power = power
@weather_max = (power + 1) * RPG::Weather::MAX_SPRITES / 10
@weather_duration = duration # In 1/20ths of a seconds
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 || @shake!=0
delta = (@shake_power*@shake_speed*@shake_direction)/10.0
if @shake_duration<=1 && @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 $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
#===============================================================================
#
#===============================================================================
def pbToneChangeAll(tone,duration)
$game_screen.start_tone_change(tone,duration*Graphics.frame_rate/20)
for picture in $game_screen.pictures
picture.start_tone_change(tone,duration*Graphics.frame_rate/20) if picture
end
end
def pbShake(power,speed,frames)
$game_screen.start_shake(power,speed,frames*Graphics.frame_rate/20)
end
def pbFlash(color,frames)
$game_screen.start_flash(color,frames*Graphics.frame_rate/20)
end

View File

@@ -0,0 +1,60 @@
#===============================================================================
# ** 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 :message_window_showing # message window showing
attr_accessor :common_event_id # common event ID
attr_accessor :in_battle # in-battle flag
attr_accessor :battle_abort # battle flag: interrupt
attr_accessor :battleback_name # battleback file name
attr_accessor :in_menu # menu is open
attr_accessor :menu_beep # menu: play sound effect flag
attr_accessor :menu_calling # menu 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 :to_title # return to title screen flag
attr_accessor :fadestate # for sprite hashes
attr_accessor :background_bitmap
attr_accessor :mart_prices
#-----------------------------------------------------------------------------
# * Object Initialization
#-----------------------------------------------------------------------------
def initialize
@message_window_showing = false
@common_event_id = 0
@in_battle = false
@battle_abort = false
@battleback_name = ''
@in_menu = false
@menu_beep = false
@menu_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 = ""
@to_title = false
@fadestate = 0
@background_bitmap = nil
@message_window_showing = false
@transition_processing = false
@mart_prices = {}
end
def clear_mart_prices
@mart_prices = {}
end
end

View File

@@ -0,0 +1,30 @@
#===============================================================================
# ** 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)
return @data[switch_id] if switch_id <= 5000 && @data[switch_id] != nil
return false
end
#-----------------------------------------------------------------------------
# * Set Switch
# switch_id : switch ID
# value : ON (true) / OFF (false)
#-----------------------------------------------------------------------------
def []=(switch_id, value)
@data[switch_id] = value if switch_id <= 5000
end
end

View File

@@ -0,0 +1,30 @@
#===============================================================================
# ** 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)
return @data[variable_id] if variable_id <= 5000 && !@data[variable_id].nil?
return 0
end
#-----------------------------------------------------------------------------
# * Set Variable
# variable_id : variable ID
# value : the variable's value
#-----------------------------------------------------------------------------
def []=(variable_id, value)
@data[variable_id] = value if variable_id <= 5000
end
end

View File

@@ -0,0 +1,29 @@
#===============================================================================
# ** 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

Some files were not shown because too many files have changed in this diff Show More