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
592 changed files with 390405 additions and 6030 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

BIN
Audio/BGM/SSANNE.ogg Normal file

Binary file not shown.

BIN
Audio/BGM/bicycle.mp3 Normal file

Binary file not shown.

View File

@@ -144,6 +144,14 @@ Nintendo
GameFreak
########################
### Translations #
########################
French translation:
anthonygourmand
locpic_
blood.wolf58 (Willi)
#######################################################################
The following ressources were also used
with their respective authors' consent

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.

Submodule Data/Scripts deleted from de8fb99cf0

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

View File

@@ -0,0 +1,286 @@
#==============================================================================
# ** Game_System
#------------------------------------------------------------------------------
# This class handles data surrounding the system. Backround music, etc.
# is managed here as well. Refer to "$game_system" for the instance of
# this class.
#==============================================================================
class Game_System
attr_reader :map_interpreter # map event interpreter
attr_reader :battle_interpreter # battle event interpreter
attr_accessor :timer # timer
attr_accessor :timer_working # timer working flag
attr_accessor :save_disabled # save forbidden
attr_accessor :menu_disabled # menu forbidden
attr_accessor :encounter_disabled # encounter forbidden
attr_accessor :message_position # text option: positioning
attr_accessor :message_frame # text option: window frame
attr_accessor :save_count # save count
attr_accessor :magic_number # magic number
attr_accessor :autoscroll_x_speed
attr_accessor :autoscroll_y_speed
attr_accessor :bgm_position
def initialize
@map_interpreter = Interpreter.new(0, true)
@battle_interpreter = Interpreter.new(0, false)
@timer = 0
@timer_working = false
@save_disabled = false
@menu_disabled = false
@encounter_disabled = false
@message_position = 2
@message_frame = 0
@save_count = 0
@magic_number = 0
@autoscroll_x_speed = 0
@autoscroll_y_speed = 0
@bgm_position = 0
@bgs_position = 0
end
################################################################################
def bgm_play(bgm)
old_pos = @bgm_position
@bgm_position = 0
bgm_play_internal(bgm,0)
@bgm_position = old_pos
end
def bgm_play_internal2(name,volume,pitch,position) # :nodoc:
vol = volume
vol *= $PokemonSystem.bgmvolume/100.0
vol = vol.to_i
begin
Audio.bgm_play(name,vol,pitch,position)
rescue ArgumentError
Audio.bgm_play(name,vol,pitch)
end
end
def bgm_play_internal(bgm,position) # :nodoc:
@bgm_position = position if !@bgm_paused
@playing_bgm = (bgm==nil) ? nil : bgm.clone
if bgm!=nil && bgm.name!=""
if FileTest.audio_exist?("Audio/BGM/"+bgm.name)
bgm_play_internal2("Audio/BGM/"+bgm.name,
bgm.volume,bgm.pitch,@bgm_position) if !@defaultBGM
end
else
@bgm_position = position if !@bgm_paused
@playing_bgm = nil
Audio.bgm_stop if !@defaultBGM
end
if @defaultBGM
bgm_play_internal2("Audio/BGM/"+@defaultBGM.name,
@defaultBGM.volume,@defaultBGM.pitch,@bgm_position)
end
Graphics.frame_reset
end
def bgm_pause(fadetime=0.0) # :nodoc:
pos = Audio.bgm_pos rescue 0
self.bgm_fade(fadetime) if fadetime>0.0
@bgm_position = pos
@bgm_paused = true
end
def bgm_unpause # :nodoc:
@bgm_position = 0
@bgm_paused = false
end
def bgm_resume(bgm) # :nodoc:
if @bgm_paused
self.bgm_play_internal(bgm,@bgm_position)
@bgm_position = 0
@bgm_paused = false
end
end
def bgm_stop # :nodoc:
@bgm_position = 0 if !@bgm_paused
@playing_bgm = nil
Audio.bgm_stop if !@defaultBGM
end
def bgm_fade(time) # :nodoc:
@bgm_position = 0 if !@bgm_paused
@playing_bgm = nil
Audio.bgm_fade((time*1000).floor) if !@defaultBGM
end
def playing_bgm
return @playing_bgm
end
# Saves the currently playing background music for later playback.
def bgm_memorize
@memorized_bgm = @playing_bgm
end
# Plays the currently memorized background music
def bgm_restore
bgm_play(@memorized_bgm)
end
# Returns an RPG::AudioFile object for the currently playing background music
def getPlayingBGM
return (@playing_bgm) ? @playing_bgm.clone : nil
end
def setDefaultBGM(bgm,volume=80,pitch=100)
bgm = RPG::AudioFile.new(bgm,volume,pitch) if bgm.is_a?(String)
if bgm!=nil && bgm.name!=""
@defaultBGM = nil
self.bgm_play(bgm)
@defaultBGM = bgm.clone
else
@defaultBGM = nil
self.bgm_play(@playing_bgm)
end
end
################################################################################
def me_play(me)
me = RPG::AudioFile.new(me) if me.is_a?(String)
if me!=nil && me.name!=""
if FileTest.audio_exist?("Audio/ME/"+me.name)
vol = me.volume
vol *= $PokemonSystem.bgmvolume/100.0
vol = vol.to_i
Audio.me_play("Audio/ME/"+me.name,vol,me.pitch)
end
else
Audio.me_stop
end
Graphics.frame_reset
end
################################################################################
def bgs_play(bgs)
@playing_bgs = (bgs==nil) ? nil : bgs.clone
if bgs!=nil && bgs.name!=""
if FileTest.audio_exist?("Audio/BGS/"+bgs.name)
vol = bgs.volume
vol *= $PokemonSystem.sevolume/100.0
vol = vol.to_i
Audio.bgs_play("Audio/BGS/"+bgs.name,vol,bgs.pitch)
end
else
@bgs_position = 0
@playing_bgs = nil
Audio.bgs_stop
end
Graphics.frame_reset
end
def bgs_pause(fadetime=0.0) # :nodoc:
if fadetime>0.0
self.bgs_fade(fadetime)
else
self.bgs_stop
end
@bgs_paused = true
end
def bgs_unpause # :nodoc:
@bgs_paused = false
end
def bgs_resume(bgs) # :nodoc:
if @bgs_paused
self.bgs_play(bgs)
@bgs_paused = false
end
end
def bgs_stop
@bgs_position = 0
@playing_bgs = nil
Audio.bgs_stop
end
def bgs_fade(time)
@bgs_position = 0
@playing_bgs = nil
Audio.bgs_fade((time*1000).floor)
end
def playing_bgs
return @playing_bgs
end
def bgs_memorize
@memorized_bgs = @playing_bgs
end
def bgs_restore
bgs_play(@memorized_bgs)
end
def getPlayingBGS
return (@playing_bgs) ? @playing_bgs.clone : nil
end
################################################################################
def se_play(se)
se = RPG::AudioFile.new(se) if se.is_a?(String)
if se!=nil && se.name!="" && FileTest.audio_exist?("Audio/SE/"+se.name)
vol = se.volume
vol *= $PokemonSystem.sevolume/100.0
vol = vol.to_i
Audio.se_play("Audio/SE/"+se.name,vol,se.pitch)
end
end
def se_stop
Audio.se_stop
end
################################################################################
def battle_bgm
return (@battle_bgm) ? @battle_bgm : $data_system.battle_bgm
end
def battle_bgm=(battle_bgm)
@battle_bgm = battle_bgm
end
def battle_end_me
return (@battle_end_me) ? @battle_end_me : $data_system.battle_end_me
end
def battle_end_me=(battle_end_me)
@battle_end_me = battle_end_me
end
################################################################################
def windowskin_name
if @windowskin_name==nil
return $data_system.windowskin_name
else
return @windowskin_name
end
end
def windowskin_name=(windowskin_name)
@windowskin_name = windowskin_name
end
def update
@timer -= 1 if @timer_working && @timer>0
if Input.trigger?(Input::SPECIAL) && pbCurrentEventCommentInput(1,"Cut Scene")
event = @map_interpreter.get_character(0)
@map_interpreter.pbSetSelfSwitch(event.id,"A",true)
@map_interpreter.command_end
event.start
end
end
end

View File

@@ -0,0 +1,156 @@
#===============================================================================
# ** Game_Picture
#-------------------------------------------------------------------------------
# This class handles the picture. It's used within the Game_Screen class
# ($game_screen).
#===============================================================================
class Game_Picture
#-----------------------------------------------------------------------------
# * Public Instance Variables
#-----------------------------------------------------------------------------
attr_reader :number # picture number
attr_accessor :name # file name
attr_reader :origin # starting point
attr_accessor :x # x-coordinate
attr_accessor :y # y-coordinate
attr_accessor :zoom_x # x directional zoom rate
attr_accessor :zoom_y # y directional zoom rate
attr_accessor :opacity # opacity level
attr_reader :blend_type # blend method
attr_reader :tone # color tone
attr_reader :angle # rotation angle
#-----------------------------------------------------------------------------
# * Object Initialization
# number : picture number
#-----------------------------------------------------------------------------
def initialize(number)
@number = number
@name = ""
@origin = 0
@x = 0.0
@y = 0.0
@zoom_x = 100.0
@zoom_y = 100.0
@opacity = 255.0
@blend_type = 1
@duration = 0
@target_x = @x
@target_y = @y
@target_zoom_x = @zoom_x
@target_zoom_y = @zoom_y
@target_opacity = @opacity
@tone = Tone.new(0, 0, 0, 0)
@tone_target = Tone.new(0, 0, 0, 0)
@tone_duration = 0
@angle = 0
@rotate_speed = 0
end
#-----------------------------------------------------------------------------
# * Show Picture
# name : file name
# origin : starting point
# x : x-coordinate
# y : y-coordinate
# zoom_x : x directional zoom rate
# zoom_y : y directional zoom rate
# opacity : opacity level
# blend_type : blend method
#-----------------------------------------------------------------------------
def show(name, origin, x, y, zoom_x=100, zoom_y=100, opacity=255, blend_type=0)
@name = name
@origin = origin
@x = x.to_f
@y = y.to_f
@zoom_x = zoom_x.to_f
@zoom_y = zoom_y.to_f
@opacity = opacity.to_f
@blend_type = blend_type ? blend_type : 0
@duration = 0
@target_x = @x
@target_y = @y
@target_zoom_x = @zoom_x
@target_zoom_y = @zoom_y
@target_opacity = @opacity
@tone = Tone.new(0, 0, 0, 0)
@tone_target = Tone.new(0, 0, 0, 0)
@tone_duration = 0
@angle = 0
@rotate_speed = 0
end
#-----------------------------------------------------------------------------
# * Move Picture
# duration : time
# origin : starting point
# x : x-coordinate
# y : y-coordinate
# zoom_x : x directional zoom rate
# zoom_y : y directional zoom rate
# opacity : opacity level
# blend_type : blend method
#-----------------------------------------------------------------------------
def move(duration, origin, x, y, zoom_x, zoom_y, opacity, blend_type)
@duration = duration
@origin = origin
@target_x = x.to_f
@target_y = y.to_f
@target_zoom_x = zoom_x.to_f
@target_zoom_y = zoom_y.to_f
@target_opacity = opacity.to_f
@blend_type = blend_type ? blend_type : 0
end
#-----------------------------------------------------------------------------
# * Change Rotation Speed
# speed : rotation speed
#-----------------------------------------------------------------------------
def rotate(speed)
@rotate_speed = speed
end
#-----------------------------------------------------------------------------
# * Start Change of Color Tone
# tone : color tone
# duration : time
#-----------------------------------------------------------------------------
def start_tone_change(tone, duration)
@tone_target = tone.clone
@tone_duration = duration
if @tone_duration == 0
@tone = @tone_target.clone
end
end
#-----------------------------------------------------------------------------
# * Erase Picture
#-----------------------------------------------------------------------------
def erase
@name = ""
end
#-----------------------------------------------------------------------------
# * Frame Update
#-----------------------------------------------------------------------------
def update
if @duration >= 1
d = @duration
@x = (@x * (d - 1) + @target_x) / d
@y = (@y * (d - 1) + @target_y) / d
@zoom_x = (@zoom_x * (d - 1) + @target_zoom_x) / d
@zoom_y = (@zoom_y * (d - 1) + @target_zoom_y) / d
@opacity = (@opacity * (d - 1) + @target_opacity) / d
@duration -= 1
end
if @tone_duration >= 1
d = @tone_duration
@tone.red = (@tone.red * (d - 1) + @tone_target.red) / d
@tone.green = (@tone.green * (d - 1) + @tone_target.green) / d
@tone.blue = (@tone.blue * (d - 1) + @tone_target.blue) / d
@tone.gray = (@tone.gray * (d - 1) + @tone_target.gray) / d
@tone_duration -= 1
end
if @rotate_speed != 0
@angle += @rotate_speed / 2.0
while @angle < 0
@angle += 360
end
@angle %= 360
end
end
end

View File

@@ -0,0 +1,547 @@
#==============================================================================
# ** Game_Map
#------------------------------------------------------------------------------
# This class handles the map. It includes scrolling and passable determining
# functions. Refer to "$game_map" for the instance of this class.
#==============================================================================
class Game_Map
attr_accessor :map_id
attr_accessor :tileset_name # tileset file name
attr_accessor :autotile_names # autotile file name
attr_reader :passages # passage table
attr_reader :priorities # priority table
attr_reader :terrain_tags # terrain tag table
attr_reader :events # events
attr_accessor :panorama_name # panorama file name
attr_accessor :panorama_hue # panorama hue
attr_accessor :fog_name # fog file name
attr_accessor :fog_hue # fog hue
attr_accessor :fog_opacity # fog opacity level
attr_accessor :fog_blend_type # fog blending method
attr_accessor :fog_zoom # fog zoom rate
attr_accessor :fog_sx # fog sx
attr_accessor :fog_sy # fog sy
attr_accessor :fog_ox # fog x-coordinate starting point
attr_accessor :fog_oy # fog y-coordinate starting point
attr_accessor :fog2_ox # fog x-coordinate starting point
attr_accessor :fog2_oy # fog y-coordinate starting point
attr_accessor :fog2_sx # fog sx
attr_accessor :fog2_sy # fog sy
attr_accessor :fog2_opacity # fog sy
attr_reader :fog_tone # fog color tone
attr_accessor :battleback_name # battleback file name
attr_reader :display_x # display x-coordinate * 128
attr_reader :display_y # display y-coordinate * 128
attr_accessor :need_refresh # refresh request flag
attr_accessor :scroll_direction
TILE_WIDTH = 32
TILE_HEIGHT = 32
X_SUBPIXELS = 4
Y_SUBPIXELS = 4
REAL_RES_X = TILE_WIDTH * X_SUBPIXELS
REAL_RES_Y = TILE_HEIGHT * Y_SUBPIXELS
def initialize
@map_id = 0
@display_x = 0
@display_y = 0
end
def setup(map_id)
@map_id = map_id
@map = load_data(sprintf("Data/Map%03d.rxdata", map_id))
tileset = $data_tilesets[@map.tileset_id]
updateTileset
@fog_ox = 0
@fog_oy = 0
@fog2_ox = 0
@fog2_oy = 0
@fog2_sx = 0
@fog2_sy = 0
@fog2_opacity = 0
@fog_tone = Tone.new(0, 0, 0, 0)
@fog_tone_target = Tone.new(0, 0, 0, 0)
@fog_tone_duration = 0
@fog_opacity_duration = 0
@fog_opacity_target = 0
self.display_x = 0
self.display_y = 0
@need_refresh = false
Events.onMapCreate.trigger(self, map_id, @map, tileset)
@events = {}
for i in @map.events.keys
@events[i] = Game_Event.new(@map_id, @map.events[i], self)
end
@common_events = {}
for i in 1...$data_common_events.size
@common_events[i] = Game_CommonEvent.new(i)
end
@scroll_direction = 2
@scroll_rest = 0
@scroll_speed = 4
end
def updateTileset
tileset = $data_tilesets[@map.tileset_id]
@tileset_name = tileset.tileset_name
@autotile_names = tileset.autotile_names
@panorama_name = tileset.panorama_name
@panorama_hue = tileset.panorama_hue
@fog_name = tileset.fog_name
@fog_hue = tileset.fog_hue
@fog_opacity = tileset.fog_opacity
@fog_blend_type = tileset.fog_blend_type
@fog_zoom = tileset.fog_zoom
@fog_sx = tileset.fog_sx
@fog_sy = tileset.fog_sy
@battleback_name = tileset.battleback_name
@passages = tileset.passages
@priorities = tileset.priorities
@terrain_tags = tileset.terrain_tags
end
def width
return @map.width;
end
def height
return @map.height;
end
def encounter_list
return @map.encounter_list;
end
def encounter_step
return @map.encounter_step;
end
def data
return @map.data;
end
def tileset_id;
return @map.tileset_id;
end
def name
ret = pbGetMessage(MessageTypes::MapNames, @map_id)
ret.gsub!(/\\PN/, $Trainer.name) if $Trainer
return ret
end
#-----------------------------------------------------------------------------
# * Autoplays background music
# Plays music called "[normal BGM]_n" if it's night time and it exists
#-----------------------------------------------------------------------------
def autoplayAsCue
if @map.autoplay_bgm
if PBDayNight.isNight? && FileTest.audio_exist?("Audio/BGM/" + @map.bgm.name + "_n")
pbCueBGM(@map.bgm.name + "_n", 1.0, @map.bgm.volume, @map.bgm.pitch)
else
pbCueBGM(@map.bgm, 1.0)
end
end
if @map.autoplay_bgs
pbBGSPlay(@map.bgs)
end
end
def setFog2(filename,sx=0,sy=0,opacity=32)
@fog2_sx=sx
@fog2_sy=-sy
@fog2_opacity = opacity
$scene.spriteset.setFog2(filename)
end
def eraseFog2()
@fog2_sx=0
@fog2_sy=-0
@fog2_opacity = 0
$scene.spriteset.disposeFog2()
end
#-----------------------------------------------------------------------------
# * Plays background music
# Plays music called "[normal BGM]_n" if it's night time and it exists
#-----------------------------------------------------------------------------
def autoplay
if @map.autoplay_bgm
if PBDayNight.isNight? && FileTest.audio_exist?("Audio/BGM/" + @map.bgm.name + "_n")
pbBGMPlay(@map.bgm.name + "_n", @map.bgm.volume, @map.bgm.pitch)
else
pbBGMPlay(@map.bgm)
end
end
if @map.autoplay_bgs
pbBGSPlay(@map.bgs)
end
end
def valid?(x, y)
return x >= 0 && x < width && y >= 0 && y < height
end
def validLax?(x, y)
return x >= -10 && x <= width + 10 && y >= -10 && y <= height + 10
end
def passable?(x, y, d, self_event = nil)
return false if !valid?(x, y)
bit = (1 << (d / 2 - 1)) & 0x0f
for event in events.values
next if event.tile_id <= 0
next if event == self_event
next if !event.at_coordinate?(x, y)
next if event.through
next if GameData::TerrainTag.try_get(@terrain_tags[event.tile_id]).ignore_passability
passage = @passages[event.tile_id]
return false if passage & bit != 0
return false if passage & 0x0f == 0x0f
return true if @priorities[event.tile_id] == 0
end
return playerPassable?(x, y, d, self_event) if self_event == $game_player
# All other events
newx = x
newy = y
case d
when 1
newx -= 1
newy += 1
when 2
newy += 1
when 3
newx += 1
newy += 1
when 4
newx -= 1
when 6
newx += 1
when 7
newx -= 1
newy -= 1
when 8
newy -= 1
when 9
newx += 1
newy -= 1
end
return false if !valid?(newx, newy)
for i in [2, 1, 0]
tile_id = data[x, y, i]
terrain = GameData::TerrainTag.try_get(@terrain_tags[tile_id])
# If already on water, only allow movement to another water tile
if self_event != nil && terrain.can_surf_freely
for j in [2, 1, 0]
facing_tile_id = data[newx, newy, j]
return false if facing_tile_id == nil
facing_terrain = GameData::TerrainTag.try_get(@terrain_tags[facing_tile_id])
if facing_terrain.id != :None && !facing_terrain.ignore_passability
return facing_terrain.can_surf_freely
end
end
return false
# Can't walk onto ice
# removed for mahogany gym. idk if this will cause problems, hopefully not
# elsif terrain.ice
# return false
elsif self_event != nil && self_event.x == x && self_event.y == y
# Can't walk onto ledges
for j in [2, 1, 0]
facing_tile_id = data[newx, newy, j]
return false if facing_tile_id == nil
facing_terrain = GameData::TerrainTag.try_get(@terrain_tags[facing_tile_id])
return false if facing_terrain.ledge
break if facing_terrain.id != :None && !facing_terrain.ignore_passability
end
end
# Regular passability checks
if !terrain || !terrain.ignore_passability
passage = @passages[tile_id]
return false if passage & bit != 0 || passage & 0x0f == 0x0f
return true if @priorities[tile_id] == 0
end
end
return true
end
def playerPassable?(x, y, d, self_event = nil)
bit = (1 << (d / 2 - 1)) & 0x0f
for i in [2, 1, 0]
tile_id = data[x, y, i]
terrain = GameData::TerrainTag.try_get(@terrain_tags[tile_id])
passage = @passages[tile_id]
if terrain
# Ignore bridge tiles if not on a bridge
next if terrain.bridge && $PokemonGlobal.bridge == 0
# Make water tiles passable if player is surfing
return true if $PokemonGlobal.surfing && terrain.can_surf && !terrain.waterfall
# Prevent cycling in really tall grass/on ice
return false if $PokemonGlobal.bicycle && terrain.must_walk
# Depend on passability of bridge tile if on bridge
if terrain.bridge && $PokemonGlobal.bridge > 0
return (passage & bit == 0 && passage & 0x0f != 0x0f)
end
end
# Regular passability checks
if !terrain || !terrain.ignore_passability
return false if passage & bit != 0 || passage & 0x0f == 0x0f
return true if @priorities[tile_id] == 0
end
end
return true
end
# Returns whether the position x,y is fully passable (there is no blocking
# event there, and the tile is fully passable in all directions)
def passableStrict?(x, y, d, self_event = nil)
return false if !valid?(x, y)
for event in events.values
next if event == self_event || event.tile_id < 0 || event.through
next if !event.at_coordinate?(x, y)
next if GameData::TerrainTag.try_get(@terrain_tags[event.tile_id]).ignore_passability
return false if @passages[event.tile_id] & 0x0f != 0
return true if @priorities[event.tile_id] == 0
end
for i in [2, 1, 0]
tile_id = data[x, y, i]
next if GameData::TerrainTag.try_get(@terrain_tags[tile_id]).ignore_passability
return false if @passages[tile_id] & 0x0f != 0
return true if @priorities[tile_id] == 0
end
return true
end
def bush?(x, y)
for i in [2, 1, 0]
tile_id = data[x, y, i]
return false if GameData::TerrainTag.try_get(@terrain_tags[tile_id]).bridge &&
$PokemonGlobal.bridge > 0
return true if @passages[tile_id] & 0x40 == 0x40
end
return false
end
def deepBush?(x, y)
for i in [2, 1, 0]
tile_id = data[x, y, i]
terrain = GameData::TerrainTag.try_get(@terrain_tags[tile_id])
return false if terrain.bridge && $PokemonGlobal.bridge > 0
return true if terrain.deep_bush && @passages[tile_id] & 0x40 == 0x40
end
return false
end
def counter?(x, y)
for i in [2, 1, 0]
tile_id = data[x, y, i]
passage = @passages[tile_id]
return true if passage & 0x80 == 0x80
end
return false
end
def terrain_tag(x, y, countBridge = false)
if valid?(x, y)
for i in [2, 1, 0]
tile_id = data[x, y, i]
terrain = GameData::TerrainTag.try_get(@terrain_tags[tile_id])
next if terrain.id == :None || terrain.ignore_passability
next if !countBridge && terrain.bridge && $PokemonGlobal.bridge == 0
return terrain
end
end
return GameData::TerrainTag.get(:None)
end
# Unused.
def check_event(x, y)
for event in self.events.values
return event.id if event.at_coordinate?(x, y)
end
end
def event_at_position(x, y)
for event in self.events.values
return true if event.at_coordinate?(x, y)
end
return false
end
def get_event_at_position(x, y, excluding_IDs = [])
for event in self.events.values
next if excluding_IDs.include?(event.id)
return event if event.at_coordinate?(x, y)
end
return nil
end
def display_x=(value)
return if @display_x == value
@display_x = value
if GameData::MapMetadata.exists?(self.map_id) && GameData::MapMetadata.get(self.map_id).snap_edges
max_x = (self.width - Graphics.width * 1.0 / TILE_WIDTH) * REAL_RES_X
@display_x = [0, [@display_x, max_x].min].max
end
$MapFactory.setMapsInRange if $MapFactory
end
def display_y=(value)
return if @display_y == value
@display_y = value
if GameData::MapMetadata.exists?(self.map_id) && GameData::MapMetadata.get(self.map_id).snap_edges
max_y = (self.height - Graphics.height * 1.0 / TILE_HEIGHT) * REAL_RES_Y
@display_y = [0, [@display_y, max_y].min].max
end
$MapFactory.setMapsInRange if $MapFactory
end
def scroll_up(distance)
self.display_y -= distance
end
def scroll_down(distance)
self.display_y += distance
end
def scroll_left(distance)
self.display_x -= distance
end
def scroll_right(distance)
self.display_x += distance
end
def start_scroll(direction, distance, speed)
@scroll_direction = direction
if direction == 2 || direction == 8 # down or up
@scroll_rest = distance * REAL_RES_Y
else
@scroll_rest = distance * REAL_RES_X
end
@scroll_speed = speed
end
def scrolling?
return @scroll_rest > 0
end
def start_fog_tone_change(tone, duration)
@fog_tone_target = tone.clone
@fog_tone_duration = duration
if @fog_tone_duration == 0
@fog_tone = @fog_tone_target.clone
end
end
def start_fog_opacity_change(opacity, duration)
@fog_opacity_target = opacity * 1.0
@fog_opacity_duration = duration
if @fog_opacity_duration == 0
@fog_opacity = @fog_opacity_target
end
end
def refresh
for event in @events.values
event.refresh
end
for common_event in @common_events.values
common_event.refresh
end
@need_refresh = false
end
def update
# refresh maps if necessary
if $MapFactory
for i in $MapFactory.maps
i.refresh if i.need_refresh
end
$MapFactory.setCurrentMap
end
# If scrolling
if @scroll_rest > 0
distance = (1 << @scroll_speed) * 40.0 / Graphics.frame_rate
distance = @scroll_rest if distance > @scroll_rest
case @scroll_direction
when 2 then
scroll_down(distance)
when 4 then
scroll_left(distance)
when 6 then
scroll_right(distance)
when 8 then
scroll_up(distance)
end
@scroll_rest -= distance
end
# Only update events that are on-screen
for event in @events.values
event.update
end
# Update common events
for common_event in @common_events.values
common_event.update
end
# Update fog
@fog_ox -= @fog_sx / 8.0
@fog_oy -= @fog_sy / 8.0
@fog2_ox -= @fog2_sx / 8.0 if @fog2_ox
@fog2_oy -= @fog2_sy / 8.0 if @fog2_oy
if @fog_tone_duration >= 1
d = @fog_tone_duration
target = @fog_tone_target
@fog_tone.red = (@fog_tone.red * (d - 1) + target.red) / d
@fog_tone.green = (@fog_tone.green * (d - 1) + target.green) / d
@fog_tone.blue = (@fog_tone.blue * (d - 1) + target.blue) / d
@fog_tone.gray = (@fog_tone.gray * (d - 1) + target.gray) / d
@fog_tone_duration -= 1
end
if @fog_opacity_duration >= 1
d = @fog_opacity_duration
@fog_opacity = (@fog_opacity * (d - 1) + @fog_opacity_target) / d
@fog_opacity_duration -= 1
end
end
end
#===============================================================================
#
#===============================================================================
def pbScrollMap(direction, distance, speed)
if speed == 0
case direction
when 2 then
$game_map.scroll_down(distance * Game_Map::REAL_RES_Y)
when 4 then
$game_map.scroll_left(distance * Game_Map::REAL_RES_X)
when 6 then
$game_map.scroll_right(distance * Game_Map::REAL_RES_X)
when 8 then
$game_map.scroll_up(distance * Game_Map::REAL_RES_Y)
end
else
$game_map.start_scroll(direction, distance, speed)
oldx = $game_map.display_x
oldy = $game_map.display_y
loop do
Graphics.update
Input.update
break if !$game_map.scrolling?
pbUpdateSceneMap
break if $game_map.display_x == oldx && $game_map.display_y == oldy
oldx = $game_map.display_x
oldy = $game_map.display_y
end
end
end

View File

@@ -0,0 +1,194 @@
#===============================================================================
# ** Map Autoscroll
#-------------------------------------------------------------------------------
# Wachunga
# Version 1.02
# 2005-12-18
#===============================================================================
=begin
This script supplements the built-in "Scroll Map" event command with the
aim of simplifying cutscenes (and map scrolling in general). Whereas the
normal event command requires a direction and number of tiles to scroll,
Map Autoscroll scrolls the map to center on the tile whose x and y
coordinates are given.
FEATURES
- automatic map scrolling to given x,y coordinate (or player)
- destination is fixed, so it's possible to scroll to same place even if
origin is variable (e.g. moving NPC)
- variable speed (just like "Scroll Map" event command)
- diagonal scrolling supported
SETUP
Instead of a "Scroll Map" event command, use the "Call Script" command
and enter on the following on the first line:
autoscroll(x,y)
(replacing "x" and "y" with the x and y coordinates of the tile to scroll to)
To specify a scroll speed other than the default (4), use:
autoscroll(x,y,speed)
(now also replacing "speed" with the scroll speed from 1-6)
Diagonal scrolling happens automatically when the destination is diagonal
relative to the starting point (i.e., not directly up, down, left or right).
To scroll to the player, instead use the following:
autoscroll_player(speed)
Note: because of how the interpreter and the "Call Script" event command
are setup, the call to autoscroll(...) can only be on the first line of
the "Call Script" event command (and not flowing down to subsequent lines).
For example, the following call may not work as expected:
autoscroll($game_variables[1],
$game_variables[2])
(since the long argument names require dropping down to a second line)
A work-around is to setup new variables with shorter names in a preceding
(separate) "Call Script" event command:
@x = $game_variables[1]
@y = $game_variables[2]
and then use those as arguments:
autoscroll(@x,@y)
The renaming must be in a separate "Call Script" because otherwise
the call to autoscroll(...) isn't on the first line.
Originally requested by militantmilo80:
http://www.rmxp.net/forums/index.php?showtopic=29519
=end
class Interpreter
SCROLL_SPEED_DEFAULT = 4
#-----------------------------------------------------------------------------
# * Map Autoscroll to Coordinates
# x : x coordinate to scroll to and center on
# y : y coordinate to scroll to and center on
# speed : (optional) scroll speed (from 1-6, default being 4)
#-----------------------------------------------------------------------------
def autoscroll(x,y,speed=SCROLL_SPEED_DEFAULT)
if $game_map.scrolling?
return false
elsif !$game_map.valid?(x,y)
print 'Map Autoscroll: given x,y is invalid'
return command_skip
elsif !(1..6).include?(speed)
print 'Map Autoscroll: invalid speed (1-6 only)'
return command_skip
end
center_x = (Graphics.width/2 - Game_Map::TILE_WIDTH/2) * 4 # X coordinate in the center of the screen
center_y = (Graphics.height/2 - Game_Map::TILE_HEIGHT/2) * 4 # Y coordinate in the center of the screen
max_x = ($game_map.width - Graphics.width*1.0/Game_Map::TILE_WIDTH) * 4 * Game_Map::TILE_WIDTH
max_y = ($game_map.height - Graphics.height*1.0/Game_Map::TILE_HEIGHT) * 4 * Game_Map::TILE_HEIGHT
count_x = ($game_map.display_x - [0,[x*Game_Map::REAL_RES_X-center_x,max_x].min].max)/Game_Map::REAL_RES_X
count_y = ($game_map.display_y - [0,[y*Game_Map::REAL_RES_Y-center_y,max_y].min].max)/Game_Map::REAL_RES_Y
if !@diag
@diag = true
dir = nil
if count_x > 0
if count_y > 0
dir = 7
elsif count_y < 0
dir = 1
end
elsif count_x < 0
if count_y > 0
dir = 9
elsif count_y < 0
dir = 3
end
end
count = [count_x.abs,count_y.abs].min
else
@diag = false
dir = nil
if count_x != 0 && count_y != 0
return false
elsif count_x > 0
dir = 4
elsif count_x < 0
dir = 6
elsif count_y > 0
dir = 8
elsif count_y < 0
dir = 2
end
count = count_x != 0 ? count_x.abs : count_y.abs
end
$game_map.start_scroll(dir, count, speed) if dir != nil
if @diag
return false
else
return true
end
end
#-----------------------------------------------------------------------------
# * Map Autoscroll (to Player)
# speed : (optional) scroll speed (from 1-6, default being 4)
#-----------------------------------------------------------------------------
def autoscroll_player(speed=SCROLL_SPEED_DEFAULT)
autoscroll($game_player.x,$game_player.y,speed)
end
end
class Game_Map
def scroll_downright(distance)
@display_x = [@display_x + distance,
(self.width - Graphics.width*1.0/TILE_WIDTH) * REAL_RES_X].min
@display_y = [@display_y + distance,
(self.height - Graphics.height*1.0/TILE_HEIGHT) * REAL_RES_Y].min
end
def scroll_downleft(distance)
@display_x = [@display_x - distance, 0].max
@display_y = [@display_y + distance,
(self.height - Graphics.height*1.0/TILE_HEIGHT) * REAL_RES_Y].min
end
def scroll_upright(distance)
@display_x = [@display_x + distance,
(self.width - Graphics.width*1.0/TILE_WIDTH) * REAL_RES_X].min
@display_y = [@display_y - distance, 0].max
end
def scroll_upleft(distance)
@display_x = [@display_x - distance, 0].max
@display_y = [@display_y - distance, 0].max
end
def update_scrolling
# If scrolling
if @scroll_rest > 0
# Change from scroll speed to distance in map coordinates
distance = (1<<@scroll_speed)*40/Graphics.frame_rate
distance = @scroll_rest if distance>@scroll_rest
# Execute scrolling
case @scroll_direction
when 1 then scroll_downleft(distance)
when 2 then scroll_down(distance)
when 3 then scroll_downright(distance)
when 4 then scroll_left(distance)
when 6 then scroll_right(distance)
when 7 then scroll_upleft(distance)
when 8 then scroll_up(distance)
when 9 then scroll_upright(distance)
end
# Subtract distance scrolled
@scroll_rest -= distance
end
end
end

View File

@@ -0,0 +1,526 @@
#===============================================================================
# Map Factory (allows multiple maps to be loaded at once and connected)
#===============================================================================
class PokemonMapFactory
attr_reader :maps
def initialize(id)
@maps = []
@fixup = false
@mapChanged = false # transient instance variable
setup(id)
end
# Clears all maps and sets up the current map with id. This function also sets
# the positions of neighboring maps and notifies the game system of a map
# change.
def setup(id)
@maps.clear
@maps[0] = Game_Map.new
@mapIndex = 0
oldID = ($game_map) ? $game_map.map_id : 0
setMapChanging(id,@maps[0]) if oldID!=0 && oldID!=@maps[0].map_id
$game_map = @maps[0]
@maps[0].setup(id)
setMapsInRange
setMapChanged(oldID)
end
def map
@mapIndex = 0 if !@mapIndex || @mapIndex<0
return @maps[@mapIndex] if @maps[@mapIndex]
raise "No maps in save file... (mapIndex=#{@mapIndex})" if @maps.length==0
if @maps[0]
echoln("Using next map, may be incorrect (mapIndex=#{@mapIndex}, length=#{@maps.length})")
return @maps[0]
end
raise "No maps in save file... (all maps empty; mapIndex=#{@mapIndex})"
end
def hasMap?(id)
for map in @maps
return true if map.map_id==id
end
return false
end
def getMapIndex(id)
for i in 0...@maps.length
return i if @maps[i].map_id==id
end
return -1
end
def getMap(id,add=true)
for map in @maps
return map if map.map_id==id
end
map = Game_Map.new
map.setup(id)
@maps.push(map) if add
return map
end
def getMapNoAdd(id)
return getMap(id,false)
end
def getNewMap(playerX,playerY)
id = $game_map.map_id
conns = MapFactoryHelper.getMapConnections
if conns[id]
for conn in conns[id]
mapidB = nil
newx = 0
newy = 0
if conn[0] == id
mapidB = conn[3]
mapB = MapFactoryHelper.getMapDims(conn[3])
newx = conn[4] - conn[1] + playerX
newy = conn[5] - conn[2] + playerY
else
mapidB = conn[0]
mapB = MapFactoryHelper.getMapDims(conn[0])
newx = conn[1] - conn[4] + playerX
newy = conn[2] - conn[5] + playerY
end
if newx >= 0 && newx < mapB[0] && newy >= 0 && newy < mapB[1]
return [getMap(mapidB), newx, newy]
end
end
end
return nil
end
# Detects whether the player has moved onto a connected map, and if so, causes
# their transfer to that map.
def setCurrentMap
return if $game_player.moving?
return if $game_map.valid?($game_player.x,$game_player.y)
newmap = getNewMap($game_player.x,$game_player.y)
return if !newmap
oldmap=$game_map.map_id
if oldmap!=0 && oldmap!=newmap[0].map_id
setMapChanging(newmap[0].map_id,newmap[0])
end
$game_map = newmap[0]
@mapIndex = getMapIndex($game_map.map_id)
$game_player.moveto(newmap[1],newmap[2])
$game_map.update
pbAutoplayOnTransition
$game_map.refresh
setMapChanged(oldmap)
$game_screen.weather_duration = 20
end
def setMapsInRange
return if @fixup
@fixup = true
id = $game_map.map_id
conns = MapFactoryHelper.getMapConnections
if conns[id]
for conn in conns[id]
if conn[0] == id
mapA = getMap(conn[0])
newdispx = (conn[4] - conn[1]) * Game_Map::REAL_RES_X + mapA.display_x
newdispy = (conn[5] - conn[2]) * Game_Map::REAL_RES_Y + mapA.display_y
if hasMap?(conn[3]) || MapFactoryHelper.mapInRangeById?(conn[3], newdispx, newdispy)
mapB = getMap(conn[3])
mapB.display_x = newdispx if mapB.display_x != newdispx
mapB.display_y = newdispy if mapB.display_y != newdispy
end
else
mapA = getMap(conn[3])
newdispx = (conn[1] - conn[4]) * Game_Map::REAL_RES_X + mapA.display_x
newdispy = (conn[2] - conn[5]) * Game_Map::REAL_RES_Y + mapA.display_y
if hasMap?(conn[0]) || MapFactoryHelper.mapInRangeById?(conn[0], newdispx, newdispy)
mapB = getMap(conn[0])
mapB.display_x = newdispx if mapB.display_x != newdispx
mapB.display_y = newdispy if mapB.display_y != newdispy
end
end
end
end
@fixup = false
end
def setMapChanging(newID,newMap)
Events.onMapChanging.trigger(self,newID,newMap)
end
def setMapChanged(prevMap)
Events.onMapChange.trigger(self,prevMap)
@mapChanged = true
end
def setSceneStarted(scene)
Events.onMapSceneChange.trigger(self,scene,@mapChanged)
@mapChanged = false
end
# Similar to Game_Player#passable?, but supports map connections
def isPassableFromEdge?(x, y)
return true if $game_map.valid?(x, y)
newmap = getNewMap(x, y)
return false if !newmap
return isPassable?(newmap[0].map_id, newmap[1], newmap[2])
end
def isPassable?(mapID, x, y, thisEvent = nil)
thisEvent = $game_player if !thisEvent
map = getMapNoAdd(mapID)
return false if !map
return false if !map.valid?(x, y)
return true if thisEvent.through
# Check passability of tile
if thisEvent.is_a?(Game_Player)
return false unless ($DEBUG && Input.press?(Input::CTRL)) ||
map.passable?(x, y, 0, thisEvent)
else
return false unless map.passable?(x, y, 0, thisEvent)
end
# Check passability of event(s) in that spot
for event in map.events.values
next if event == thisEvent || !event.at_coordinate?(x, y)
return false if !event.through && event.character_name != ""
end
# Check passability of player
if !thisEvent.is_a?(Game_Player)
if $game_map.map_id == mapID && $game_player.x == x && $game_player.y == y
return false if !$game_player.through && $game_player.character_name != ""
end
end
return true
end
# Only used by dependent events
def isPassableStrict?(mapID,x,y,thisEvent=nil)
thisEvent = $game_player if !thisEvent
map = getMapNoAdd(mapID)
return false if !map
return false if !map.valid?(x,y)
return true if thisEvent.through
if thisEvent==$game_player
if !($DEBUG && Input.press?(Input::CTRL))
return false if !map.passableStrict?(x,y,0,thisEvent)
end
else
return false if !map.passableStrict?(x,y,0,thisEvent)
end
for event in map.events.values
next if event == thisEvent || !event.at_coordinate?(x, y)
return false if !event.through && event.character_name!=""
end
return true
end
def getTerrainTag(mapid,x,y,countBridge=false)
map = getMapNoAdd(mapid)
return map.terrain_tag(x,y,countBridge)
end
# NOTE: Assumes the event is 1x1 tile in size. Only returns one terrain tag.
def getFacingTerrainTag(dir=nil,event=nil)
tile = getFacingTile(dir,event)
return GameData::TerrainTag.get(:None) if !tile
return getTerrainTag(tile[0],tile[1],tile[2])
end
def getTerrainTagFromCoords(mapid,x,y,countBridge=false)
tile = getRealTilePos(mapid,x,y)
return GameData::TerrainTag.get(:None) if !tile
return getTerrainTag(tile[0],tile[1],tile[2])
end
def areConnected?(mapID1, mapID2)
return true if mapID1 == mapID2
conns = MapFactoryHelper.getMapConnections
if conns[mapID1]
for conn in conns[mapID1]
return true if conn[0] == mapID2 || conn[3] == mapID2
end
end
return false
end
def getRelativePos(thisMapID, thisX, thisY, otherMapID, otherX, otherY)
if thisMapID == otherMapID # Both events share the same map
return [otherX - thisX, otherY - thisY]
end
conns = MapFactoryHelper.getMapConnections
if conns[thisMapID]
for conn in conns[thisMapID]
if conn[0] == otherMapID
posX = thisX + conn[1] - conn[4] + otherX
posY = thisY + conn[2] - conn[5] + otherY
return [posX, posY]
elsif conn[3] == otherMapID
posX = thisX + conn[4] - conn[1] + otherX
posY = thisY + conn[5] - conn[2] + otherY
return [posX, posY]
end
end
end
return [0, 0]
end
# Gets the distance from this event to another event. Example: If this event's
# coordinates are (2,5) and the other event's coordinates are (5,1), returns
# the array (3,-4), because (5-2=3) and (1-5=-4).
def getThisAndOtherEventRelativePos(thisEvent,otherEvent)
return [0,0] if !thisEvent || !otherEvent
return getRelativePos(
thisEvent.map.map_id,thisEvent.x,thisEvent.y,
otherEvent.map.map_id,otherEvent.x,otherEvent.y)
end
def getThisAndOtherPosRelativePos(thisEvent,otherMapID,otherX,otherY)
return [0,0] if !thisEvent
return getRelativePos(
thisEvent.map.map_id,thisEvent.x,thisEvent.y,otherMapID,otherX,otherY)
end
# Unused
def getOffsetEventPos(event,xOffset,yOffset)
event = $game_player if !event
return nil if !event
return getRealTilePos(event.map.map_id,event.x+xOffset,event.y+yOffset)
end
# NOTE: Assumes the event is 1x1 tile in size. Only returns one tile.
def getFacingTile(direction=nil,event=nil,steps=1)
event = $game_player if event==nil
return [0,0,0] if !event
x = event.x
y = event.y
id = event.map.map_id
direction = event.direction if direction==nil
return getFacingTileFromPos(id,x,y,direction,steps)
end
def getFacingTileFromPos(mapID,x,y,direction=0,steps=1)
id = mapID
case direction
when 1
x -= steps
y += steps
when 2
y += steps
when 3
x += steps
y += steps
when 4
x -= steps
when 6
x += steps
when 7
x -= steps
y -= steps
when 8
y -= steps
when 9
x += steps
y -= steps
else
return [id,x,y]
end
return getRealTilePos(mapID,x,y)
end
def getRealTilePos(mapID, x, y)
id = mapID
return [id, x, y] if getMapNoAdd(id).valid?(x, y)
conns = MapFactoryHelper.getMapConnections
if conns[id]
for conn in conns[id]
if conn[0] == id
newX = x + conn[4] - conn[1]
newY = y + conn[5] - conn[2]
next if newX < 0 || newY < 0
dims = MapFactoryHelper.getMapDims(conn[3])
next if newX >= dims[0] || newY >= dims[1]
return [conn[3], newX, newY]
else
newX = x + conn[1] - conn[4]
newY = y + conn[2] - conn[5]
next if newX < 0 || newY < 0
dims = MapFactoryHelper.getMapDims(conn[0])
next if newX >= dims[0] || newY >= dims[1]
return [conn[0], newX, newY]
end
end
end
return nil
end
def getFacingCoords(x,y,direction=0,steps=1)
case direction
when 1
x -= steps
y += steps
when 2
y += steps
when 3
x += steps
y += steps
when 4
x -= steps
when 6
x += steps
when 7
x -= steps
y -= steps
when 8
y -= steps
when 9
x += steps
y -= steps
end
return [x,y]
end
def updateMaps(scene)
updateMapsInternal
$MapFactory.setSceneStarted(scene) if @mapChanged
end
def updateMapsInternal
return if $game_player.moving?
if !MapFactoryHelper.hasConnections?($game_map.map_id)
return if @maps.length==1
for i in 0...@maps.length
@maps[i] = nil if $game_map.map_id!=@maps[i].map_id
end
@maps.compact!
@mapIndex = getMapIndex($game_map.map_id)
return
end
setMapsInRange
deleted = false
for i in 0...@maps.length
next if MapFactoryHelper.mapInRange?(@maps[i])
@maps[i] = nil
deleted = true
end
if deleted
@maps.compact!
@mapIndex = getMapIndex($game_map.map_id)
end
end
end
#===============================================================================
# Map Factory Helper (stores map connection and size data and calculations
# involving them)
#===============================================================================
module MapFactoryHelper
@@MapConnections = nil
@@MapDims = nil
def self.clear
@@MapConnections = nil
@@MapDims = nil
end
def self.getMapConnections
if !@@MapConnections
@@MapConnections = []
conns = load_data("Data/map_connections.dat")
conns.each do |conn|
# Ensure both maps in a connection are valid
dimensions = getMapDims(conn[0])
next if dimensions[0] == 0 || dimensions[1] == 0
dimensions = getMapDims(conn[3])
next if dimensions[0] == 0 || dimensions[1] == 0
# Convert first map's edge and coordinate to pair of coordinates
edge = getMapEdge(conn[0], conn[1])
case conn[1]
when "N", "S"
conn[1] = conn[2]
conn[2] = edge
when "E", "W"
conn[1] = edge
end
# Convert second map's edge and coordinate to pair of coordinates
edge = getMapEdge(conn[3], conn[4])
case conn[4]
when "N", "S"
conn[4] = conn[5]
conn[5] = edge
when "E", "W"
conn[4] = edge
end
# Add connection to arrays for both maps
@@MapConnections[conn[0]] = [] if !@@MapConnections[conn[0]]
@@MapConnections[conn[0]].push(conn)
@@MapConnections[conn[3]] = [] if !@@MapConnections[conn[3]]
@@MapConnections[conn[3]].push(conn)
end
end
return @@MapConnections
end
def self.hasConnections?(id)
conns = MapFactoryHelper.getMapConnections
return conns[id] ? true : false
end
# Gets the height and width of the map with id
def self.getMapDims(id)
# Create cache if doesn't exist
@@MapDims = [] if !@@MapDims
# Add map to cache if can't be found
if !@@MapDims[id]
begin
map = load_data(sprintf("Data/Map%03d.rxdata", id))
@@MapDims[id] = [map.width,map.height]
rescue
@@MapDims[id] = [0,0]
end
end
# Return map in cache
return @@MapDims[id]
end
# Returns the X or Y coordinate of an edge on the map with id.
# Considers the special strings "N","W","E","S"
def self.getMapEdge(id,edge)
return 0 if edge=="N" || edge=="W"
dims = getMapDims(id) # Get dimensions
return dims[0] if edge=="E"
return dims[1] if edge=="S"
return dims[0] # real dimension (use width)
end
def self.mapInRange?(map)
range = 6 # Number of tiles
dispx = map.display_x
dispy = map.display_y
return false if dispx >= (map.width + range) * Game_Map::REAL_RES_X
return false if dispy >= (map.height + range) * Game_Map::REAL_RES_Y
return false if dispx <= -(Graphics.width + range * Game_Map::TILE_WIDTH) * Game_Map::X_SUBPIXELS
return false if dispy <= -(Graphics.height + range * Game_Map::TILE_HEIGHT) * Game_Map::Y_SUBPIXELS
return true
end
def self.mapInRangeById?(id,dispx,dispy)
range = 6 # Number of tiles
dims = MapFactoryHelper.getMapDims(id)
return false if dispx >= (dims[0] + range) * Game_Map::REAL_RES_X
return false if dispy >= (dims[1] + range) * Game_Map::REAL_RES_Y
return false if dispx <= -(Graphics.width + range * Game_Map::TILE_WIDTH) * Game_Map::X_SUBPIXELS
return false if dispy <= -(Graphics.height + range * Game_Map::TILE_HEIGHT) * Game_Map::Y_SUBPIXELS
return true
end
end
#===============================================================================
#
#===============================================================================
# Unused
def updateTilesets
maps = $MapFactory.maps
for map in maps
map.updateTileset if map
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,292 @@
class Game_Event < Game_Character
attr_reader :map_id
attr_accessor :trigger
attr_reader :list
attr_reader :starting
attr_reader :tempSwitches # Temporary self-switches
attr_reader :character_name
attr_accessor :need_refresh
attr_accessor :opacity
attr_accessor :through
def initialize(map_id, event, map=nil)
super(map)
@map_id = map_id
@event = event
@id = @event.id
@original_x = @event.x
@original_y = @event.y
if @event.name[/size\((\d+),(\d+)\)/i]
@width = $~[1].to_i
@height = $~[2].to_i
end
@erased = false
@starting = false
@need_refresh = false
@route_erased = false
@through = true
@to_update = true
@tempSwitches = {}
if @event.name[/forced_z\s*=\s*(-?\d+)/i]
@forced_z = $1.to_i
end
moveto(@event.x, @event.y) if map
refresh
end
def id; return @event.id; end
def name; return @event.name; end
def set_starting
@starting = true
end
def clear_starting
@starting = false
end
def start
@starting = true if @list.size > 1
end
def erase
@erased = true
refresh
end
def erase_route
@route_erased = true
refresh
end
def tsOn?(c)
return @tempSwitches && @tempSwitches[c]==true
end
def tsOff?(c)
return !@tempSwitches || !@tempSwitches[c]
end
def setTempSwitchOn(c)
@tempSwitches[c]=true
refresh
end
def setTempSwitchOff(c)
@tempSwitches[c]=false
refresh
end
def isOff?(c)
return !$game_self_switches[[@map_id,@event.id,c]]
end
def switchIsOn?(id)
switchname = $data_system.switches[id]
return false if !switchname
if switchname[/^s\:/]
return eval($~.post_match)
else
return $game_switches[id]
end
end
def variable
return nil if !$PokemonGlobal.eventvars
return $PokemonGlobal.eventvars[[@map_id,@event.id]]
end
def setVariable(variable)
$PokemonGlobal.eventvars[[@map_id,@event.id]]=variable
end
def varAsInt
return 0 if !$PokemonGlobal.eventvars
return $PokemonGlobal.eventvars[[@map_id,@event.id]].to_i
end
def expired?(secs=86400)
ontime=self.variable
time=pbGetTimeNow
return ontime && (time.to_i>ontime+secs)
end
def expiredDays?(days=1)
ontime=self.variable.to_i
return false if !ontime
now=pbGetTimeNow
elapsed=(now.to_i-ontime)/86400
elapsed+=1 if (now.to_i-ontime)%86400>(now.hour*3600+now.min*60+now.sec)
return elapsed>=days
end
def cooledDown?(seconds)
return true if expired?(seconds) && tsOff?("A")
self.need_refresh = true
return false
end
def cooledDownDays?(days)
return true if expiredDays?(days) && tsOff?("A")
self.need_refresh = true
return false
end
def onEvent?
return @map_id == $game_map.map_id && at_coordinate?($game_player.x, $game_player.y)
end
def over_trigger?
return false if @character_name != "" && !@through
return false if @event.name[/hiddenitem/i]
each_occupied_tile do |i, j|
return true if self.map.passable?(i, j, 0, $game_player)
end
return false
end
def pbCheckEventTriggerAfterTurning
return if $game_system.map_interpreter.running? || @starting
if @event.name[/trainer\((\d+)\)/i]
distance = $~[1].to_i
if @trigger==2 && pbEventCanReachPlayer?(self,$game_player,distance)
start if !jumping? && !over_trigger?
end
end
end
def check_event_trigger_touch(dir)
return if $game_system.map_interpreter.running?
return if @trigger != 2 # Event touch
case dir
when 2
return if $game_player.y != @y + 1
when 4
return if $game_player.x != @x - 1
when 6
return if $game_player.x != @x + @width
when 8
return if $game_player.y != @y - @height
end
return if !in_line_with_coordinate?($game_player.x, $game_player.y)
return if jumping? || over_trigger?
start
end
def check_event_trigger_auto
if @trigger == 2 # Event touch
if at_coordinate?($game_player.x, $game_player.y)
start if !jumping? && over_trigger?
end
elsif @trigger == 3 # Autorun
start
end
end
def refresh
new_page = nil
unless @erased
for page in @event.pages.reverse
c = page.condition
next if c.switch1_valid && !switchIsOn?(c.switch1_id)
next if c.switch2_valid && !switchIsOn?(c.switch2_id)
next if c.variable_valid && $game_variables[c.variable_id] < c.variable_value
if c.self_switch_valid
key = [@map_id, @event.id, c.self_switch_ch]
next if $game_self_switches[key] != true
end
new_page = page
break
end
end
return if new_page == @page
@page = new_page
clear_starting
if @page == nil
@tile_id = 0
@character_name = ""
@character_hue = 0
@move_type = 0
@through = true
@trigger = nil
@list = nil
@interpreter = nil
return
end
@tile_id = @page.graphic.tile_id
@character_name = @page.graphic.character_name
@character_hue = @page.graphic.character_hue
if @original_direction != @page.graphic.direction
@direction = @page.graphic.direction
@original_direction = @direction
@prelock_direction = 0
end
if @original_pattern != @page.graphic.pattern
@pattern = @page.graphic.pattern
@original_pattern = @pattern
end
@opacity = @page.graphic.opacity
@blend_type = @page.graphic.blend_type
@move_type = @page.move_type
self.move_speed = @page.move_speed
self.move_frequency = @page.move_frequency
@move_route = (@route_erased) ? RPG::MoveRoute.new : @page.move_route
@move_route_index = 0
@move_route_forcing = false
@walk_anime = @page.walk_anime
@step_anime = @page.step_anime
@direction_fix = @page.direction_fix
@through = @page.through
@always_on_top = @page.always_on_top
calculate_bush_depth
@trigger = @page.trigger
@list = @page.list
@interpreter = nil
if @trigger == 4 # Parallel Process
@interpreter = Interpreter.new
end
check_event_trigger_auto
end
def should_update?(recalc=false)
return @to_update if !recalc
return true if @trigger && (@trigger == 3 || @trigger == 4)
return true if @move_route_forcing
return true if @event.name[/update/i]
range = 2 # Number of tiles
return false if self.screen_x - @sprite_size[0]/2 > Graphics.width + range * Game_Map::TILE_WIDTH
return false if self.screen_x + @sprite_size[0]/2 < -range * Game_Map::TILE_WIDTH
return false if self.screen_y_ground - @sprite_size[1] > Graphics.height + range * Game_Map::TILE_HEIGHT
return false if self.screen_y_ground < -range * Game_Map::TILE_HEIGHT
return true
end
def update
@to_update = should_update?(true)
return if !@to_update
last_moving = moving?
super
if !moving? && last_moving
$game_player.pbCheckEventTriggerFromDistance([2])
end
if @need_refresh
@need_refresh = false
refresh
end
check_event_trigger_auto
if @interpreter != nil
unless @interpreter.running?
@interpreter.setup(@list, @event.id, @map_id)
end
@interpreter.update
end
end
def active?
return !@erased && @page != nil
end
end

View File

@@ -0,0 +1,447 @@
#===============================================================================
# ** Game_Player
#-------------------------------------------------------------------------------
# This class handles the player. Its functions include event starting
# determinants and map scrolling. Refer to "$game_player" for the one
# instance of this class.
#===============================================================================
class Game_Player < Game_Character
attr_accessor :bump_se
attr_accessor :charsetData
attr_accessor :encounter_count
attr_accessor :x
attr_accessor :y
SCREEN_CENTER_X = (Settings::SCREEN_WIDTH / 2 - Game_Map::TILE_WIDTH / 2) * Game_Map::X_SUBPIXELS
SCREEN_CENTER_Y = (Settings::SCREEN_HEIGHT / 2 - Game_Map::TILE_HEIGHT / 2) * Game_Map::Y_SUBPIXELS
def initialize(*arg)
super(*arg)
@lastdir=0
@lastdirframe=0
@bump_se=0
end
def map
@map = nil
return $game_map
end
def pbHasDependentEvents?
return $PokemonGlobal.dependentEvents.length>0
end
def bump_into_object
return if @bump_se && @bump_se>0
pbSEPlay("Player bump")
@bump_se = Graphics.frame_rate/2
end
def move_generic(dir, turn_enabled = true)
turn_generic(dir, true) if turn_enabled
if !$PokemonTemp.encounterTriggered
if can_move_in_direction?(dir)
x_offset = (dir == 4) ? -1 : (dir == 6) ? 1 : 0
y_offset = (dir == 8) ? -1 : (dir == 2) ? 1 : 0
return if pbLedge(x_offset, y_offset)
return if pbEndSurf(x_offset, y_offset)
turn_generic(dir, true)
if !$PokemonTemp.encounterTriggered
@x += x_offset
@y += y_offset
$PokemonTemp.dependentEvents.pbMoveDependentEvents
increase_steps
end
elsif !check_event_trigger_touch(dir)
bump_into_object
end
end
$PokemonTemp.encounterTriggered = false
end
def turn_generic(dir, keep_enc_indicator = false)
old_direction = @direction
super(dir)
if @direction != old_direction && !@move_route_forcing && !pbMapInterpreterRunning?
Events.onChangeDirection.trigger(self, self)
$PokemonTemp.encounterTriggered = false if !keep_enc_indicator
end
end
def pbTriggeredTrainerEvents(triggers,checkIfRunning=true)
result = []
# If event is running
return result if checkIfRunning && $game_system.map_interpreter.running?
# All event loops
for event in $game_map.events.values
next if !event.name[/trainer\((\d+)\)/i]
distance = $~[1].to_i
# If event coordinates and triggers are consistent
if pbEventCanReachPlayer?(event,self,distance) && triggers.include?(event.trigger)
# If starting determinant is front event (other than jumping)
result.push(event) if !event.jumping? && !event.over_trigger?
end
end
return result
end
def pbTriggeredCounterEvents(triggers,checkIfRunning=true)
result = []
# If event is running
return result if checkIfRunning && $game_system.map_interpreter.running?
# All event loops
for event in $game_map.events.values
next if !event.name[/counter\((\d+)\)/i]
distance = $~[1].to_i
# If event coordinates and triggers are consistent
if pbEventFacesPlayer?(event,self,distance) && triggers.include?(event.trigger)
# If starting determinant is front event (other than jumping)
result.push(event) if !event.jumping? && !event.over_trigger?
end
end
return result
end
def pbCheckEventTriggerAfterTurning; end
def pbCheckEventTriggerFromDistance(triggers)
ret = pbTriggeredTrainerEvents(triggers)
ret.concat(pbTriggeredCounterEvents(triggers))
return false if ret.length==0
for event in ret
event.start
end
return true
end
def pbTerrainTag(countBridge = false)
return $MapFactory.getTerrainTag(self.map.map_id, @x, @y, countBridge) if $MapFactory
return $game_map.terrain_tag(@x, @y, countBridge)
end
def pbFacingEvent(ignoreInterpreter=false)
return nil if $game_system.map_interpreter.running? && !ignoreInterpreter
# Check the tile in front of the player for events
new_x = @x + (@direction == 6 ? 1 : @direction == 4 ? -1 : 0)
new_y = @y + (@direction == 2 ? 1 : @direction == 8 ? -1 : 0)
return nil if !$game_map.valid?(new_x, new_y)
for event in $game_map.events.values
next if !event.at_coordinate?(new_x, new_y)
next if event.jumping? || event.over_trigger?
return event
end
# If the tile in front is a counter, check one tile beyond that for events
if $game_map.counter?(new_x, new_y)
new_x += (@direction == 6 ? 1 : @direction == 4 ? -1 : 0)
new_y += (@direction == 2 ? 1 : @direction == 8 ? -1 : 0)
for event in $game_map.events.values
next if !event.at_coordinate?(new_x, new_y)
next if event.jumping? || event.over_trigger?
return event
end
end
return nil
end
#-----------------------------------------------------------------------------
# * Passable Determinants
# x : x-coordinate
# y : y-coordinate
# d : direction (0,2,4,6,8)
# * 0 = Determines if all directions are impassable (for jumping)
#-----------------------------------------------------------------------------
def passable?(x, y, d, strict = false)
# Get new coordinates
new_x = x + (d == 6 ? 1 : d == 4 ? -1 : 0)
new_y = y + (d == 2 ? 1 : d == 8 ? -1 : 0)
# If coordinates are outside of map
return false if !$game_map.validLax?(new_x, new_y)
if !$game_map.valid?(new_x, new_y)
return false if !$MapFactory
return $MapFactory.isPassableFromEdge?(new_x, new_y)
end
# If debug mode is ON and Ctrl key was pressed
return true if $DEBUG && Input.press?(Input::CTRL)
return super
end
#-----------------------------------------------------------------------------
# * Set Map Display Position to Center of Screen
#-----------------------------------------------------------------------------
def center(x, y)
self.map.display_x = x * Game_Map::REAL_RES_X - SCREEN_CENTER_X
self.map.display_y = y * Game_Map::REAL_RES_Y - SCREEN_CENTER_Y
end
def isCentered()
x_centered = self.map.display_x == x * Game_Map::REAL_RES_X - SCREEN_CENTER_X
y_centered = self.map.display_y == y * Game_Map::REAL_RES_Y - SCREEN_CENTER_Y
return x_centered && y_centered
end
#-----------------------------------------------------------------------------
# * Move to Designated Position
# x : x-coordinate
# y : y-coordinate
#-----------------------------------------------------------------------------
def moveto(x, y)
super
# Centering
center(x, y)
# Make encounter count
make_encounter_count
end
#-----------------------------------------------------------------------------
# * Make Encounter Count
#-----------------------------------------------------------------------------
def make_encounter_count
# Image of two dice rolling
if $game_map.map_id != 0
n = $game_map.encounter_step
@encounter_count = rand(n) + rand(n) + 1
end
end
#-----------------------------------------------------------------------------
# * Refresh
#-----------------------------------------------------------------------------
def refresh
@opacity = 255
@blend_type = 0
end
#-----------------------------------------------------------------------------
# * Trigger event(s) at the same coordinates as self with the appropriate
# trigger(s) that can be triggered
#-----------------------------------------------------------------------------
def check_event_trigger_here(triggers)
result = false
# If event is running
return result if $game_system.map_interpreter.running?
# All event loops
for event in $game_map.events.values
# If event coordinates and triggers are consistent
next if !event.at_coordinate?(@x, @y)
next if !triggers.include?(event.trigger)
# If starting determinant is same position event (other than jumping)
next if event.jumping? || !event.over_trigger?
event.start
result = true
end
return result
end
#-----------------------------------------------------------------------------
# * Front Event Starting Determinant
#-----------------------------------------------------------------------------
def check_event_trigger_there(triggers)
result = false
# If event is running
return result if $game_system.map_interpreter.running?
# Calculate front event coordinates
new_x = @x + (@direction == 6 ? 1 : @direction == 4 ? -1 : 0)
new_y = @y + (@direction == 2 ? 1 : @direction == 8 ? -1 : 0)
return false if !$game_map.valid?(new_x, new_y)
# All event loops
for event in $game_map.events.values
# If event coordinates and triggers are consistent
next if !event.at_coordinate?(new_x, new_y)
next if !triggers.include?(event.trigger)
# If starting determinant is front event (other than jumping)
next if event.jumping? || event.over_trigger?
event.start
result = true
end
# If fitting event is not found
if result == false
# If front tile is a counter
if $game_map.counter?(new_x, new_y)
# Calculate coordinates of 1 tile further away
new_x += (@direction == 6 ? 1 : @direction == 4 ? -1 : 0)
new_y += (@direction == 2 ? 1 : @direction == 8 ? -1 : 0)
return false if !$game_map.valid?(new_x, new_y)
# All event loops
for event in $game_map.events.values
# If event coordinates and triggers are consistent
next if !event.at_coordinate?(new_x, new_y)
next if !triggers.include?(event.trigger)
# If starting determinant is front event (other than jumping)
next if event.jumping? || event.over_trigger?
event.start
result = true
end
end
end
return result
end
#-----------------------------------------------------------------------------
# * Touch Event Starting Determinant
#-----------------------------------------------------------------------------
def check_event_trigger_touch(dir)
result = false
return result if $game_system.map_interpreter.running?
# All event loops
x_offset = (dir == 4) ? -1 : (dir == 6) ? 1 : 0
y_offset = (dir == 8) ? -1 : (dir == 2) ? 1 : 0
for event in $game_map.events.values
next if ![1, 2].include?(event.trigger) # Player touch, event touch
# If event coordinates and triggers are consistent
next if !event.at_coordinate?(@x + x_offset, @y + y_offset)
if event.name[/trainer\((\d+)\)/i]
distance = $~[1].to_i
next if !pbEventCanReachPlayer?(event,self,distance)
elsif event.name[/counter\((\d+)\)/i]
distance = $~[1].to_i
next if !pbEventFacesPlayer?(event,self,distance)
end
# If starting determinant is front event (other than jumping)
next if event.jumping? || event.over_trigger?
event.start
result = true
end
return result
end
#-----------------------------------------------------------------------------
# * Frame Update
#-----------------------------------------------------------------------------
def update
last_real_x = @real_x
last_real_y = @real_y
super
update_screen_position(last_real_x, last_real_y)
# Update dependent events
$PokemonTemp.dependentEvents.updateDependentEvents
# Count down the time between allowed bump sounds
@bump_se -= 1 if @bump_se && @bump_se>0
# Finish up dismounting from surfing
if $PokemonTemp.endSurf && !moving?
pbCancelVehicles
$PokemonTemp.surfJump = nil
$PokemonTemp.endSurf = false
end
update_event_triggering
end
def update_command_new
dir = Input.dir4
unless pbMapInterpreterRunning? || $game_temp.message_window_showing ||
$PokemonTemp.miniupdate || $game_temp.in_menu
# Move player in the direction the directional button is being pressed
if @moved_last_frame ||
(dir > 0 && dir == @lastdir && Graphics.frame_count - @lastdirframe > Graphics.frame_rate / 20)
case dir
when 2 then move_down
when 4 then move_left
when 6 then move_right
when 8 then move_up
end
elsif dir != @lastdir
case dir
when 2 then turn_down
when 4 then turn_left
when 6 then turn_right
when 8 then turn_up
end
end
end
# Record last direction input
@lastdirframe = Graphics.frame_count if dir != @lastdir
@lastdir = dir
end
# Center player on-screen
def update_screen_position(last_real_x, last_real_y)
return if self.map.scrolling? || !(@moved_last_frame || @moved_this_frame)
self.map.display_x = @real_x - SCREEN_CENTER_X
self.map.display_y = @real_y - SCREEN_CENTER_Y
end
def update_event_triggering
return if moving?
# Try triggering events upon walking into them/in front of them
if @moved_this_frame
$PokemonTemp.dependentEvents.pbTurnDependentEvents
result = pbCheckEventTriggerFromDistance([2])
# Event determinant is via touch of same position event
result |= check_event_trigger_here([1,2])
# No events triggered, try other event triggers upon finishing a step
pbOnStepTaken(result)
end
# Try to manually interact with events
if Input.trigger?(Input::USE) && !$PokemonTemp.miniupdate
# Same position and front event determinant
check_event_trigger_here([0])
check_event_trigger_there([0,2])
end
end
end
def pbGetPlayerCharset(meta,charset,trainer=nil,force=false)
trainer = $Trainer if !trainer
outfit = (trainer) ? trainer.outfit : 0
if $game_player && $game_player.charsetData && !force
return nil if $game_player.charsetData[0] == $Trainer.character_ID &&
$game_player.charsetData[1] == charset &&
$game_player.charsetData[2] == outfit
end
$game_player.charsetData = [$Trainer.character_ID,charset,outfit] if $game_player
ret = meta[charset]
ret = meta[1] if nil_or_empty?(ret)
# if pbResolveBitmap("Graphics/Characters/player/"+ret+"_"+outfit.to_s)
# ret = ret+"_"+outfit.to_s
# end
return ret
end
def pbUpdateVehicle
meta = GameData::Metadata.get_player($Trainer.character_ID)
if meta
charset = 1 # Regular graphic
if $PokemonGlobal.diving; charset = 5 # Diving graphic
elsif $PokemonGlobal.surfing; charset = 3 # Surfing graphic
elsif $PokemonGlobal.bicycle; charset = 2 # Bicycle graphic
end
newCharName = pbGetPlayerCharset(meta,charset)
$game_player.character_name = newCharName if newCharName
end
end
def pbCancelVehicles(destination=nil)
$PokemonGlobal.surfing = false
$PokemonGlobal.diving = false
$PokemonGlobal.bicycle = false if !destination || !pbCanUseBike?(destination)
pbUpdateVehicle
end
def pbCanUseBike?(map_id)
map_metadata = GameData::MapMetadata.try_get(map_id)
return false if !map_metadata
return true if map_metadata.always_bicycle
val = map_metadata.can_bicycle || map_metadata.outdoor_map
return (val) ? true : false
end
def pbMountBike
return if $PokemonGlobal.bicycle
$PokemonGlobal.bicycle = true
pbUpdateVehicle
bike_bgm = GameData::Metadata.get.bicycle_BGM
pbCueBGM(bike_bgm, 0.5) if bike_bgm
pbPokeRadarCancel
end
def pbDismountBike
return if !$PokemonGlobal.bicycle
$PokemonGlobal.bicycle = false
pbUpdateVehicle
$game_map.autoplayAsCue
end

View File

@@ -0,0 +1,120 @@
class Game_Player < Game_Character
@@bobFrameSpeed = 1.0/15
def fullPattern
case self.direction
when 2 then return self.pattern
when 4 then return self.pattern + 4
when 6 then return self.pattern + 8
when 8 then return self.pattern + 12
end
return 0
end
def setDefaultCharName(chname,pattern,lockpattern=false)
return if pattern<0 || pattern>=16
@defaultCharacterName = chname
@direction = [2,4,6,8][pattern/4]
@pattern = pattern%4
@lock_pattern = lockpattern
end
def pbCanRun?
return false if $game_temp.in_menu || $game_temp.in_battle ||
@move_route_forcing || $game_temp.message_window_showing ||
pbMapInterpreterRunning?
input = ($PokemonSystem.runstyle == 1) ^ Input.press?(Input::ACTION)
return input && $Trainer.has_running_shoes && !jumping? &&
!$PokemonGlobal.diving && !$PokemonGlobal.surfing &&
!$PokemonGlobal.bicycle && !$game_player.pbTerrainTag.must_walk
end
def pbIsRunning?
return moving? && !@move_route_forcing && pbCanRun?
end
#Override the player's graphics
# Path from Graphics/Characters/player/
def setPlayerGraphicsOverride(path)
@defaultCharacterName=path
end
def removeGraphicsOverride
@defaultCharacterName = ""
end
def hasGraphicsOverride?
return @defaultCharacterName!=""
end
def character_name
@defaultCharacterName = "" if !@defaultCharacterName
return @defaultCharacterName if hasGraphicsOverride?
if !@move_route_forcing && $Trainer.character_ID>=0
meta = GameData::Metadata.get_player($Trainer.character_ID)
if meta && !$PokemonGlobal.bicycle && !$PokemonGlobal.diving && !$PokemonGlobal.surfing
charset = 1 # Display normal character sprite
player_is_moving = moving?
if pbCanRun? && (player_is_moving || @wasmoving) && Input.dir4!=0 && meta[4] && meta[4]!=""
charset = 4 # Display running character sprite
end
newCharName = pbGetPlayerCharset(meta,charset)
@character_name = newCharName if newCharName
@wasmoving = player_is_moving
end
end
return @character_name
end
def update_command
self.move_speed = 0.5 if $game_switches[SWITCH_SUPER_SLOW_SPEED]
if $game_player.pbTerrainTag.ice
self.move_speed = 4 # Sliding on ice
elsif !moving? && !@move_route_forcing && $PokemonGlobal
if $PokemonGlobal.bicycle
self.move_speed = $game_switches[SWITCH_RACE_BIKE] && !Input.press?(Input::ACTION) ? 5.5 : 5 # Cycling
elsif pbCanRun? || $PokemonGlobal.surfing
self.move_speed = 4 # Running, surfing
else
self.move_speed = 3 # Walking, diving
end
end
super
end
def update_pattern
if $PokemonGlobal.surfing || $PokemonGlobal.diving
p = ((Graphics.frame_count%60)*@@bobFrameSpeed).floor
@pattern = p if !@lock_pattern
@pattern_surf = p
@bob_height = (p>=2) ? 2 : 0
else
@bob_height = 0
super
end
end
end
=begin
class Game_Character
alias update_old2 update
def update
if self.is_a?(Game_Event)
if @dependentEvents
for i in 0...@dependentEvents.length
if @dependentEvents[i][0]==$game_map.map_id &&
@dependentEvents[i][1]==self.id
self.move_speed_real = $game_player.move_speed_real
break
end
end
end
end
update_old2
end
end
=end

View File

@@ -0,0 +1,82 @@
#===============================================================================
# ** Game_CommonEvent
#-------------------------------------------------------------------------------
# This class handles common events. It includes execution of parallel process
# event. This class is used within the Game_Map class ($game_map).
#===============================================================================
class Game_CommonEvent
#-----------------------------------------------------------------------------
# * Object Initialization
# common_event_id : common event ID
#-----------------------------------------------------------------------------
def initialize(common_event_id)
@common_event_id = common_event_id
@interpreter = nil
refresh
end
#-----------------------------------------------------------------------------
# * Get Name
#-----------------------------------------------------------------------------
def name
return $data_common_events[@common_event_id].name
end
#-----------------------------------------------------------------------------
# * Get Trigger
#-----------------------------------------------------------------------------
def trigger
return $data_common_events[@common_event_id].trigger
end
#-----------------------------------------------------------------------------
# * Get Condition Switch ID
#-----------------------------------------------------------------------------
def switch_id
return $data_common_events[@common_event_id].switch_id
end
#-----------------------------------------------------------------------------
# * Get List of Event Commands
#-----------------------------------------------------------------------------
def list
return $data_common_events[@common_event_id].list
end
#-----------------------------------------------------------------------------
# * Checks if switch is on
#-----------------------------------------------------------------------------
def switchIsOn?(id)
switchName = $data_system.switches[id]
return false if !switchName
if switchName[/^s\:/]
return eval($~.post_match)
else
return $game_switches[id]
end
end
#-----------------------------------------------------------------------------
# * Refresh
#-----------------------------------------------------------------------------
def refresh
# Create an interpreter for parallel process if necessary
if self.trigger == 2 && switchIsOn?(self.switch_id)
if @interpreter == nil
@interpreter = Interpreter.new
end
else
@interpreter = nil
end
end
#-----------------------------------------------------------------------------
# * Frame Update
#-----------------------------------------------------------------------------
def update
# If parallel process is valid
if @interpreter != nil
# If not running
unless @interpreter.running?
# Set up event
@interpreter.setup(self.list, 0)
end
# Update interpreter
@interpreter.update
end
end
end

View File

@@ -0,0 +1,588 @@
class PokemonTemp
attr_writer :dependentEvents
def dependentEvents
@dependentEvents = DependentEvents.new if !@dependentEvents
return @dependentEvents
end
end
def pbRemoveDependencies()
$PokemonTemp.dependentEvents.removeAllEvents()
pbDeregisterPartner() rescue nil
end
def pbAddDependency(event,follows=true)
$PokemonTemp.dependentEvents.addEvent(event)
$PokemonTemp.dependentEvents.follows_player = follows
end
def pbRemoveDependency(event)
$PokemonTemp.dependentEvents.removeEvent(event)
end
def pbAddDependency2(eventID, eventName, commonEvent)
$PokemonTemp.dependentEvents.addEvent($game_map.events[eventID],eventName,commonEvent)
end
# Gets the Game_Character object associated with a dependent event.
def pbGetDependency(eventName)
return $PokemonTemp.dependentEvents.getEventByName(eventName)
end
def pbRemoveDependency2(eventName)
$PokemonTemp.dependentEvents.removeEventByName(eventName)
end
class PokemonGlobalMetadata
attr_writer :dependentEvents
def dependentEvents
@dependentEvents = [] if !@dependentEvents
return @dependentEvents
end
end
def pbTestPass(follower,x,y,_direction=nil)
return $MapFactory.isPassableStrict?(follower.map.map_id,x,y,follower)
end
# Same map only
def moveThrough(follower,direction)
oldThrough=follower.through
follower.through=true
case direction
when 2 then follower.move_down
when 4 then follower.move_left
when 6 then follower.move_right
when 8 then follower.move_up
end
follower.through=oldThrough
end
# Same map only
def moveFancy(follower,direction)
deltaX=(direction == 6 ? 1 : (direction == 4 ? -1 : 0))
deltaY=(direction == 2 ? 1 : (direction == 8 ? -1 : 0))
newX = follower.x + deltaX
newY = follower.y + deltaY
# Move if new position is the player's, or the new position is passable,
# or the current position is not passable
if ($game_player.x==newX && $game_player.y==newY) ||
pbTestPass(follower,newX,newY,0) ||
!pbTestPass(follower,follower.x,follower.y,0)
oldThrough=follower.through
follower.through=true
case direction
when 2 then follower.move_down
when 4 then follower.move_left
when 6 then follower.move_right
when 8 then follower.move_up
end
follower.through=oldThrough
end
end
# Same map only
def jumpFancy(follower,direction,leader)
deltaX=(direction == 6 ? 2 : (direction == 4 ? -2 : 0))
deltaY=(direction == 2 ? 2 : (direction == 8 ? -2 : 0))
halfDeltaX=(direction == 6 ? 1 : (direction == 4 ? -1 : 0))
halfDeltaY=(direction == 2 ? 1 : (direction == 8 ? -1 : 0))
middle=pbTestPass(follower,follower.x+halfDeltaX,follower.y+halfDeltaY,0)
ending=pbTestPass(follower,follower.x+deltaX, follower.y+deltaY, 0)
if middle
moveFancy(follower,direction)
moveFancy(follower,direction)
elsif ending
if pbTestPass(follower,follower.x,follower.y,0)
if leader.jumping?
follower.jump_speed_real = leader.jump_speed_real * Graphics.frame_rate / 40.0
else
follower.jump_speed_real = leader.move_speed_real * Graphics.frame_rate / 20.0
end
follower.jump(deltaX,deltaY)
else
moveThrough(follower,direction)
moveThrough(follower,direction)
end
end
end
def pbFancyMoveTo(follower,newX,newY,leader)
if follower.x-newX==-1 && follower.y==newY
moveFancy(follower,6)
elsif follower.x-newX==1 && follower.y==newY
moveFancy(follower,4)
elsif follower.y-newY==-1 && follower.x==newX
moveFancy(follower,2)
elsif follower.y-newY==1 && follower.x==newX
moveFancy(follower,8)
elsif follower.x-newX==-2 && follower.y==newY
jumpFancy(follower,6,leader)
elsif follower.x-newX==2 && follower.y==newY
jumpFancy(follower,4,leader)
elsif follower.y-newY==-2 && follower.x==newX
jumpFancy(follower,2,leader)
elsif follower.y-newY==2 && follower.x==newX
jumpFancy(follower,8,leader)
elsif follower.x!=newX || follower.y!=newY
follower.moveto(newX,newY)
end
end
class DependentEvents
attr_reader :lastUpdate
attr_reader :realEvents
attr_writer :follows_player
def createEvent(eventData)
rpgEvent = RPG::Event.new(eventData[3],eventData[4])
rpgEvent.id = eventData[1]
if eventData[9]
# Must setup common event list here and now
commonEvent = Game_CommonEvent.new(eventData[9])
rpgEvent.pages[0].list = commonEvent.list
end
newEvent = Game_Event.new(eventData[0],rpgEvent,$MapFactory.getMap(eventData[2]))
newEvent.character_name = eventData[6]
newEvent.character_hue = eventData[7]
case eventData[5] # direction
when 2 then newEvent.turn_down
when 4 then newEvent.turn_left
when 6 then newEvent.turn_right
when 8 then newEvent.turn_up
end
return newEvent
end
def initialize
# Original map, Event ID, Current map, X, Y, Direction
events=$PokemonGlobal.dependentEvents
@realEvents=[]
@lastUpdate=-1
for event in events
@realEvents.push(createEvent(event))
end
@follows_player=true
end
def pbEnsureEvent(event, newMapID)
events=$PokemonGlobal.dependentEvents
for i in 0...events.length
# Check original map ID and original event ID
if events[i][0]==event.map_id && events[i][1]==event.id
# Change current map ID
events[i][2]=newMapID
newEvent=createEvent(events[i])
# Replace event
@realEvents[i]=newEvent
@lastUpdate+=1
return i
end
end
return -1
end
def pbFollowEventAcrossMaps(leader,follower,instant=false,leaderIsTrueLeader=true)
d=leader.direction
areConnected=$MapFactory.areConnected?(leader.map.map_id,follower.map.map_id)
# Get the rear facing tile of leader
facingDirection=10-d
if !leaderIsTrueLeader && areConnected
relativePos=$MapFactory.getThisAndOtherEventRelativePos(leader,follower)
# Assumes leader and follower are both 1x1 tile in size
if (relativePos[1]==0 && relativePos[0]==2) # 2 spaces to the right of leader
facingDirection=6
elsif (relativePos[1]==0 && relativePos[0]==-2) # 2 spaces to the left of leader
facingDirection=4
elsif relativePos[1]==-2 && relativePos[0]==0 # 2 spaces above leader
facingDirection=8
elsif relativePos[1]==2 && relativePos[0]==0 # 2 spaces below leader
facingDirection=2
end
end
facings=[facingDirection] # Get facing from behind
# facings.push([0,0,4,0,8,0,2,0,6][d]) # Get right facing
# facings.push([0,0,6,0,2,0,8,0,4][d]) # Get left facing
if !leaderIsTrueLeader
facings.push(d) # Get forward facing
end
mapTile=nil
if areConnected
bestRelativePos=-1
oldthrough=follower.through
follower.through=false
for i in 0...facings.length
facing=facings[i]
tile=$MapFactory.getFacingTile(facing,leader)
# Assumes leader is 1x1 tile in size
passable=tile && $MapFactory.isPassableStrict?(tile[0],tile[1],tile[2],follower)
if i==0 && !passable && tile &&
$MapFactory.getTerrainTag(tile[0],tile[1],tile[2]).ledge
# If the tile isn't passable and the tile is a ledge,
# get tile from further behind
tile=$MapFactory.getFacingTileFromPos(tile[0],tile[1],tile[2],facing)
passable=tile && $MapFactory.isPassableStrict?(tile[0],tile[1],tile[2],follower)
end
if passable
relativePos=$MapFactory.getThisAndOtherPosRelativePos(
follower,tile[0],tile[1],tile[2])
# Assumes follower is 1x1 tile in size
distance=Math.sqrt(relativePos[0]*relativePos[0]+relativePos[1]*relativePos[1])
if bestRelativePos==-1 || bestRelativePos>distance
bestRelativePos=distance
mapTile=tile
end
if i==0 && distance<=1 # Prefer behind if tile can move up to 1 space
break
end
end
end
follower.through=oldthrough
else
tile=$MapFactory.getFacingTile(facings[0],leader)
# Assumes leader is 1x1 tile in size
passable=tile && $MapFactory.isPassableStrict?(tile[0],tile[1],tile[2],follower)
mapTile=passable ? mapTile : nil
end
if mapTile && follower.map.map_id==mapTile[0]
return if !@follows_player
# Follower is on same map
newX=mapTile[1]
newY=mapTile[2]
deltaX=(d == 6 ? -1 : d == 4 ? 1 : 0)
deltaY=(d == 2 ? -1 : d == 8 ? 1 : 0)
posX = newX + deltaX
posY = newY + deltaY
# if !@follows_player
# posX=follower.original_x
# posY=follower.original_y
#
# end
follower.move_speed=leader.move_speed # sync movespeed
if (follower.x-newX==-1 && follower.y==newY) ||
(follower.x-newX==1 && follower.y==newY) ||
(follower.y-newY==-1 && follower.x==newX) ||
(follower.y-newY==1 && follower.x==newX)
if instant
follower.moveto(newX,newY)
else
pbFancyMoveTo(follower,newX,newY,leader)
end
elsif (follower.x-newX==-2 && follower.y==newY) ||
(follower.x-newX==2 && follower.y==newY) ||
(follower.y-newY==-2 && follower.x==newX) ||
(follower.y-newY==2 && follower.x==newX)
if instant
follower.moveto(newX,newY)
else
pbFancyMoveTo(follower,newX,newY,leader)
end
elsif follower.x!=posX || follower.y!=posY
if instant
follower.moveto(newX,newY)
else
pbFancyMoveTo(follower,posX,posY,leader)
pbFancyMoveTo(follower,newX,newY,leader)
end
end
else
if !mapTile
# Make current position into leader's position
mapTile=[leader.map.map_id,leader.x,leader.y]
end
if follower.map.map_id==mapTile[0]
# Follower is on same map as leader
follower.moveto(leader.x,leader.y)
else
# Follower will move to different map
events=$PokemonGlobal.dependentEvents
eventIndex=pbEnsureEvent(follower,mapTile[0])
if eventIndex>=0
newFollower=@realEvents[eventIndex]
newEventData=events[eventIndex]
newFollower.moveto(mapTile[1],mapTile[2])
newEventData[3]=mapTile[1]
newEventData[4]=mapTile[2]
end
end
end
end
def debugEcho
self.eachEvent { |e,d|
echoln d
echoln [e.map_id,e.map.map_id,e.id]
}
end
def pbMapChangeMoveDependentEvents
events=$PokemonGlobal.dependentEvents
updateDependentEvents
leader=$game_player
for i in 0...events.length
event=@realEvents[i]
pbFollowEventAcrossMaps(leader,event,true,i==0)
# Update X and Y for this event
events[i][3]=event.x
events[i][4]=event.y
events[i][5]=event.direction
# Set leader to this event
leader=event
end
end
def pbMoveDependentEvents
events=$PokemonGlobal.dependentEvents
updateDependentEvents
leader=$game_player
for i in 0...events.length
event=@realEvents[i]
if !@follows_player
pbRemoveDependencies if leader.map.map_id != event.map.map_id
pbFollowEventAcrossMaps(leader,event,false,i==0)
events[i][3]=event.original_x
events[i][4]=event.original_y
else
pbFollowEventAcrossMaps(leader,event,false,i==0)
# Update X and Y for this event
events[i][3]=event.x
events[i][4]=event.y
events[i][5]=event.direction
# Set leader to this event
leader=event
end
end
end
def pbTurnDependentEvents
events=$PokemonGlobal.dependentEvents
updateDependentEvents
leader=$game_player
for i in 0...events.length
event=@realEvents[i]
pbTurnTowardEvent(event,leader)
# Update direction for this event
events[i][5]=event.direction
# Set leader to this event
leader=event
end
end
def eachEvent
events=$PokemonGlobal.dependentEvents
for i in 0...events.length
yield @realEvents[i],events[i]
end
end
def updateDependentEvents
events=$PokemonGlobal.dependentEvents
return if events.length==0
for i in 0...events.length
break if !@follows_player
event=@realEvents[i]
next if !@realEvents[i]
event.transparent=$game_player.transparent
if event.jumping? || event.moving? ||
!($game_player.jumping? || $game_player.moving?)
event.update
elsif !event.starting
event.set_starting
event.update
event.clear_starting
end
events[i][3]=event.x
events[i][4]=event.y
events[i][5]=event.direction
end
# Check event triggers
#
if Input.trigger?(Input::USE) && !$game_temp.in_menu && !$game_temp.in_battle &&
!$game_player.move_route_forcing && !$game_temp.message_window_showing &&
!pbMapInterpreterRunning?
# Get position of tile facing the player
facingTile=$MapFactory.getFacingTile()
# Assumes player is 1x1 tile in size
self.eachEvent { |e,d|
next if !d[9]
if e.at_coordinate?($game_player.x, $game_player.y)
# On same position
if !e.jumping? && (!e.respond_to?("over_trigger") || e.over_trigger?)
if e.list.size>1
# Start event
$game_map.refresh if $game_map.need_refresh
e.lock
pbMapInterpreter.setup(e.list,e.id,e.map.map_id)
end
end
elsif facingTile && e.map.map_id==facingTile[0] &&
e.at_coordinate?(facingTile[1], facingTile[2])
# On facing tile
if !e.jumping? && (!e.respond_to?("over_trigger") || !e.over_trigger?)
if e.list.size>1
# Start event
$game_map.refresh if $game_map.need_refresh
e.lock
pbMapInterpreter.setup(e.list,e.id,e.map.map_id)
end
end
end
}
end
end
def removeEvent(event)
events=$PokemonGlobal.dependentEvents
mapid=$game_map.map_id
for i in 0...events.length
if events[i][2]==mapid && # Refer to current map
events[i][0]==event.map_id && # Event's map ID is original ID
events[i][1]==event.id
events[i]=nil
@realEvents[i]=nil
@lastUpdate+=1
end
end
events.compact!
@realEvents.compact!
end
def getEventByName(name)
events=$PokemonGlobal.dependentEvents
for i in 0...events.length
if events[i] && events[i][8]==name # Arbitrary name given to dependent event
return @realEvents[i]
end
end
return nil
end
def removeAllEvents
events=$PokemonGlobal.dependentEvents
events.clear
@realEvents.clear
@lastUpdate+=1
end
def removeEventByName(name)
events=$PokemonGlobal.dependentEvents
for i in 0...events.length
if events[i] && events[i][8]==name # Arbitrary name given to dependent event
events[i]=nil
@realEvents[i]=nil
@lastUpdate+=1
end
end
events.compact!
@realEvents.compact!
end
def addEvent(event,eventName=nil,commonEvent=nil)
return if !event
events=$PokemonGlobal.dependentEvents
for i in 0...events.length
if events[i] && events[i][0]==$game_map.map_id && events[i][1]==event.id
# Already exists
return
end
end
# Original map ID, original event ID, current map ID,
# event X, event Y, event direction,
# event's filename,
# event's hue, event's name, common event ID
eventData=[
$game_map.map_id,event.id,$game_map.map_id,
event.x,event.y,event.direction,
event.character_name.clone,
event.character_hue,eventName,commonEvent
]
newEvent=createEvent(eventData)
events.push(eventData)
@realEvents.push(newEvent)
@lastUpdate+=1
event.erase
end
end
class DependentEventSprites
def initialize(viewport,map)
@disposed=false
@sprites=[]
@map=map
@viewport=viewport
refresh
@lastUpdate=nil
end
def refresh
for sprite in @sprites
sprite.dispose
end
@sprites.clear
$PokemonTemp.dependentEvents.eachEvent { |event,data|
if data[0]==@map.map_id # Check original map
@map.events[data[1]].erase
end
if data[2]==@map.map_id # Check current map
@sprites.push(Sprite_Character.new(@viewport,event))
end
}
end
def update
if $PokemonTemp.dependentEvents.lastUpdate!=@lastUpdate
refresh
@lastUpdate=$PokemonTemp.dependentEvents.lastUpdate
end
for sprite in @sprites
sprite.update
end
end
def dispose
return if @disposed
for sprite in @sprites
sprite.dispose
end
@sprites.clear
@disposed=true
end
def disposed?
@disposed
end
end
Events.onSpritesetCreate += proc { |_sender,e|
spriteset = e[0] # Spriteset being created
viewport = e[1] # Viewport used for tilemap and characters
map = spriteset.map # Map associated with the spriteset (not necessarily the current map)
spriteset.addUserSprite(DependentEventSprites.new(viewport,map))
}
Events.onMapSceneChange += proc { |_sender,e|
mapChanged = e[1]
if mapChanged
$PokemonTemp.dependentEvents.pbMapChangeMoveDependentEvents
end
}

View File

@@ -0,0 +1,921 @@
#===============================================================================
#
#===============================================================================
class CustomTilemapAutotiles
attr_accessor :changed
def initialize
@changed = true
@tiles = [nil,nil,nil,nil,nil,nil,nil]
end
def [](i)
return @tiles[i]
end
def []=(i,value)
@tiles[i] = value
@changed = true
end
end
#Console::setup_console
class CustomTilemapSprite < Sprite
end
#===============================================================================
#
#===============================================================================
class CustomTilemap
attr_reader :tileset
attr_reader :autotiles
attr_reader :map_data
attr_reader :flash_data
attr_reader :priorities
attr_reader :terrain_tags
attr_reader :visible
attr_reader :viewport
attr_reader :graphicsWidth
attr_reader :graphicsHeight
attr_reader :ox
attr_reader :oy
attr_accessor :tone
attr_accessor :color
Autotiles = [
[ [27, 28, 33, 34], [ 5, 28, 33, 34], [27, 6, 33, 34], [ 5, 6, 33, 34],
[27, 28, 33, 12], [ 5, 28, 33, 12], [27, 6, 33, 12], [ 5, 6, 33, 12] ],
[ [27, 28, 11, 34], [ 5, 28, 11, 34], [27, 6, 11, 34], [ 5, 6, 11, 34],
[27, 28, 11, 12], [ 5, 28, 11, 12], [27, 6, 11, 12], [ 5, 6, 11, 12] ],
[ [25, 26, 31, 32], [25, 6, 31, 32], [25, 26, 31, 12], [25, 6, 31, 12],
[15, 16, 21, 22], [15, 16, 21, 12], [15, 16, 11, 22], [15, 16, 11, 12] ],
[ [29, 30, 35, 36], [29, 30, 11, 36], [ 5, 30, 35, 36], [ 5, 30, 11, 36],
[39, 40, 45, 46], [ 5, 40, 45, 46], [39, 6, 45, 46], [ 5, 6, 45, 46] ],
[ [25, 30, 31, 36], [15, 16, 45, 46], [13, 14, 19, 20], [13, 14, 19, 12],
[17, 18, 23, 24], [17, 18, 11, 24], [41, 42, 47, 48], [ 5, 42, 47, 48] ],
[ [37, 38, 43, 44], [37, 6, 43, 44], [13, 18, 19, 24], [13, 14, 43, 44],
[37, 42, 43, 48], [17, 18, 47, 48], [13, 18, 43, 48], [ 1, 2, 7, 8] ]
]
Animated_Autotiles_Frames = 5*Graphics.frame_rate/20 # Frequency of updating animated autotiles
FlashOpacity = [100,90,80,70,80,90]
def initialize(viewport)
@tileset = nil # Refers to Map Tileset Name
@autotiles = CustomTilemapAutotiles.new
@map_data = nil # Refers to 3D Array Of Tile Settings
@flash_data = nil # Refers to 3D Array of Tile Flashdata
@priorities = nil # Refers to Tileset Priorities
@terrain_tags = nil # Refers to Tileset Terrain Tags
@visible = true # Refers to Tileset Visibleness
@ox = 0 # Bitmap Offsets
@oy = 0 # Bitmap Offsets
@plane = false
@haveGraphicsWH = (Graphics.width!=nil rescue false)
if @haveGraphicsWH
@graphicsWidth = Graphics.width
@graphicsHeight = Graphics.height
else
@graphicsWidth = 640
@graphicsHeight = 480
end
@tileWidth = Game_Map::TILE_WIDTH rescue 32
@tileHeight = Game_Map::TILE_HEIGHT rescue 32
@tileSrcWidth = 32
@tileSrcHeight = 32
@diffsizes = (@tileWidth!=@tileSrcWidth) || (@tileHeight!=@tileSrcHeight)
@tone = Tone.new(0,0,0,0)
@oldtone = Tone.new(0,0,0,0)
@color = Color.new(0,0,0,0)
@oldcolor = Color.new(0,0,0,0)
@selfviewport = Viewport.new(0,0,graphicsWidth,graphicsHeight)
@viewport = (viewport) ? viewport : @selfviewport
@tiles = []
@autotileInfo = []
@regularTileInfo = []
@oldOx = 0
@oldOy = 0
@oldViewportOx = 0
@oldViewportOy = 0
@layer0 = CustomTilemapSprite.new(viewport)
@layer0.visible = true
@nowshown = false
@layer0.bitmap = Bitmap.new([graphicsWidth+320,1].max,[graphicsHeight+320,1].max)
@layer0.z = 0
@layer0.ox = 0
@layer0.oy = 0
@oxLayer0 = 0
@oyLayer0 = 0
@flash = nil
@oxFlash = 0
@oyFlash = 0
@priotiles = {}
@priotilesfast = []
@prioautotiles = {}
@autosprites = []
@framecount = [0,0,0,0,0,0,0,0] # For autotiles
@tilesetChanged = true
@flashChanged = false
@firsttime = true
@disposed = false
@usedsprites = false
@layer0clip = true
@firsttimeflash = true
@fullyrefreshed = false
@fullyrefreshedautos = false
end
def dispose
return if disposed?
@help.dispose if @help
@help = nil
i = 0; len = @autotileInfo.length
while i<len
if @autotileInfo[i]
@autotileInfo[i].dispose
@autotileInfo[i] = nil
end
i += 1
end
i = 0; len = @regularTileInfo.length
while i<len
if @regularTileInfo[i]
@regularTileInfo[i].dispose
@regularTileInfo[i] = nil
end
i += 1
end
i = 0; len = @tiles.length
while i<len
@tiles[i].dispose
@tiles[i] = nil
i += 2
end
i = 0; len = @autosprites.length
while i<len
@autosprites[i].dispose
@autosprites[i] = nil
i += 2
end
if @layer0
@layer0.bitmap.dispose if !@layer0.disposed?
@layer0.bitmap = nil if !@layer0.disposed?
@layer0.dispose
@layer0 = nil
end
if @flash
@flash.bitmap.dispose if !@flash.disposed?
@flash.bitmap = nil if !@flash.disposed?
@flash.dispose
@flash = nil
end
for i in 0...7
self.autotiles[i] = nil
end
@tiles.clear
@autosprites.clear
@autotileInfo.clear
@regularTileInfo.clear
@tilemap = nil
@tileset = nil
@priorities = nil
@selfviewport.dispose
@selfviewport = nil
@disposed = true
end
def disposed?
return @disposed
end
def flash_data=(value)
@flash_data = value
@flashChanged = true
end
def map_data=(value)
@map_data = value
@tilesetChanged = true
end
def priorities=(value)
@priorities = value
@tilesetChanged = true
end
def terrain_tags=(value)
@terrain_tags = value
@tilesetChanged = true
end
def tileset=(value)
@tileset = value
@tilesetChanged = true
end
def getResizeFactor
return $ResizeFactor || 1.0
end
def ox=(val)
rf = getResizeFactor
if rf!=1.0
val = (val*rf).to_i
val = (val/rf).to_i
end
wasshown = self.shown?
@ox = val.floor
@nowshown = (!wasshown && self.shown?)
end
def oy=(val)
rf = getResizeFactor
if rf!=1.0
val = (val*rf).to_i
val = (val/rf).to_i
end
wasshown = self.shown?
@oy = val.floor
@nowshown = (!wasshown && self.shown?)
end
def visible=(val)
wasshown = @visible
@visible = val
@nowshown = (!wasshown && val)
end
def shown?
return false if !@visible
xsize = @map_data.xsize
xStart = @ox/@tileWidth - 1
xStart = 0 if xStart<0
xStart = xsize-1 if xStart>=xsize
xEnd = (@ox+@viewport.rect.width)/@tileWidth + 1
xEnd = 0 if xEnd<0
xEnd = xsize-1 if xEnd>=xsize
return false if xStart>=xEnd
ysize = @map_data.ysize
yStart = @oy/@tileHeight - 1
yStart = 0 if yStart<0
yStart = ysize-1 if yStart>=ysize
yEnd = (@oy+@viewport.rect.height)/@tileHeight + 1
yEnd = 0 if yEnd<0
yEnd = ysize-1 if yEnd>=ysize
return false if yStart>=yEnd
return true
end
def autotileNumFrames(id)
autotile = @autotiles[id/48-1]
return 0 if !autotile || autotile.disposed?
frames = 1
if autotile.height==@tileHeight
frames = autotile.width/@tileWidth
else
frames = autotile.width/(3*@tileWidth)
end
return frames
end
def autotileFrame(id)
autotile = @autotiles[id/48-1]
return -1 if !autotile || autotile.disposed?
frames = 1
if autotile.height==@tileHeight
frames = autotile.width/@tileWidth
else
frames = autotile.width/(3*@tileWidth)
end
return (Graphics.frame_count/Animated_Autotiles_Frames)%frames
end
def repaintAutotiles
for i in 0...@autotileInfo.length
next if !@autotileInfo[i]
frame = autotileFrame(i)
@autotileInfo[i].clear
bltAutotile(@autotileInfo[i],0,0,i,frame)
end
end
def bltAutotile(bitmap,x,y,id,frame)
return if frame<0
autotile = @autotiles[id/48-1]
return if !autotile || autotile.disposed?
if autotile.height==@tileSrcHeight
anim = frame*@tileSrcWidth
src_rect = Rect.new(anim,0,@tileSrcWidth,@tileSrcHeight)
if @diffsizes
bitmap.stretch_blt(Rect.new(x,y,@tileWidth,@tileHeight),autotile,src_rect)
else
bitmap.blt(x,y,autotile,src_rect)
end
else
anim = frame*3*@tileSrcWidth
id %= 48
tiles = Autotiles[id>>3][id&7]
src = Rect.new(0,0,0,0)
halfTileWidth = @tileWidth>>1
halfTileHeight = @tileHeight>>1
halfTileSrcWidth = @tileSrcWidth>>1
halfTileSrcHeight = @tileSrcHeight>>1
for i in 0...4
tile_position = tiles[i] - 1
src.set( (tile_position % 6)*halfTileSrcWidth + anim,
(tile_position / 6)*halfTileSrcHeight, halfTileSrcWidth, halfTileSrcHeight)
if @diffsizes
bitmap.stretch_blt(
Rect.new(i%2*halfTileWidth+x,i/2*halfTileHeight+y,halfTileWidth,halfTileHeight),
autotile,src)
else
bitmap.blt(i%2*halfTileWidth+x,i/2*halfTileHeight+y, autotile, src)
end
end
end
end
def getAutotile(sprite,id)
frames = @framecount[id/48-1]
if frames<=1
anim = 0
else
anim = (Graphics.frame_count/Animated_Autotiles_Frames)%frames
end
return if anim<0
bitmap = @autotileInfo[id]
if !bitmap
bitmap = Bitmap.new(@tileWidth,@tileHeight)
bltAutotile(bitmap,0,0,id,anim)
@autotileInfo[id] = bitmap
end
sprite.bitmap = bitmap if sprite.bitmap!=bitmap
end
def getRegularTile(sprite,id)
if @diffsizes
bitmap = @regularTileInfo[id]
if !bitmap
bitmap = Bitmap.new(@tileWidth,@tileHeight)
rect = Rect.new(((id - 384)&7)*@tileSrcWidth,((id - 384)>>3)*@tileSrcHeight,
@tileSrcWidth,@tileSrcHeight)
bitmap.stretch_blt(Rect.new(0,0,@tileWidth,@tileHeight),@tileset,rect)
@regularTileInfo[id] = bitmap
end
sprite.bitmap = bitmap if sprite.bitmap!=bitmap
else
sprite.bitmap = @tileset if sprite.bitmap!=@tileset
sprite.src_rect.set(((id - 384)&7)*@tileSrcWidth,((id - 384)>>3)*@tileSrcHeight,
@tileSrcWidth,@tileSrcHeight)
end
end
def addTile(tiles,count,xpos,ypos,id)
terrain = @terrain_tags[id]
priority = @priorities[id]
if id >= 384 # Tileset tile
if count>=tiles.length
sprite = CustomTilemapSprite.new(@viewport)
tiles.push(sprite,0)
else
sprite = tiles[count]
tiles[count+1] = 0
end
sprite.visible = @visible
sprite.x = xpos
sprite.y = ypos
sprite.tone = @tone
sprite.color = @color
getRegularTile(sprite,id)
else # Autotile
if count>=tiles.length
sprite = CustomTilemapSprite.new(@viewport)
tiles.push(sprite,1)
else
sprite = tiles[count]
tiles[count+1] = 1
end
sprite.visible = @visible
sprite.x = xpos
sprite.y = ypos
sprite.tone = @tone
sprite.color = @color
getAutotile(sprite,id)
end
if PBTerrain.hasReflections?(terrain)
spriteZ = -100
elsif $PokemonGlobal.bridge>0 && PBTerrain.isBridge?(terrain)
spriteZ = 1
else
spriteZ = (priority==0) ? 0 : ypos+priority*32+32
end
sprite.z = spriteZ
count += 2
return count
end
def refresh_flash
if @flash_data && !@flash
@flash = CustomTilemapSprite.new(viewport)
@flash.visible = true
@flash.z = 1
@flash.tone = tone
@flash.color = color
@flash.blend_type = 1
@flash.bitmap = Bitmap.new([graphicsWidth*2,1].max,[graphicsHeight*2,1].max)
@firsttimeflash = true
elsif !@flash_data && @flash
@flash.bitmap.dispose if @flash.bitmap
@flash.dispose
@flash = nil
@firsttimeflash = false
end
end
def refreshFlashSprite
return if !@flash || @flash_data.nil?
ptX = @ox-@oxFlash
ptY = @oy-@oyFlash
if !@firsttimeflash && !@usedsprites &&
ptX>=0 && ptX+@viewport.rect.width<=@flash.bitmap.width &&
ptY>=0 && ptY+@viewport.rect.height<=@flash.bitmap.height
@flash.ox = 0
@flash.oy = 0
@flash.src_rect.set(ptX.round,ptY.round,
@viewport.rect.width,@viewport.rect.height)
return
end
width = @flash.bitmap.width
height = @flash.bitmap.height
bitmap = @flash.bitmap
ysize = @map_data.ysize
xsize = @map_data.xsize
@firsttimeflash = false
@oxFlash = @ox-(width>>2)
@oyFlash = @oy-(height>>2)
@flash.ox = 0
@flash.oy = 0
@flash.src_rect.set(width>>2,height>>2,
@viewport.rect.width,@viewport.rect.height)
@flash.bitmap.clear
@oxFlash = @oxFlash.floor
@oyFlash = @oyFlash.floor
xStart = @oxFlash/@tileWidth
xStart = 0 if xStart<0
yStart = @oyFlash/@tileHeight
yStart = 0 if yStart<0
xEnd = xStart+(width/@tileWidth)+1
yEnd = yStart+(height/@tileHeight)+1
xEnd = xsize if xEnd>=xsize
yEnd = ysize if yEnd>=ysize
if xStart<xEnd && yStart<yEnd
yrange = yStart...yEnd
xrange = xStart...xEnd
tmpcolor = Color.new(0,0,0,0)
for y in yrange
ypos = (y*@tileHeight)-@oyFlash
for x in xrange
xpos = (x*@tileWidth)-@oxFlash
id = @flash_data[x, y, 0]
r = (id>>8)&15
g = (id>>4)&15
b = (id)&15
tmpcolor.set(r<<4,g<<4,b<<4)
bitmap.fill_rect(xpos,ypos,@tileWidth,@tileHeight,tmpcolor)
end
end
end
end
def refresh_tileset
i = 0
len = @regularTileInfo.length
while i < len
if @regularTileInfo[i]
@regularTileInfo[i].dispose
@regularTileInfo[i] = nil
end
i += 1
end
@regularTileInfo.clear
@priotiles.clear
ysize = @map_data.ysize
xsize = @map_data.xsize
zsize = @map_data.zsize
if xsize > 100 || ysize > 100
@fullyrefreshed = false
else
for z in 0...zsize
for y in 0...ysize
for x in 0...xsize
id = @map_data[x, y, z]
next if id == 0
next if @priorities[id] == 0 && !PBTerrain.hasReflections?(@terrain_tags[id])
@priotiles[[x, y]] = [] if !@priotiles[[x, y]]
@priotiles[[x, y]].push([z, id])
end
end
end
@fullyrefreshed = true
end
end
def refresh_autotiles
i = 0
len = @autotileInfo.length
while i < len
if @autotileInfo[i]
@autotileInfo[i].dispose
@autotileInfo[i] = nil
end
i += 1
end
i = 0
len = @autosprites.length
while i < len
if @autosprites[i]
@autosprites[i].dispose
@autosprites[i] = nil
end
i += 2
end
@autosprites.clear
@autotileInfo.clear
@prioautotiles.clear
@priorect = nil
@priorectautos = nil
hasanimated = false
for i in 0...7
numframes = autotileNumFrames(48 * (i + 1))
hasanimated = true if numframes >= 2
@framecount[i] = numframes
end
if hasanimated
ysize = @map_data.ysize
xsize = @map_data.xsize
zsize = @map_data.zsize
if xsize > 100 || ysize > 100
@fullyrefreshedautos = false
else
for y in 0...ysize
for x in 0...xsize
for z in 0...zsize
id = @map_data[x, y, z]
next if id == 0 || id >= 384 # Skip non-autotiles
next if @priorities[id] != 0 || PBTerrain.hasReflections?(@terrain_tags[id])
next if @framecount[id / 48 - 1] < 2
@prioautotiles[[x, y]] = true
break
end
end
end
@fullyrefreshedautos = true
end
else
@fullyrefreshedautos = true
end
end
def refreshLayer0(autotiles = false)
return true if autotiles && !shown?
ptX = @ox - @oxLayer0
ptY = @oy - @oyLayer0
if !autotiles && !@firsttime && !@usedsprites &&
ptX >= 0 && ptX + @viewport.rect.width <= @layer0.bitmap.width &&
ptY >= 0 && ptY + @viewport.rect.height <= @layer0.bitmap.height
if @layer0clip && @viewport.ox == 0 && @viewport.oy == 0
@layer0.ox = 0
@layer0.oy = 0
@layer0.src_rect.set(ptX.round, ptY.round, @viewport.rect.width, @viewport.rect.height)
else
@layer0.ox = ptX.round
@layer0.oy = ptY.round
@layer0.src_rect.set(0, 0, @layer0.bitmap.width, @layer0.bitmap.height)
end
return true
end
width = @layer0.bitmap.width
height = @layer0.bitmap.height
bitmap = @layer0.bitmap
ysize = @map_data.ysize
xsize = @map_data.xsize
zsize = @map_data.zsize
twidth = @tileWidth
theight = @tileHeight
mapdata = @map_data
if autotiles
return true if @fullyrefreshedautos && @prioautotiles.length == 0
xStart = @oxLayer0 / twidth
xStart = 0 if xStart < 0
yStart = @oyLayer0 / theight
yStart = 0 if yStart < 0
xEnd = xStart + (width / twidth) + 1
xEnd = xsize if xEnd > xsize
yEnd = yStart + (height / theight) + 1
yEnd = ysize if yEnd > ysize
return true if xStart >= xEnd || yStart >= yEnd
trans = Color.new(0, 0, 0, 0)
temprect = Rect.new(0, 0, 0, 0)
tilerect = Rect.new(0, 0, twidth, theight)
zrange = 0...zsize
overallcount = 0
count = 0
if !@fullyrefreshedautos
for y in yStart..yEnd
for x in xStart..xEnd
for z in zrange
id = mapdata[x, y, z]
next if !id || id < 48 || id >= 384 # Skip non-autotiles
prioid = @priorities[id]
next if prioid != 0 || PBTerrain.hasReflections?(@terrain_tags[id])
fcount = @framecount[id / 48 - 1]
next if !fcount || fcount < 2
overallcount += 1
xpos = (x * twidth) - @oxLayer0
ypos = (y * theight) - @oyLayer0
bitmap.fill_rect(xpos, ypos, twidth, theight, trans) if overallcount <= 2000
break
end
for z in zrange
id = mapdata[x, y, z]
next if !id || id < 48
prioid = @priorities[id]
next if prioid != 0 || PBTerrain.hasReflections?(@terrain_tags[id])
if overallcount > 2000
xpos = (x * twidth) - @oxLayer0
ypos = (y * theight) - @oyLayer0
count = addTile(@autosprites, count, xpos, ypos, id)
elsif id >= 384 # Tileset tiles
temprect.set(((id - 384) & 7) * @tileSrcWidth,
((id - 384) >> 3) * @tileSrcHeight,
@tileSrcWidth, @tileSrcHeight)
xpos = (x * twidth) - @oxLayer0
ypos = (y * theight) - @oyLayer0
if @diffsizes
bitmap.stretch_blt(Rect.new(xpos, ypos, twidth, theight), @tileset, temprect)
else
bitmap.blt(xpos, ypos, @tileset, temprect)
end
else # Autotiles
tilebitmap = @autotileInfo[id]
if !tilebitmap
anim = autotileFrame(id)
next if anim < 0
tilebitmap = Bitmap.new(twidth, theight)
bltAutotile(tilebitmap, 0, 0, id, anim)
@autotileInfo[id] = tilebitmap
end
xpos = (x * twidth) - @oxLayer0
ypos = (y * theight) - @oyLayer0
bitmap.blt(xpos, ypos, tilebitmap, tilerect)
end
end
end
end
Graphics.frame_reset
else
if !@priorect || !@priorectautos ||
@priorect[0] != xStart || @priorect[1] != yStart ||
@priorect[2] != xEnd || @priorect[3] != yEnd
@priorect = [xStart, yStart, xEnd, yEnd]
@priorectautos = []
for y in yStart..yEnd
for x in xStart..xEnd
@priorectautos.push([x, y]) if @prioautotiles[[x, y]]
end
end
end
for tile in @priorectautos
x = tile[0]
y = tile[1]
overallcount += 1
xpos = (x * twidth) - @oxLayer0
ypos = (y * theight) - @oyLayer0
bitmap.fill_rect(xpos, ypos, twidth, theight, trans)
z = 0
while z < zsize
id = mapdata[x, y, z]
z += 1
next if !id || id < 48
prioid = @priorities[id]
next if prioid != 0 || PBTerrain.hasReflections?(@terrain_tags[id])
if id >= 384 # Tileset tiles
temprect.set(((id - 384) & 7) * @tileSrcWidth,
((id - 384) >> 3) * @tileSrcHeight,
@tileSrcWidth, @tileSrcHeight)
if @diffsizes
bitmap.stretch_blt(Rect.new(xpos, ypos, twidth, theight), @tileset, temprect)
else
bitmap.blt(xpos, ypos, @tileset, temprect)
end
else # Autotiles
tilebitmap = @autotileInfo[id]
if !tilebitmap
anim = autotileFrame(id)
next if anim < 0
tilebitmap = Bitmap.new(twidth, theight)
bltAutotile(tilebitmap, 0, 0, id, anim)
@autotileInfo[id] = tilebitmap
end
bitmap.blt(xpos, ypos, tilebitmap, tilerect)
end
end
end
Graphics.frame_reset if overallcount > 500
end
@usedsprites = false
return true
end
return false if @usedsprites
@firsttime = false
@oxLayer0 = @ox - (width >> 2)
@oyLayer0 = @oy - (height >> 2)
if @layer0clip
@layer0.ox = 0
@layer0.oy = 0
@layer0.src_rect.set(width >> 2, height >> 2, @viewport.rect.width, @viewport.rect.height)
else
@layer0.ox = (width >> 2)
@layer0.oy = (height >> 2)
end
@layer0.bitmap.clear
@oxLayer0 = @oxLayer0.round
@oyLayer0 = @oyLayer0.round
xStart = @oxLayer0 / twidth
xStart = 0 if xStart < 0
yStart = @oyLayer0 / theight
yStart = 0 if yStart < 0
xEnd = xStart + (width / twidth) + 1
xEnd = xsize if xEnd >= xsize
yEnd = yStart + (height / theight) + 1
yEnd = ysize if yEnd >= ysize
if xStart < xEnd && yStart < yEnd
tmprect = Rect.new(0, 0, 0, 0)
yrange = yStart...yEnd
xrange = xStart...xEnd
for z in 0...zsize
for y in yrange
ypos = (y * theight) - @oyLayer0
for x in xrange
xpos = (x * twidth) - @oxLayer0
id = mapdata[x, y, z]
next if id == 0 || @priorities[id] != 0 || PBTerrain.hasReflections?(@terrain_tags[id])
if id >= 384 # Tileset tiles
tmprect.set(((id - 384) & 7) * @tileSrcWidth,
((id - 384) >> 3) * @tileSrcHeight,
@tileSrcWidth, @tileSrcHeight)
if @diffsizes
bitmap.stretch_blt(Rect.new(xpos, ypos, twidth, theight), @tileset, tmprect)
else
bitmap.blt(xpos, ypos, @tileset, tmprect)
end
else # Autotiles
frames = @framecount[id / 48 - 1]
if frames <= 1
frame = 0
else
frame = (Graphics.frame_count / Animated_Autotiles_Frames) % frames
end
bltAutotile(bitmap, xpos, ypos, id, frame)
end
end
end
end
Graphics.frame_reset
end
return true
end
def refresh(autotiles = false)
@oldOx = @ox
@oldOy = @oy
usesprites = false
if @layer0
@layer0.visible = @visible
usesprites = !refreshLayer0(autotiles)
return if autotiles && !usesprites
else
usesprites = true
end
refreshFlashSprite
xsize = @map_data.xsize
ysize = @map_data.ysize
minX = (@ox / @tileWidth) - 1
minX.clamp(0, xsize - 1)
maxX = ((@ox + @viewport.rect.width) / @tileWidth) + 1
maxX.clamp(0, xsize - 1)
minY = (@oy / @tileHeight) - 1
minY.clamp(0, ysize - 1)
maxY = ((@oy + @viewport.rect.height) / @tileHeight) + 1
maxY.clamp(0, ysize - 1)
count = 0
if minX < maxX && minY < maxY
@usedsprites = usesprites || @usedsprites
@layer0.visible = false if usesprites && @layer0
if !@priotilesrect || !@priotilesfast ||
@priotilesrect[0] != minX || @priotilesrect[1] != minY ||
@priotilesrect[2] != maxX || @priotilesrect[3] != maxY
@priotilesrect = [minX, minY, maxX, maxY]
@priotilesfast = []
if @fullyrefreshed
for y in minY..maxY
for x in minX..maxX
next if !@priotiles[[x, y]]
@priotiles[[x, y]].each { |tile| @priotilesfast.push([x, y, tile[0], tile[1]]) }
end
end
else
for z in 0...@map_data.zsize
for y in minY..maxY
for x in minX..maxX
id = @map_data[x, y, z]
next if id == 0
next if @priorities[id] == 0 && !PBTerrain.hasReflections?(@terrain_tags[id])
@priotilesfast.push([x, y, z, id])
end
end
end
end
end
for prio in @priotilesfast
xpos = (prio[0] * @tileWidth) - @ox
ypos = (prio[1] * @tileHeight) - @oy
count = addTile(@tiles, count, xpos, ypos, prio[3])
end
end
if count < @tiles.length
bigchange = (count <= (@tiles.length * 2 / 3)) && @tiles.length > 40
j = count
len = @tiles.length
while j < len
sprite = @tiles[j]
@tiles[j + 1] = -1
if bigchange
sprite.dispose
@tiles[j] = nil
@tiles[j + 1] = nil
elsif !@tiles[j].disposed?
sprite.visible = false if sprite.visible
end
j += 2
end
@tiles.compact! if bigchange
end
end
def update
if @haveGraphicsWH
@graphicsWidth = Graphics.width
@graphicsHeight = Graphics.height
end
# Update tone
if @oldtone != @tone
@layer0.tone = @tone
@flash.tone = @tone if @flash
for sprite in @autosprites
sprite.tone = @tone if sprite.is_a?(Sprite)
end
for sprite in @tiles
sprite.tone = @tone if sprite.is_a?(Sprite)
end
@oldtone = @tone.clone
end
# Update color
if @oldcolor != @color
@layer0.color = @color
@flash.color = @color if @flash
for sprite in @autosprites
sprite.color = @color if sprite.is_a?(Sprite)
end
for sprite in @tiles
sprite.color = @color if sprite.is_a?(Sprite)
end
@oldcolor = @color.clone
end
# Refresh anything that has changed
if @autotiles.changed
refresh_autotiles
repaintAutotiles
end
refresh_flash if @flashChanged
refresh_tileset if @tilesetChanged
@flash.opacity = FlashOpacity[(Graphics.frame_count / 2) % 6] if @flash
mustrefresh = (@oldOx != @ox || @oldOy != @oy || @tilesetChanged || @autotiles.changed)
if @viewport.ox != @oldViewportOx || @viewport.oy != @oldViewportOy
mustrefresh = true
@oldViewportOx = @viewport.ox
@oldViewportOy = @viewport.oy
end
refresh if mustrefresh
if (Graphics.frame_count % Animated_Autotiles_Frames) == 0 || @nowshown
repaintAutotiles
refresh(true)
end
@nowshown = false
@autotiles.changed = false
@tilesetChanged = false
end
end

View File

@@ -0,0 +1,58 @@
class Sprite_Picture
def initialize(viewport, picture)
@viewport = viewport
@picture = picture
@sprite = nil
update
end
def dispose
@sprite.dispose if @sprite
end
def update
@sprite.update if @sprite
# If picture file name is different from current one
if @picture_name != @picture.name
# Remember file name to instance variables
@picture_name = @picture.name
# If file name is not empty
if @picture_name != ""
# Get picture graphic
@sprite=IconSprite.new(0,0,@viewport) if !@sprite
@sprite.setBitmap("Graphics/Pictures/"+@picture_name)
end
end
# If file name is empty
if @picture_name == ""
# Set sprite to invisible
if @sprite
@sprite.dispose if @sprite
@sprite=nil
end
return
end
# Set sprite to visible
@sprite.visible = true
# Set transfer starting point
if @picture.origin == 0
@sprite.ox = 0
@sprite.oy = 0
else
@sprite.ox = @sprite.bitmap.width / 2
@sprite.oy = @sprite.bitmap.height / 2
end
# Set sprite coordinates
@sprite.x = @picture.x
@sprite.y = @picture.y
@sprite.z = @picture.number
# Set zoom rate, opacity level, and blend method
@sprite.zoom_x = @picture.zoom_x / 100.0
@sprite.zoom_y = @picture.zoom_y / 100.0
@sprite.opacity = @picture.opacity
@sprite.blend_type = @picture.blend_type
# Set rotation angle and color tone
@sprite.angle = @picture.angle
@sprite.tone = @picture.tone
end
end

View File

@@ -0,0 +1,45 @@
class Sprite_Timer
def initialize(viewport=nil)
@viewport=viewport
@timer=nil
@total_sec=nil
@disposed=false
end
def dispose
@timer.dispose if @timer
@timer=nil
@disposed=true
end
def disposed?
@disposed
end
def update
return if disposed?
if $game_system.timer_working
@timer.visible = true if @timer
if !@timer
@timer=Window_AdvancedTextPokemon.newWithSize("",Graphics.width-120,0,120,64)
@timer.width=@timer.borderX+96
@timer.x=Graphics.width-@timer.width
@timer.viewport=@viewport
@timer.z=99998
end
curtime=$game_system.timer / Graphics.frame_rate
curtime=0 if curtime<0
if curtime != @total_sec
# Calculate total number of seconds
@total_sec = curtime
# Make a string for displaying the timer
min = @total_sec / 60
sec = @total_sec % 60
@timer.text = _ISPRINTF("<ac>{1:02d}:{2:02d}", min, sec)
end
@timer.update
else
@timer.visible=false if @timer
end
end
end

View File

@@ -0,0 +1,270 @@
class BushBitmap
def initialize(bitmap, isTile, depth)
@bitmaps = []
@bitmap = bitmap
@isTile = isTile
@isBitmap = @bitmap.is_a?(Bitmap)
@depth = depth
@manual_refresh = false
end
def dispose
@bitmaps.each { |b| b.dispose if b }
end
def bitmap
thisBitmap = (@isBitmap) ? @bitmap : @bitmap.bitmap
current = (@isBitmap) ? 0 : @bitmap.currentIndex
if !@bitmaps[current]
if @isTile
@bitmaps[current] = pbBushDepthTile(thisBitmap, @depth)
else
@bitmaps[current] = pbBushDepthBitmap(thisBitmap, @depth)
end
end
return @bitmaps[current]
end
def pbBushDepthBitmap(bitmap, depth)
ret = Bitmap.new(bitmap.width, bitmap.height)
charheight = ret.height / 4
cy = charheight - depth - 2
for i in 0...4
y = i * charheight
if cy >= 0
ret.blt(0, y, bitmap, Rect.new(0, y, ret.width, cy))
ret.blt(0, y + cy, bitmap, Rect.new(0, y + cy, ret.width, 2), 170)
end
ret.blt(0, y + cy + 2, bitmap, Rect.new(0, y + cy + 2, ret.width, 2), 85) if cy + 2 >= 0
end
return ret
end
def pbBushDepthTile(bitmap, depth)
ret = Bitmap.new(bitmap.width, bitmap.height)
charheight = ret.height
cy = charheight - depth - 2
y = charheight
if cy >= 0
ret.blt(0, y, bitmap, Rect.new(0, y, ret.width, cy))
ret.blt(0, y + cy, bitmap, Rect.new(0, y + cy, ret.width, 2), 170)
end
ret.blt(0, y + cy + 2, bitmap, Rect.new(0, y + cy + 2, ret.width, 2), 85) if cy + 2 >= 0
return ret
end
end
def event_is_trainer(event)
return $game_map.events[event.id] && event.name[/trainer\((\d+)\)/i]
end
class Sprite_Character < RPG::Sprite
attr_accessor :character
attr_accessor :pending_bitmap
attr_accessor :bitmap_override
attr_accessor :charbitmap
def initialize(viewport, character = nil)
super(viewport)
@character = character
if darknessEffectOnCurrentMap()
if @character.is_a?(Game_Event)
$game_map.events[@character.id].erase if event_is_trainer(@character)
end
end
@oldbushdepth = 0
@spriteoffset = false
if Settings::USE_REFLECTIONS && (!character || character == $game_player || (character.name[/reflection/i] rescue false))
@reflection = Sprite_Reflection.new(self, viewport)
end
@surfbase = Sprite_SurfBase.new(self, character, viewport) if character == $game_player
if @character && @character != $game_player
checkModifySpriteGraphics(@character) if @character.active?
end
update
end
def event_is_active?(game_event)
return !game_event.event.page.nil?
end
def checkModifySpriteGraphics(character)
return if character == $game_player || !character.name
if TYPE_EXPERTS_APPEARANCES.keys.include?(character.name.to_sym)
typeExpert = character.name.to_sym
setSpriteToAppearance(TYPE_EXPERTS_APPEARANCES[typeExpert])
end
end
def setSpriteToAppearance(trainerAppearance)
#return if !@charbitmap || !@charbitmap.bitmap
begin
new_bitmap = AnimatedBitmap.new(getBaseOverworldSpriteFilename()) #@charbitmap
new_bitmap.bitmap = generateNPCClothedBitmapStatic(trainerAppearance)
@bitmap_override = new_bitmap
updateBitmap
rescue
end
end
def clearBitmapOverride()
@bitmap_override = nil
updateBitmap
end
def setSurfingPokemon(pokemonSpecies)
@surfingPokemon = pokemonSpecies
@surfbase.setPokemon(pokemonSpecies) if @surfbase
end
def groundY
return @character.screen_y_ground
end
def visible=(value)
super(value)
@reflection.visible = value if @reflection
end
def dispose
@bushbitmap.dispose if @bushbitmap
@bushbitmap = nil
@charbitmap.dispose if @charbitmap
@charbitmap = nil
@reflection.dispose if @reflection
@reflection = nil
@surfbase.dispose if @surfbase
@surfbase = nil
super
end
def updateBitmap
@manual_refresh = true
end
def pbLoadOutfitBitmap(outfitFileName)
# Construct the file path for the outfit bitmap based on the given value
#outfitFileName = sprintf("Graphics/Outfits/%s", value)
# Attempt to load the outfit bitmap
begin
outfitBitmap = RPG::Cache.load_bitmap("", outfitFileName)
return outfitBitmap
rescue
return nil
end
end
def generateClothedBitmap()
return
end
def applyDayNightTone()
if @character.is_a?(Game_Event) && @character.name[/regulartone/i]
self.tone.set(0, 0, 0, 0)
else
pbDayNightTint(self)
end
end
def updateCharacterBitmap
AnimatedBitmap.new('Graphics/Characters/' + @character_name, @character_hue)
end
def should_update?
return @tile_id != @character.tile_id ||
@character_name != @character.character_name ||
@character_hue != @character.character_hue ||
@oldbushdepth != @character.bush_depth ||
@manual_refresh
end
def refreshOutfit()
self.pending_bitmap = getClothedPlayerSprite(true)
end
def update
if self.pending_bitmap
self.bitmap = self.pending_bitmap
self.pending_bitmap = nil
end
return if @character.is_a?(Game_Event) && !@character.should_update?
super
if should_update?
@manual_refresh = false
@tile_id = @character.tile_id
@character_name = @character.character_name
@character_hue = @character.character_hue
@oldbushdepth = @character.bush_depth
if @tile_id >= 384
@charbitmap.dispose if @charbitmap
@charbitmap = pbGetTileBitmap(@character.map.tileset_name, @tile_id,
@character_hue, @character.width, @character.height)
@charbitmapAnimated = false
@bushbitmap.dispose if @bushbitmap
@bushbitmap = nil
@spriteoffset = false
@cw = Game_Map::TILE_WIDTH * @character.width
@ch = Game_Map::TILE_HEIGHT * @character.height
self.src_rect.set(0, 0, @cw, @ch)
self.ox = @cw / 2
self.oy = @ch
@character.sprite_size = [@cw, @ch]
else
@charbitmap.dispose if @charbitmap
@charbitmap = updateCharacterBitmap()
@charbitmap = @bitmap_override.clone if @bitmap_override
RPG::Cache.retain('Graphics/Characters/', @character_name, @character_hue) if @charbitmapAnimated = true
@bushbitmap.dispose if @bushbitmap
@bushbitmap = nil
#@spriteoffset = @character_name[/offset/i]
@spriteoffset = @character_name[/fish/i] || @character_name[/dive/i] || @character_name[/surf/i]
@cw = @charbitmap.width / 4 if !@charbitmap.disposed?
@ch = @charbitmap.height / 4 if !@charbitmap.disposed?
self.ox = @cw / 2
@character.sprite_size = [@cw, @ch]
end
end
@charbitmap.update if @charbitmapAnimated
bushdepth = @character.bush_depth
if bushdepth == 0
if @character == $game_player
self.bitmap = getClothedPlayerSprite() #generateClothedBitmap()
else
self.bitmap = (@charbitmapAnimated) ? @charbitmap.bitmap : @charbitmap
end
else
@bushbitmap = BushBitmap.new(@charbitmap, (@tile_id >= 384), bushdepth) if !@bushbitmap
self.bitmap = @bushbitmap.bitmap
end
self.visible = !@character.transparent
if @tile_id == 0
sx = @character.pattern * @cw
sy = ((@character.direction - 2) / 2) * @ch
self.src_rect.set(sx, sy, @cw, @ch)
self.oy = (@spriteoffset rescue false) ? @ch - 16 : @ch
self.oy -= @character.bob_height
end
if self.visible
applyDayNightTone()
end
self.x = @character.screen_x
self.y = @character.screen_y
self.z = @character.screen_z(@ch)
# self.zoom_x = Game_Map::TILE_WIDTH / 32.0
# self.zoom_y = Game_Map::TILE_HEIGHT / 32.0
self.opacity = @character.opacity
self.blend_type = @character.blend_type
# self.bush_depth = @character.bush_depth
if @character.animation_id != 0
animation = $data_animations[@character.animation_id]
animation(animation, true)
@character.animation_id = 0
end
@reflection.update if @reflection
@surfbase.update if @surfbase
end
end

View File

@@ -0,0 +1,93 @@
#===============================================================================
#
#===============================================================================
class Sprite_Reflection
attr_reader :visible
def initialize(parent_sprite, viewport = nil)
@parent_sprite = parent_sprite
@sprite = nil
@height = 0
@fixedheight = false
if @parent_sprite.character && @parent_sprite.character != $game_player &&
@parent_sprite.character.name[/reflection\((\d+)\)/i]
@height = $~[1].to_i || 0
@fixedheight = true
end
@viewport = viewport
@disposed = false
update
end
def dispose
return if @disposed
@sprite&.dispose
@sprite = nil
@parent_sprite = nil
@disposed = true
end
def disposed?
return @disposed
end
def event
return @parent_sprite.character
end
def visible=(value)
@visible = value
@sprite.visible = value if @sprite && !@sprite.disposed?
end
def update
return if disposed?
shouldShow = @parent_sprite.visible
if !shouldShow
# Just-in-time disposal of sprite
if @sprite
@sprite.dispose
@sprite = nil
end
return
end
# Just-in-time creation of sprite
@sprite = Sprite.new(@viewport) if !@sprite
if @sprite
x = @parent_sprite.x - (@parent_sprite.ox * TilemapRenderer::ZOOM_X)
y = @parent_sprite.y - (@parent_sprite.oy * TilemapRenderer::ZOOM_Y)
y -= Game_Map::TILE_HEIGHT * TilemapRenderer::ZOOM_Y if event.character_name[/offset/i]
@height = $PokemonGlobal.bridge if !@fixedheight
y += @height * TilemapRenderer::ZOOM_Y * Game_Map::TILE_HEIGHT / 2
width = @parent_sprite.src_rect.width
height = @parent_sprite.src_rect.height
@sprite.x = x + ((width / 2) * TilemapRenderer::ZOOM_X)
@sprite.y = y + ((height + (height / 2)) * TilemapRenderer::ZOOM_Y)
@sprite.ox = width / 2
@sprite.oy = (height / 2) - 2 # Hard-coded 2 pixel shift up
@sprite.oy -= event.bob_height * 2
@sprite.z = @parent_sprite.groundY - (Graphics.height / 2)
@sprite.z -= 1000 # Still water is -2000, map is 0 and above
@sprite.z += 1 if event == $game_player
@sprite.zoom_x = @parent_sprite.zoom_x
if Settings::ANIMATE_REFLECTIONS
@sprite.zoom_x += 0.05 * @sprite.zoom_x * Math.sin(2 * Math::PI * System.uptime)
end
@sprite.zoom_y = @parent_sprite.zoom_y
@sprite.angle = 180.0
@sprite.mirror = true
@sprite.bitmap = @parent_sprite.bitmap
@sprite.tone = @parent_sprite.tone
if @height > 0
@sprite.color = Color.new(48, 96, 160, 255) # Dark still water
@sprite.opacity = @parent_sprite.opacity
@sprite.visible = !Settings::TIME_SHADING # Can't time-tone a colored sprite
else
@sprite.color = Color.new(224, 224, 224, 96)
@sprite.opacity = @parent_sprite.opacity * 3 / 4
@sprite.visible = true
end
@sprite.src_rect = @parent_sprite.src_rect
end
end
end

View File

@@ -0,0 +1,150 @@
class Sprite_SurfBase
attr_reader :visible
attr_accessor :event
def initialize(sprite, event, viewport = nil)
@rsprite = sprite
@sprite = nil
@event = event
@viewport = viewport
@disposed = false
#@surfbitmap = AnimatedBitmap.new("Graphics/Characters/base_surf")
@surfbitmap = update_surf_bitmap(:SURF)
@divebitmap = update_surf_bitmap(:DIVE)
# RPG::Cache.retain("Graphics/Characters/base_surf")
# RPG::Cache.retain("Graphics/Characters/base_dive")
@cws = @surfbitmap.width / 4
@chs = @surfbitmap.height / 4
@cwd = @divebitmap.width / 4
@chd = @divebitmap.height / 4
update
end
def dispose
return if @disposed
@sprite.dispose if @sprite
@sprite = nil
@surfbitmap.dispose
@divebitmap.dispose
@disposed = true
end
def disposed?
@disposed
end
def visible=(value)
@visible = value
@sprite.visible = value if @sprite && !@sprite.disposed?
end
def update_surf_bitmap(type)
species = $Trainer.surfing_pokemon
path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "surfmon_board" if type == :SURF
#path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "divemon_scuba" if type == :DIVE
path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "divemon_Head" if type == :DIVE
if species
shape = species.shape
basePath = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER
action = "divemon" if type == :DIVE
action = "surfmon" if type == :SURF
path = "#{basePath}#{action}_#{shape.to_s}"
end
return AnimatedBitmap.new(path)
end
# case species.shape
# when :Head
# path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "divemon_Head" if type == :DIVE
# path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "surfmon_Head" if type == :SURF
# when :Serpentine
# path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "divemon_HeadBase" if type == :DIVE
# path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "surfmon_HeadBase" if type == :SURF
# when :Finned
# path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "divemon_HeadBase" if type == :DIVE
# path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "surfmon_HeadBase" if type == :SURF
# when :HeadArms
# path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "divemon_HeadBase" if type == :DIVE
# path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "surfmon_HeadBase" if type == :SURF
# when :HeadBase
# path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "divemon_HeadBase" if type == :DIVE
# path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "surfmon_HeadBase" if type == :SURF
# when :BipedalTail
# path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "divemon_HeadBase" if type == :DIVE
# path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "surfmon_HeadBase" if type == :SURF
# when :HeadLegs
# path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "divemon_HeadBase" if type == :DIVE
# path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "surfmon_HeadBase" if type == :SURF
# when :Quadruped
# path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "divemon_HeadBase" if type == :DIVE
# path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "surfmon_HeadBase" if type == :SURF
# when :Winged
# path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "divemon_HeadBase" if type == :DIVE
# path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "surfmon_HeadBase" if type == :SURF
# when :Multiped
# path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "divemon_HeadBase" if type == :DIVE
# path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "surfmon_HeadBase" if type == :SURF
# when :MultiBody
# path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "divemon_HeadBase" if type == :DIVE
# path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "surfmon_HeadBase" if type == :SURF
# when :Bipedal
# path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "divemon_HeadBase" if type == :DIVE
# path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "surfmon_HeadBase" if type == :SURF
# when :MultiWinged
# path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "divemon_HeadBase" if type == :DIVE
# path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "surfmon_HeadBase" if type == :SURF
# when :Insectoid
# path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "divemon_HeadBase" if type == :DIVE
# path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "surfmon_HeadBase" if type == :SURF
# else
# path = Settings::PLAYER_GRAPHICS_FOLDER + Settings::PLAYER_SURFBASE_FOLDER + "divemon_01"
# end
def update
return if disposed?
if !$PokemonGlobal.surfing && !$PokemonGlobal.diving
# Just-in-time disposal of sprite
if @sprite
@sprite.dispose
@sprite = nil
end
return
end
# Just-in-time creation of sprite
@sprite = Sprite.new(@viewport) if !@sprite
if @sprite
if $PokemonGlobal.surfing
@surfbitmap = update_surf_bitmap(:SURF)
@sprite.bitmap = @surfbitmap.bitmap
cw = @cws
ch = @chs
elsif $PokemonGlobal.diving
@divebitmap = update_surf_bitmap(:DIVE)
@sprite.bitmap = @divebitmap.bitmap
cw = @cwd
ch = @chd
end
sx = @event.pattern_surf * cw
sy = ((@event.direction - 2) / 2) * ch
@sprite.src_rect.set(sx, sy, cw, ch)
if $PokemonTemp.surfJump
@sprite.x = ($PokemonTemp.surfJump[0] * Game_Map::REAL_RES_X - @event.map.display_x + 3) / 4 + (Game_Map::TILE_WIDTH / 2)
@sprite.y = ($PokemonTemp.surfJump[1] * Game_Map::REAL_RES_Y - @event.map.display_y + 3) / 4 + (Game_Map::TILE_HEIGHT / 2) + 16
else
@sprite.x = @rsprite.x
@sprite.y = @rsprite.y
end
@sprite.ox = cw / 2
@sprite.oy = ch - 16 # Assume base needs offsetting
@sprite.oy -= @event.bob_height
@sprite.z = @event.screen_z(ch) - 1
@sprite.zoom_x = @rsprite.zoom_x
@sprite.zoom_y = @rsprite.zoom_y
@sprite.tone = @rsprite.tone
@sprite.color = @rsprite.color
@sprite.opacity = @rsprite.opacity
end
end
end

View File

@@ -0,0 +1,30 @@
class Spriteset_Global
attr_reader :playersprite
@@viewport2 = Viewport.new(0, 0, Settings::SCREEN_WIDTH, Settings::SCREEN_HEIGHT)
@@viewport2.z = 200
def initialize
@playersprite = Sprite_Player.new(Spriteset_Map.viewport, $game_player)
@picture_sprites = []
for i in 1..100
@picture_sprites.push(Sprite_Picture.new(@@viewport2, $game_screen.pictures[i]))
end
@timer_sprite = Sprite_Timer.new
update
end
def dispose
@playersprite.dispose
@picture_sprites.each { |sprite| sprite.dispose }
@timer_sprite.dispose
@playersprite = nil
@picture_sprites.clear
@timer_sprite = nil
end
def update
@playersprite.update
@picture_sprites.each { |sprite| sprite.update }
@timer_sprite.update
end
end

View File

@@ -0,0 +1,168 @@
# Unused
class ClippableSprite < Sprite_Character
def initialize(viewport,event,tilemap)
@tilemap = tilemap
@_src_rect = Rect.new(0,0,0,0)
super(viewport,event)
end
def update
super
@_src_rect = self.src_rect
tmright = @tilemap.map_data.xsize*Game_Map::TILE_WIDTH-@tilemap.ox
echoln("x=#{self.x},ox=#{self.ox},tmright=#{tmright},tmox=#{@tilemap.ox}")
if @tilemap.ox-self.ox<-self.x
# clipped on left
diff = -self.x-@tilemap.ox+self.ox
self.src_rect = Rect.new(@_src_rect.x+diff,@_src_rect.y,
@_src_rect.width-diff,@_src_rect.height)
echoln("clipped out left: #{diff} #{@tilemap.ox-self.ox} #{self.x}")
elsif tmright-self.ox<self.x
# clipped on right
diff = self.x-tmright+self.ox
self.src_rect = Rect.new(@_src_rect.x,@_src_rect.y,
@_src_rect.width-diff,@_src_rect.height)
echoln("clipped out right: #{diff} #{tmright+self.ox} #{self.x}")
else
echoln("-not- clipped out left: #{diff} #{@tilemap.ox-self.ox} #{self.x}")
end
end
end
class Spriteset_Map
attr_reader :map
@@viewport0 = Viewport.new(0, 0, Settings::SCREEN_WIDTH, Settings::SCREEN_HEIGHT) # Panorama
@@viewport0.z = -100
@@viewport1 = Viewport.new(0, 0, Settings::SCREEN_WIDTH, Settings::SCREEN_HEIGHT) # Map, events, player, fog
@@viewport1.z = 0
@@viewport3 = Viewport.new(0, 0, Settings::SCREEN_WIDTH, Settings::SCREEN_HEIGHT) # Flashing
@@viewport3.z = 500
def Spriteset_Map.viewport # For access by Spriteset_Global
return @@viewport1
end
def initialize(map=nil)
@map = (map) ? map : $game_map
$scene.map_renderer.add_tileset(@map.tileset_name)
@map.autotile_names.each do |filename|
$scene.map_renderer.add_autotile(filename)
$scene.map_renderer.add_extra_autotiles(@map.tileset_id,@map.map_id)
end
@panorama = AnimatedPlane.new(@@viewport0)
@fog = AnimatedPlane.new(@@viewport1)
@fog.z = 3000
@fog2=nil
@character_sprites = []
for i in @map.events.keys.sort
sprite = Sprite_Character.new(@@viewport1,@map.events[i])
@character_sprites.push(sprite)
end
@weather = RPG::Weather.new(@@viewport1)
pbOnSpritesetCreate(self,@@viewport1)
update
end
def setFog2(filename="010-Water04")
disposeFog2()
@fog2 = AnimatedPlane.new(@@viewport1)
@fog2.z = 3001
@fog2.setFog(filename)
end
def disposeFog2()
@fog2.dispose if @fog2
@fog2 =nil
end
def dispose
if $scene.is_a?(Scene_Map)
$scene.map_renderer.remove_tileset(@map.tileset_name)
@map.autotile_names.each do |filename|
$scene.map_renderer.remove_autotile(filename)
$scene.map_renderer.remove_extra_autotiles(@map.tileset_id)
end
end
@panorama.dispose
@fog.dispose
@fog2.dispose if @fog2
for sprite in @character_sprites
sprite.dispose
end
@weather.dispose
@panorama = nil
@fog = nil
@character_sprites.clear
@weather = nil
end
def getAnimations
return @usersprites
end
def restoreAnimations(anims)
@usersprites = anims
end
def update
if @panorama_name!=@map.panorama_name || @panorama_hue!=@map.panorama_hue
@panorama_name = @map.panorama_name
@panorama_hue = @map.panorama_hue
@panorama.setPanorama(nil) if @panorama.bitmap!=nil
@panorama.setPanorama(@panorama_name,@panorama_hue) if @panorama_name!=""
Graphics.frame_reset
end
if @fog_name!=@map.fog_name || @fog_hue!=@map.fog_hue
@fog_name = @map.fog_name
@fog_hue = @map.fog_hue
@fog.setFog(nil) if @fog.bitmap!=nil
@fog.setFog(@fog_name,@fog_hue) if @fog_name!=""
Graphics.frame_reset
end
tmox = (@map.display_x/Game_Map::X_SUBPIXELS).round
tmoy = (@map.display_y/Game_Map::Y_SUBPIXELS).round
@@viewport1.rect.set(0,0,Graphics.width,Graphics.height)
@@viewport1.ox = 0
@@viewport1.oy = 0
@@viewport1.ox += $game_screen.shake
@panorama.ox = tmox/2
@panorama.oy = tmoy/2
@fog.ox = tmox+@map.fog_ox
@fog.oy = tmoy+@map.fog_oy
@fog.zoom_x = @map.fog_zoom/100.0
@fog.zoom_y = @map.fog_zoom/100.0
@fog.opacity = @map.fog_opacity
@fog.blend_type = @map.fog_blend_type
@fog.tone = @map.fog_tone
@fog2.ox = tmox+@map.fog2_ox if @fog2
@fog2.oy = tmoy+@map.fog2_oy if @fog2
@fog2.zoom_x = @map.fog_zoom/100.0 if @fog2
@fog2.zoom_y = @map.fog_zoom/100.0 if @fog2
@fog2.opacity = @map.fog2_opacity if @fog2
@panorama.update
@fog.update
@fog2.update if @fog2
for sprite in @character_sprites
sprite.update
end
if self.map!=$game_map
#@weather.fade_in(:None, 0, 20)
else
@weather.fade_in($game_screen.weather_type, $game_screen.weather_power, $game_screen.weather_duration)
end
@weather.ox = tmox
@weather.oy = tmoy
@weather.update
@@viewport1.tone = $game_screen.tone
@@viewport3.color = $game_screen.flash_color
@@viewport1.update
@@viewport3.update
end
end

View File

@@ -0,0 +1,84 @@
=begin
A sprite whose sole purpose is to display an animation. This sprite
can be displayed anywhere on the map and is disposed
automatically when its animation is finished.
Used for grass rustling and so forth.
=end
class AnimationSprite < RPG::Sprite
def initialize(animID,map,tileX,tileY,viewport=nil,tinting=false,height=3)
super(viewport)
@tileX = tileX
@tileY = tileY
self.bitmap = Bitmap.new(1, 1)
self.bitmap.clear
@map = map
setCoords
pbDayNightTint(self) if tinting
self.animation($data_animations[animID],true,height)
end
def setCoords
self.x = ((@tileX * Game_Map::REAL_RES_X - @map.display_x) / Game_Map::X_SUBPIXELS).ceil
self.x += Game_Map::TILE_WIDTH / 2
self.y = ((@tileY * Game_Map::REAL_RES_Y - @map.display_y) / Game_Map::Y_SUBPIXELS).ceil
self.y += Game_Map::TILE_HEIGHT
end
def dispose
self.bitmap.dispose
super
end
def update
if !self.disposed?
setCoords
super
self.dispose if !self.effect?
end
end
end
class Spriteset_Map
alias _animationSprite_initialize initialize unless method_defined?(:_animationSprite_initialize)
alias _animationSprite_update update unless method_defined?(:_animationSprite_update)
alias _animationSprite_dispose dispose unless method_defined?(:_animationSprite_dispose)
def initialize(map=nil)
@usersprites=[]
_animationSprite_initialize(map)
end
def addUserAnimation(animID,x,y,tinting=false,height=3)
sprite=AnimationSprite.new(animID,$game_map,x,y,@@viewport1,tinting,height)
addUserSprite(sprite)
return sprite
end
def addUserSprite(sprite)
for i in 0...@usersprites.length
if @usersprites[i]==nil || @usersprites[i].disposed?
@usersprites[i]=sprite
return
end
end
@usersprites.push(sprite)
end
def dispose
_animationSprite_dispose
for i in 0...@usersprites.length
@usersprites[i].dispose
end
@usersprites.clear
end
def update
@@viewport3.tone.set(0,0,0,0)
_animationSprite_update
for i in 0...@usersprites.length
@usersprites[i].update if !@usersprites[i].disposed?
end
end
end

View File

@@ -0,0 +1,253 @@
#===============================================================================
# Sprite_Shadow (Sprite_Ombre )
# Based on Genzai Kawakami's shadows, dynamisme & features by Rataime, extra
# features Boushy
# Modified by Peter O. to be compatible with Pokémon Essentials
#===============================================================================
class Sprite_Shadow < RPG::Sprite
attr_accessor :character
def initialize(viewport, character = nil, params = [])
super(viewport)
@source = params[0]
@anglemin = (params.size > 1) ? params[1] : 0
@anglemax = (params.size > 2) ? params[2] : 0
@self_opacity = (params.size > 4) ? params[4] : 100
@distancemax = (params.size > 3) ? params[3] : 350
@character = character
update
end
def dispose
@chbitmap&.dispose
super
end
def update
if !in_range?(@character, @source, @distancemax)
self.opacity = 0
return
end
super
if @tile_id != @character.tile_id ||
@character_name != @character.character_name ||
@character_hue != @character.character_hue
@tile_id = @character.tile_id
@character_name = @character.character_name
@character_hue = @character.character_hue
@chbitmap&.dispose
if @tile_id >= 384
@chbitmap = pbGetTileBitmap(@character.map.tileset_name,
@tile_id, @character.character_hue)
self.src_rect.set(0, 0, 32, 32)
@ch = 32
@cw = 32
self.ox = 16
self.oy = 32
else
@chbitmap = AnimatedBitmap.new('Graphics/Characters/' + @character.character_name,
@character.character_hue)
@cw = @chbitmap.width / 4
@ch = @chbitmap.height / 4
self.ox = @cw / 2
self.oy = @ch
end
end
if @chbitmap.is_a?(AnimatedBitmap)
@chbitmap.update
self.bitmap = @chbitmap.bitmap
else
self.bitmap = @chbitmap
end
self.visible = !@character.transparent
if @tile_id == 0
sx = @character.pattern * @cw
sy = (@character.direction - 2) / 2 * @ch
if self.angle > 90 || angle < -90
case @character.direction
when 2 then sy = @ch * 3
when 4 then sy = @ch * 2
when 6 then sy = @ch
when 8 then sy = 0
end
end
self.src_rect.set(sx, sy, @cw, @ch)
end
self.x = ScreenPosHelper.pbScreenX(@character)
self.y = ScreenPosHelper.pbScreenY(@character) - 5
self.z = ScreenPosHelper.pbScreenZ(@character, @ch) - 1
self.zoom_x = ScreenPosHelper.pbScreenZoomX(@character)
self.zoom_y = ScreenPosHelper.pbScreenZoomY(@character)
self.blend_type = @character.blend_type
self.bush_depth = @character.bush_depth
if @character.animation_id != 0
animation = $data_animations[@character.animation_id]
animation(animation, true)
@character.animation_id = 0
end
@deltax = ScreenPosHelper.pbScreenX(@source) - self.x
@deltay = ScreenPosHelper.pbScreenY(@source) - self.y
self.color = Color.new(0, 0, 0)
@distance = ((@deltax**2) + (@deltay**2))
self.opacity = @self_opacity * 13_000 / ((@distance * 370 / @distancemax) + 6000)
self.angle = 57.3 * Math.atan2(@deltax, @deltay)
@angle_trigo = self.angle + 90
@angle_trigo += 360 if @angle_trigo < 0
if @anglemin != 0 || @anglemax != 0
if (@angle_trigo < @anglemin || @angle_trigo > @anglemax) && @anglemin < @anglemax
self.opacity = 0
return
end
if @angle_trigo < @anglemin && @angle_trigo > @anglemax && @anglemin > @anglemax
self.opacity = 0
return
end
end
end
def in_range?(element, object, range) # From Near's Anti Lag Script, edited
elemScreenX = ScreenPosHelper.pbScreenX(element)
elemScreenY = ScreenPosHelper.pbScreenY(element)
objScreenX = ScreenPosHelper.pbScreenX(object)
objScreenY = ScreenPosHelper.pbScreenY(object)
x = (elemScreenX - objScreenX) * (elemScreenX - objScreenX)
y = (elemScreenY - objScreenY) * (elemScreenY - objScreenY)
r = x + y
return r <= range * range
end
end
#===================================================
# ? CLASS Sprite_Character edit
#===================================================
class Sprite_Character < RPG::Sprite
alias shadow_initialize initialize unless method_defined?(:shadow_initialize)
def initialize(viewport, character = nil)
@ombrelist = []
@character = character
shadow_initialize(viewport, @character)
end
def setShadows(map, shadows)
if character.is_a?(Game_Event) && shadows.length > 0
params = XPML_read(map, "Shadow", @character, 4)
if params != nil
shadows.each do |shadow|
@ombrelist.push(Sprite_Shadow.new(viewport, @character, shadows))
end
end
end
if character.is_a?(Game_Player) && shadows.length > 0
shadows.each do |shadow|
@ombrelist.push(Sprite_Shadow.new(viewport, $game_player, shadow))
end
end
update
end
def clearShadows
@ombrelist.each { |s| s&.dispose }
@ombrelist.clear
end
alias shadow_update update unless method_defined?(:shadow_update)
def update
shadow_update
@ombrelist.each { |ombre| ombre.update }
end
end
#===================================================
# ? CLASS Game_Event edit
#===================================================
class Game_Event
attr_accessor :id
end
#===================================================
# ? CLASS Spriteset_Map edit
#===================================================
class Spriteset_Map
attr_accessor :shadows
alias shadow_initialize initialize unless method_defined?(:shadow_initialize)
def initialize(map = nil)
@shadows = []
warn = false
map = $game_map if !map
map.events.keys.sort.each do |k|
ev = map.events[k]
warn = true if ev.list != nil && ev.list.length > 0 && ev.list[0].code == 108 &&
(ev.list[0].parameters == ["s"] || ev.list[0].parameters == ["o"])
params = XPML_read(map, "Shadow Source", ev, 4)
@shadows.push([ev] + params) if params != nil
end
if warn == true
p "Warning : At least one event on this map uses the obsolete way to add shadows"
end
shadow_initialize(map)
@character_sprites.each do |sprite|
sprite.setShadows(map, @shadows)
end
$scene.spritesetGlobal.playersprite.setShadows(map, @shadows)
end
end
#===================================================
# ? XPML Definition, by Rataime, using ideas from Near Fantastica
#
# Returns nil if the markup wasn't present at all,
# returns [] if there wasn't any parameters, else
# returns a parameters list with "int" converted as int
# eg :
# begin first
# begin second
# param1 1
# param2 two
# begin third
# anything 3
#
# p XPML_read("first", event_id) -> []
# p XPML_read("second", event_id) -> [1, "two"]
# p XPML_read("third", event_id) -> [3]
# p XPML_read("forth", event_id) -> nil
#===================================================
def XPML_read(map, markup, event, max_param_number = 0)
parameter_list = nil
return nil if !event || event.list.nil?
event.list.size.times do |i|
if event.list[i].code == 108 &&
event.list[i].parameters[0].downcase == "begin " + markup.downcase
parameter_list = [] if parameter_list.nil?
((i + 1)...event.list.size).each do |j|
if event.list[j].code == 108
parts = event.list[j].parameters[0].split
if parts.size != 1 && parts[0].downcase != "begin"
if parts[1].to_i != 0 || parts[1] == "0"
parameter_list.push(parts[1].to_i)
else
parameter_list.push(parts[1])
end
else
return parameter_list
end
else
return parameter_list
end
return parameter_list if max_param_number != 0 && j == i + max_param_number
end
end
end
return parameter_list
end

View File

@@ -0,0 +1,586 @@
# Particle Engine, Peter O., 2007-11-03
# Based on version 2 by Near Fantastica, 04.01.06
# In turn based on the Particle Engine designed by PinkMan
class Particle_Engine
def initialize(viewport = nil, map = nil)
@map = (map) ? map : $game_map
@viewport = viewport
@effect = []
@disposed = false
@firsttime = true
@effects = {
# PinkMan's Effects
"fire" => Particle_Engine::Fire,
"smoke" => Particle_Engine::Smoke,
"teleport" => Particle_Engine::Teleport,
"spirit" => Particle_Engine::Spirit,
"explosion" => Particle_Engine::Explosion,
"aura" => Particle_Engine::Aura,
# BlueScope's Effects
"soot" => Particle_Engine::Soot,
"sootsmoke" => Particle_Engine::SootSmoke,
"rocket" => Particle_Engine::Rocket,
"fixteleport" => Particle_Engine::FixedTeleport,
"smokescreen" => Particle_Engine::Smokescreen,
"flare" => Particle_Engine::Flare,
"splash" => Particle_Engine::Splash,
# By Peter O.
"starteleport" => Particle_Engine::StarTeleport
}
end
def dispose
return if disposed?
@effect.each do |particle|
next if particle.nil?
particle.dispose
end
@effect.clear
@map = nil
@disposed = true
end
def disposed?
return @disposed
end
def add_effect(event)
@effect[event.id] = pbParticleEffect(event)
end
def remove_effect(event)
return if @effect[event.id].nil?
@effect[event.id].dispose
@effect.delete_at(event.id)
end
def realloc_effect(event, particle)
type = pbEventCommentInput(event, 1, "Particle Engine Type")
if type.nil?
particle&.dispose
return nil
end
type = type[0].downcase
cls = @effects[type]
if cls.nil?
particle&.dispose
return nil
end
if !particle || !particle.is_a?(cls)
particle&.dispose
particle = cls.new(event, @viewport)
end
return particle
end
def pbParticleEffect(event)
return realloc_effect(event, nil)
end
def update
if @firsttime
@firsttime = false
@map.events.values.each do |event|
remove_effect(event)
add_effect(event)
end
end
@effect.each_with_index do |particle, i|
next if particle.nil?
if particle.event.pe_refresh
event = particle.event
event.pe_refresh = false
particle = realloc_effect(event, particle)
@effect[i] = particle
end
particle&.update
end
end
end
class ParticleEffect
attr_accessor :x, :y, :z
def initialize
@x = 0
@y = 0
@z = 0
end
def update; end
def dispose; end
end
class ParticleSprite
attr_accessor :x, :y, :z, :ox, :oy, :opacity, :blend_type
attr_reader :bitmap
def initialize(viewport)
@viewport = viewport
@sprite = nil
@x = 0
@y = 0
@z = 0
@ox = 0
@oy = 0
@opacity = 255
@bitmap = nil
@blend_type = 0
@minleft = 0
@mintop = 0
end
def dispose
@sprite&.dispose
end
def bitmap=(value)
@bitmap = value
if value
@minleft = -value.width
@mintop = -value.height
else
@minleft = 0
@mintop = 0
end
end
def update
w = Graphics.width
h = Graphics.height
if !@sprite && @x >= @minleft && @y >= @mintop && @x < w && @y < h
@sprite = Sprite.new(@viewport)
elsif @sprite && (@x < @minleft || @y < @mintop || @x >= w || @y >= h)
@sprite.dispose
@sprite = nil
end
if @sprite
@sprite.x = @x if @sprite.x != @x
@sprite.x -= @ox
@sprite.y = @y if @sprite.y != @y
@sprite.y -= @oy
@sprite.z = @z if @sprite.z != @z
@sprite.opacity = @opacity if @sprite.opacity != @opacity
@sprite.blend_type = @blend_type if @sprite.blend_type != @blend_type
@sprite.bitmap = @bitmap if @sprite.bitmap != @bitmap
end
end
end
class ParticleEffect_Event < ParticleEffect
attr_accessor :event
def initialize(event, viewport = nil)
@event = event
@viewport = viewport
@particles = []
@bitmaps = {}
end
def setParameters(params)
@randomhue, @leftright, @fade,
@maxparticless, @hue, @slowdown,
@ytop, @ybottom, @xleft, @xright,
@xgravity, @ygravity, @xoffset, @yoffset,
@opacityvar, @originalopacity = params
end
def loadBitmap(filename, hue)
key = [filename, hue]
bitmap = @bitmaps[key]
if !bitmap || bitmap.disposed?
bitmap = AnimatedBitmap.new("Graphics/Fogs/" + filename, hue).deanimate
@bitmaps[key] = bitmap
end
return bitmap
end
def initParticles(filename, opacity, zOffset = 0, blendtype = 1)
@particles = []
@particlex = []
@particley = []
@opacity = []
@startingx = self.x + @xoffset
@startingy = self.y + @yoffset
@screen_x = self.x
@screen_y = self.y
@real_x = @event.real_x
@real_y = @event.real_y
@filename = filename
@zoffset = zOffset
@bmwidth = 32
@bmheight = 32
@maxparticless.times do |i|
@particlex[i] = -@xoffset
@particley[i] = -@yoffset
@particles[i] = ParticleSprite.new(@viewport)
@particles[i].bitmap = loadBitmap(filename, @hue) if filename
if i == 0 && @particles[i].bitmap
@bmwidth = @particles[i].bitmap.width
@bmheight = @particles[i].bitmap.height
end
@particles[i].blend_type = blendtype
@particles[i].y = @startingy
@particles[i].x = @startingx
@particles[i].z = self.z + zOffset
@opacity[i] = rand(opacity / 4)
@particles[i].opacity = @opacity[i]
@particles[i].update
end
end
def x; return ScreenPosHelper.pbScreenX(@event); end
def y; return ScreenPosHelper.pbScreenY(@event); end
def z; return ScreenPosHelper.pbScreenZ(@event); end
def update
if @viewport &&
(@viewport.rect.x >= Graphics.width ||
@viewport.rect.y >= Graphics.height)
return
end
selfX = self.x
selfY = self.y
selfZ = self.z
newRealX = @event.real_x
newRealY = @event.real_y
@startingx = selfX + @xoffset
@startingy = selfY + @yoffset
@__offsetx = (@real_x == newRealX) ? 0 : selfX - @screen_x
@__offsety = (@real_y == newRealY) ? 0 : selfY - @screen_y
@screen_x = selfX
@screen_y = selfY
@real_x = newRealX
@real_y = newRealY
if @opacityvar > 0 && @viewport
opac = 255.0 / @opacityvar
minX = (opac * (-@xgravity.to_f / @slowdown).floor) + @startingx
maxX = (opac * (@xgravity.to_f / @slowdown).floor) + @startingx
minY = (opac * (-@ygravity.to_f / @slowdown).floor) + @startingy
maxY = @startingy
minX -= @bmwidth
minY -= @bmheight
maxX += @bmwidth
maxY += @bmheight
if maxX < 0 || maxY < 0 || minX >= Graphics.width || minY >= Graphics.height
# echo "skipped"
return
end
end
particleZ = selfZ + @zoffset
@maxparticless.times do |i|
@particles[i].z = particleZ
if @particles[i].y <= @ytop
@particles[i].y = @startingy + @yoffset
@particles[i].x = @startingx + @xoffset
@particlex[i] = 0.0
@particley[i] = 0.0
end
if @particles[i].x <= @xleft
@particles[i].y = @startingy + @yoffset
@particles[i].x = @startingx + @xoffset
@particlex[i] = 0.0
@particley[i] = 0.0
end
if @particles[i].y >= @ybottom
@particles[i].y = @startingy + @yoffset
@particles[i].x = @startingx + @xoffset
@particlex[i] = 0.0
@particley[i] = 0.0
end
if @particles[i].x >= @xright
@particles[i].y = @startingy + @yoffset
@particles[i].x = @startingx + @xoffset
@particlex[i] = 0.0
@particley[i] = 0.0
end
if @fade == 0
if @opacity[i] <= 0
@opacity[i] = @originalopacity
@particles[i].y = @startingy + @yoffset
@particles[i].x = @startingx + @xoffset
@particlex[i] = 0.0
@particley[i] = 0.0
end
elsif @opacity[i] <= 0
@opacity[i] = 250
@particles[i].y = @startingy + @yoffset
@particles[i].x = @startingx + @xoffset
@particlex[i] = 0.0
@particley[i] = 0.0
end
calcParticlePos(i)
if @randomhue == 1
@hue += 0.5
@hue = 0 if @hue >= 360
@particles[i].bitmap = loadBitmap(@filename, @hue) if @filename
end
@opacity[i] = @opacity[i] - rand(@opacityvar)
@particles[i].opacity = @opacity[i]
@particles[i].update
end
end
def calcParticlePos(i)
@leftright = rand(2)
if @leftright == 1
xo = -@xgravity.to_f / @slowdown
else
xo = @xgravity.to_f / @slowdown
end
yo = -@ygravity.to_f / @slowdown
@particlex[i] += xo
@particley[i] += yo
@particlex[i] -= @__offsetx
@particley[i] -= @__offsety
@particlex[i] = @particlex[i].floor
@particley[i] = @particley[i].floor
@particles[i].x = @particlex[i] + @startingx + @xoffset
@particles[i].y = @particley[i] + @startingy + @yoffset
end
def dispose
@particles.each do |particle|
particle.dispose
end
@bitmaps.values.each do |bitmap|
bitmap.dispose
end
@particles.clear
@bitmaps.clear
end
end
class Particle_Engine::Fire < ParticleEffect_Event
def initialize(event, viewport)
super
setParameters([0, 0, 1, 20, 40, 0.5, -64,
Graphics.height, -64, Graphics.width, 0.5, 0.10, -5, -13, 30, 0])
initParticles("particle", 250)
end
end
class Particle_Engine::Smoke < ParticleEffect_Event
def initialize(event, viewport)
super
setParameters([0, 0, 0, 80, 20, 0.5, -64,
Graphics.height, -64, Graphics.width, 0.5, 0.10, -5, -15, 5, 80])
initParticles("smoke", 250)
end
end
class Particle_Engine::Teleport < ParticleEffect_Event
def initialize(event, viewport)
super
setParameters([1, 1, 1, 10, rand(360), 1, -64,
Graphics.height, -64, Graphics.width, 0, 3, -8, -15, 20, 0])
initParticles("wideportal", 250)
@maxparticless.times do |i|
@particles[i].ox = 16
@particles[i].oy = 16
end
end
end
class Particle_Engine::Spirit < ParticleEffect_Event
def initialize(event, viewport)
super
setParameters([1, 0, 1, 20, rand(360), 0.5, -64,
Graphics.height, -64, Graphics.width, 0.5, 0.10, -5, -13, 30, 0])
initParticles("particle", 250)
end
end
class Particle_Engine::Explosion < ParticleEffect_Event
def initialize(event, viewport)
super
setParameters([0, 0, 1, 20, 0, 0.5, -64,
Graphics.height, -64, Graphics.width, 0.5, 0.10, -5, -13, 30, 0])
initParticles("explosion", 250)
end
end
class Particle_Engine::Aura < ParticleEffect_Event
def initialize(event, viewport)
super
setParameters([0, 0, 1, 20, 0, 1, -64,
Graphics.height, -64, Graphics.width, 2, 2, -5, -13, 30, 0])
initParticles("particle", 250)
end
end
class Particle_Engine::Soot < ParticleEffect_Event
def initialize(event, viewport)
super
setParameters([0, 0, 0, 20, 0, 0.5, -64,
Graphics.height, -64, Graphics.width, 0.5, 0.10, -5, -15, 5, 80])
initParticles("smoke", 100, 0, 2)
end
end
class Particle_Engine::SootSmoke < ParticleEffect_Event
def initialize(event, viewport)
super
setParameters([0, 0, 0, 30, 0, 0.5, -64,
Graphics.height, -64, Graphics.width, 0.5, 0.10, -5, -15, 5, 80])
initParticles("smoke", 100, 0)
@maxparticless.times do |i|
@particles[i].blend_type = rand(6) < 3 ? 1 : 2
end
end
end
class Particle_Engine::Rocket < ParticleEffect_Event
def initialize(event, viewport)
super
setParameters([0, 0, 0, 60, 0, 0.5, -64,
Graphics.height, -64, Graphics.width, 0.5, 0, -5, -15, 5, 80])
initParticles("smoke", 100, -1)
end
end
class Particle_Engine::FixedTeleport < ParticleEffect_Event
def initialize(event, viewport)
super
setParameters([1, 0, 1, 10, rand(360), 1,
-Graphics.height, Graphics.height, 0, Graphics.width, 0, 3, -8, -15, 20, 0])
initParticles("wideportal", 250)
@maxparticless.times do |i|
@particles[i].ox = 16
@particles[i].oy = 16
end
end
end
# By Peter O.
class Particle_Engine::StarTeleport < ParticleEffect_Event
def initialize(event, viewport)
super
setParameters([0, 0, 1, 10, 0, 1,
-Graphics.height, Graphics.height, 0, Graphics.width, 0, 3, -8, -15, 10, 0])
initParticles("star", 250)
@maxparticless.times do |i|
@particles[i].ox = 48
@particles[i].oy = 48
end
end
end
class Particle_Engine::Smokescreen < ParticleEffect_Event
def initialize(event, viewport)
super
setParameters([0, 0, 0, 250, 0, 0.2, -64,
Graphics.height, -64, Graphics.width, 0.8, 0.8, -5, -15, 5, 80])
initParticles(nil, 100)
@maxparticless.times do |i|
rnd = rand(3)
@opacity[i] = (rnd == 0) ? 1 : 100
filename = (rnd == 0) ? "explosionsmoke" : "smoke"
@particles[i].bitmap = loadBitmap(filename, @hue)
end
end
def calcParticlePos(i)
if @randomhue == 1
filename = (rand(3) == 0) ? "explosionsmoke" : "smoke"
@particles[i].bitmap = loadBitmap(filename, @hue)
end
multiple = 1.7
xgrav = @xgravity * multiple / @slowdown
xgrav = -xgrav if rand(2) == 1
ygrav = @ygravity * multiple / @slowdown
ygrav = -ygrav if rand(2) == 1
@particlex[i] += xgrav
@particley[i] += ygrav
@particlex[i] -= @__offsetx
@particley[i] -= @__offsety
@particlex[i] = @particlex[i].floor
@particley[i] = @particley[i].floor
@particles[i].x = @particlex[i] + @startingx + @xoffset
@particles[i].y = @particley[i] + @startingy + @yoffset
end
end
class Particle_Engine::Flare < ParticleEffect_Event
def initialize(event, viewport)
super
setParameters([0, 0, 1, 30, 10, 1, -64,
Graphics.height, -64, Graphics.width, 2, 2, -5, -12, 30, 0])
initParticles("particle", 255)
end
end
class Particle_Engine::Splash < ParticleEffect_Event
def initialize(event, viewport)
super
setParameters([0, 0, 1, 30, 255, 1, -64,
Graphics.height, -64, Graphics.width, 4, 2, -5, -12, 30, 0])
initParticles("smoke", 50)
end
def update
super
@maxparticless.times do |i|
@particles[i].opacity = 50
@particles[i].update
end
end
end
class Game_Event < Game_Character
attr_accessor :pe_refresh
alias nf_particles_game_map_initialize initialize unless method_defined?(:nf_particles_game_map_initialize)
def initialize(map_id, event, map = nil)
@pe_refresh = false
begin
nf_particles_game_map_initialize(map_id, event, map)
rescue ArgumentError
nf_particles_game_map_initialize(map_id, event)
end
end
alias nf_particles_game_map_refresh refresh unless method_defined?(:nf_particles_game_map_refresh)
def refresh
nf_particles_game_map_refresh
@pe_refresh = true
end
end

View File

@@ -0,0 +1,519 @@
class PictureOrigin
TopLeft = 0
Center = 1
TopRight = 2
BottomLeft = 3
LowerLeft = 3
BottomRight = 4
LowerRight = 4
Top = 5
Bottom = 6
Left = 7
Right = 8
end
class Processes
XY = 0
DeltaXY = 1
Z = 2
Curve = 3
Zoom = 4
Angle = 5
Tone = 6
Color = 7
Hue = 8
Opacity = 9
Visible = 10
BlendType = 11
SE = 12
Name = 13
Origin = 14
Src = 15
SrcSize = 16
CropBottom = 17
end
def getCubicPoint2(src,t)
x0 = src[0]; y0 = src[1]
cx0 = src[2]; cy0 = src[3]
cx1 = src[4]; cy1 = src[5]
x1 = src[6]; y1 = src[7]
x1 = cx1+(x1-cx1)*t
x0 = x0+(cx0-x0)*t
cx0 = cx0+(cx1-cx0)*t
cx1 = cx0+(x1-cx0)*t
cx0 = x0+(cx0-x0)*t
cx = cx0+(cx1-cx0)*t
# a = x1 - 3 * cx1 + 3 * cx0 - x0
# b = 3 * (cx1 - 2 * cx0 + x0)
# c = 3 * (cx0 - x0)
# d = x0
# cx = a*t*t*t + b*t*t + c*t + d
y1 = cy1+(y1-cy1)*t
y0 = y0+(cy0-y0)*t
cy0 = cy0+(cy1-cy0)*t
cy1 = cy0+(y1-cy0)*t
cy0 = y0+(cy0-y0)*t
cy = cy0+(cy1-cy0)*t
# a = y1 - 3 * cy1 + 3 * cy0 - y0
# b = 3 * (cy1 - 2 * cy0 + y0)
# c = 3 * (cy0 - y0)
# d = y0
# cy = a*t*t*t + b*t*t + c*t + d
return [cx,cy]
end
#===============================================================================
# PictureEx
#===============================================================================
class PictureEx
attr_accessor :x # x-coordinate
attr_accessor :y # y-coordinate
attr_accessor :z # z value
attr_accessor :zoom_x # x directional zoom rate
attr_accessor :zoom_y # y directional zoom rate
attr_accessor :angle # rotation angle
attr_accessor :tone # tone
attr_accessor :color # color
attr_accessor :hue # filename hue
attr_accessor :opacity # opacity level
attr_accessor :visible # visibility boolean
attr_accessor :blend_type # blend method
attr_accessor :name # file name
attr_accessor :origin # starting point
attr_reader :src_rect # source rect
attr_reader :cropBottom # crops sprite to above this y-coordinate
attr_reader :frameUpdates # Array of processes updated in a frame
def initialize(z)
# process: [type, delay, total_duration, frame_counter, cb, etc.]
@processes = []
@x = 0.0
@y = 0.0
@z = z
@zoom_x = 100.0
@zoom_y = 100.0
@angle = 0
@rotate_speed = 0
@tone = Tone.new(0, 0, 0, 0)
@tone_duration = 0
@color = Color.new(0, 0, 0, 0)
@hue = 0
@opacity = 255.0
@visible = true
@blend_type = 0
@name = ""
@origin = PictureOrigin::TopLeft
@src_rect = Rect.new(0,0,-1,-1)
@cropBottom = -1
@frameUpdates = []
end
def callback(cb)
if cb.is_a?(Proc); cb.call(self)
elsif cb.is_a?(Array); cb[0].method(cb[1]).call(self)
elsif cb.is_a?(Method); cb.call(self)
end
end
def setCallback(delay, cb=nil)
delay = ensureDelayAndDuration(delay)
@processes.push([nil,delay,0,0,cb])
end
def running?
return @processes.length>0
end
def totalDuration
ret = 0
for process in @processes
dur = process[1]+process[2]
ret = dur if dur>ret
end
ret *= 20.0/Graphics.frame_rate
return ret.to_i
end
def ensureDelayAndDuration(delay, duration=nil)
delay = self.totalDuration if delay<0
delay *= Graphics.frame_rate/20.0
if !duration.nil?
duration *= Graphics.frame_rate/20.0
return delay.to_i, duration.to_i
end
return delay.to_i
end
def ensureDelay(delay)
return ensureDelayAndDuration(delay)
end
# speed is the angle to change by in 1/20 of a second. @rotate_speed is the
# angle to change by per frame.
# NOTE: This is not compatible with manually changing the angle at a certain
# point. If you make a sprite auto-rotate, you should not try to alter
# the angle another way too.
def rotate(speed)
@rotate_speed = speed*20.0/Graphics.frame_rate
while @rotate_speed<0; @rotate_speed += 360; end
@rotate_speed %= 360
end
def erase
self.name = ""
end
def clearProcesses
@processes = []
end
def adjustPosition(xOffset, yOffset)
for process in @processes
next if process[0]!=Processes::XY
process[5] += xOffset
process[6] += yOffset
process[7] += xOffset
process[8] += yOffset
end
end
def move(delay, duration, origin, x, y, zoom_x=100.0, zoom_y=100.0, opacity=255)
setOrigin(delay,duration,origin)
moveXY(delay,duration,x,y)
moveZoomXY(delay,duration,zoom_x,zoom_y)
moveOpacity(delay,duration,opacity)
end
def moveXY(delay, duration, x, y, cb=nil)
delay, duration = ensureDelayAndDuration(delay,duration)
@processes.push([Processes::XY,delay,duration,0,cb,@x,@y,x,y])
end
def setXY(delay, x, y, cb=nil)
moveXY(delay,0,x,y,cb)
end
def moveCurve(delay, duration, x1, y1, x2, y2, x3, y3, cb=nil)
delay, duration = ensureDelayAndDuration(delay,duration)
@processes.push([Processes::Curve,delay,duration,0,cb,[@x,@y,x1,y1,x2,y2,x3,y3]])
end
def moveDelta(delay, duration, x, y, cb=nil)
delay, duration = ensureDelayAndDuration(delay,duration)
@processes.push([Processes::DeltaXY,delay,duration,0,cb,@x,@y,x,y])
end
def setDelta(delay, x, y, cb=nil)
moveDelta(delay,0,x,y,cb)
end
def moveZ(delay, duration, z, cb=nil)
delay, duration = ensureDelayAndDuration(delay,duration)
@processes.push([Processes::Z,delay,duration,0,cb,@z,z])
end
def setZ(delay, z, cb=nil)
moveZ(delay,0,z,cb)
end
def moveZoomXY(delay, duration, zoom_x, zoom_y, cb=nil)
delay, duration = ensureDelayAndDuration(delay,duration)
@processes.push([Processes::Zoom,delay,duration,0,cb,@zoom_x,@zoom_y,zoom_x,zoom_y])
end
def setZoomXY(delay, zoom_x, zoom_y, cb=nil)
moveZoomXY(delay,0,zoom_x,zoom_y,cb)
end
def moveZoom(delay, duration, zoom, cb=nil)
moveZoomXY(delay,duration,zoom,zoom,cb)
end
def setZoom(delay, zoom, cb=nil)
moveZoomXY(delay,0,zoom,zoom,cb)
end
def moveAngle(delay, duration, angle, cb=nil)
delay, duration = ensureDelayAndDuration(delay,duration)
@processes.push([Processes::Angle,delay,duration,0,cb,@angle,angle])
end
def setAngle(delay, angle, cb=nil)
moveAngle(delay,0,angle,cb)
end
def moveTone(delay, duration, tone, cb=nil)
delay, duration = ensureDelayAndDuration(delay,duration)
target = (tone) ? tone.clone : Tone.new(0,0,0,0)
@processes.push([Processes::Tone,delay,duration,0,cb,@tone.clone,target])
end
def setTone(delay, tone, cb=nil)
moveTone(delay,0,tone,cb)
end
def moveColor(delay, duration, color, cb=nil)
delay, duration = ensureDelayAndDuration(delay,duration)
target = (color) ? color.clone : Color.new(0,0,0,0)
@processes.push([Processes::Color,delay,duration,0,cb,@color.clone,target])
end
def setColor(delay, color, cb=nil)
moveColor(delay,0,color,cb)
end
# Hue changes don't actually work.
def moveHue(delay, duration, hue, cb=nil)
delay, duration = ensureDelayAndDuration(delay,duration)
@processes.push([Processes::Hue,delay,duration,0,cb,@hue,hue])
end
# Hue changes don't actually work.
def setHue(delay, hue, cb=nil)
moveHue(delay,0,hue,cb)
end
def moveOpacity(delay, duration, opacity, cb=nil)
delay, duration = ensureDelayAndDuration(delay,duration)
@processes.push([Processes::Opacity,delay,duration,0,cb,@opacity,opacity])
end
def setOpacity(delay, opacity, cb=nil)
moveOpacity(delay,0,opacity,cb)
end
def setVisible(delay, visible, cb=nil)
delay = ensureDelay(delay)
@processes.push([Processes::Visible,delay,0,0,cb,visible])
end
# Only values of 0 (normal), 1 (additive) and 2 (subtractive) are allowed.
def setBlendType(delay, blend, cb=nil)
delay = ensureDelayAndDuration(delay)
@processes.push([Processes::BlendType,delay,0,0,cb,blend])
end
def setSE(delay, seFile, volume=nil, pitch=nil, cb=nil)
delay = ensureDelay(delay)
@processes.push([Processes::SE,delay,0,0,cb,seFile,volume,pitch])
end
def setName(delay, name, cb=nil)
delay = ensureDelay(delay)
@processes.push([Processes::Name,delay,0,0,cb,name])
end
def setOrigin(delay, origin, cb=nil)
delay = ensureDelay(delay)
@processes.push([Processes::Origin,delay,0,0,cb,origin])
end
def setSrc(delay, srcX, srcY, cb=nil)
delay = ensureDelay(delay)
@processes.push([Processes::Src,delay,0,0,cb,srcX,srcY])
end
def setSrcSize(delay, srcWidth, srcHeight, cb=nil)
delay = ensureDelay(delay)
@processes.push([Processes::SrcSize,delay,0,0,cb,srcWidth,srcHeight])
end
# Used to cut Pokémon sprites off when they faint and sink into the ground.
def setCropBottom(delay, y, cb=nil)
delay = ensureDelay(delay)
@processes.push([Processes::CropBottom,delay,0,0,cb,y])
end
def update
procEnded = false
@frameUpdates.clear
for i in 0...@processes.length
process = @processes[i]
# Decrease delay of processes that are scheduled to start later
if process[1]>=0
# Set initial values if the process will start this frame
if process[1]==0
case process[0]
when Processes::XY
process[5] = @x
process[6] = @y
when Processes::DeltaXY
process[5] = @x
process[6] = @y
process[7] += @x
process[8] += @y
when Processes::Curve
process[5][0] = @x
process[5][1] = @y
when Processes::Z
process[5] = @z
when Processes::Zoom
process[5] = @zoom_x
process[6] = @zoom_y
when Processes::Angle
process[5] = @angle
when Processes::Tone
process[5] = @tone.clone
when Processes::Color
process[5] = @color.clone
when Processes::Hue
process[5] = @hue
when Processes::Opacity
process[5] = @opacity
end
end
# Decrease delay counter
process[1] -= 1
# Process hasn't started yet, skip to the next one
next if process[1]>=0
end
# Update process
@frameUpdates.push(process[0]) if !@frameUpdates.include?(process[0])
fra = (process[2]==0) ? 1 : process[3] # Frame counter
dur = (process[2]==0) ? 1 : process[2] # Total duration of process
case process[0]
when Processes::XY, Processes::DeltaXY
@x = process[5] + fra * (process[7] - process[5]) / dur
@y = process[6] + fra * (process[8] - process[6]) / dur
when Processes::Curve
@x, @y = getCubicPoint2(process[5],fra.to_f/dur)
when Processes::Z
@z = process[5] + fra * (process[6] - process[5]) / dur
when Processes::Zoom
@zoom_x = process[5] + fra * (process[7] - process[5]) / dur
@zoom_y = process[6] + fra * (process[8] - process[6]) / dur
when Processes::Angle
@angle = process[5] + fra * (process[6] - process[5]) / dur
when Processes::Tone
@tone.red = process[5].red + fra * (process[6].red - process[5].red) / dur
@tone.green = process[5].green + fra * (process[6].green - process[5].green) / dur
@tone.blue = process[5].blue + fra * (process[6].blue - process[5].blue) / dur
@tone.gray = process[5].gray + fra * (process[6].gray - process[5].gray) / dur
when Processes::Color
@color.red = process[5].red + fra * (process[6].red - process[5].red) / dur
@color.green = process[5].green + fra * (process[6].green - process[5].green) / dur
@color.blue = process[5].blue + fra * (process[6].blue - process[5].blue) / dur
@color.alpha = process[5].alpha + fra * (process[6].alpha - process[5].alpha) / dur
when Processes::Hue
@hue = (process[6] - process[5]).to_f / dur
when Processes::Opacity
@opacity = process[5] + fra * (process[6] - process[5]) / dur
when Processes::Visible
@visible = process[5]
when Processes::BlendType
@blend_type = process[5]
when Processes::SE
pbSEPlay(process[5],process[6],process[7])
when Processes::Name
@name = process[5]
when Processes::Origin
@origin = process[5]
when Processes::Src
@src_rect.x = process[5]
@src_rect.y = process[6]
when Processes::SrcSize
@src_rect.width = process[5]
@src_rect.height = process[6]
when Processes::CropBottom
@cropBottom = process[5]
end
# Increase frame counter
process[3] += 1
if process[3]>process[2]
# Process has ended, erase it
callback(process[4]) if process[4]
@processes[i] = nil
procEnded = true
end
end
# Clear out empty spaces in @processes array caused by finished processes
@processes.compact! if procEnded
# Add the constant rotation speed
if @rotate_speed != 0
@frameUpdates.push(Processes::Angle) if !@frameUpdates.include?(Processes::Angle)
@angle += @rotate_speed
while @angle<0; @angle += 360; end
@angle %= 360
end
end
end
#===============================================================================
#
#===============================================================================
def setPictureSprite(sprite, picture, iconSprite=false)
return if picture.frameUpdates.length==0
for i in 0...picture.frameUpdates.length
case picture.frameUpdates[i]
when Processes::XY, Processes::DeltaXY
sprite.x = picture.x.round
sprite.y = picture.y.round
when Processes::Z
sprite.z = picture.z
when Processes::Zoom
sprite.zoom_x = picture.zoom_x / 100.0
sprite.zoom_y = picture.zoom_y / 100.0
when Processes::Angle
sprite.angle = picture.angle
when Processes::Tone
sprite.tone = picture.tone
when Processes::Color
sprite.color = picture.color
when Processes::Hue
# This doesn't do anything.
when Processes::BlendType
sprite.blend_type = picture.blend_type
when Processes::Opacity
sprite.opacity = picture.opacity
when Processes::Visible
sprite.visible = picture.visible
when Processes::Name
sprite.name = picture.name if iconSprite && sprite.name != picture.name
when Processes::Origin
case picture.origin
when PictureOrigin::TopLeft, PictureOrigin::Left, PictureOrigin::BottomLeft
sprite.ox = 0
when PictureOrigin::Top, PictureOrigin::Center, PictureOrigin::Bottom
sprite.ox = (sprite.bitmap && !sprite.bitmap.disposed?) ? sprite.src_rect.width/2 : 0
when PictureOrigin::TopRight, PictureOrigin::Right, PictureOrigin::BottomRight
sprite.ox = (sprite.bitmap && !sprite.bitmap.disposed?) ? sprite.src_rect.width : 0
end
case picture.origin
when PictureOrigin::TopLeft, PictureOrigin::Top, PictureOrigin::TopRight
sprite.oy = 0
when PictureOrigin::Left, PictureOrigin::Center, PictureOrigin::Right
sprite.oy = (sprite.bitmap && !sprite.bitmap.disposed?) ? sprite.src_rect.height/2 : 0
when PictureOrigin::BottomLeft, PictureOrigin::Bottom, PictureOrigin::BottomRight
sprite.oy = (sprite.bitmap && !sprite.bitmap.disposed?) ? sprite.src_rect.height : 0
end
when Processes::Src
next unless iconSprite && sprite.src_rect
sprite.src_rect.x = picture.src_rect.x
sprite.src_rect.y = picture.src_rect.y
when Processes::SrcSize
next unless iconSprite && sprite.src_rect
sprite.src_rect.width = picture.src_rect.width
sprite.src_rect.height = picture.src_rect.height
end
end
if iconSprite && sprite.src_rect && picture.cropBottom>=0
spriteBottom = sprite.y-sprite.oy+sprite.src_rect.height
if spriteBottom>picture.cropBottom
sprite.src_rect.height = [picture.cropBottom-sprite.y+sprite.oy,0].max
end
end
end
def setPictureIconSprite(sprite, picture)
setPictureSprite(sprite,picture,true)
end

View File

@@ -0,0 +1,172 @@
class Interpolator
ZOOM_X = 1
ZOOM_Y = 2
X = 3
Y = 4
OPACITY = 5
COLOR = 6
WAIT = 7
def initialize
@tweening = false
@tweensteps = []
@sprite = nil
@frames = 0
@step = 0
end
def tweening?
return @tweening
end
def tween(sprite,items,frames)
@tweensteps = []
if sprite && !sprite.disposed? && frames>0
@frames = frames
@step = 0
@sprite = sprite
for item in items
case item[0]
when ZOOM_X
@tweensteps[item[0]] = [sprite.zoom_x,item[1]-sprite.zoom_x]
when ZOOM_Y
@tweensteps[item[0]] = [sprite.zoom_y,item[1]-sprite.zoom_y]
when X
@tweensteps[item[0]] = [sprite.x,item[1]-sprite.x]
when Y
@tweensteps[item[0]] = [sprite.y,item[1]-sprite.y]
when OPACITY
@tweensteps[item[0]] = [sprite.opacity,item[1]-sprite.opacity]
when COLOR
@tweensteps[item[0]] = [sprite.color.clone,Color.new(
item[1].red-sprite.color.red,
item[1].green-sprite.color.green,
item[1].blue-sprite.color.blue,
item[1].alpha-sprite.color.alpha
)]
end
end
@tweening = true
end
end
def update
if @tweening
t = (@step*1.0)/@frames
for i in 0...@tweensteps.length
item = @tweensteps[i]
next if !item
case i
when ZOOM_X
@sprite.zoom_x = item[0]+item[1]*t
when ZOOM_Y
@sprite.zoom_y = item[0]+item[1]*t
when X
@sprite.x = item[0]+item[1]*t
when Y
@sprite.y = item[0]+item[1]*t
when OPACITY
@sprite.opacity = item[0]+item[1]*t
when COLOR
@sprite.color = Color.new(
item[0].red+item[1].red*t,
item[0].green+item[1].green*t,
item[0].blue+item[1].blue*t,
item[0].alpha+item[1].alpha*t
)
end
end
@step += 1
if @step==@frames
@step = 0
@frames = 0
@tweening = false
end
end
end
end
class RectInterpolator
def initialize(oldrect,newrect,frames)
restart(oldrect,newrect,frames)
end
def restart(oldrect,newrect,frames)
@oldrect = oldrect
@newrect = newrect
@frames = [frames,1].max
@curframe = 0
@rect = oldrect.clone
end
def set(rect)
rect.set(@rect.x,@rect.y,@rect.width,@rect.height)
end
def done?
@curframe>@frames
end
def update
return if done?
t = (@curframe*1.0/@frames)
x1 = @oldrect.x
x2 = @newrect.x
x = x1+t*(x2-x1)
y1 = @oldrect.y
y2 = @newrect.y
y = y1+t*(y2-y1)
rx1 = @oldrect.x+@oldrect.width
rx2 = @newrect.x+@newrect.width
rx = rx1+t*(rx2-rx1)
ry1 = @oldrect.y+@oldrect.height
ry2 = @newrect.y+@newrect.height
ry = ry1+t*(ry2-ry1)
minx = x<rx ? x : rx
maxx = x>rx ? x : rx
miny = y<ry ? y : ry
maxy = y>ry ? y : ry
@rect.set(minx,miny,maxx-minx,maxy-miny)
@curframe += 1
end
end
class PointInterpolator
attr_reader :x
attr_reader :y
def initialize(oldx,oldy,newx,newy,frames)
restart(oldx,oldy,newx,newy,frames)
end
def restart(oldx,oldy,newx,newy,frames)
@oldx = oldx
@oldy = oldy
@newx = newx
@newy = newy
@frames = frames
@curframe = 0
@x = oldx
@y = oldy
end
def done?
@curframe>@frames
end
def update
return if done?
t = (@curframe*1.0/@frames)
rx1 = @oldx
rx2 = @newx
@x = rx1+t*(rx2-rx1)
ry1 = @oldy
ry2 = @newy
@y = ry1+t*(ry2-ry1)
@curframe += 1
end
end

View File

@@ -0,0 +1,43 @@
module ScreenPosHelper
def self.pbScreenZoomX(ch)
return Game_Map::TILE_WIDTH/32.0
end
def self.pbScreenZoomY(ch)
return Game_Map::TILE_HEIGHT/32.0
end
def self.pbScreenX(ch)
return ch.screen_x
end
def self.pbScreenY(ch)
return ch.screen_y
end
@heightcache={}
def self.bmHeight(bm)
h=@heightcache[bm]
if !h
bmap=AnimatedBitmap.new("Graphics/Characters/"+bm,0)
h=bmap.height
@heightcache[bm]=h
bmap.dispose
end
return h
end
def self.pbScreenZ(ch,height=nil)
if height==nil
height=0
if ch.tile_id > 0
height=32
elsif ch.character_name!=""
height=bmHeight(ch.character_name)/4
end
end
ret=ch.screen_z(height)
return ret
end
end

View File

@@ -0,0 +1,35 @@
#[FRAME1 [x,y]],[FRAME2 [x,y], etc.]
#
# exact number of pixels that the sprite needs to be moved for each frame
# add 2 pixels on even frames
module Outfit_Offsets
BASE_OFFSET = [[0, 0], [0, 0], [0, 0], [0, 0]]
RUN_OFFSETS_DOWN = [[0, 2], [0, 6], [0, 2], [0, 6]]
RUN_OFFSETS_LEFT = [[-2, -2], [-2, -2], [-2, -2], [-2, -2]]
RUN_OFFSETS_RIGHT = [[2, -2], [2, -2], [2, -2], [2, -2]]
RUN_OFFSETS_UP = [[0, -2], [0, -2], [0, -2], [0, -2]]
SURF_OFFSETS_DOWN = [[0, -6], [0, -4], [0, -6], [0, -4]]
SURF_OFFSETS_LEFT = [[-2, -10], [-2, -8], [-2, -10], [-2, -8]]
SURF_OFFSETS_RIGHT = [[4, -10], [4, -8], [4, -10], [4, -8]]
#SURF_OFFSETS_UP = [[0, -6], [0, -4], [0, -6], [0, -4]]
SURF_OFFSETS_UP = [[0, -10], [0, -8], [0, -10], [0, -8]]
DIVE_OFFSETS_DOWN = [[0, -6], [0, -4], [0, -6], [0, -4]]
DIVE_OFFSETS_LEFT = [[6, -8], [6, -6], [6, -8], [6, -6]]
DIVE_OFFSETS_RIGHT = [[-6, -8], [-6, -6], [-6, -8], [-6, -6]]
DIVE_OFFSETS_UP = [[0, -2], [0, 0], [0, -2], [0, 0]]
BIKE_OFFSETS_DOWN = [[0, -2], [2, 0], [0, -2], [-2, 0]]
BIKE_OFFSETS_LEFT = [[-4, -4], [-2, -2], [-4, -4], [-6, -2]]
BIKE_OFFSETS_RIGHT = [[4, -4], [2, -2], [4, -4], [6, -2]]
BIKE_OFFSETS_UP = [[0, -2], [-2, 0], [0, -2], [2, 0]]
FISH_OFFSETS_DOWN = [[0, -6], [0, -2], [0, -8], [2, -6]]
FISH_OFFSETS_LEFT = [[0, -8], [-6, -6], [0, -8], [2, -8]]
FISH_OFFSETS_RIGHT = [[0, -8], [6, -6], [0, -8], [-2, -8]]
FISH_OFFSETS_UP = [[0, -6], [0, -6], [0, -6], [2, -4]]
end

View File

@@ -0,0 +1,172 @@
class Sprite_Wearable < RPG::Sprite
attr_accessor :filename
attr_accessor :action
attr_accessor :sprite
def initialize(player_sprite, filename, action, viewport, relative_z=0)
@player_sprite = player_sprite
@viewport = viewport
@sprite = Sprite.new(@viewport)
@wearableBitmap = AnimatedBitmap.new(filename) if pbResolveBitmap(filename)
@filename = filename
@sprite.bitmap = @wearableBitmap.bitmap if @wearableBitmap
@action = action
@color = 0
@frameWidth = 80 #@sprite.width
@frameHeight = 80 #@sprite.height / 4
@sprite.z = 0
@relative_z=relative_z #relative to player
echoln(_INTL("init had at z = {1}, player sprite at {2}",@sprite.z,@player_sprite.z))
#Unused position offset
# @x_pos_base_offset = 0
# @y_pos_base_offset = 0
end
def apply_sprite_offset(offsets_array, current_frame)
@sprite.x += offsets_array[current_frame][0]
@sprite.y += offsets_array[current_frame][1]
end
def adjustPositionForScreenScrolling
return if !$game_map.scrolling? && !@was_just_scrolling
if $game_map.scrolling?
@was_just_scrolling=true
else
@was_just_scrolling=false
end
offset_x = 0
offset_y = 0
case $game_map.scroll_direction
when DIRECTION_RIGHT
offset_x=-8
when DIRECTION_LEFT
offset_x=8
when DIRECTION_UP
offset_y=8
@sprite.z+=50 #weird layering glitch for some reason otherwise. It's reset to the correct value in the next animation frame
when DIRECTION_DOWN
offset_y=-8
end
@sprite.x+=offset_x
@sprite.y+=offset_y
end
def set_sprite_position(action, direction, current_frame)
@sprite.x = @player_sprite.x - @player_sprite.ox
@sprite.y = @player_sprite.y - @player_sprite.oy
case action
when "run"
if direction == DIRECTION_DOWN
apply_sprite_offset(Outfit_Offsets::RUN_OFFSETS_DOWN, current_frame)
elsif direction == DIRECTION_LEFT
apply_sprite_offset(Outfit_Offsets::RUN_OFFSETS_LEFT, current_frame)
elsif direction == DIRECTION_RIGHT
apply_sprite_offset(Outfit_Offsets::RUN_OFFSETS_RIGHT, current_frame)
elsif direction == DIRECTION_UP
apply_sprite_offset(Outfit_Offsets::RUN_OFFSETS_UP, current_frame)
end
when "surf"
if direction == DIRECTION_DOWN
apply_sprite_offset(Outfit_Offsets::SURF_OFFSETS_DOWN,current_frame)
elsif direction == DIRECTION_LEFT
apply_sprite_offset( Outfit_Offsets::SURF_OFFSETS_LEFT,current_frame)
elsif direction == DIRECTION_RIGHT
apply_sprite_offset( Outfit_Offsets::SURF_OFFSETS_RIGHT,current_frame)
elsif direction == DIRECTION_UP
apply_sprite_offset( Outfit_Offsets::SURF_OFFSETS_UP,current_frame)
end
when "dive"
if direction == DIRECTION_DOWN
apply_sprite_offset(Outfit_Offsets::DIVE_OFFSETS_DOWN,current_frame)
elsif direction == DIRECTION_LEFT
apply_sprite_offset( Outfit_Offsets::DIVE_OFFSETS_LEFT,current_frame)
elsif direction == DIRECTION_RIGHT
apply_sprite_offset( Outfit_Offsets::DIVE_OFFSETS_RIGHT,current_frame)
elsif direction == DIRECTION_UP
apply_sprite_offset( Outfit_Offsets::DIVE_OFFSETS_UP,current_frame)
end
when "bike"
if direction == DIRECTION_DOWN
apply_sprite_offset(Outfit_Offsets::BIKE_OFFSETS_DOWN,current_frame)
elsif direction == DIRECTION_LEFT
apply_sprite_offset( Outfit_Offsets::BIKE_OFFSETS_LEFT,current_frame)
elsif direction == DIRECTION_RIGHT
apply_sprite_offset( Outfit_Offsets::BIKE_OFFSETS_RIGHT,current_frame)
elsif direction == DIRECTION_UP
apply_sprite_offset( Outfit_Offsets::BIKE_OFFSETS_UP,current_frame)
end
when "fish"
if direction == DIRECTION_DOWN
apply_sprite_offset(Outfit_Offsets::FISH_OFFSETS_DOWN,current_frame)
elsif direction == DIRECTION_LEFT
apply_sprite_offset( Outfit_Offsets::FISH_OFFSETS_LEFT,current_frame)
elsif direction == DIRECTION_RIGHT
apply_sprite_offset( Outfit_Offsets::FISH_OFFSETS_RIGHT,current_frame)
elsif direction == DIRECTION_UP
apply_sprite_offset( Outfit_Offsets::FISH_OFFSETS_UP,current_frame)
end
else
@sprite.x = @player_sprite.x - @player_sprite.ox
@sprite.y = @player_sprite.y - @player_sprite.oy
end
adjustPositionForScreenScrolling()
@sprite.y -= 2 if current_frame % 2 == 1
end
def animate(action, frame=nil)
@action = action
current_frame = @player_sprite.character.pattern if !frame
direction = @player_sprite.character.direction
crop_spritesheet(direction)
adjust_layer()
set_sprite_position(@action, direction, current_frame)
end
def update(action, filename,color)
@sprite.opacity = @player_sprite.opacity if @wearableBitmap
@sprite.opacity=0 if $game_player.hasGraphicsOverride?
if filename != @filename || color != @color
if pbResolveBitmap(filename)
#echoln pbResolveBitmap(filename)
@wearableBitmap = AnimatedBitmap.new(filename,color)
@sprite.bitmap = @wearableBitmap.bitmap
else
@wearableBitmap = nil
@sprite.bitmap = nil
end
@color =color
@filename = filename
end
animate(action)
end
def adjust_layer()
if @sprite.z != @player_sprite.z+@relative_z
@sprite.z = @player_sprite.z+@relative_z
end
end
def crop_spritesheet(direction)
sprite_x = 0
sprite_y = ((direction - 2) / 2) * @frameHeight
@sprite.src_rect.set(sprite_x, sprite_y, @frameWidth, @frameHeight)
end
def dispose
return if @disposed
@sprite.dispose if @sprite
@sprite = nil
@disposed = true
end
def disposed?
@disposed
end
end

View File

@@ -0,0 +1,85 @@
class Sprite_Hair < Sprite_Wearable
def initialize(player_sprite, filename, action, viewport)
super
@relative_z = 1
#@sprite.z = @player_sprite.z + 1
end
def animate(action, frame = nil)
@action = action
current_frame = @player_sprite.character.pattern if !frame
direction = @player_sprite.character.direction
crop_spritesheet(direction, current_frame, action)
adjust_layer()
set_sprite_position(@action, direction, current_frame)
end
def crop_spritesheet(direction, current_frame, action)
sprite_x = ((current_frame)) * @frameWidth
# Don't animate surf
sprite_x = 0 if action == "surf"
sprite_y = ((direction - 2) / 2) * @frameHeight
@sprite.src_rect.set(sprite_x, sprite_y, @frameWidth, @frameHeight)
end
def set_sprite_position(action, direction, current_frame)
@sprite.x = @player_sprite.x - @player_sprite.ox
@sprite.y = @player_sprite.y - @player_sprite.oy
case action
when "run"
if direction == DIRECTION_DOWN
apply_sprite_offset(Outfit_Offsets::RUN_OFFSETS_DOWN, current_frame)
elsif direction == DIRECTION_LEFT
apply_sprite_offset(Outfit_Offsets::RUN_OFFSETS_LEFT, current_frame)
elsif direction == DIRECTION_RIGHT
apply_sprite_offset(Outfit_Offsets::RUN_OFFSETS_RIGHT, current_frame)
elsif direction == DIRECTION_UP
apply_sprite_offset(Outfit_Offsets::RUN_OFFSETS_UP, current_frame)
end
when "surf"
if direction == DIRECTION_DOWN # Always animate as if on the first frame
apply_sprite_offset(Outfit_Offsets::SURF_OFFSETS_DOWN, 0)
elsif direction == DIRECTION_LEFT
apply_sprite_offset(Outfit_Offsets::SURF_OFFSETS_LEFT, 0)
elsif direction == DIRECTION_RIGHT
apply_sprite_offset(Outfit_Offsets::SURF_OFFSETS_RIGHT, 0)
elsif direction == DIRECTION_UP
apply_sprite_offset(Outfit_Offsets::SURF_OFFSETS_UP, 0)
end
when "dive"
if direction == DIRECTION_DOWN
apply_sprite_offset(Outfit_Offsets::DIVE_OFFSETS_DOWN, current_frame)
elsif direction == DIRECTION_LEFT
apply_sprite_offset(Outfit_Offsets::DIVE_OFFSETS_LEFT, current_frame)
elsif direction == DIRECTION_RIGHT
apply_sprite_offset(Outfit_Offsets::DIVE_OFFSETS_RIGHT, current_frame)
elsif direction == DIRECTION_UP
apply_sprite_offset(Outfit_Offsets::DIVE_OFFSETS_UP, current_frame)
end
when "bike"
if direction == DIRECTION_DOWN
apply_sprite_offset(Outfit_Offsets::BIKE_OFFSETS_DOWN, current_frame)
elsif direction == DIRECTION_LEFT
apply_sprite_offset(Outfit_Offsets::BIKE_OFFSETS_LEFT, current_frame)
elsif direction == DIRECTION_RIGHT
apply_sprite_offset(Outfit_Offsets::BIKE_OFFSETS_RIGHT, current_frame)
elsif direction == DIRECTION_UP
apply_sprite_offset(Outfit_Offsets::BIKE_OFFSETS_UP, current_frame)
end
when "fish"
if direction == DIRECTION_DOWN
apply_sprite_offset(Outfit_Offsets::FISH_OFFSETS_DOWN, current_frame)
elsif direction == DIRECTION_LEFT
apply_sprite_offset(Outfit_Offsets::FISH_OFFSETS_LEFT, current_frame)
elsif direction == DIRECTION_RIGHT
apply_sprite_offset(Outfit_Offsets::FISH_OFFSETS_RIGHT, current_frame)
elsif direction == DIRECTION_UP
apply_sprite_offset(Outfit_Offsets::FISH_OFFSETS_UP, current_frame)
end
end
adjustPositionForScreenScrolling()
end
end

View File

@@ -0,0 +1,8 @@
class Sprite_Hat < Sprite_Wearable
def initialize(player_sprite, filename, action, viewport, relative_z=2)
super
@relative_z = relative_z
#@sprite.z = @player_sprite.z + 2
end
end

View File

@@ -0,0 +1,129 @@
class Sprite_Player < Sprite_Character
def initialize(viewport, character = nil)
super
@viewport = viewport
@outfit_bitmap = nil
# @hat_bitmap = nil
# @hat2_bitmap = nil
hatFilename = ""
hairFilename = ""
@hat = Sprite_Hat.new(self, hatFilename, @character_name, @viewport,3)
@hat2 = Sprite_Hat.new(self, hatFilename, @character_name, @viewport,2)
@hair = Sprite_Hair.new(self, hairFilename, @character_name, @viewport)
@previous_skinTone = 0
@current_bitmap = nil
@previous_action =nil
echoln "init playa"
getClothedPlayerSprite(true)
end
def updateCharacterBitmap
skinTone = $Trainer.skin_tone ? $Trainer.skin_tone : 0
baseBitmapFilename = getBaseOverworldSpriteFilename(@character_name, skinTone)
if !pbResolveBitmap(baseBitmapFilename)
baseBitmapFilename = Settings::PLAYER_GRAPHICS_FOLDER + @character_name
end
AnimatedBitmap.new(baseBitmapFilename, @character_hue)
end
def applyDayNightTone
super
pbDayNightTint(@hat.sprite) if @hat && @hat.sprite.bitmap
pbDayNightTint(@hat2.sprite) if @hat2 && @hat2.sprite.bitmap
pbDayNightTint(@hair.sprite) if @hair && @hair.sprite.bitmap
end
def opacity=(value)
super
@hat.sprite.opacity= value if @hat && @hat.sprite.bitmap
@hat2.sprite.opacity= value if @hat2 && @hat2.sprite.bitmap
@hair.sprite.opacity= value if @hair && @hair.sprite.bitmap
end
def getClothedPlayerSprite(forceUpdate=false)
if @previous_action != @character_name || forceUpdate
@current_bitmap = generateClothedBitmap
end
@previous_action = @character_name
@hair.animate(@character_name) if @hair
@hat.animate(@character_name) if @hat
@hat2.animate(@character_name) if @hat2
return @current_bitmap
end
def generateClothedBitmap()
@charbitmap.bitmap.clone #nekkid sprite
baseBitmap = @charbitmap.bitmap.clone #nekkid sprite
if $game_player.hasGraphicsOverride?
@hair.update(@character_name, "", 0) if @hair
@hat.update(@character_name, "", 0) if @hat
@hat2.update(@character_name, "", 0) if @hat2
return baseBitmap
end
outfitFilename = getOverworldOutfitFilename($Trainer.clothes, @character_name) #
outfitFilename = getOverworldOutfitFilename(Settings::PLAYER_TEMP_OUTFIT_FALLBACK) if !pbResolveBitmap(outfitFilename)
hairFilename = getOverworldHairFilename($Trainer.hair)
hatFilename = getOverworldHatFilename($Trainer.hat)
hat2Filename = getOverworldHatFilename($Trainer.hat2)
hair_color_shift = $Trainer.hair_color
hat_color_shift = $Trainer.hat_color
hat2_color_shift = $Trainer.hat2_color
clothes_color_shift = $Trainer.clothes_color
hair_color_shift = 0 if !hair_color_shift
hat_color_shift = 0 if !hat_color_shift
hat2_color_shift = 0 if !hat2_color_shift
clothes_color_shift = 0 if !clothes_color_shift
@hair.update(@character_name, hairFilename, hair_color_shift) if @hair
@hat.update(@character_name, hatFilename, hat_color_shift) if @hat
@hat2.update(@character_name, hat2Filename, hat2_color_shift) if @hat2
if !pbResolveBitmap(outfitFilename)
raise "No temp clothes graphics available"
end
outfitBitmap = AnimatedBitmap.new(outfitFilename, clothes_color_shift) if pbResolveBitmap(outfitFilename)
baseBitmap.blt(0, 0, outfitBitmap.bitmap, outfitBitmap.bitmap.rect) if outfitBitmap
@previous_action = @character_name
return baseBitmap
end
def update
super
if Settings::GAME_ID == :IF_HOENN && $PokemonGlobal.diving
self.z = -4
@hat.adjust_layer if @hat
@hat2.adjust_layer if @hat2
@hair.adjust_layer if @hair
end
end
def dispose
super
@hat.dispose if @hat
@hat2.dispose if @hat2
@hair.dispose if @hair
end
def pbLoadOutfitBitmap(outfitFileName)
begin
outfitBitmap = RPG::Cache.load_bitmap("", outfitFileName)
return outfitBitmap
rescue
return nil
end
end
end

View File

@@ -0,0 +1,845 @@
#===============================================================================
#
#===============================================================================
class TilemapRenderer
attr_reader :tilesets
attr_reader :autotiles
attr_reader :custom_autotile_ids
attr_accessor :tone
attr_accessor :color
attr_reader :viewport
attr_accessor :ox # Does nothing
attr_accessor :oy # Does nothing
attr_accessor :visible # Does nothing
DISPLAY_TILE_WIDTH = Game_Map::TILE_WIDTH rescue 32
DISPLAY_TILE_HEIGHT = Game_Map::TILE_HEIGHT rescue 32
SOURCE_TILE_WIDTH = 32
SOURCE_TILE_HEIGHT = 32
ZOOM_X = DISPLAY_TILE_WIDTH / SOURCE_TILE_WIDTH
ZOOM_Y = DISPLAY_TILE_HEIGHT / SOURCE_TILE_HEIGHT
TILESET_TILES_PER_ROW = 8
AUTOTILES_COUNT = 8 # Counting the blank tile as an autotile
TILES_PER_AUTOTILE = 48
TILESET_START_ID = AUTOTILES_COUNT * TILES_PER_AUTOTILE
# If an autotile's filename ends with "[x]", its frame duration will be x/20
# seconds instead.
AUTOTILE_FRAME_DURATION = 5 # In 1/20ths of a second
# Filenames of extra autotiles for each tileset. Each tileset's entry is an
# array containing two other arrays (you can leave either of those empty, but
# they must be defined):
# - The first sub-array is for large autotiles, i.e. ones with 48 different
# tile layouts. For example, "Brick path" and "Sea".
# - The second is for single tile autotiles. For example, "Flowers1" and
# "Waterfall"
# The top tiles of the tileset will instead use these autotiles. Large
# autotiles come first, in the same 8x6 layout as you see when you double-
# click on a real autotile in RMXP. After that are the single tile autotiles.
# Extra autotiles are only useful if the tiles are animated, because otherwise
# you just have some tiles which belong in the tileset instead.
# Examples:
# 1 => [["Sand shore"], ["Flowers2"]],
# 2 => [[], ["Flowers2", "Waterfall", "Waterfall crest", "Waterfall bottom"]],
# 6 => [["Water rock", "Sea deep"], []]
EXTRA_AUTOTILES = {
1 => { #route-field
996 => "flowers_orange[10]",
991 => "flowers_pink[10]",
999 => "flowers_yellow[10]",
1007 => "flowers_blue[10]",
1015 => "flowers_purple[10]",
1023 => "flowers_red[10]",
1031 => "flowers_grey[10]",
1039 => "flowers_white[10]",
#water cliffs
1363 => "water_rock10", 1364 => "water_rock11",
1389 => "water_rock01", 1391 => "water_rock09",
1381 => "water_rock_shore08", 1382 => "water_rock_shore09",
1377 => "water_rock08", 1379 => "water_rock07",
1384 => "water_rock_shore01", 1385 => "water_rock02", 1387 => "water_rock06", 1397 => "water_rock_shore07",
1392 => "water_rock_shore02", 1393 =>"water_rock03", 1394 => "water_rock04", 1395 => "water_rock05", 1396 => "water_rock_shore06",
1401 =>"water_rock_shore03", 1402 =>"water_rock_shore04", 1403 =>"water_rock_shore05",
},
2 => { #small-town
996 => "flowers_orange[10]",
991 => "flowers_pink[10]",
999 => "flowers_yellow[10]",
1007 => "flowers_blue[10]",
1015 => "flowers_purple[10]",
1023 => "flowers_red[10]",
1031 => "flowers_grey[10]",
1039 => "flowers_white[10]",
},
5 => { #Rustboro
996 => "flowers_orange[10]",
991 => "flowers_pink[10]",
999 => "flowers_yellow[10]",
1007 => "flowers_blue[10]",
1015 => "flowers_purple[10]",
1023 => "flowers_red[10]",
1031 => "flowers_grey[10]",
1039 => "flowers_white[10]",
},
6 => { #Dewford Town
#water cliffs
1363 => "water_rock10", 1364 => "water_rock11",
1389 => "water_rock01", 1391 => "water_rock09",
1381 => "water_rock_shore08", 1382 => "water_rock_shore09",
1377 => "water_rock08", 1379 => "water_rock07",
1384 => "water_rock_shore01", 1385 => "water_rock02", 1387 => "water_rock06", 1397 => "water_rock_shore07",
1392 => "water_rock_shore02", 1393 =>"water_rock03", 1394 => "water_rock04", 1395 => "water_rock05", 1396 => "water_rock_shore06",
1401 =>"water_rock_shore03", 1402 =>"water_rock_shore04", 1403 =>"water_rock_shore05",
},
7 => { #Sea Route
#water rocks
1173 => "water_rock_medium[15]",
#water cliffs
1363 => "water_rock10", 1364 => "water_rock11",
1389 => "water_rock01", 1391 => "water_rock09",
1381 => "water_rock_shore08", 1382 => "water_rock_shore09",
1377 => "water_rock08", 1379 => "water_rock07",
1384 => "water_rock_shore01", 1385 => "water_rock02", 1387 => "water_rock06", 1397 => "water_rock_shore07",
1392 => "water_rock_shore02", 1393 =>"water_rock03", 1394 => "water_rock04", 1395 => "water_rock05", 1396 => "water_rock_shore06",
1401 =>"water_rock_shore03", 1402 =>"water_rock_shore04", 1403 =>"water_rock_shore05",
},
23 => { #outdoor
1232 => "flowers_orange[10]",
1240 => "flowers_pink[10]",
1248 => "flowers_yellow[10]",
1256 => "flowers_blue[10]",
1264 => "flowers_purple[10]",
1272 => "flowers_red[10]",
1280 => "flowers_grey[10]",
1288 => "flowers_white[10]",
},
30 => {
2620 => "flowers_orange[10]",
2628 => "flowers_pink[10]",
2636 => "flowers_yellow[10]",
2644 => "flowers_blue[10]",
2652 => "flowers_purple[10]",
2660 => "flowers_red[10]",
2668 => "flowers_grey[10]",
2676 => "flowers_white[10]",
}
}
WIND_TREE_AUTOTILES = {
1 => { #Route-field
864 => "tree_sway_single_1",
865 => "tree_sway_single_2",
872 => "tree_sway_single_3",
873 => "tree_sway_single_4",
880 => "tree_sway_single_5",
881 => "tree_sway_single_6",
866 => "tree_sway_group_1",
867 => "tree_sway_group_2",
874 => "tree_sway_group_3",
875 => "tree_sway_group_4",
},
2 => { #small-town
#trees
864 => "tree_sway_single_1",
865 => "tree_sway_single_2",
872 => "tree_sway_single_3",
873 => "tree_sway_single_4",
880 => "tree_sway_single_5",
881 => "tree_sway_single_6",
866 => "tree_sway_group_1",
867 => "tree_sway_group_2",
874 => "tree_sway_group_3",
875 => "tree_sway_group_4",
},
5 => { #Rustboro
#trees
864 => "tree_sway_single_1",
865 => "tree_sway_single_2",
872 => "tree_sway_single_3",
873 => "tree_sway_single_4",
880 => "tree_sway_single_5",
881 => "tree_sway_single_6",
866 => "tree_sway_group_1",
867 => "tree_sway_group_2",
874 => "tree_sway_group_3",
875 => "tree_sway_group_4",
},
9 => { #Route Forest
#trees
864 => "tree_sway_single_1",
865 => "tree_sway_single_2",
872 => "tree_sway_single_3",
873 => "tree_sway_single_4",
880 => "tree_sway_single_5",
881 => "tree_sway_single_6",
866 => "tree_sway_group_1",
867 => "tree_sway_group_2",
874 => "tree_sway_group_3",
875 => "tree_sway_group_4",
},
}
#=============================================================================
#
#=============================================================================
class TilesetBitmaps
attr_accessor :changed
attr_accessor :bitmaps
def initialize
@bitmaps = {}
@bitmap_wraps = {} # Whether each tileset is a mega texture and has multiple columns
@load_counts = {}
@bridge = 0
@changed = true
end
def [](filename)
return @bitmaps[filename]
end
def []=(filename, bitmap)
return if nil_or_empty?(filename)
@bitmaps[filename] = bitmap
@bitmap_wraps[filename] = false
@changed = true
end
def add(filename)
return if nil_or_empty?(filename)
if @bitmaps[filename]
@load_counts[filename] += 1
return
end
bitmap = pbGetTileset(filename)
@bitmap_wraps[filename] = false
if bitmap.mega?
self[filename] = TilemapRenderer::TilesetWrapper.wrapTileset(bitmap)
@bitmap_wraps[filename] = true
bitmap.dispose
else
self[filename] = bitmap
end
@load_counts[filename] = 1
end
def remove(filename)
return if nil_or_empty?(filename) || !@bitmaps[filename]
if @load_counts[filename] > 1
@load_counts[filename] -= 1
return
end
@bitmaps[filename].dispose
@bitmaps.delete(filename)
@bitmap_wraps.delete(filename)
@load_counts.delete(filename)
end
def set_src_rect(tile, tile_id)
return if nil_or_empty?(tile.filename)
return if !@bitmaps[tile.filename]
tile.src_rect.x = ((tile_id - TILESET_START_ID) % TILESET_TILES_PER_ROW) * SOURCE_TILE_WIDTH
tile.src_rect.y = ((tile_id - TILESET_START_ID) / TILESET_TILES_PER_ROW) * SOURCE_TILE_HEIGHT
if @bitmap_wraps[tile.filename]
height = @bitmaps[tile.filename].height
col = (tile_id - TILESET_START_ID) * SOURCE_TILE_HEIGHT / (TILESET_TILES_PER_ROW * height)
tile.src_rect.x += col * TILESET_TILES_PER_ROW * SOURCE_TILE_WIDTH
tile.src_rect.y -= col * height
end
end
def update; end
end
#=============================================================================
#
#=============================================================================
class AutotileBitmaps < TilesetBitmaps
attr_reader :current_frames
def initialize
super
@frame_counts = {} # Number of frames in each autotile
@frame_durations = {} # How long each frame lasts per autotile
@current_frames = {} # Which frame each autotile is currently showing
@timer = 0.0 # System.uptime
end
def []=(filename, value)
super
return if nil_or_empty?(filename)
frame_count(filename, true)
set_current_frame(filename)
end
EXPANDED_AUTOTILES_FOLDER = "Graphics/Autotiles/ExpandedAutotiles/"
def add(filename)
return if nil_or_empty?(filename)
if @bitmaps[filename]
@load_counts[filename] += 1
return
end
# Try to load expanded autotile from cache first
cached_path = File.join("Graphics", "Autotiles/ExpandedAutotiles", "#{filename}.png")
if safeExists?(cached_path)
#echoln "Loading cached expanded autotile for #{filename}"
bitmap = RPG::Cache.load_bitmap(EXPANDED_AUTOTILES_FOLDER, filename)
duration = AUTOTILE_FRAME_DURATION
if filename[/\[\s*(\d+?)\s*\]\s*$/]
duration = $~[1].to_i
end
@frame_durations[filename] = duration.to_f / 20
else
orig_bitmap = pbGetAutotile(filename)
@bitmap_wraps[filename] = false
duration = AUTOTILE_FRAME_DURATION
if filename[/\[\s*(\d+?)\s*\]\s*$/]
duration = $~[1].to_i
end
@frame_durations[filename] = duration.to_f / 20
expanded_bitmap = AutotileExpander.expand(orig_bitmap)
# Save expanded bitmap to cache for next time
Dir.mkdir(EXPANDED_AUTOTILES_FOLDER) unless Dir.exist?(EXPANDED_AUTOTILES_FOLDER)
expanded_bitmap.save_to_png(cached_path)
bitmap = expanded_bitmap
orig_bitmap.dispose if orig_bitmap != expanded_bitmap
end
self[filename] = bitmap
if bitmap.height > SOURCE_TILE_HEIGHT && bitmap.height < TILES_PER_AUTOTILE * SOURCE_TILE_HEIGHT
@bitmap_wraps[filename] = true
end
@load_counts[filename] = 1
end
def remove(filename)
super
return if @load_counts[filename] && @load_counts[filename] > 0
@frame_counts.delete(filename)
@current_frames.delete(filename)
@frame_durations.delete(filename)
end
def frame_count(filename, force_recalc = false)
if !@frame_counts[filename] || force_recalc
return 0 if !@bitmaps[filename]
bitmap = @bitmaps[filename]
@frame_counts[filename] = [bitmap.width / SOURCE_TILE_WIDTH, 1].max
if bitmap.height > SOURCE_TILE_HEIGHT && @bitmap_wraps[filename]
@frame_counts[filename] /= 2
end
end
return @frame_counts[filename]
end
def animated?(filename)
return frame_count(filename) > 1
end
def current_frame(filename)
set_current_frame(filename) if !@current_frames[filename]
return @current_frames[filename]
end
def set_current_frame(filename)
frames = frame_count(filename)
if frames < 2
@current_frames[filename] = 0
else
@current_frames[filename] = (@timer / @frame_durations[filename]).floor % frames
end
end
def set_src_rect(tile, tile_id)
filename = tile.filename
# Check if this tile_id was overridden to use a specific autotile
override_filename = @custom_autotile_ids && @custom_autotile_ids[tile_id]
filename = override_filename if override_filename
return if nil_or_empty?(filename)
return unless @bitmaps[filename]
frame = current_frame(filename)
if @bitmaps[filename].height == SOURCE_TILE_HEIGHT
tile.src_rect.x = frame * SOURCE_TILE_WIDTH
tile.src_rect.y = 0
return
end
wraps = @bitmap_wraps[filename]
high_id = ((tile_id % TILES_PER_AUTOTILE) >= TILES_PER_AUTOTILE / 2)
tile.src_rect.x = 0
tile.src_rect.y = (tile_id % TILES_PER_AUTOTILE) * SOURCE_TILE_HEIGHT
if wraps && high_id
tile.src_rect.x = SOURCE_TILE_WIDTH
tile.src_rect.y -= SOURCE_TILE_HEIGHT * TILES_PER_AUTOTILE / 2
end
tile.src_rect.x += frame * SOURCE_TILE_WIDTH * (wraps ? 2 : 1)
# Override the filename in the tile object for consistency
tile.filename = filename if override_filename
end
def update
super
@timer += Graphics.delta_s
# Update the current frame for each autotile
@bitmaps.each_key do |filename|
next if !@bitmaps[filename] || @bitmaps[filename].disposed?
old_frame = @current_frames[filename]
set_current_frame(filename)
@changed = true if @current_frames[filename] != old_frame
end
end
end
#=============================================================================
#
#=============================================================================
class TileSprite < Sprite
attr_accessor :filename
attr_accessor :tile_id
attr_accessor :is_autotile
attr_accessor :animated
attr_accessor :priority
attr_accessor :shows_reflection
attr_accessor :underwater_tile
attr_accessor :bridge
attr_accessor :need_refresh
def set_bitmap(filename, tile_id, autotile, animated, priority, bitmap)
self.bitmap = bitmap
self.src_rect = Rect.new(0, 0, SOURCE_TILE_WIDTH, SOURCE_TILE_HEIGHT)
self.zoom_x = ZOOM_X
self.zoom_y = ZOOM_Y
@filename = filename
@tile_id = tile_id
@is_autotile = autotile
@animated = animated
@priority = priority
@shows_reflection = false
@bridge = false
self.visible = !bitmap.nil?
@need_refresh = true
end
end
#-----------------------------------------------------------------------------
def initialize(viewport)
@tilesets = TilesetBitmaps.new
@autotiles = AutotileBitmaps.new
@custom_autotile_ids = {} # key: tile_id, value: filename
@tiles_horizontal_count = (Graphics.width.to_f / DISPLAY_TILE_WIDTH).ceil + 1
@tiles_vertical_count = (Graphics.height.to_f / DISPLAY_TILE_HEIGHT).ceil + 1
@tone = Tone.new(0, 0, 0, 0)
@old_tone = Tone.new(0, 0, 0, 0)
@color = Color.new(0, 0, 0, 0)
@old_color = Color.new(0, 0, 0, 0)
@self_viewport = Viewport.new(0, 0, Graphics.width, Graphics.height)
@viewport = (viewport) ? viewport : @self_viewport
@old_viewport_ox = 0
@old_viewport_oy = 0
# NOTE: The extra tiles horizontally/vertically hang off the left and top
# edges of the screen, because the pixel_offset values are positive
# and are added to the tile sprite coordinates.
@tiles = []
@tiles_horizontal_count.times do |i|
@tiles[i] = []
@tiles_vertical_count.times do |j|
@tiles[i][j] = Array.new(3) { TileSprite.new(@viewport) }
end
end
@current_map_id = 0
@tile_offset_x = 0
@tile_offset_y = 0
@pixel_offset_x = 0
@pixel_offset_y = 0
@ox = 0
@oy = 0
@visible = true
@need_refresh = true
@disposed = false
end
def dispose
return if disposed?
@tiles.each do |col|
col.each do |coord|
coord.each { |tile| tile.dispose }
coord.clear
end
end
@tiles.clear
@tilesets.bitmaps.each_value { |bitmap| bitmap.dispose }
@tilesets.bitmaps.clear
@autotiles.bitmaps.each_value { |bitmap| bitmap.dispose }
@autotiles.bitmaps.clear
@self_viewport.dispose
@self_viewport = nil
@disposed = true
end
def disposed?
return @disposed
end
#-----------------------------------------------------------------------------
def add_tileset(filename)
@tilesets.add(filename)
end
def remove_tileset(filename)
@tilesets.remove(filename)
end
def add_autotile(filename)
@autotiles.add(filename)
end
def remove_autotile(filename)
@autotiles.remove(filename)
end
def get_autotile_overrides(tileset_id,map_id)
base_overrides = EXTRA_AUTOTILES[tileset_id] || {}
return base_overrides unless $game_weather
wind_overrides =WIND_TREE_AUTOTILES[tileset_id] || {}
if $game_weather.map_current_weather_type(map_id) == :Wind && WIND_TREE_AUTOTILES[tileset_id]
return base_overrides.merge(wind_overrides)
end
return base_overrides
end
def add_extra_autotiles(tileset_id,map_id)
overrides = get_autotile_overrides(tileset_id,map_id)
return if !overrides || overrides.empty?
overrides.each do |tile_id, filename|
@autotiles.add(filename)
@custom_autotile_ids[tile_id] = filename
end
end
def remove_extra_autotiles(tileset_id)
return if !EXTRA_AUTOTILES[tileset_id]
EXTRA_AUTOTILES[tileset_id].each do |arr|
arr.each { |filename| remove_autotile(filename) }
end
end
#-----------------------------------------------------------------------------
def refresh
@need_refresh = true
end
def refresh_tile_bitmap(tile, map, tile_id)
tile.tile_id = tile_id
if tile_id < TILES_PER_AUTOTILE
tile.set_bitmap("", tile_id, false, false, 0, nil)
tile.shows_reflection = false
tile.bridge = false
else
terrain_tag = map.terrain_tags[tile_id] || 0
terrain_tag_data = GameData::TerrainTag.try_get(terrain_tag)
priority = map.priorities[tile_id] || 0
single_autotile_start_id = TILESET_START_ID
true_tileset_start_id = TILESET_START_ID
# extra_autotile_arrays = EXTRA_AUTOTILES[map.tileset_id]
# if extra_autotile_arrays
# large_autotile_count = extra_autotile_arrays[0].length
# single_autotile_count = extra_autotile_arrays[1].length
# single_autotile_start_id += large_autotile_count * TILES_PER_AUTOTILE
# true_tileset_start_id += large_autotile_count * TILES_PER_AUTOTILE
# true_tileset_start_id += single_autotile_count
# end
filename = nil
extra_autotile_hash = get_autotile_overrides(map.tileset_id,map.map_id)
if extra_autotile_hash && extra_autotile_hash[tile_id]
# Custom tile_id override
filename = extra_autotile_hash[tile_id]
tile.set_bitmap(filename, tile_id, true, @autotiles.animated?(filename),
priority, @autotiles[filename])
elsif tile_id < true_tileset_start_id
# Default behavior
if tile_id < TILESET_START_ID # Real autotiles
filename = map.autotile_names[(tile_id / TILES_PER_AUTOTILE) - 1]
elsif tile_id < single_autotile_start_id # Large extra autotiles
filename = extra_autotile_arrays[0][(tile_id - TILESET_START_ID) / TILES_PER_AUTOTILE]
else
# Single extra autotiles
filename = extra_autotile_arrays[1][tile_id - single_autotile_start_id]
end
tile.set_bitmap(filename, tile_id, true, @autotiles.animated?(filename),
priority, @autotiles[filename])
else
filename = map.tileset_name
tile.set_bitmap(filename, tile_id, false, false, priority, @tilesets[filename])
end
tile.shows_reflection = terrain_tag_data&.shows_reflections
tile.underwater_tile = terrain_tag_data&.underwater
tile.bridge = terrain_tag_data&.bridge
end
refresh_tile_src_rect(tile, tile_id)
end
def refresh_tile_src_rect(tile, tile_id)
if tile.is_autotile
@autotiles.set_src_rect(tile, tile_id)
else
@tilesets.set_src_rect(tile, tile_id)
end
end
# For animated autotiles only
def refresh_tile_frame(tile, tile_id)
return if !tile.animated
@autotiles.set_src_rect(tile, tile_id)
end
# x and y are the positions of tile within @tiles, not a map x/y
def refresh_tile_coordinates(tile, x, y)
tile.x = (x * DISPLAY_TILE_WIDTH) - @pixel_offset_x
tile.y = (y * DISPLAY_TILE_HEIGHT) - @pixel_offset_y
end
def refresh_tile_z(tile, map, y, layer, tile_id)
if tile.underwater_tile#tile.shows_reflection -2000
tile.z = -5
elsif tile.bridge && $PokemonGlobal.bridge > 0
tile.z = 0
else
priority = tile.priority
tile.z = (priority == 0) ? 0 : y * DISPLAY_TILE_HEIGHT + priority * 32 + 32
end
end
def refresh_tile(tile, x, y, map, layer, tile_id)
refresh_tile_bitmap(tile, map, tile_id)
refresh_tile_coordinates(tile, x, y)
refresh_tile_z(tile, map, y, layer, tile_id)
tile.need_refresh = false
end
#-----------------------------------------------------------------------------
def check_if_screen_moved
ret = false
# Check for map change
if @current_map_id != $game_map.map_id
if MapFactoryHelper.hasConnections?(@current_map_id)
offsets = $MapFactory.getRelativePos(@current_map_id, 0, 0, $game_map.map_id, 0, 0)
if offsets
@tile_offset_x -= offsets[0]
@tile_offset_y -= offsets[1]
else
ret = true # Need a full refresh
end
else
ret = true
end
@current_map_id = $game_map.map_id
end
# Check for tile movement
current_map_display_x = ($game_map.display_x.to_f / Game_Map::X_SUBPIXELS).round
current_map_display_y = ($game_map.display_y.to_f / Game_Map::Y_SUBPIXELS).round
new_tile_offset_x = (current_map_display_x / SOURCE_TILE_WIDTH) * ZOOM_X
new_tile_offset_y = (current_map_display_y / SOURCE_TILE_HEIGHT) * ZOOM_Y
if new_tile_offset_x != @tile_offset_x
if new_tile_offset_x > @tile_offset_x
# Take tile stacks off the right and insert them at the beginning (left)
(new_tile_offset_x - @tile_offset_x).times do
c = @tiles.shift
@tiles.push(c)
c.each do |coord|
coord.each { |tile| tile.need_refresh = true }
end
end
else
# Take tile stacks off the beginning (left) and push them onto the end (right)
(@tile_offset_x - new_tile_offset_x).times do
c = @tiles.pop
@tiles.prepend(c)
c.each do |coord|
coord.each { |tile| tile.need_refresh = true }
end
end
end
@screen_moved = true
@tile_offset_x = new_tile_offset_x
end
if new_tile_offset_y != @tile_offset_y
if new_tile_offset_y > @tile_offset_y
# Take tile stacks off the bottom and insert them at the beginning (top)
@tiles.each do |col|
(new_tile_offset_y - @tile_offset_y).times do
c = col.shift
col.push(c)
c.each { |tile| tile.need_refresh = true }
end
end
else
# Take tile stacks off the beginning (top) and push them onto the end (bottom)
@tiles.each do |col|
(@tile_offset_y - new_tile_offset_y).times do
c = col.pop
col.prepend(c)
c.each { |tile| tile.need_refresh = true }
end
end
end
@screen_moved = true
@screen_moved_vertically = true
@tile_offset_y = new_tile_offset_y
end
# Check for pixel movement
new_pixel_offset_x = (current_map_display_x % SOURCE_TILE_WIDTH) * ZOOM_X
new_pixel_offset_y = (current_map_display_y % SOURCE_TILE_HEIGHT) * ZOOM_Y
if new_pixel_offset_x != @pixel_offset_x
@screen_moved = true
@pixel_offset_x = new_pixel_offset_x
end
if new_pixel_offset_y != @pixel_offset_y
@screen_moved = true
@screen_moved_vertically = true
@pixel_offset_y = new_pixel_offset_y
end
return ret
end
#-----------------------------------------------------------------------------
def update
# Update tone
if @old_tone != @tone
@tiles.each do |col|
col.each do |coord|
coord.each { |tile| tile.tone = @tone }
end
end
@old_tone = @tone.clone
end
# Update color
if @old_color != @color
@tiles.each do |col|
col.each do |coord|
coord.each { |tile| tile.color = @color }
end
end
@old_color = @color.clone
end
# Recalculate autotile frames
@tilesets.update
@autotiles.update
do_full_refresh = @need_refresh
if @viewport.ox != @old_viewport_ox || @viewport.oy != @old_viewport_oy
@old_viewport_ox = @viewport.ox
@old_viewport_oy = @viewport.oy
do_full_refresh = true
end
# Check whether the screen has moved since the last update
@screen_moved = false
@screen_moved_vertically = false
if $PokemonGlobal.bridge != @bridge
@bridge = $PokemonGlobal.bridge
@screen_moved_vertically = true # To update bridge tiles' z values
end
do_full_refresh = true if check_if_screen_moved
# Update all tile sprites
visited = []
@tiles_horizontal_count.times do |i|
visited[i] = []
@tiles_vertical_count.times { |j| visited[i][j] = false }
end
$MapFactory.maps.each do |map|
# Calculate x/y ranges of tile sprites that represent them
map_display_x = (map.display_x.to_f / Game_Map::X_SUBPIXELS).round
map_display_x = ((map_display_x + (Graphics.width / 2)) * ZOOM_X) - (Graphics.width / 2) if ZOOM_X != 1
map_display_y = (map.display_y.to_f / Game_Map::Y_SUBPIXELS).round
map_display_y = ((map_display_y + (Graphics.height / 2)) * ZOOM_Y) - (Graphics.height / 2) if ZOOM_Y != 1
map_display_x_tile = map_display_x / DISPLAY_TILE_WIDTH
map_display_y_tile = map_display_y / DISPLAY_TILE_HEIGHT
start_x = [-map_display_x_tile, 0].max
start_y = [-map_display_y_tile, 0].max
end_x = @tiles_horizontal_count - 1
end_x = [end_x, map.width - map_display_x_tile - 1].min
end_y = @tiles_vertical_count - 1
end_y = [end_y, map.height - map_display_y_tile - 1].min
next if start_x > end_x || start_y > end_y || end_x < 0 || end_y < 0
# Update all tile sprites representing this map
(start_x..end_x).each do |i|
tile_x = i + map_display_x_tile
(start_y..end_y).each do |j|
tile_y = j + map_display_y_tile
@tiles[i][j].each_with_index do |tile, layer|
tile_id = map.data[tile_x, tile_y, layer]
if do_full_refresh || tile.need_refresh || tile.tile_id != tile_id
refresh_tile(tile, i, j, map, layer, tile_id)
else
refresh_tile_frame(tile, tile_id) if tile.animated && @autotiles.changed
# Update tile's x/y coordinates
refresh_tile_coordinates(tile, i, j) if @screen_moved
# Update tile's z value
refresh_tile_z(tile, map, j, layer, tile_id) if @screen_moved_vertically
end
end
# Record x/y as visited
visited[i][j] = true
end
end
end
# Clear all unvisited tile sprites
@tiles.each_with_index do |col, i|
col.each_with_index do |coord, j|
next if visited[i][j]
coord.each do |tile|
tile.set_bitmap("", 0, false, false, 0, nil)
tile.shows_reflection = false
tile.bridge = false
end
end
end
@need_refresh = false
@autotiles.changed = false
end
end

View File

@@ -0,0 +1,97 @@
#===============================================================================
# This module is a little fix that works around PC hardware limitations. Since
# Essentials isn't working with software rendering anymore, it now has to deal
# with the limits of the GPU. For the most part this is no big deal, but people
# do have some really big tilesets.
#
# The fix is simple enough: If your tileset is too big, a new bitmap will be
# constructed with all the excess pixels sent to the image's right side. This
# basically means that you now have a limit far higher than you should ever
# actually need.
#
# Hardware limit -> max tileset length:
# 1024px -> 4096px
# 2048px -> 16384px (enough to get the normal limit)
# 4096px -> 65536px (enough to load pretty much any tileset)
# 8192px -> 262144px
# 16384px -> 1048576px (what most people have at this point)
#===============================================================================
class TilemapRenderer
module TilesetWrapper
TILESET_WIDTH = SOURCE_TILE_WIDTH * TILESET_TILES_PER_ROW
# Looks useless, but covers weird numbers given to mkxp.json or a funky driver
MAX_TEX_SIZE = (Bitmap.max_size / 1024) * 1024
MAX_TEX_SIZE_BOOSTED = (MAX_TEX_SIZE**2) / TILESET_WIDTH
module_function
def wrapTileset(originalbmp)
width = originalbmp.width
height = originalbmp.height
if width == TILESET_WIDTH && originalbmp.mega?
columns = (height / MAX_TEX_SIZE.to_f).ceil
if columns * TILESET_WIDTH > MAX_TEX_SIZE
raise "Tileset is too long!\n\nSIZE: #{originalbmp.height}px\nHARDWARE LIMIT: #{MAX_TEX_SIZE}px\nBOOSTED LIMIT: #{MAX_TEX_SIZE_BOOSTED}px"
end
bmp = Bitmap.new(TILESET_WIDTH * columns, MAX_TEX_SIZE)
remainder = height % MAX_TEX_SIZE
remainder = MAX_TEX_SIZE if remainder == 0
columns.times do |col|
srcrect = Rect.new(0, col * MAX_TEX_SIZE, width, (col + 1 == columns) ? remainder : MAX_TEX_SIZE)
bmp.blt(col * TILESET_WIDTH, 0, originalbmp, srcrect)
end
return bmp
end
return originalbmp
end
def getWrappedRect(src_rect)
ret = Rect.new(0, 0, 0, 0)
col = (src_rect.y / MAX_TEX_SIZE.to_f).floor
ret.x = (col * TILESET_WIDTH) + src_rect.x.clamp(0, TILESET_WIDTH)
ret.y = src_rect.y % MAX_TEX_SIZE
ret.width = src_rect.width.clamp(0, TILESET_WIDTH - src_rect.x)
ret.height = src_rect.height.clamp(0, MAX_TEX_SIZE)
return ret
end
#---------------------------------------------------------------------------
private
def blitWrappedPixels(destX, destY, dest, src, srcrect)
if srcrect.y + srcrect.width < MAX_TEX_SIZE
# Save the processing power
dest.blt(destX, destY, src, srcrect)
return
end
merge = (srcrect.y % MAX_TEX_SIZE) > ((srcrect.y + srcrect.height) % MAX_TEX_SIZE)
srcrect_mod = getWrappedRect(srcrect)
if merge
# FIXME: won't work on heights longer than two columns, but nobody should need
# more than 32k pixels high at once anyway
side = {
:a => MAX_TEX_SIZE - srcrect_mod.y,
:b => srcrect_mod.height - MAX_TEX_SIZE + srcrect_mod.y
}
dest.blt(destX, destY, src, Rect.new(srcrect_mod.x, srcrect_mod.y, srcrect_mod.width, side[:a]))
dest.blt(destX, destY + side[:a], src, Rect.new(srcrect_mod.x + TILESET_WIDTH, 0, srcrect_mod.width, side[:b]))
else
dest.blt(destX, destY, src, srcrect_mod)
end
end
def stretchBlitWrappedPixels(destrect, dest, src, srcrect)
if srcrect.y + srcrect.width < MAX_TEX_SIZE
# Save the processing power
dest.stretch_blt(destrect, src, srcrect)
return
end
# Does a regular blit to a non-megasurface, then stretch_blts that to
# the destination. Yes it is slow
tmp = Bitmap.new(srcrect.width, srcrect.height)
blitWrappedPixels(0, 0, tmp, src, srcrect)
dest.stretch_blt(destrect, tmp, Rect.new(0, 0, srcrect.width, srcrect.height))
end
end
end

View File

@@ -0,0 +1,75 @@
#===============================================================================
#
#===============================================================================
class TilemapRenderer
module AutotileExpander
MAX_TEXTURE_SIZE = (Bitmap.max_size / 1024) * 1024
module_function
# This doesn't allow for cache sizes smaller than 768, but if that applies
# to you, you've got bigger problems.
def expand(bitmap)
return bitmap if bitmap.height == SOURCE_TILE_HEIGHT
expanded_format = (bitmap.height == SOURCE_TILE_HEIGHT * 6)
wrap = false
if MAX_TEXTURE_SIZE < TILES_PER_AUTOTILE * SOURCE_TILE_HEIGHT
wrap = true # Each autotile will occupy two columns instead of one
end
frames_count = [bitmap.width / (3 * SOURCE_TILE_WIDTH), 1].max
new_bitmap = Bitmap.new(frames_count * (wrap ? 2 : 1) * SOURCE_TILE_WIDTH,
TILES_PER_AUTOTILE * SOURCE_TILE_HEIGHT / (wrap ? 2 : 1))
rect = Rect.new(0, 0, SOURCE_TILE_WIDTH / 2, SOURCE_TILE_HEIGHT / 2)
TILES_PER_AUTOTILE.times do |id|
pattern = TileDrawingHelper::AUTOTILE_PATTERNS[id >> 3][id % TILESET_TILES_PER_ROW]
wrap_offset_x = (wrap && id >= TILES_PER_AUTOTILE / 2) ? SOURCE_TILE_WIDTH : 0
wrap_offset_y = (wrap && id >= TILES_PER_AUTOTILE / 2) ? (TILES_PER_AUTOTILE / 2) * SOURCE_TILE_HEIGHT : 0
frames_count.times do |frame|
if expanded_format && [1, 2, 4, 8].include?(id)
dest_x = frame * SOURCE_TILE_WIDTH * (wrap ? 2 : 1)
dest_x += wrap_offset_x
next if dest_x > MAX_TEXTURE_SIZE
dest_y = id * SOURCE_TILE_HEIGHT
dest_y -= wrap_offset_y
next if dest_y > MAX_TEXTURE_SIZE
case id
when 1 # Top left corner
new_bitmap.blt(dest_x, dest_y, bitmap,
Rect.new(frame * SOURCE_TILE_WIDTH * 3, SOURCE_TILE_HEIGHT * 4,
SOURCE_TILE_WIDTH, SOURCE_TILE_HEIGHT))
when 2 # Top right corner
new_bitmap.blt(dest_x, dest_y, bitmap,
Rect.new(SOURCE_TILE_WIDTH + (frame * SOURCE_TILE_WIDTH * 3), SOURCE_TILE_HEIGHT * 4,
SOURCE_TILE_WIDTH, SOURCE_TILE_HEIGHT))
when 4 # Bottom right corner
new_bitmap.blt(dest_x, dest_y, bitmap,
Rect.new(SOURCE_TILE_WIDTH + (frame * SOURCE_TILE_WIDTH * 3), SOURCE_TILE_HEIGHT * 5,
SOURCE_TILE_WIDTH, SOURCE_TILE_HEIGHT))
when 8 # Bottom left corner
new_bitmap.blt(dest_x, dest_y, bitmap,
Rect.new(frame * SOURCE_TILE_WIDTH * 3, SOURCE_TILE_HEIGHT * 5,
SOURCE_TILE_WIDTH, SOURCE_TILE_HEIGHT))
end
next
end
pattern.each_with_index do |src_chunk, i|
real_src_chunk = src_chunk - 1
dest_x = (i % 2) * SOURCE_TILE_WIDTH / 2
dest_x += frame * SOURCE_TILE_WIDTH * (wrap ? 2 : 1)
dest_x += wrap_offset_x
next if dest_x > MAX_TEXTURE_SIZE
dest_y = (i / 2) * SOURCE_TILE_HEIGHT / 2
dest_y += id * SOURCE_TILE_HEIGHT
dest_y -= wrap_offset_y
next if dest_y > MAX_TEXTURE_SIZE
rect.x = (real_src_chunk % 6) * SOURCE_TILE_WIDTH / 2
rect.x += SOURCE_TILE_WIDTH * 3 * frame
rect.y = (real_src_chunk / 6) * SOURCE_TILE_HEIGHT / 2
new_bitmap.blt(dest_x, dest_y, bitmap, rect)
end
end
end
return new_bitmap
end
end
end

View File

@@ -0,0 +1,246 @@
#===============================================================================
#
#===============================================================================
class TileDrawingHelper
attr_accessor :tileset
attr_accessor :autotiles
AUTOTILE_PATTERNS = [
[[27, 28, 33, 34], [5, 28, 33, 34], [27, 6, 33, 34], [5, 6, 33, 34],
[27, 28, 33, 12], [5, 28, 33, 12], [27, 6, 33, 12], [5, 6, 33, 12]],
[[27, 28, 11, 34], [5, 28, 11, 34], [27, 6, 11, 34], [5, 6, 11, 34],
[27, 28, 11, 12], [5, 28, 11, 12], [27, 6, 11, 12], [5, 6, 11, 12]],
[[25, 26, 31, 32], [25, 6, 31, 32], [25, 26, 31, 12], [25, 6, 31, 12],
[15, 16, 21, 22], [15, 16, 21, 12], [15, 16, 11, 22], [15, 16, 11, 12]],
[[29, 30, 35, 36], [29, 30, 11, 36], [5, 30, 35, 36], [5, 30, 11, 36],
[39, 40, 45, 46], [5, 40, 45, 46], [39, 6, 45, 46], [5, 6, 45, 46]],
[[25, 30, 31, 36], [15, 16, 45, 46], [13, 14, 19, 20], [13, 14, 19, 12],
[17, 18, 23, 24], [17, 18, 11, 24], [41, 42, 47, 48], [5, 42, 47, 48]],
[[37, 38, 43, 44], [37, 6, 43, 44], [13, 18, 19, 24], [13, 14, 43, 44],
[37, 42, 43, 48], [17, 18, 47, 48], [13, 18, 43, 48], [1, 2, 7, 8]]
]
# converts neighbors returned from tableNeighbors to tile indexes
NEIGHBORS_TO_AUTOTILE_INDEX = [
46, 44, 46, 44, 43, 41, 43, 40, 46, 44, 46, 44, 43, 41, 43, 40,
42, 32, 42, 32, 35, 19, 35, 18, 42, 32, 42, 32, 34, 17, 34, 16,
46, 44, 46, 44, 43, 41, 43, 40, 46, 44, 46, 44, 43, 41, 43, 40,
42, 32, 42, 32, 35, 19, 35, 18, 42, 32, 42, 32, 34, 17, 34, 16,
45, 39, 45, 39, 33, 31, 33, 29, 45, 39, 45, 39, 33, 31, 33, 29,
37, 27, 37, 27, 23, 15, 23, 13, 37, 27, 37, 27, 22, 11, 22, 9,
45, 39, 45, 39, 33, 31, 33, 29, 45, 39, 45, 39, 33, 31, 33, 29,
36, 26, 36, 26, 21, 7, 21, 5, 36, 26, 36, 26, 20, 3, 20, 1,
46, 44, 46, 44, 43, 41, 43, 40, 46, 44, 46, 44, 43, 41, 43, 40,
42, 32, 42, 32, 35, 19, 35, 18, 42, 32, 42, 32, 34, 17, 34, 16,
46, 44, 46, 44, 43, 41, 43, 40, 46, 44, 46, 44, 43, 41, 43, 40,
42, 32, 42, 32, 35, 19, 35, 18, 42, 32, 42, 32, 34, 17, 34, 16,
45, 38, 45, 38, 33, 30, 33, 28, 45, 38, 45, 38, 33, 30, 33, 28,
37, 25, 37, 25, 23, 14, 23, 12, 37, 25, 37, 25, 22, 10, 22, 8,
45, 38, 45, 38, 33, 30, 33, 28, 45, 38, 45, 38, 33, 30, 33, 28,
36, 24, 36, 24, 21, 6, 21, 4, 36, 24, 36, 24, 20, 2, 20, 0
]
def self.tableNeighbors(data, x, y, layer = nil)
return 0 if x < 0 || x >= data.xsize
return 0 if y < 0 || y >= data.ysize
if layer.nil?
t = data[x, y]
else
t = data[x, y, layer]
end
xp1 = [x + 1, data.xsize - 1].min
yp1 = [y + 1, data.ysize - 1].min
xm1 = [x - 1, 0].max
ym1 = [y - 1, 0].max
i = 0
if layer.nil?
i |= 0x01 if data[ x, ym1] == t # N
i |= 0x02 if data[xp1, ym1] == t # NE
i |= 0x04 if data[xp1, y] == t # E
i |= 0x08 if data[xp1, yp1] == t # SE
i |= 0x10 if data[ x, yp1] == t # S
i |= 0x20 if data[xm1, yp1] == t # SW
i |= 0x40 if data[xm1, y] == t # W
i |= 0x80 if data[xm1, ym1] == t # NW
else
i |= 0x01 if data[ x, ym1, layer] == t # N
i |= 0x02 if data[xp1, ym1, layer] == t # NE
i |= 0x04 if data[xp1, y, layer] == t # E
i |= 0x08 if data[xp1, yp1, layer] == t # SE
i |= 0x10 if data[ x, yp1, layer] == t # S
i |= 0x20 if data[xm1, yp1, layer] == t # SW
i |= 0x40 if data[xm1, y, layer] == t # W
i |= 0x80 if data[xm1, ym1, layer] == t # NW
end
return i
end
def self.fromTileset(tileset)
bmtileset = pbGetTileset(tileset.tileset_name)
bmautotiles = []
7.times do |i|
bmautotiles.push(pbGetAutotile(tileset.autotile_names[i]))
end
return self.new(bmtileset, bmautotiles)
end
#-----------------------------------------------------------------------------
def initialize(tileset, autotiles)
if tileset.mega?
@tileset = TilemapRenderer::TilesetWrapper.wrapTileset(tileset)
tileset.dispose
@shouldWrap = true
else
@tileset = tileset
@shouldWrap = false
end
@autotiles = autotiles
end
def dispose
@tileset&.dispose
@tileset = nil
@autotiles.each_with_index do |autotile, i|
autotile.dispose
@autotiles[i] = nil
end
end
def bltSmallAutotile(bitmap, x, y, cxTile, cyTile, id, frame)
return if id >= 384 || frame < 0 || !@autotiles
autotile = @autotiles[(id / 48) - 1]
return if !autotile || autotile.disposed?
cxTile = [cxTile / 2, 1].max
cyTile = [cyTile / 2, 1].max
if autotile.height == 32
anim = frame * 32
src_rect = Rect.new(anim, 0, 32, 32)
bitmap.stretch_blt(Rect.new(x, y, cxTile * 2, cyTile * 2), autotile, src_rect)
else
anim = frame * 96
id %= 48
tiles = AUTOTILE_PATTERNS[id >> 3][id & 7]
src = Rect.new(0, 0, 0, 0)
4.times do |i|
tile_position = tiles[i] - 1
src.set(((tile_position % 6) * 16) + anim, (tile_position / 6) * 16, 16, 16)
bitmap.stretch_blt(Rect.new((i % 2 * cxTile) + x, (i / 2 * cyTile) + y, cxTile, cyTile),
autotile, src)
end
end
end
def bltSmallRegularTile(bitmap, x, y, cxTile, cyTile, id)
return if id < 384 || !@tileset || @tileset.disposed?
rect = Rect.new(((id - 384) % 8) * 32, ((id - 384) / 8) * 32, 32, 32)
rect = TilemapRenderer::TilesetWrapper.getWrappedRect(rect) if @shouldWrap
bitmap.stretch_blt(Rect.new(x, y, cxTile, cyTile), @tileset, rect)
end
def bltSmallTile(bitmap, x, y, cxTile, cyTile, id, frame = 0)
if id >= 384
bltSmallRegularTile(bitmap, x, y, cxTile, cyTile, id)
elsif id > 0
bltSmallAutotile(bitmap, x, y, cxTile, cyTile, id, frame)
end
end
def bltAutotile(bitmap, x, y, id, frame)
bltSmallAutotile(bitmap, x, y, 32, 32, id, frame)
end
def bltRegularTile(bitmap, x, y, id)
bltSmallRegularTile(bitmap, x, y, 32, 32, id)
end
def bltTile(bitmap, x, y, id, frame = 0)
if id >= 384
bltRegularTile(bitmap, x, y, id)
elsif id > 0
bltAutotile(bitmap, x, y, id, frame)
end
end
end
#===============================================================================
#
#===============================================================================
def createMinimap(mapid)
map = load_data(sprintf("Data/Map%03d.rxdata", mapid)) rescue nil
return Bitmap.new(32, 32) if !map
bitmap = Bitmap.new(map.width * 4, map.height * 4)
black=Color.new(0,0,0)
tilesets = $data_tilesets
tileset = tilesets[map.tileset_id]
return bitmap if !tileset
helper = TileDrawingHelper.fromTileset(tileset)
map.height.times do |y|
map.width.times do |x|
3.times do |z|
id = map.data[x, y, z]
id = 0 if !id
helper.bltSmallTile(bitmap, x * 4, y * 4, 4, 4, id)
end
end
end
bitmap.fill_rect(0, 0, bitmap.width, 1, black)
bitmap.fill_rect(0, bitmap.height - 1, bitmap.width, 1, black)
bitmap.fill_rect(0, 0, 1, bitmap.height, black)
bitmap.fill_rect(bitmap.width - 1, 0, 1, bitmap.height, black)
return bitmap
end
def bltMinimapAutotile(dstBitmap, x, y, srcBitmap, id)
return if id >= 48 || !srcBitmap || srcBitmap.disposed?
anim = 0
cxTile = 3
cyTile = 3
tiles = TileDrawingHelper::AUTOTILE_PATTERNS[id >> 3][id & 7]
src = Rect.new(0, 0, 0, 0)
4.times do |i|
tile_position = tiles[i] - 1
src.set((tile_position % 6 * cxTile) + anim,
tile_position / 6 * cyTile, cxTile, cyTile)
dstBitmap.blt((i % 2 * cxTile) + x, (i / 2 * cyTile) + y, srcBitmap, src)
end
end
def passable?(passages, tile_id)
return false if tile_id.nil?
passage = passages[tile_id]
return (passage && passage < 15)
end
# Unused
def getPassabilityMinimap(mapid)
map = load_data(sprintf("Data/Map%03d.rxdata", mapid))
tileset = $data_tilesets[map.tileset_id]
minimap = AnimatedBitmap.new("Graphics/UI/minimap_tiles")
ret = Bitmap.new(map.width * 6, map.height * 6)
passtable = Table.new(map.width, map.height)
passages = tileset.passages
map.width.times do |i|
map.height.times do |j|
pass = true
[2, 1, 0].each do |z|
if !passable?(passages, map.data[i, j, z])
pass = false
break
end
end
passtable[i, j] = pass ? 1 : 0
end
end
neighbors = TileDrawingHelper::NEIGHBORS_TO_AUTOTILE_INDEX
map.width.times do |i|
map.height.times do |j|
next if passtable[i, j] != 0
nb = TileDrawingHelper.tableNeighbors(passtable, i, j)
tile = neighbors[nb]
bltMinimapAutotile(ret, i * 6, j * 6, minimap.bitmap, tile)
end
end
minimap.dispose
return ret
end

View File

@@ -0,0 +1,167 @@
class Hangup < Exception; end
module RPG
module Cache
def self.debug
t = Time.now
filename = t.strftime("%H %M %S.%L.txt")
File.open("cache_" + filename, "wb") { |f|
@cache.each do |key, value|
if !value
f.write("#{key} (nil)\r\n")
elsif value.disposed?
f.write("#{key} (disposed)\r\n")
else
f.write("#{key} (#{value.refcount}, #{value.width}x#{value.height})\r\n")
end
end
}
end
def self.setKey(key, obj)
@cache[key] = obj
end
def self.fromCache(i)
return nil if !@cache.include?(i)
obj = @cache[i]
return nil if obj && obj.disposed?
return obj
end
def self.need_clearing()
return @cache.size >= 100
end
def self.load_bitmap(folder_name, filename, hue = 0)
path = folder_name + filename
cached = true
ret = fromCache(path)
if !ret
if filename == ""
ret = BitmapWrapper.new(32, 32)
else
ret = BitmapWrapper.new(path)
end
@cache[path] = ret
cached = false
end
if hue == 0
ret.addRef if cached
return ret
end
key = [path, hue]
ret2 = fromCache(key)
if ret2
ret2.addRef
else
ret2 = ret.copy
ret2.hue_change(hue)
@cache[key] = ret2
end
return ret2
end
def self.load_bitmap_path(path, hue = 0)
cached = true
ret = fromCache(path)
if !ret
if path == ""
ret = BitmapWrapper.new(32, 32)
else
ret = BitmapWrapper.new(path)
end
@cache[path] = ret
cached = false
end
if hue == 0
ret.addRef if cached
return ret
end
key = [path, hue]
ret2 = fromCache(key)
if ret2
ret2.addRef
else
ret2 = ret.copy
ret2.hue_change(hue)
@cache[key] = ret2
end
return ret2
end
def self.tileEx(filename, tile_id, hue, width = 1, height = 1)
key = [filename, tile_id, hue, width, height]
ret = fromCache(key)
if ret
ret.addRef
else
ret = BitmapWrapper.new(32 * width, 32 * height)
x = (tile_id - 384) % 8 * 32
y = (((tile_id - 384) / 8) - height + 1) * 32
tileset = yield(filename)
ret.blt(0, 0, tileset, Rect.new(x, y, 32 * width, 32 * height))
tileset.dispose
ret.hue_change(hue) if hue != 0
@cache[key] = ret
end
return ret
end
def self.tile(filename, tile_id, hue)
return self.tileEx(filename, tile_id, hue) { |f| self.tileset(f) }
end
def self.transition(filename)
self.load_bitmap("Graphics/Transitions/", filename)
end
def self.retain(folder_name, filename = "", hue = 0)
path = folder_name + filename
ret = fromCache(path)
if hue > 0
key = [path, hue]
ret2 = fromCache(key)
if ret2
ret2.never_dispose = true
return
end
end
ret.never_dispose = true if ret
end
end
end
class BitmapWrapper < Bitmap
attr_reader :refcount
attr_accessor :never_dispose
def dispose
return if self.disposed?
@refcount -= 1
super if @refcount <= 0 && !never_dispose
end
def initialize(*arg)
super
@refcount = 1
end
def resetRef
@refcount = 1
end
def copy
bm = self.clone
bm.resetRef
return bm
end
def addRef
@refcount += 1
end
end

View File

@@ -0,0 +1,818 @@
module MessageConfig
LIGHT_TEXT_MAIN_COLOR = Color.new(248, 248, 248)
LIGHT_TEXT_SHADOW_COLOR = Color.new(72, 80, 88)
DARK_TEXT_MAIN_COLOR = Color.new(80, 80, 88)
DARK_TEXT_SHADOW_COLOR = Color.new(160, 160, 168)
BLUE_TEXT_MAIN_COLOR = Color.new(35, 130, 200)
BLUE_TEXT_SHADOW_COLOR = Color.new(20, 75, 115)
FONT_NAME = "Power Green"
FONT_SIZE = 29
SMALL_FONT_NAME = "Power Green Small"
SMALL_FONT_SIZE = 25
NARROW_FONT_NAME = "Power Green Narrow"
NARROW_FONT_SIZE = 29
BUBBLE_TEXT_BASE = Color.new(248,248,248)#(72,80,88)#DIALOG
BUBBLE_TEXT_SHADOW= Color.new(166,160,151)
# 0 = Pause cursor is displayed at end of text
# 1 = Pause cursor is displayed at bottom right
# 2 = Pause cursor is displayed at lower middle side
CURSOR_POSITION = 1
WINDOW_OPACITY = 255
TEXT_SPEED = nil # can be positive to wait frames or negative to
# show multiple characters in a single frame
@@systemFrame = nil
@@defaultTextSkin = nil
@@textSpeed = nil
@@systemFont = nil
@@smallFont = nil
@@narrowFont = nil
def self.pbDefaultSystemFrame
if $PokemonSystem
return pbResolveBitmap("Graphics/Windowskins/" + Settings::MENU_WINDOWSKINS[$PokemonSystem.frame]) || ""
else
return pbResolveBitmap("Graphics/Windowskins/" + Settings::MENU_WINDOWSKINS[0]) || ""
end
end
def self.pbDefaultSpeechFrame
if $PokemonSystem
return pbResolveBitmap("Graphics/Windowskins/" + Settings::SPEECH_WINDOWSKINS[$PokemonSystem.textskin]) || ""
else
return pbResolveBitmap("Graphics/Windowskins/" + Settings::SPEECH_WINDOWSKINS[0]) || ""
end
end
def self.pbDefaultWindowskin
skin=($data_system) ? $data_system.windowskin_name : nil
if skin && skin!=""
skin=pbResolveBitmap("Graphics/Windowskins/"+skin) || ""
end
skin=pbResolveBitmap("Graphics/System/Window") if nil_or_empty?(skin)
skin=pbResolveBitmap("Graphics/Windowskins/001-Blue01") if nil_or_empty?(skin)
return skin || ""
end
def self.pbGetSystemFrame
if !@@systemFrame
skin=MessageConfig.pbDefaultSystemFrame
skin=MessageConfig.pbDefaultWindowskin if nil_or_empty?(skin)
@@systemFrame=skin || ""
end
return @@systemFrame
end
def self.pbGetSpeechFrame
if !@@defaultTextSkin
skin=MessageConfig.pbDefaultSpeechFrame
skin=MessageConfig.pbDefaultWindowskin if nil_or_empty?(skin)
@@defaultTextSkin=skin || ""
end
return @@defaultTextSkin
end
def self.pbSetSystemFrame(value)
@@systemFrame=pbResolveBitmap(value) || ""
end
def self.pbSetSpeechFrame(value)
@@defaultTextSkin=pbResolveBitmap(value) || ""
end
#-----------------------------------------------------------------------------
def self.pbDefaultTextSpeed
return ($PokemonSystem) ? pbSettingToTextSpeed($PokemonSystem.textspeed) : pbSettingToTextSpeed(nil)
end
def self.pbGetTextSpeed
@@textSpeed=pbDefaultTextSpeed if !@@textSpeed
return @@textSpeed
end
def self.pbSetTextSpeed(value)
@@textSpeed=value
end
def self.pbSettingToTextSpeed(speed)
case speed
when 0 then return 1
when 1 then return -3
when 2 then return -999
end
return TEXT_SPEED || 1
end
#-----------------------------------------------------------------------------
def self.pbDefaultSystemFontName
return MessageConfig.pbTryFonts(FONT_NAME)
end
def self.pbDefaultSmallFontName
return MessageConfig.pbTryFonts(SMALL_FONT_NAME)
end
def self.pbDefaultNarrowFontName
return MessageConfig.pbTryFonts(NARROW_FONT_NAME)
end
def self.pbGetSystemFontName
@@systemFont = pbDefaultSystemFontName if !@@systemFont
return @@systemFont
end
def self.pbGetSmallFontName
@@smallFont = pbDefaultSmallFontName if !@@smallFont
return @@smallFont
end
def self.pbGetNarrowFontName
@@narrowFont = pbDefaultNarrowFontName if !@@narrowFont
return @@narrowFont
end
def self.pbSetSystemFontName(value)
@@systemFont = MessageConfig.pbTryFonts(value)
@@systemFont = MessageConfig.pbDefaultSystemFontName if @@systemFont == ""
end
def self.pbSetSmallFontName(value)
@@smallFont = MessageConfig.pbTryFonts(value)
@@smallFont = MessageConfig.pbDefaultSmallFontName if @@smallFont == ""
end
def self.pbSetNarrowFontName(value)
@@narrowFont = MessageConfig.pbTryFonts(value)
@@narrowFont = MessageConfig.pbDefaultNarrowFontName if @@narrowFont == ""
end
def self.pbTryFonts(*args)
for a in args
next if !a
if a.is_a?(String)
return a if Font.exist?(a)
elsif a.is_a?(Array)
for aa in a
ret = MessageConfig.pbTryFonts(aa)
return ret if ret != ""
end
end
end
return ""
end
end
#===============================================================================
# Position a window
#===============================================================================
def pbBottomRight(window)
window.x=Graphics.width-window.width
window.y=Graphics.height-window.height
end
def pbBottomLeft(window)
window.x=0
window.y=Graphics.height-window.height
end
def pbBottomLeftLines(window,lines,width=nil)
window.x=0
window.width=width ? width : Graphics.width
window.height=(window.borderY rescue 32)+lines*32
window.y=Graphics.height-window.height
end
def pbPositionFaceWindow(facewindow,msgwindow)
return if !facewindow
if msgwindow
if facewindow.height<=msgwindow.height
facewindow.y=msgwindow.y
else
facewindow.y=msgwindow.y+msgwindow.height-facewindow.height
end
facewindow.x=Graphics.width-facewindow.width
msgwindow.x=0
msgwindow.width=Graphics.width-facewindow.width
else
facewindow.height=Graphics.height if facewindow.height>Graphics.height
facewindow.x=0
facewindow.y=0
end
end
def pbPositionNearMsgWindow(cmdwindow,msgwindow,side, x_offset=nil,y_offset=nil)
return if !cmdwindow
if msgwindow
height=[cmdwindow.height,Graphics.height-msgwindow.height].min
if cmdwindow.height!=height
cmdwindow.height=height
end
cmdwindow.y=msgwindow.y-cmdwindow.height
if cmdwindow.y<0
cmdwindow.y=msgwindow.y+msgwindow.height
if cmdwindow.y+cmdwindow.height>Graphics.height
cmdwindow.y=msgwindow.y-cmdwindow.height
end
end
case side
when :left
cmdwindow.x=msgwindow.x
when :right
cmdwindow.x=msgwindow.x+msgwindow.width-cmdwindow.width
else
cmdwindow.x=msgwindow.x+msgwindow.width-cmdwindow.width
end
else
cmdwindow.height=Graphics.height if cmdwindow.height>Graphics.height
cmdwindow.x=0
cmdwindow.y=0
end
cmdwindow.x+= x_offset if x_offset
cmdwindow.y+= y_offset if y_offset
end
# internal function
def pbRepositionMessageWindow(msgwindow, linecount=2)
msgwindow.height=32*linecount+msgwindow.borderY
msgwindow.y=(Graphics.height)-(msgwindow.height)
if $game_system && $game_system.respond_to?("message_position")
case $game_system.message_position
when 0 # up
msgwindow.y=0
when 1 # middle
msgwindow.y=(Graphics.height/2)-(msgwindow.height/2)
when 2
msgwindow.y=(Graphics.height)-(msgwindow.height)
end
end
if $game_system && $game_system.respond_to?("message_frame")
if $game_system.message_frame != 0
msgwindow.opacity = 0
end
end
end
# internal function
def pbUpdateMsgWindowPos(msgwindow,event,eventChanged=false)
if event
if eventChanged
msgwindow.resizeToFit2(msgwindow.text,Graphics.width*2/3,msgwindow.height)
end
msgwindow.y=event.screen_y-48-msgwindow.height
if msgwindow.y<0
msgwindow.y=event.screen_y+24
end
msgwindow.x=event.screen_x-(msgwindow.width/2)
msgwindow.x=0 if msgwindow.x<0
if msgwindow.x>Graphics.width-msgwindow.width
msgwindow.x=Graphics.width-msgwindow.width
end
else
curwidth=msgwindow.width
if curwidth!=Graphics.width
msgwindow.width=Graphics.width
msgwindow.width=Graphics.width
end
end
end
#===============================================================================
# Determine the colour of a background
#===============================================================================
def isDarkBackground(background,rect=nil)
return true if !background || background.disposed?
rect = background.rect if !rect
return true if rect.width<=0 || rect.height<=0
xSeg = (rect.width/16)
xLoop = (xSeg==0) ? 1 : 16
xStart = (xSeg==0) ? rect.x+(rect.width/2) : rect.x+xSeg/2
ySeg = (rect.height/16)
yLoop = (ySeg==0) ? 1 : 16
yStart = (ySeg==0) ? rect.y+(rect.height/2) : rect.y+ySeg/2
count = 0
y = yStart
r = 0; g = 0; b = 0
yLoop.times do
x = xStart
xLoop.times do
clr = background.get_pixel(x,y)
if clr.alpha!=0
r += clr.red
g += clr.green
b += clr.blue
count += 1
end
x += xSeg
end
y += ySeg
end
return true if count==0
r /= count
g /= count
b /= count
return (r*0.299+g*0.587+b*0.114)<160
end
def isDarkWindowskin(windowskin)
if $PokemonTemp.speechbubble_bubble
return false if $PokemonTemp.speechbubble_bubble > 0
end
return true if !windowskin || windowskin.disposed?
if windowskin.width==192 && windowskin.height==128
return isDarkBackground(windowskin,Rect.new(0,0,128,128))
elsif windowskin.width==128 && windowskin.height==128
return isDarkBackground(windowskin,Rect.new(0,0,64,64))
elsif windowskin.width==96 && windowskin.height==48
return isDarkBackground(windowskin,Rect.new(32,16,16,16))
else
clr = windowskin.get_pixel(windowskin.width/2, windowskin.height/2)
return (clr.red*0.299+clr.green*0.587+clr.blue*0.114)<160
end
end
#===============================================================================
# Determine which text colours to use based on the darkness of the background
#===============================================================================
def getSkinColor(windowskin,color,isDarkSkin)
if !windowskin || windowskin.disposed? ||
windowskin.width!=128 || windowskin.height!=128
# Base color, shadow color (these are reversed on dark windowskins)
textcolors = [
"0070F8","78B8E8", # 1 Blue
"E82010","F8A8B8", # 2 Red
"60B048","B0D090", # 3 Green
"48D8D8","A8E0E0", # 4 Cyan
"D038B8","E8A0E0", # 5 Magenta
"E8D020","F8E888", # 6 Yellow
"A0A0A8","D0D0D8", # 7 Grey
"F0F0F8","C8C8D0", # 8 White
"9040E8","B8A8E0", # 9 Purple
"F89818","F8C898", # 10 Orange
colorToRgb32(MessageConfig::DARK_TEXT_MAIN_COLOR),
colorToRgb32(MessageConfig::DARK_TEXT_SHADOW_COLOR), # 11 Dark default
colorToRgb32(MessageConfig::LIGHT_TEXT_MAIN_COLOR),
colorToRgb32(MessageConfig::LIGHT_TEXT_SHADOW_COLOR) # 12 Light default
]
if color==0 || color>textcolors.length/2 # No special colour, use default
if isDarkSkin # Dark background, light text
return shadowc3tag(MessageConfig::LIGHT_TEXT_MAIN_COLOR, MessageConfig::LIGHT_TEXT_SHADOW_COLOR)
end
# Light background, dark text
return shadowc3tag(MessageConfig::DARK_TEXT_MAIN_COLOR, MessageConfig::DARK_TEXT_SHADOW_COLOR)
end
# Special colour as listed above
if isDarkSkin && color!=12 # Dark background, light text
return sprintf("<c3=%s,%s>",textcolors[2*(color-1)+1],textcolors[2*(color-1)])
end
# Light background, dark text
return sprintf("<c3=%s,%s>",textcolors[2*(color-1)],textcolors[2*(color-1)+1])
else # VX windowskin
color = 0 if color>=32
x = 64 + (color % 8) * 8
y = 96 + (color / 8) * 8
pixel = windowskin.get_pixel(x, y)
return shadowctagFromColor(pixel)
end
end
def getDefaultTextColors(windowskin)
if !windowskin || windowskin.disposed? ||
windowskin.width!=128 || windowskin.height!=128
if isDarkWindowskin(windowskin)
return [MessageConfig::LIGHT_TEXT_MAIN_COLOR, MessageConfig::LIGHT_TEXT_SHADOW_COLOR] # White
else
return [MessageConfig::DARK_TEXT_MAIN_COLOR, MessageConfig::DARK_TEXT_SHADOW_COLOR] # Dark gray
end
else # VX windowskin
color = windowskin.get_pixel(64, 96)
shadow = nil
isDark = (color.red+color.green+color.blue)/3 < 128
if isDark
shadow = Color.new(color.red+64,color.green+64,color.blue+64)
else
shadow = Color.new(color.red-64,color.green-64,color.blue-64)
end
return [color,shadow]
end
end
#===============================================================================
# Makes sure a bitmap exists
#===============================================================================
def pbDoEnsureBitmap(bitmap,dwidth,dheight)
if !bitmap || bitmap.disposed? || bitmap.width<dwidth || bitmap.height<dheight
oldfont = (bitmap && !bitmap.disposed?) ? bitmap.font : nil
bitmap.dispose if bitmap
bitmap = Bitmap.new([1,dwidth].max,[1,dheight].max)
(oldfont) ? bitmap.font = oldfont : pbSetSystemFont(bitmap)
bitmap.font.shadow = false if bitmap.font && bitmap.font.respond_to?("shadow")
end
return bitmap
end
#===============================================================================
# Set a bitmap's font
#===============================================================================
# Sets a bitmap's font to the system font.
def pbSetSystemFont(bitmap)
bitmap.font.name = MessageConfig.pbGetSystemFontName
bitmap.font.size = MessageConfig::FONT_SIZE
end
# Sets a bitmap's font to the system small font.
def pbSetSmallFont(bitmap)
bitmap.font.name = MessageConfig.pbGetSmallFontName
bitmap.font.size = MessageConfig::SMALL_FONT_SIZE
end
# Sets a bitmap's font to the system narrow font.
def pbSetNarrowFont(bitmap)
bitmap.font.name = MessageConfig.pbGetNarrowFontName
bitmap.font.size = MessageConfig::NARROW_FONT_SIZE
end
#===============================================================================
# Blend colours, set the colour of all bitmaps in a sprite hash
#===============================================================================
def pbAlphaBlend(dstColor,srcColor)
r=(255*(srcColor.red-dstColor.red)/255)+dstColor.red
g=(255*(srcColor.green-dstColor.green)/255)+dstColor.green
b=(255*(srcColor.blue-dstColor.blue)/255)+dstColor.blue
a=(255*(srcColor.alpha-dstColor.alpha)/255)+dstColor.alpha
return Color.new(r,g,b,a)
end
def pbSrcOver(dstColor,srcColor)
er=srcColor.red*srcColor.alpha/255
eg=srcColor.green*srcColor.alpha/255
eb=srcColor.blue*srcColor.alpha/255
iea=255-srcColor.alpha
cr=dstColor.red*dstColor.alpha/255
cg=dstColor.green*dstColor.alpha/255
cb=dstColor.blue*dstColor.alpha/255
ica=255-dstColor.alpha
a=255-(iea*ica)/255
r=(iea*cr)/255+er
g=(iea*cg)/255+eg
b=(iea*cb)/255+eb
r=(a==0) ? 0 : r*255/a
g=(a==0) ? 0 : g*255/a
b=(a==0) ? 0 : b*255/a
return Color.new(r,g,b,a)
end
def pbSetSpritesToColor(sprites,color)
return if !sprites || !color
colors={}
for i in sprites
next if !i[1] || pbDisposed?(i[1])
colors[i[0]]=i[1].color.clone
i[1].color=pbSrcOver(i[1].color,color)
end
Graphics.update
Input.update
for i in colors
next if !sprites[i[0]]
sprites[i[0]].color=i[1]
end
end
#===============================================================================
# Update and dispose sprite hashes
#===============================================================================
def using(window)
begin
yield if block_given?
ensure
window.dispose
end
end
def pbUpdateSpriteHash(windows)
for i in windows
window=i[1]
if window
if window.is_a?(Sprite) || window.is_a?(Window)
window.update if !pbDisposed?(window)
elsif window.is_a?(Plane)
begin
window.update if !window.disposed?
rescue NoMethodError
end
elsif window.respond_to?("update")
begin
window.update
rescue RGSSError
end
end
end
end
end
# Disposes all objects in the specified hash.
def pbDisposeSpriteHash(sprites)
return if !sprites
for i in sprites.keys
pbDisposeSprite(sprites,i)
end
sprites.clear
end
# Disposes the specified graphics object within the specified hash. Basically
# like: sprites[id].dispose
def pbDisposeSprite(sprites,id)
sprite = sprites[id]
sprite.dispose if sprite && !pbDisposed?(sprite)
sprites[id] = nil
end
def pbDisposed?(x)
return true if !x
return x.disposed? if !x.is_a?(Viewport)
begin
x.rect = x.rect
rescue
return true
end
return false
end
#===============================================================================
# Fades and window activations for sprite hashes
#===============================================================================
def pbPushFade
$game_temp.fadestate = [$game_temp.fadestate+1,0].max if $game_temp
end
def pbPopFade
$game_temp.fadestate = [$game_temp.fadestate-1,0].max if $game_temp
end
def pbIsFaded?
return ($game_temp) ? $game_temp.fadestate>0 : false
end
# pbFadeOutIn(z) { block }
# Fades out the screen before a block is run and fades it back in after the
# block exits. z indicates the z-coordinate of the viewport used for this effect
def pbFadeOutIn(z=99999,nofadeout=false)
col=Color.new(0,0,0,0)
viewport=Viewport.new(0,0,Graphics.width,Graphics.height)
viewport.z=z
numFrames = (Graphics.frame_rate*Settings::FADEOUT_SPEED).floor
alphaDiff = (255.0/numFrames).ceil
for j in 0..numFrames
col.set(0,0,0,j*alphaDiff)
viewport.color=col
Graphics.update
Input.update
end
pbPushFade
begin
yield if block_given?
ensure
pbPopFade
if !nofadeout
for j in 0..numFrames
col.set(0,0,0,(numFrames-j)*alphaDiff)
viewport.color=col
Graphics.update
Input.update
end
end
viewport.dispose
end
end
def pbFadeOutInWithUpdate(z,sprites,nofadeout=false)
col=Color.new(0,0,0,0)
viewport=Viewport.new(0,0,Graphics.width,Graphics.height)
viewport.z=z
numFrames = (Graphics.frame_rate*Settings::FADEOUT_SPEED).floor
alphaDiff = (255.0/numFrames).ceil
for j in 0..numFrames
col.set(0,0,0,j*alphaDiff)
viewport.color=col
pbUpdateSpriteHash(sprites)
Graphics.update
Input.update
end
pbPushFade
begin
yield if block_given?
ensure
pbPopFade
if !nofadeout
for j in 0..numFrames
col.set(0,0,0,(numFrames-j)*alphaDiff)
viewport.color=col
pbUpdateSpriteHash(sprites)
Graphics.update
Input.update
end
end
viewport.dispose
end
end
# Similar to pbFadeOutIn, but pauses the music as it fades out.
# Requires scripts "Audio" (for bgm_pause) and "SpriteWindow" (for pbFadeOutIn).
def pbFadeOutInWithMusic(zViewport=99999)
playingBGS = $game_system.getPlayingBGS
playingBGM = $game_system.getPlayingBGM
$game_system.bgm_pause(1.0)
$game_system.bgs_pause(1.0)
pos = $game_system.bgm_position
pbFadeOutIn(zViewport) {
yield
$game_system.bgm_position = pos
$game_system.bgm_resume(playingBGM)
$game_system.bgs_resume(playingBGS)
}
end
def pbFadeOutAndHide(sprites)
visiblesprites = {}
numFrames = (Graphics.frame_rate*Settings::FADEOUT_SPEED).floor
alphaDiff = (255.0/numFrames).ceil
pbDeactivateWindows(sprites) {
for j in 0..numFrames
pbSetSpritesToColor(sprites,Color.new(0,0,0,j*alphaDiff))
(block_given?) ? yield : pbUpdateSpriteHash(sprites)
end
}
for i in sprites
next if !i[1]
next if pbDisposed?(i[1])
visiblesprites[i[0]] = true if i[1].visible
i[1].visible = false
end
return visiblesprites
end
def pbFadeInAndShow(sprites,visiblesprites=nil)
if visiblesprites
for i in visiblesprites
if i[1] && sprites[i[0]] && !pbDisposed?(sprites[i[0]])
sprites[i[0]].visible = true
end
end
end
numFrames = (Graphics.frame_rate*Settings::FADEOUT_SPEED).floor
alphaDiff = (255.0/numFrames).ceil
pbDeactivateWindows(sprites) {
for j in 0..numFrames
pbSetSpritesToColor(sprites,Color.new(0,0,0,((numFrames-j)*alphaDiff)))
(block_given?) ? yield : pbUpdateSpriteHash(sprites)
end
}
end
# Restores which windows are active for the given sprite hash.
# _activeStatuses_ is the result of a previous call to pbActivateWindows
def pbRestoreActivations(sprites,activeStatuses)
return if !sprites || !activeStatuses
for k in activeStatuses.keys
if sprites[k] && sprites[k].is_a?(Window) && !pbDisposed?(sprites[k])
sprites[k].active=activeStatuses[k] ? true : false
end
end
end
# Deactivates all windows. If a code block is given, deactivates all windows,
# runs the code in the block, and reactivates them.
def pbDeactivateWindows(sprites)
if block_given?
pbActivateWindow(sprites,nil) { yield }
else
pbActivateWindow(sprites,nil)
end
end
# Activates a specific window of a sprite hash. _key_ is the key of the window
# in the sprite hash. If a code block is given, deactivates all windows except
# the specified window, runs the code in the block, and reactivates them.
def pbActivateWindow(sprites,key)
return if !sprites
activeStatuses={}
for i in sprites
if i[1] && i[1].is_a?(Window) && !pbDisposed?(i[1])
activeStatuses[i[0]]=i[1].active
i[1].active=(i[0]==key)
end
end
if block_given?
begin
yield
ensure
pbRestoreActivations(sprites,activeStatuses)
end
return {}
else
return activeStatuses
end
end
#===============================================================================
# Create background planes for a sprite hash
#===============================================================================
# Adds a background to the sprite hash.
# _planename_ is the hash key of the background.
# _background_ is a filename within the Graphics/Pictures/ folder and can be
# an animated image.
# _viewport_ is a viewport to place the background in.
def addBackgroundPlane(sprites,planename,background,viewport=nil)
sprites[planename]=AnimatedPlane.new(viewport)
bitmapName=pbResolveBitmap("Graphics/Pictures/#{background}")
if bitmapName==nil
# Plane should exist in any case
sprites[planename].bitmap=nil
sprites[planename].visible=false
else
sprites[planename].setBitmap(bitmapName)
for spr in sprites.values
if spr.is_a?(Window)
spr.windowskin=nil
end
end
end
end
# Adds a background to the sprite hash.
# _planename_ is the hash key of the background.
# _background_ is a filename within the Graphics/Pictures/ folder and can be
# an animated image.
# _color_ is the color to use if the background can't be found.
# _viewport_ is a viewport to place the background in.
def addBackgroundOrColoredPlane(sprites,planename,background,color,viewport=nil)
bitmapName=pbResolveBitmap("Graphics/Pictures/#{background}")
if bitmapName==nil
# Plane should exist in any case
sprites[planename]=ColoredPlane.new(color,@viewport)
else
sprites[planename]=AnimatedPlane.new(viewport)
sprites[planename].setBitmap(bitmapName)
for spr in sprites.values
if spr.is_a?(Window)
spr.windowskin=nil
end
end
end
end
#===============================================================================
# Ensure required method definitions
#===============================================================================
module Graphics
if !self.respond_to?("width")
def self.width; return 640; end
end
if !self.respond_to?("height")
def self.height; return 480; end
end
end
if !defined?(_INTL)
def _INTL(*args)
string=args[0].clone
for i in 1...args.length
string.gsub!(/\{#{i}\}/,"#{args[i]}")
end
return string
end
end
if !defined?(_ISPRINTF)
def _ISPRINTF(*args)
string=args[0].clone
for i in 1...args.length
string.gsub!(/\{#{i}\:([^\}]+?)\}/) { |m|
next sprintf("%"+$1,args[i])
}
end
return string
end
end
if !defined?(_MAPINTL)
def _MAPINTL(*args)
string=args[1].clone
for i in 2...args.length
string.gsub!(/\{#{i}\}/,"#{args[i+1]}")
end
return string
end
end

View File

@@ -0,0 +1,604 @@
class WindowCursorRect < Rect
def initialize(window)
super(0, 0, 0, 0)
@window = window
end
def empty
return unless needs_update?(0, 0, 0, 0)
set(0, 0, 0, 0)
end
def empty?
return self.x == 0 && self.y == 0 && self.width == 0 && self.height == 0
end
def set(x, y, width, height)
return unless needs_update?(x, y, width, height)
super(x, y, width, height)
@window.width = @window.width
end
def height=(value)
super(value)
@window.width = @window.width
end
def width=(value)
super(value)
@window.width = @window.width
end
def x=(value)
super(value)
@window.width = @window.width
end
def y=(value)
super(value)
@window.width = @window.width
end
private
def needs_update?(x, y, width, height)
return self.x != x || self.y != y || self.width != width || self.height != height
end
end
class Window
attr_reader :tone
attr_reader :color
attr_reader :blend_type
attr_reader :contents_blend_type
attr_reader :viewport
attr_reader :contents
attr_reader :ox
attr_reader :oy
attr_reader :x
attr_reader :y
attr_reader :z
attr_reader :width
attr_reader :active
attr_reader :pause
attr_reader :height
attr_reader :opacity
attr_reader :back_opacity
attr_reader :contents_opacity
attr_reader :visible
attr_reader :cursor_rect
attr_reader :openness
attr_reader :stretch
def windowskin
@_windowskin
end
def initialize(viewport=nil)
@sprites={}
@spritekeys=[
"back",
"corner0","side0","scroll0",
"corner1","side1","scroll1",
"corner2","side2","scroll2",
"corner3","side3","scroll3",
"cursor","contents","pause"
]
@sidebitmaps=[nil,nil,nil,nil]
@cursorbitmap=nil
@bgbitmap=nil
@viewport=viewport
for i in @spritekeys
@sprites[i]=Sprite.new(@viewport)
end
@disposed=false
@tone=Tone.new(0,0,0)
@color=Color.new(0,0,0,0)
@blankcontents=Bitmap.new(1,1) # RGSS2 requires this
@contents=@blankcontents
@_windowskin=nil
@rpgvx=false # Set to true to emulate RPGVX windows
@x=0
@y=0
@width=0
@openness=255
@height=0
@ox=0
@oy=0
@z=0
@stretch=true
@visible=true
@active=true
@blend_type=0
@contents_blend_type=0
@opacity=255
@back_opacity=255
@contents_opacity=255
@cursor_rect=WindowCursorRect.new(self)
@cursorblink=0
@cursoropacity=255
@pause=false
@pauseopacity=255
@pauseframe=0
privRefresh(true)
end
def dispose
if !self.disposed?
for i in @sprites
i[1].dispose if i[1]
@sprites[i[0]]=nil
end
for i in 0...@sidebitmaps.length
@sidebitmaps[i].dispose if @sidebitmaps[i]
@sidebitmaps[i]=nil
end
@blankcontents.dispose
@cursorbitmap.dispose if @cursorbitmap
@backbitmap.dispose if @backbitmap
@sprites.clear
@sidebitmaps.clear
@_windowskin=nil
@_contents=nil
@disposed=true
end
end
def openness=(value)
@openness=value
@openness=0 if @openness<0
@openness=255 if @openness>255
privRefresh
end
def stretch=(value)
@stretch=value
privRefresh(true)
end
def visible=(value)
@visible=value
privRefresh
end
def viewport=(value)
@viewport=value
for i in @spritekeys
@sprites[i].dispose
if @sprites[i].is_a?(Sprite)
@sprites[i]=Sprite.new(@viewport)
else
@sprites[i]=nil
end
end
privRefresh(true)
end
def z=(value)
@z=value
privRefresh
end
def disposed?
return @disposed
end
def contents=(value)
@contents=value
privRefresh
end
def windowskin=(value)
@_windowskin=value
if value && value.is_a?(Bitmap) && !value.disposed? && value.width==128
@rpgvx=true
else
@rpgvx=false
end
privRefresh(true)
end
def ox=(value)
@ox=value
privRefresh
end
def active=(value)
@active=value
privRefresh(true)
end
def cursor_rect=(value)
if !value
@cursor_rect.empty
else
@cursor_rect.set(value.x,value.y,value.width,value.height)
end
end
def oy=(value)
@oy=value
privRefresh
end
def width=(value)
@width=value
privRefresh(true)
end
def height=(value)
@height=value
privRefresh(true)
end
def pause=(value)
@pause=value
@pauseopacity=0 if !value
privRefresh
end
def x=(value)
@x=value
privRefresh
end
def y=(value)
@y=value
privRefresh
end
def opacity=(value)
@opacity=value
@opacity=0 if @opacity<0
@opacity=255 if @opacity>255
privRefresh
end
def back_opacity=(value)
@back_opacity=value
@back_opacity=0 if @back_opacity<0
@back_opacity=255 if @back_opacity>255
privRefresh
end
def contents_opacity=(value)
@contents_opacity=value
@contents_opacity=0 if @contents_opacity<0
@contents_opacity=255 if @contents_opacity>255
privRefresh
end
def tone=(value)
@tone=value
privRefresh
end
def color=(value)
@color=value
privRefresh
end
def blend_type=(value)
@blend_type=value
privRefresh
end
def flash(color,duration)
return if disposed?
for i in @sprites
i[1].flash(color,duration)
end
end
def update
return if disposed?
mustchange=false
if @active
if @cursorblink==0
@cursoropacity-=8
@cursorblink=1 if @cursoropacity<=128
else
@cursoropacity+=8
@cursorblink=0 if @cursoropacity>=255
end
mustchange=true if !@cursor_rect.empty?
else
mustchange=true if @cursoropacity!=128
@cursoropacity=128
end
if @pause
@pauseframe=(Graphics.frame_count / 8) % 4
@pauseopacity=[@pauseopacity+64,255].min
mustchange=true
end
privRefresh if mustchange
for i in @sprites
i[1].update
end
end
private
def ensureBitmap(bitmap,dwidth,dheight)
if !bitmap||bitmap.disposed?||bitmap.width<dwidth||bitmap.height<dheight
bitmap.dispose if bitmap
bitmap=Bitmap.new([1,dwidth].max,[1,dheight].max)
end
return bitmap
end
def tileBitmap(dstbitmap,dstrect,srcbitmap,srcrect)
return if !srcbitmap || srcbitmap.disposed?
left=dstrect.x
top=dstrect.y
y=0;loop do break unless y<dstrect.height
x=0;loop do break unless x<dstrect.width
dstbitmap.blt(x+left,y+top,srcbitmap,srcrect)
x+=srcrect.width
end
y+=srcrect.height
end
end
def privRefresh(changeBitmap=false)
return if self.disposed?
backopac=self.back_opacity*self.opacity/255
contopac=self.contents_opacity
cursoropac=@cursoropacity*contopac/255
for i in 0...4
@sprites["corner#{i}"].bitmap=@_windowskin
@sprites["scroll#{i}"].bitmap=@_windowskin
end
@sprites["pause"].bitmap=@_windowskin
@sprites["contents"].bitmap=@contents
if @_windowskin && !@_windowskin.disposed?
for i in 0...4
@sprites["corner#{i}"].opacity=@opacity
@sprites["corner#{i}"].tone=@tone
@sprites["corner#{i}"].color=@color
@sprites["corner#{i}"].blend_type=@blend_type
@sprites["corner#{i}"].visible=@visible
@sprites["side#{i}"].opacity=@opacity
@sprites["side#{i}"].tone=@tone
@sprites["side#{i}"].color=@color
@sprites["side#{i}"].blend_type=@blend_type
@sprites["side#{i}"].visible=@visible
@sprites["scroll#{i}"].opacity=@opacity
@sprites["scroll#{i}"].tone=@tone
@sprites["scroll#{i}"].blend_type=@blend_type
@sprites["scroll#{i}"].color=@color
@sprites["scroll#{i}"].visible=@visible
end
for i in ["back","cursor","pause","contents"]
@sprites[i].color=@color
@sprites[i].tone=@tone
@sprites[i].blend_type=@blend_type
end
@sprites["contents"].blend_type=@contents_blend_type
@sprites["back"].opacity=backopac
@sprites["contents"].opacity=contopac
@sprites["cursor"].opacity=cursoropac
@sprites["pause"].opacity=@pauseopacity
@sprites["back"].visible=@visible
@sprites["contents"].visible=@visible && @openness==255
@sprites["pause"].visible=@visible && @pause
@sprites["cursor"].visible=@visible && @openness==255
hascontents=(@contents && !@contents.disposed?)
@sprites["scroll0"].visible = @visible && hascontents && @oy > 0
@sprites["scroll1"].visible = @visible && hascontents && @ox > 0
@sprites["scroll2"].visible = @visible && hascontents &&
(@contents.width - @ox) > @width-32
@sprites["scroll3"].visible = @visible && hascontents &&
(@contents.height - @oy) > @height-32
else
for i in 0...4
@sprites["corner#{i}"].visible=false
@sprites["side#{i}"].visible=false
@sprites["scroll#{i}"].visible=false
end
@sprites["contents"].visible=@visible && @openness==255
@sprites["contents"].color=@color
@sprites["contents"].tone=@tone
@sprites["contents"].blend_type=@contents_blend_type
@sprites["contents"].opacity=contopac
@sprites["back"].visible=false
@sprites["pause"].visible=false
@sprites["cursor"].visible=false
end
for i in @sprites
i[1].z=@z
end
if @rpgvx
@sprites["cursor"].z=@z # For Compatibility
@sprites["contents"].z=@z # For Compatibility
@sprites["pause"].z=@z # For Compatibility
else
@sprites["cursor"].z=@z+1 # For Compatibility
@sprites["contents"].z=@z+2 # For Compatibility
@sprites["pause"].z=@z+2 # For Compatibility
end
if @rpgvx
trimX=64
trimY=0
backRect=Rect.new(0,0,64,64)
blindsRect=Rect.new(0,64,64,64)
else
trimX=128
trimY=0
backRect=Rect.new(0,0,128,128)
blindsRect=nil
end
@sprites["corner0"].src_rect.set(trimX,trimY+0,16,16);
@sprites["corner1"].src_rect.set(trimX+48,trimY+0,16,16);
@sprites["corner2"].src_rect.set(trimX,trimY+48,16,16);
@sprites["corner3"].src_rect.set(trimX+48,trimY+48,16,16);
@sprites["scroll0"].src_rect.set(trimX+24, trimY+16, 16, 8) # up
@sprites["scroll3"].src_rect.set(trimX+24, trimY+40, 16, 8) # down
@sprites["scroll1"].src_rect.set(trimX+16, trimY+24, 8, 16) # left
@sprites["scroll2"].src_rect.set(trimX+40, trimY+24, 8, 16) # right
cursorX=trimX
cursorY=trimY+64
sideRects=[
Rect.new(trimX+16,trimY+0,32,16),
Rect.new(trimX,trimY+16,16,32),
Rect.new(trimX+48,trimY+16,16,32),
Rect.new(trimX+16,trimY+48,32,16)
]
if @width>32 && @height>32
@sprites["contents"].src_rect.set(@ox,@oy,@width-32,@height-32)
else
@sprites["contents"].src_rect.set(0,0,0,0)
end
pauseRects=[
trimX+32,trimY+64,
trimX+48,trimY+64,
trimX+32,trimY+80,
trimX+48,trimY+80,
]
pauseWidth=16
pauseHeight=16
@sprites["pause"].src_rect.set(
pauseRects[@pauseframe*2],
pauseRects[@pauseframe*2+1],
pauseWidth,pauseHeight
)
@sprites["pause"].x=@x+(@width/2)-(pauseWidth/2)
@sprites["pause"].y=@y+@height-16 # 16 refers to skin margin
@sprites["contents"].x=@x+16
@sprites["contents"].y=@y+16
@sprites["corner0"].x=@x
@sprites["corner0"].y=@y
@sprites["corner1"].x=@x+@width-16
@sprites["corner1"].y=@y
@sprites["corner2"].x=@x
@sprites["corner2"].y=@y+@height-16
@sprites["corner3"].x=@x+@width-16
@sprites["corner3"].y=@y+@height-16
@sprites["side0"].x=@x+16
@sprites["side0"].y=@y
@sprites["side1"].x=@x
@sprites["side1"].y=@y+16
@sprites["side2"].x=@x+@width-16
@sprites["side2"].y=@y+16
@sprites["side3"].x=@x+16
@sprites["side3"].y=@y+@height-16
@sprites["scroll0"].x = @x+@width / 2 - 8
@sprites["scroll0"].y = @y+8
@sprites["scroll1"].x = @x+8
@sprites["scroll1"].y = @y+@height / 2 - 8
@sprites["scroll2"].x = @x+@width - 16
@sprites["scroll2"].y = @y+@height / 2 - 8
@sprites["scroll3"].x = @x+@width / 2 - 8
@sprites["scroll3"].y = @y+@height - 16
@sprites["back"].x=@x+2
@sprites["back"].y=@y+2
@sprites["cursor"].x=@x+16+@cursor_rect.x
@sprites["cursor"].y=@y+16+@cursor_rect.y
if changeBitmap && @_windowskin && !@_windowskin.disposed?
width=@cursor_rect.width
height=@cursor_rect.height
if width > 0 && height > 0
cursorrects=[
# sides
Rect.new(cursorX+2, cursorY+0, 28, 2),
Rect.new(cursorX+0, cursorY+2, 2, 28),
Rect.new(cursorX+30, cursorY+2, 2, 28),
Rect.new(cursorX+2, cursorY+30, 28, 2),
# corners
Rect.new(cursorX+0, cursorY+0, 2, 2),
Rect.new(cursorX+30, cursorY+0, 2, 2),
Rect.new(cursorX+0, cursorY+30, 2, 2),
Rect.new(cursorX+30, cursorY+30, 2, 2),
# back
Rect.new(cursorX+2, cursorY+2, 28, 28)
]
margin=2
fullmargin=4
@cursorbitmap = ensureBitmap(@cursorbitmap, width, height)
@cursorbitmap.clear
@sprites["cursor"].bitmap=@cursorbitmap
@sprites["cursor"].src_rect.set(0,0,width,height)
rect = Rect.new(margin,margin,
width - fullmargin, height - fullmargin)
@cursorbitmap.stretch_blt(rect, @_windowskin, cursorrects[8])
@cursorbitmap.blt(0, 0, @_windowskin, cursorrects[4])# top left
@cursorbitmap.blt(width-margin, 0, @_windowskin, cursorrects[5]) # top right
@cursorbitmap.blt(0, height-margin, @_windowskin, cursorrects[6]) # bottom right
@cursorbitmap.blt(width-margin, height-margin, @_windowskin, cursorrects[7]) # bottom left
rect = Rect.new(margin, 0,
width - fullmargin, margin)
@cursorbitmap.stretch_blt(rect, @_windowskin, cursorrects[0])
rect = Rect.new(0, margin,
margin, height - fullmargin)
@cursorbitmap.stretch_blt(rect, @_windowskin, cursorrects[1])
rect = Rect.new(width - margin, margin,
margin, height - fullmargin)
@cursorbitmap.stretch_blt(rect, @_windowskin, cursorrects[2])
rect = Rect.new(margin, height-margin,
width - fullmargin, margin)
@cursorbitmap.stretch_blt(rect, @_windowskin, cursorrects[3])
else
@sprites["cursor"].visible=false
@sprites["cursor"].src_rect.set(0,0,0,0)
end
for i in 0...4
dwidth = (i==0 || i==3) ? @width-32 : 16
dheight = (i==0 || i==3) ? 16 : @height-32
@sidebitmaps[i]=ensureBitmap(@sidebitmaps[i],dwidth,dheight)
@sprites["side#{i}"].bitmap=@sidebitmaps[i]
@sprites["side#{i}"].src_rect.set(0,0,dwidth,dheight)
@sidebitmaps[i].clear
if sideRects[i].width>0 && sideRects[i].height>0
@sidebitmaps[i].stretch_blt(@sprites["side#{i}"].src_rect,
@_windowskin,sideRects[i])
end
end
backwidth=@width-4
backheight=@height-4
if backwidth>0 && backheight>0
@backbitmap=ensureBitmap(@backbitmap,backwidth,backheight)
@sprites["back"].bitmap=@backbitmap
@sprites["back"].src_rect.set(0,0,backwidth,backheight)
@backbitmap.clear
if @stretch
@backbitmap.stretch_blt(@sprites["back"].src_rect,@_windowskin,backRect)
else
tileBitmap(@backbitmap,@sprites["back"].src_rect,@_windowskin,backRect)
end
if blindsRect
tileBitmap(@backbitmap,@sprites["back"].src_rect,@_windowskin,blindsRect)
end
else
@sprites["back"].visible=false
@sprites["back"].src_rect.set(0,0,0,0)
end
end
if @openness!=255
opn=@openness/255.0
for k in @spritekeys
sprite=@sprites[k]
ratio=(@height<=0) ? 0 : (sprite.y-@y)*1.0/@height
sprite.zoom_y=opn
sprite.oy=0
sprite.y=(@y+(@height/2.0)+(@height*ratio*opn)-(@height/2*opn)).floor
end
else
for k in @spritekeys
sprite=@sprites[k]
sprite.zoom_y=1.0
end
end
i=0
# Ensure Z order
for k in @spritekeys
sprite=@sprites[k]
y=sprite.y
sprite.y=i
sprite.oy=(sprite.zoom_y<=0) ? 0 : (i-y)/sprite.zoom_y
end
end
end

View File

@@ -0,0 +1,936 @@
#===============================================================================
# SpriteWindow is a class based on Window which emulates Window's functionality.
# This class is necessary in order to change the viewport of windows (with
# viewport=) and to make windows fade in and out (with tone=).
#===============================================================================
class SpriteWindow < Window
attr_reader :tone
attr_reader :color
attr_reader :viewport
attr_reader :contents
attr_reader :ox
attr_reader :oy
attr_reader :x
attr_reader :y
attr_reader :z
attr_reader :zoom_x
attr_reader :zoom_y
attr_reader :offset_x
attr_reader :offset_y
attr_reader :width
attr_reader :active
attr_reader :pause
attr_reader :height
attr_reader :opacity
attr_reader :back_opacity
attr_reader :contents_opacity
attr_reader :visible
attr_reader :cursor_rect
attr_reader :contents_blend_type
attr_reader :blend_type
attr_reader :openness
def windowskin
@_windowskin
end
# Flags used to preserve compatibility
# with RGSS/RGSS2's version of Window
module CompatBits
CorrectZ = 1
ExpandBack = 2
ShowScrollArrows = 4
StretchSides = 8
ShowPause = 16
ShowCursor = 32
end
attr_reader :compat
def compat=(value)
@compat=value
privRefresh(true)
end
def initialize(viewport=nil)
@sprites={}
@spritekeys=[
"back",
"corner0","side0","scroll0",
"corner1","side1","scroll1",
"corner2","side2","scroll2",
"corner3","side3","scroll3",
"cursor","contents","pause"
]
@viewport=viewport
@sidebitmaps=[nil,nil,nil,nil]
@cursorbitmap=nil
@bgbitmap=nil
for i in @spritekeys
@sprites[i]=Sprite.new(@viewport)
end
@disposed=false
@tone=Tone.new(0,0,0)
@color=Color.new(0,0,0,0)
@blankcontents=Bitmap.new(1,1) # RGSS2 requires this
@contents=@blankcontents
@_windowskin=nil
@rpgvx=false
@compat=CompatBits::ExpandBack|CompatBits::StretchSides
@x=0
@y=0
@width=0
@height=0
@offset_x=0
@offset_y=0
@zoom_x=1.0
@zoom_y=1.0
@ox=0
@oy=0
@z=0
@stretch=true
@visible=true
@active=true
@openness=255
@opacity=255
@back_opacity=255
@blend_type=0
@contents_blend_type=0
@contents_opacity=255
@cursor_rect=WindowCursorRect.new(self)
@cursorblink=0
@cursoropacity=255
@pause=false
@pauseframe=0
@flash=0
@pauseopacity=0
@skinformat=0
@skinrect=Rect.new(0,0,0,0)
@trim=[16,16,16,16]
privRefresh(true)
end
def dispose
if !self.disposed?
for i in @sprites
i[1].dispose if i[1]
@sprites[i[0]]=nil
end
for i in 0...@sidebitmaps.length
@sidebitmaps[i].dispose if @sidebitmaps[i]
@sidebitmaps[i]=nil
end
@blankcontents.dispose
@cursorbitmap.dispose if @cursorbitmap
@backbitmap.dispose if @backbitmap
@sprites.clear
@sidebitmaps.clear
@_windowskin=nil
@disposed=true
end
end
def stretch=(value)
@stretch=value
privRefresh(true)
end
def visible=(value)
@visible=value
privRefresh
end
def viewport=(value)
@viewport=value
for i in @spritekeys
@sprites[i].dispose if @sprites[i]
end
for i in @spritekeys
if @sprites[i].is_a?(Sprite)
@sprites[i]=Sprite.new(@viewport)
else
@sprites[i]=nil
end
end
privRefresh(true)
end
def z=(value)
@z=value
privRefresh
end
def disposed?
return @disposed
end
def contents=(value)
if @contents!=value
@contents=value
privRefresh if @visible
end
end
def ox=(value)
if @ox!=value
@ox=value
privRefresh if @visible
end
end
def oy=(value)
if @oy!=value
@oy=value
privRefresh if @visible
end
end
def active=(value)
@active=value
privRefresh(true)
end
def cursor_rect=(value)
if !value
@cursor_rect.empty
else
@cursor_rect.set(value.x,value.y,value.width,value.height)
end
end
def openness=(value)
@openness=value
@openness=0 if @openness<0
@openness=255 if @openness>255
privRefresh
end
def width=(value)
@width=value
privRefresh(true)
end
def height=(value)
@height=value
privRefresh(true)
end
def pause=(value)
@pause=value
@pauseopacity=0 if !value
privRefresh if @visible
end
def x=(value)
@x=value
privRefresh if @visible
end
def y=(value)
@y=value
privRefresh if @visible
end
def zoom_x=(value)
@zoom_x=value
privRefresh if @visible
end
def zoom_y=(value)
@zoom_y=value
privRefresh if @visible
end
def offset_x=(value)
@x=value
privRefresh if @visible
end
def offset_y=(value)
@y=value
privRefresh if @visible
end
def opacity=(value)
@opacity=value
@opacity=0 if @opacity<0
@opacity=255 if @opacity>255
privRefresh if @visible
end
def back_opacity=(value)
@back_opacity=value
@back_opacity=0 if @back_opacity<0
@back_opacity=255 if @back_opacity>255
privRefresh if @visible
end
def contents_opacity=(value)
@contents_opacity=value
@contents_opacity=0 if @contents_opacity<0
@contents_opacity=255 if @contents_opacity>255
privRefresh if @visible
end
def tone=(value)
@tone=value
privRefresh if @visible
end
def color=(value)
@color=value
privRefresh if @visible
end
def blend_type=(value)
@blend_type=value
privRefresh if @visible
end
def flash(color,duration)
return if disposed?
@flash=duration+1
for i in @sprites
i[1].flash(color,duration)
end
end
def update
return if disposed?
mustchange=false
if @active
if @cursorblink==0
@cursoropacity-=8
@cursorblink=1 if @cursoropacity<=128
else
@cursoropacity+=8
@cursorblink=0 if @cursoropacity>=255
end
privRefreshCursor
else
@cursoropacity=128
privRefreshCursor
end
if @pause
oldpauseframe=@pauseframe
oldpauseopacity=@pauseopacity
@pauseframe=(Graphics.frame_count / 8) % 4
@pauseopacity=[@pauseopacity+64,255].min
mustchange=@pauseframe!=oldpauseframe || @pauseopacity!=oldpauseopacity
end
privRefresh if mustchange
if @flash>0
for i in @sprites.values
i.update
end
@flash-=1
end
end
#############
attr_reader :skinformat
attr_reader :skinrect
def loadSkinFile(_file)
if (self.windowskin.width==80 || self.windowskin.width==96) &&
self.windowskin.height==48
# Body = X, Y, width, height of body rectangle within windowskin
@skinrect.set(32,16,16,16)
# Trim = X, Y, width, height of trim rectangle within windowskin
@trim=[32,16,16,16]
elsif self.windowskin.width==80 && self.windowskin.height==80
@skinrect.set(32,32,16,16)
@trim=[32,16,16,48]
end
end
def windowskin=(value)
oldSkinWidth=(@_windowskin && !@_windowskin.disposed?) ? @_windowskin.width : -1
oldSkinHeight=(@_windowskin && !@_windowskin.disposed?) ? @_windowskin.height : -1
@_windowskin=value
if @skinformat==1
@rpgvx=false
if @_windowskin && !@_windowskin.disposed?
if @_windowskin.width!=oldSkinWidth || @_windowskin.height!=oldSkinHeight
# Update skinrect and trim if windowskin's dimensions have changed
@skinrect.set((@_windowskin.width-16)/2,(@_windowskin.height-16)/2,16,16)
@trim=[@skinrect.x,@skinrect.y,@skinrect.x,@skinrect.y]
end
else
@skinrect.set(16,16,16,16)
@trim=[16,16,16,16]
end
else
if value && value.is_a?(Bitmap) && !value.disposed? && value.width==128
@rpgvx=true
else
@rpgvx=false
end
@trim=[16,16,16,16]
end
privRefresh(true)
end
def skinrect=(value)
@skinrect=value
privRefresh
end
def skinformat=(value)
if @skinformat!=value
@skinformat=value
privRefresh(true)
end
end
def borderX
return 32 if !@trim || skinformat==0
if @_windowskin && !@_windowskin.disposed?
return @trim[0]+(@_windowskin.width-@trim[2]-@trim[0])
end
return 32
end
def borderY
return 32 if !@trim || skinformat==0
if @_windowskin && !@_windowskin.disposed?
return @trim[1]+(@_windowskin.height-@trim[3]-@trim[1])
end
return 32
end
def leftEdge; self.startX; end
def topEdge; self.startY; end
def rightEdge; self.borderX-self.leftEdge; end
def bottomEdge; self.borderY-self.topEdge; end
def startX
return !@trim || skinformat==0 ? 16 : @trim[0]
end
def startY
return !@trim || skinformat==0 ? 16 : @trim[1]
end
def endX
return !@trim || skinformat==0 ? 16 : @trim[2]
end
def endY
return !@trim || skinformat==0 ? 16 : @trim[3]
end
def startX=(value)
@trim[0]=value
privRefresh
end
def startY=(value)
@trim[1]=value
privRefresh
end
def endX=(value)
@trim[2]=value
privRefresh
end
def endY=(value)
@trim[3]=value
privRefresh
end
#############
private
def ensureBitmap(bitmap,dwidth,dheight)
if !bitmap||bitmap.disposed?||bitmap.width<dwidth||bitmap.height<dheight
bitmap.dispose if bitmap
bitmap=Bitmap.new([1,dwidth].max,[1,dheight].max)
end
return bitmap
end
def tileBitmap(dstbitmap,dstrect,srcbitmap,srcrect)
return if !srcbitmap || srcbitmap.disposed?
left=dstrect.x
top=dstrect.y
y=0;loop do break unless y<dstrect.height
x=0;loop do break unless x<dstrect.width
dstbitmap.blt(x+left,y+top,srcbitmap,srcrect)
x+=srcrect.width
end
y+=srcrect.height
end
end
def privRefreshCursor
contopac=self.contents_opacity
cursoropac=@cursoropacity*contopac/255
@sprites["cursor"].opacity=cursoropac
end
def privRefresh(changeBitmap=false)
return if !self || self.disposed?
backopac=self.back_opacity*self.opacity/255
contopac=self.contents_opacity
cursoropac=@cursoropacity*contopac/255
haveskin=@_windowskin && !@_windowskin.disposed?
for i in 0...4
@sprites["corner#{i}"].bitmap=@_windowskin
@sprites["scroll#{i}"].bitmap=@_windowskin
end
@sprites["pause"].bitmap=@_windowskin
@sprites["contents"].bitmap=@contents
if haveskin
for i in 0...4
@sprites["corner#{i}"].opacity=@opacity
@sprites["corner#{i}"].tone=@tone
@sprites["corner#{i}"].color=@color
@sprites["corner#{i}"].visible=@visible
@sprites["corner#{i}"].blend_type=@blend_type
@sprites["side#{i}"].opacity=@opacity
@sprites["side#{i}"].tone=@tone
@sprites["side#{i}"].color=@color
@sprites["side#{i}"].blend_type=@blend_type
@sprites["side#{i}"].visible=@visible
@sprites["scroll#{i}"].opacity=@opacity
@sprites["scroll#{i}"].tone=@tone
@sprites["scroll#{i}"].color=@color
@sprites["scroll#{i}"].visible=@visible
@sprites["scroll#{i}"].blend_type=@blend_type
end
for i in ["back","cursor","pause","contents"]
@sprites[i].color=@color
@sprites[i].tone=@tone
@sprites[i].blend_type=@blend_type
end
@sprites["contents"].blend_type=@contents_blend_type
@sprites["back"].opacity=backopac
@sprites["contents"].opacity=contopac
@sprites["cursor"].opacity=cursoropac
@sprites["pause"].opacity=@pauseopacity
supported=(@skinformat==0)
hascontents=(@contents && !@contents.disposed?)
@sprites["back"].visible=@visible
@sprites["contents"].visible=@visible && @openness==255
@sprites["pause"].visible=supported && @visible && @pause &&
(@combat & CompatBits::ShowPause)
@sprites["cursor"].visible=supported && @visible && @openness==255 &&
(@combat & CompatBits::ShowCursor)
@sprites["scroll0"].visible = false
@sprites["scroll1"].visible = false
@sprites["scroll2"].visible = false
@sprites["scroll3"].visible = false
else
for i in 0...4
@sprites["corner#{i}"].visible=false
@sprites["side#{i}"].visible=false
@sprites["scroll#{i}"].visible=false
end
@sprites["contents"].visible=@visible && @openness==255
@sprites["contents"].color=@color
@sprites["contents"].tone=@tone
@sprites["contents"].blend_type=@contents_blend_type
@sprites["contents"].opacity=contopac
@sprites["back"].visible=false
@sprites["pause"].visible=false
@sprites["cursor"].visible=false
end
for i in @spritekeys
@sprites[i].z=@z
end
if (@compat & CompatBits::CorrectZ)>0 && @skinformat==0 && !@rpgvx
# Compatibility Mode: Cursor, pause, and contents have higher Z
@sprites["cursor"].z=@z+1
@sprites["contents"].z=@z+2
@sprites["pause"].z=@z+2
end
if @skinformat==0
startX=16
startY=16
endX=16
endY=16
trimStartX=16
trimStartY=16
trimWidth=32
trimHeight=32
if @rpgvx
trimX=64
trimY=0
backRect=Rect.new(0,0,64,64)
blindsRect=Rect.new(0,64,64,64)
else
trimX=128
trimY=0
backRect=Rect.new(0,0,128,128)
blindsRect=nil
end
if @_windowskin && !@_windowskin.disposed?
@sprites["corner0"].src_rect.set(trimX,trimY+0,16,16);
@sprites["corner1"].src_rect.set(trimX+48,trimY+0,16,16);
@sprites["corner2"].src_rect.set(trimX,trimY+48,16,16);
@sprites["corner3"].src_rect.set(trimX+48,trimY+48,16,16);
@sprites["scroll0"].src_rect.set(trimX+24, trimY+16, 16, 8) # up
@sprites["scroll3"].src_rect.set(trimX+24, trimY+40, 16, 8) # down
@sprites["scroll1"].src_rect.set(trimX+16, trimY+24, 8, 16) # left
@sprites["scroll2"].src_rect.set(trimX+40, trimY+24, 8, 16) # right
cursorX=trimX
cursorY=trimY+64
sideRects=[
Rect.new(trimX+16,trimY+0,32,16),
Rect.new(trimX,trimY+16,16,32),
Rect.new(trimX+48,trimY+16,16,32),
Rect.new(trimX+16,trimY+48,32,16)
]
pauseRects=[
trimX+32,trimY+64,
trimX+48,trimY+64,
trimX+32,trimY+80,
trimX+48,trimY+80,
]
pauseWidth=16
pauseHeight=16
@sprites["pause"].src_rect.set(
pauseRects[@pauseframe*2],
pauseRects[@pauseframe*2+1],
pauseWidth,pauseHeight
)
end
else
trimStartX=@trim[0]
trimStartY=@trim[1]
trimWidth=@trim[0]+(@skinrect.width-@trim[2]+@trim[0])
trimHeight=@trim[1]+(@skinrect.height-@trim[3]+@trim[1])
if @_windowskin && !@_windowskin.disposed?
# width of left end of window
startX=@skinrect.x
# width of top end of window
startY=@skinrect.y
cx=@skinrect.x+@skinrect.width # right side of BODY rect
cy=@skinrect.y+@skinrect.height # bottom side of BODY rect
# width of right end of window
endX=(!@_windowskin || @_windowskin.disposed?) ? @skinrect.x : @_windowskin.width-cx
# height of bottom end of window
endY=(!@_windowskin || @_windowskin.disposed?) ? @skinrect.y : @_windowskin.height-cy
@sprites["corner0"].src_rect.set(0,0,startX,startY);
@sprites["corner1"].src_rect.set(cx,0,endX,startY);
@sprites["corner2"].src_rect.set(0,cy,startX,endY);
@sprites["corner3"].src_rect.set(cx,cy,endX,endY);
backRect=Rect.new(@skinrect.x,@skinrect.y,
@skinrect.width,@skinrect.height);
blindsRect=nil
sideRects=[
Rect.new(startX,0,@skinrect.width,startY), # side0 (top)
Rect.new(0,startY,startX,@skinrect.height), # side1 (left)
Rect.new(cx,startY,endX,@skinrect.height), # side2 (right)
Rect.new(startX,cy,@skinrect.width,endY) # side3 (bottom)
]
end
end
if @width>trimWidth && @height>trimHeight
@sprites["contents"].src_rect.set(@ox,@oy,@width-trimWidth,@height-trimHeight)
else
@sprites["contents"].src_rect.set(0,0,0,0)
end
@sprites["contents"].x=@x+trimStartX
@sprites["contents"].y=@y+trimStartY
if (@compat & CompatBits::ShowScrollArrows)>0 && @skinformat==0
# Compatibility mode: Make scroll arrows visible
if @skinformat==0 && @_windowskin && !@_windowskin.disposed? &&
@contents && !@contents.disposed?
@sprites["scroll0"].visible = @visible && hascontents && @oy > 0
@sprites["scroll1"].visible = @visible && hascontents && @ox > 0
@sprites["scroll2"].visible = @visible && (@contents.width - @ox) > @width-trimWidth
@sprites["scroll3"].visible = @visible && (@contents.height - @oy) > @height-trimHeight
end
end
if @_windowskin && !@_windowskin.disposed?
borderX=startX+endX
borderY=startY+endY
@sprites["corner0"].x=@x
@sprites["corner0"].y=@y
@sprites["corner1"].x=@x+@width-endX
@sprites["corner1"].y=@y
@sprites["corner2"].x=@x
@sprites["corner2"].y=@y+@height-endY
@sprites["corner3"].x=@x+@width-endX
@sprites["corner3"].y=@y+@height-endY
@sprites["side0"].x=@x+startX
@sprites["side0"].y=@y
@sprites["side1"].x=@x
@sprites["side1"].y=@y+startY
@sprites["side2"].x=@x+@width-endX
@sprites["side2"].y=@y+startY
@sprites["side3"].x=@x+startX
@sprites["side3"].y=@y+@height-endY
@sprites["scroll0"].x = @x+@width / 2 - 8
@sprites["scroll0"].y = @y+8
@sprites["scroll1"].x = @x+8
@sprites["scroll1"].y = @y+@height / 2 - 8
@sprites["scroll2"].x = @x+@width - 16
@sprites["scroll2"].y = @y+@height / 2 - 8
@sprites["scroll3"].x = @x+@width / 2 - 8
@sprites["scroll3"].y = @y+@height - 16
@sprites["cursor"].x=@x+startX+@cursor_rect.x
@sprites["cursor"].y=@y+startY+@cursor_rect.y
if (@compat & CompatBits::ExpandBack)>0 && @skinformat==0
# Compatibility mode: Expand background
@sprites["back"].x=@x+2
@sprites["back"].y=@y+2
else
@sprites["back"].x=@x+startX
@sprites["back"].y=@y+startY
end
end
if changeBitmap && @_windowskin && !@_windowskin.disposed?
if @skinformat==0
@sprites["cursor"].x=@x+startX+@cursor_rect.x
@sprites["cursor"].y=@y+startY+@cursor_rect.y
width=@cursor_rect.width
height=@cursor_rect.height
if width > 0 && height > 0
cursorrects=[
# sides
Rect.new(cursorX+2, cursorY+0, 28, 2),
Rect.new(cursorX+0, cursorY+2, 2, 28),
Rect.new(cursorX+30, cursorY+2, 2, 28),
Rect.new(cursorX+2, cursorY+30, 28, 2),
# corners
Rect.new(cursorX+0, cursorY+0, 2, 2),
Rect.new(cursorX+30, cursorY+0, 2, 2),
Rect.new(cursorX+0, cursorY+30, 2, 2),
Rect.new(cursorX+30, cursorY+30, 2, 2),
# back
Rect.new(cursorX+2, cursorY+2, 28, 28)
]
margin=2
fullmargin=4
@cursorbitmap = ensureBitmap(@cursorbitmap, width, height)
@cursorbitmap.clear
@sprites["cursor"].bitmap=@cursorbitmap
@sprites["cursor"].src_rect.set(0,0,width,height)
rect = Rect.new(margin,margin,width - fullmargin, height - fullmargin)
@cursorbitmap.stretch_blt(rect, @_windowskin, cursorrects[8])
@cursorbitmap.blt(0, 0, @_windowskin, cursorrects[4])# top left
@cursorbitmap.blt(width-margin, 0, @_windowskin, cursorrects[5]) # top right
@cursorbitmap.blt(0, height-margin, @_windowskin, cursorrects[6]) # bottom right
@cursorbitmap.blt(width-margin, height-margin, @_windowskin, cursorrects[7]) # bottom left
rect = Rect.new(margin, 0,width - fullmargin, margin)
@cursorbitmap.stretch_blt(rect, @_windowskin, cursorrects[0])
rect = Rect.new(0, margin,margin, height - fullmargin)
@cursorbitmap.stretch_blt(rect, @_windowskin, cursorrects[1])
rect = Rect.new(width - margin, margin, margin, height - fullmargin)
@cursorbitmap.stretch_blt(rect, @_windowskin, cursorrects[2])
rect = Rect.new(margin, height-margin, width - fullmargin, margin)
@cursorbitmap.stretch_blt(rect, @_windowskin, cursorrects[3])
else
@sprites["cursor"].visible=false
@sprites["cursor"].src_rect.set(0,0,0,0)
end
end
for i in 0..3
case i
when 0
dwidth = @width-startX-endX
dheight = startY
when 1
dwidth = startX
dheight = @height-startY-endY
when 2
dwidth = endX
dheight = @height-startY-endY
when 3
dwidth = @width-startX-endX
dheight = endY
end
@sidebitmaps[i]=ensureBitmap(@sidebitmaps[i],dwidth,dheight)
@sprites["side#{i}"].bitmap=@sidebitmaps[i]
@sprites["side#{i}"].src_rect.set(0,0,dwidth,dheight)
@sidebitmaps[i].clear
if sideRects[i].width>0 && sideRects[i].height>0
if (@compat & CompatBits::StretchSides)>0 && @skinformat==0
# Compatibility mode: Stretch sides
@sidebitmaps[i].stretch_blt(@sprites["side#{i}"].src_rect,
@_windowskin,sideRects[i])
else
tileBitmap(@sidebitmaps[i],@sprites["side#{i}"].src_rect,
@_windowskin,sideRects[i])
end
end
end
if (@compat & CompatBits::ExpandBack)>0 && @skinformat==0
# Compatibility mode: Expand background
backwidth=@width-4
backheight=@height-4
else
backwidth=@width-borderX
backheight=@height-borderY
end
if backwidth>0 && backheight>0
@backbitmap=ensureBitmap(@backbitmap,backwidth,backheight)
@sprites["back"].bitmap=@backbitmap
@sprites["back"].src_rect.set(0,0,backwidth,backheight)
@backbitmap.clear
if @stretch
@backbitmap.stretch_blt(@sprites["back"].src_rect,@_windowskin,backRect)
else
tileBitmap(@backbitmap,@sprites["back"].src_rect,@_windowskin,backRect)
end
if blindsRect
tileBitmap(@backbitmap,@sprites["back"].src_rect,@_windowskin,blindsRect)
end
else
@sprites["back"].visible=false
@sprites["back"].src_rect.set(0,0,0,0)
end
end
if @openness!=255
opn=@openness/255.0
for k in @spritekeys
sprite=@sprites[k]
ratio=(@height<=0) ? 0 : (sprite.y-@y)*1.0/@height
sprite.zoom_y=opn
sprite.zoom_x=1.0
sprite.oy=0
sprite.y=(@y+(@height/2.0)+(@height*ratio*opn)-(@height/2*opn)).floor
end
else
for k in @spritekeys
sprite=@sprites[k]
sprite.zoom_x=1.0
sprite.zoom_y=1.0
end
end
i=0
# Ensure Z order
for k in @spritekeys
sprite=@sprites[k]
y=sprite.y
sprite.y=i
sprite.oy=(sprite.zoom_y<=0) ? 0 : (i-y)/sprite.zoom_y
sprite.zoom_x*=@zoom_x
sprite.zoom_y*=@zoom_y
sprite.x*=@zoom_x
sprite.y*=@zoom_y
sprite.x+=(@offset_x/sprite.zoom_x)
sprite.y+=(@offset_y/sprite.zoom_y)
end
end
end
#===============================================================================
#
#===============================================================================
class SpriteWindow_Base < SpriteWindow
TEXTPADDING=4 # In pixels
def initialize(x, y, width, height)
super()
self.x = x
self.y = y
self.width = width
self.height = height
self.z = 100
@curframe=MessageConfig.pbGetSystemFrame()
@curfont=MessageConfig.pbGetSystemFontName()
@sysframe=AnimatedBitmap.new(@curframe)
RPG::Cache.retain(@curframe) if @curframe && !@curframe.empty?
@customskin=nil
__setWindowskin(@sysframe.bitmap)
__resolveSystemFrame()
pbSetSystemFont(self.contents) if self.contents
end
def __setWindowskin(skin)
if skin && (skin.width==192 && skin.height==128) || # RPGXP Windowskin
(skin.width==128 && skin.height==128) # RPGVX Windowskin
self.skinformat=0
else
self.skinformat=1
end
self.windowskin=skin
end
def __resolveSystemFrame
if self.skinformat==1
if !@resolvedFrame
@resolvedFrame=MessageConfig.pbGetSystemFrame()
@resolvedFrame.sub!(/\.[^\.\/\\]+$/,"")
end
self.loadSkinFile("#{@resolvedFrame}.txt") if @resolvedFrame!=""
end
end
def setSkin(skin) # Filename of windowskin to apply. Supports XP, VX, and animated skins.
@customskin.dispose if @customskin
@customskin=nil
resolvedName=pbResolveBitmap(skin)
return if nil_or_empty?(resolvedName)
@customskin=AnimatedBitmap.new(resolvedName)
RPG::Cache.retain(resolvedName)
__setWindowskin(@customskin.bitmap)
if self.skinformat==1
skinbase=resolvedName.sub(/\.[^\.\/\\]+$/,"")
self.loadSkinFile("#{skinbase}.txt")
end
end
def setSystemFrame
@customskin.dispose if @customskin
@customskin=nil
__setWindowskin(@sysframe.bitmap)
__resolveSystemFrame()
end
def update
super
if self.windowskin
if @customskin
if @customskin.totalFrames>1
@customskin.update
__setWindowskin(@customskin.bitmap)
end
elsif @sysframe
if @sysframe.totalFrames>1
@sysframe.update
__setWindowskin(@sysframe.bitmap)
end
end
end
if @curframe!=MessageConfig.pbGetSystemFrame()
@curframe=MessageConfig.pbGetSystemFrame()
if @sysframe && !@customskin
@sysframe.dispose if @sysframe
@sysframe=AnimatedBitmap.new(@curframe)
RPG::Cache.retain(@curframe) if @curframe && !@curframe.empty?
@resolvedFrame=nil
__setWindowskin(@sysframe.bitmap)
__resolveSystemFrame()
end
begin
refresh
rescue NoMethodError
end
end
if @curfont!=MessageConfig.pbGetSystemFontName()
@curfont=MessageConfig.pbGetSystemFontName()
if self.contents && !self.contents.disposed?
pbSetSystemFont(self.contents)
end
begin
refresh
rescue NoMethodError
end
end
end
def dispose
self.contents.dispose if self.contents
@sysframe.dispose
@customskin.dispose if @customskin
super
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,123 @@
#===============================================================================
# Displays an icon bitmap in a window. Supports animated images.
#===============================================================================
class IconWindow < SpriteWindow_Base
attr_reader :name
def initialize(x,y,width,height,viewport=nil)
super(x,y,width,height)
self.viewport=viewport
self.contents=nil
@name=""
@_iconbitmap=nil
end
def dispose
clearBitmaps()
super
end
def update
super
if @_iconbitmap
@_iconbitmap.update
self.contents=@_iconbitmap.bitmap
end
end
def clearBitmaps
@_iconbitmap.dispose if @_iconbitmap
@_iconbitmap=nil
self.contents=nil if !self.disposed?
end
# Sets the icon's filename. Alias for setBitmap.
def name=(value)
setBitmap(value)
end
# Sets the icon's filename.
def setBitmap(file,hue=0)
clearBitmaps()
@name=file
return if file==nil
if file!=""
@_iconbitmap=AnimatedBitmap.new(file,hue)
# for compatibility
self.contents=@_iconbitmap ? @_iconbitmap.bitmap : nil
else
@_iconbitmap=nil
end
end
end
#===============================================================================
# Displays an icon bitmap in a window. Supports animated images.
# Accepts bitmaps and paths to bitmap files in its constructor.
#===============================================================================
class PictureWindow < SpriteWindow_Base
def initialize(pathOrBitmap)
super(0,0,32,32)
self.viewport=viewport
self.contents=nil
@_iconbitmap=nil
setBitmap(pathOrBitmap)
end
def picture; @_iconbitmap; end
def dispose
clearBitmaps()
super
end
def update
super
if @_iconbitmap
if @_iconbitmap.is_a?(Bitmap)
self.contents=@_iconbitmap
else
@_iconbitmap.update
self.contents=@_iconbitmap.bitmap
end
end
end
def clearBitmaps
@_iconbitmap.dispose if @_iconbitmap
@_iconbitmap=nil
self.contents=nil if !self.disposed?
end
# Sets the icon's bitmap or filename. (hue parameter
# is ignored unless pathOrBitmap is a filename)
def setBitmap(pathOrBitmap,hue=0)
clearBitmaps()
if pathOrBitmap!=nil && pathOrBitmap!=""
if pathOrBitmap.is_a?(Bitmap)
@_iconbitmap=pathOrBitmap
self.contents=@_iconbitmap
self.width=@_iconbitmap.width+self.borderX
self.height=@_iconbitmap.height+self.borderY
elsif pathOrBitmap.is_a?(AnimatedBitmap)
@_iconbitmap=pathOrBitmap
self.contents=@_iconbitmap.bitmap
self.width=@_iconbitmap.bitmap.width+self.borderX
self.height=@_iconbitmap.bitmap.height+self.borderY
else
@_iconbitmap=AnimatedBitmap.new(pathOrBitmap,hue)
self.contents=@_iconbitmap ? @_iconbitmap.bitmap : nil
self.width=@_iconbitmap ? @_iconbitmap.bitmap.width+self.borderX :
32+self.borderX
self.height=@_iconbitmap ? @_iconbitmap.bitmap.height+self.borderY :
32+self.borderY
end
else
@_iconbitmap=nil
self.width=32+self.borderX
self.height=32+self.borderY
end
end
end

View File

@@ -0,0 +1,496 @@
#===============================================================================
# SpriteWrapper is a class which wraps (most of) Sprite's properties.
#===============================================================================
class SpriteWrapper
def initialize(viewport = nil)
@sprite = Sprite.new(viewport)
end
def dispose
@sprite.dispose;
end
def disposed?
return @sprite.disposed?;
end
def viewport
return @sprite.viewport;
end
def flash(color, duration)
; return @sprite.flash(color, duration);
end
def update
return @sprite.update;
end
def x
@sprite.x;
end
def x=(value)
; @sprite.x = value;
end
def y
@sprite.y;
end
def y=(value)
; @sprite.y = value;
end
def bitmap
@sprite.bitmap;
end
def bitmap=(value)
; @sprite.bitmap = value;
end
def src_rect
@sprite.src_rect;
end
def src_rect=(value)
; @sprite.src_rect = value;
end
def visible
@sprite.visible;
end
def visible=(value)
; @sprite.visible = value;
end
def z
@sprite.z;
end
def z=(value)
; @sprite.z = value;
end
def ox
@sprite.ox;
end
def ox=(value)
; @sprite.ox = value;
end
def oy
@sprite.oy;
end
def oy=(value)
; @sprite.oy = value;
end
def zoom_x
@sprite.zoom_x;
end
def zoom_x=(value)
; @sprite.zoom_x = value;
end
def zoom_y
@sprite.zoom_y;
end
def zoom_y=(value)
; @sprite.zoom_y = value;
end
def angle
@sprite.angle;
end
def angle=(value)
; @sprite.angle = value;
end
def mirror
@sprite.mirror;
end
def mirror=(value)
; @sprite.mirror = value;
end
def bush_depth
@sprite.bush_depth;
end
def bush_depth=(value)
; @sprite.bush_depth = value;
end
def opacity
@sprite.opacity;
end
def opacity=(value)
; @sprite.opacity = value;
end
def blend_type
@sprite.blend_type;
end
def blend_type=(value)
; @sprite.blend_type = value;
end
def color
@sprite.color;
end
def color=(value)
; @sprite.color = value;
end
def tone
@sprite.tone;
end
def tone=(value)
; @sprite.tone = value;
end
def viewport=(value)
return if self.viewport == value
bitmap = @sprite.bitmap
src_rect = @sprite.src_rect
visible = @sprite.visible
x = @sprite.x
y = @sprite.y
z = @sprite.z
ox = @sprite.ox
oy = @sprite.oy
zoom_x = @sprite.zoom_x
zoom_y = @sprite.zoom_y
angle = @sprite.angle
mirror = @sprite.mirror
bush_depth = @sprite.bush_depth
opacity = @sprite.opacity
blend_type = @sprite.blend_type
color = @sprite.color
tone = @sprite.tone
@sprite.dispose
@sprite = Sprite.new(value)
@sprite.bitmap = bitmap
@sprite.src_rect = src_rect
@sprite.visible = visible
@sprite.x = x
@sprite.y = y
@sprite.z = z
@sprite.ox = ox
@sprite.oy = oy
@sprite.zoom_x = zoom_x
@sprite.zoom_y = zoom_y
@sprite.angle = angle
@sprite.mirror = mirror
@sprite.bush_depth = bush_depth
@sprite.opacity = opacity
@sprite.blend_type = blend_type
@sprite.color = color
@sprite.tone = tone
end
end
#===============================================================================
# Sprite class that maintains a bitmap of its own.
# This bitmap can't be changed to a different one.
#===============================================================================
class BitmapSprite < SpriteWrapper
def initialize(width, height, viewport = nil)
super(viewport)
self.bitmap = Bitmap.new(width, height)
@initialized = true
end
def bitmap=(value)
super(value) if !@initialized
end
def dispose
self.bitmap.dispose if !self.disposed?
super
end
end
#===============================================================================
#
#===============================================================================
class AnimatedSprite < SpriteWrapper
attr_reader :frame
attr_reader :framewidth
attr_reader :frameheight
attr_reader :framecount
attr_reader :animname
attr_reader :playing
def initializeLong(animname, framecount, framewidth, frameheight, frameskip)
@animname = pbBitmapName(animname)
@realframes = 0
@frameskip = [1, frameskip].max
@frameskip *= Graphics.frame_rate / 20
raise _INTL("Frame width is 0") if framewidth == 0
raise _INTL("Frame height is 0") if frameheight == 0
begin
@animbitmap = AnimatedBitmap.new(animname).deanimate
rescue
@animbitmap = Bitmap.new(framewidth, frameheight)
end
if @animbitmap.width % framewidth != 0
raise _INTL("Bitmap's width ({1}) is not a multiple of frame width ({2}) [Bitmap={3}]",
@animbitmap.width, framewidth, animname)
end
if @animbitmap.height % frameheight != 0
raise _INTL("Bitmap's height ({1}) is not a multiple of frame height ({2}) [Bitmap={3}]",
@animbitmap.height, frameheight, animname)
end
@framecount = framecount
@framewidth = framewidth
@frameheight = frameheight
@framesperrow = @animbitmap.width / @framewidth
@playing = false
self.bitmap = @animbitmap
self.src_rect.width = @framewidth
self.src_rect.height = @frameheight
self.frame = 0
end
# Shorter version of AnimationSprite. All frames are placed on a single row
# of the bitmap, so that the width and height need not be defined beforehand
def initializeShort(animname, framecount, frameskip)
@animname = pbBitmapName(animname)
@realframes = 0
@frameskip = [1, frameskip].max
@frameskip *= Graphics.frame_rate / 20
begin
@animbitmap = AnimatedBitmap.new(animname).deanimate
rescue
@animbitmap = Bitmap.new(framecount * 4, 32)
end
if @animbitmap.width % framecount != 0
raise _INTL("Bitmap's width ({1}) is not a multiple of frame count ({2}) [Bitmap={3}]",
@animbitmap.width, framewidth, animname)
end
@framecount = framecount
@framewidth = @animbitmap.width / @framecount
@frameheight = @animbitmap.height
@framesperrow = framecount
@playing = false
self.bitmap = @animbitmap
self.src_rect.width = @framewidth
self.src_rect.height = @frameheight
self.frame = 0
end
def initialize(*args)
if args.length == 1
super(args[0][3])
initializeShort(args[0][0], args[0][1], args[0][2])
else
super(args[5])
initializeLong(args[0], args[1], args[2], args[3], args[4])
end
end
def self.create(animname, framecount, frameskip, viewport = nil)
return self.new([animname, framecount, frameskip, viewport])
end
def dispose
return if disposed?
@animbitmap.dispose
@animbitmap = nil
super
end
def playing?
return @playing
end
def frame=(value)
@frame = value
@realframes = 0
self.src_rect.x = @frame % @framesperrow * @framewidth
self.src_rect.y = @frame / @framesperrow * @frameheight
end
def start
@playing = true
@realframes = 0
end
alias play start
def stop
@playing = false
end
def reset
@frame=0
@realframes = 0
end
def update
super
if @playing
@realframes += 1
if @realframes == @frameskip
@realframes = 0
self.frame += 1
self.frame %= self.framecount
end
end
end
end
#===============================================================================
# Displays an icon bitmap in a sprite. Supports animated images.
#===============================================================================
class IconSprite < SpriteWrapper
attr_reader :name
def initialize(*args)
if args.length == 0
super(nil)
self.bitmap = nil
elsif args.length == 1
super(args[0])
self.bitmap = nil
elsif args.length == 2
super(nil)
self.x = args[0]
self.y = args[1]
else
super(args[2])
self.x = args[0]
self.y = args[1]
end
@name = ""
@_iconbitmap = nil
end
def dispose
clearBitmaps()
super
end
# Sets the icon's filename. Alias for setBitmap.
def name=(value)
setBitmap(value)
end
def setBitmapDirectly(bitmap)
oldrc = self.src_rect
clearBitmaps()
@name = ""
return if bitmap == nil
@_iconbitmap = bitmap
# for compatibility
#
self.bitmap = @_iconbitmap ? @_iconbitmap.bitmap : nil
self.src_rect = oldrc
end
def setColor(r = 0, g = 0, b = 0, a = 255)
@_iconbitmap.pbSetColor(r,g,b,a)
end
# Sets the icon's filename.
def setBitmap(file, hue = 0)
oldrc = self.src_rect
clearBitmaps()
@name = file
return if file == nil
if file != ""
@_iconbitmap = AnimatedBitmap.new(file, hue)
# for compatibility
self.bitmap = @_iconbitmap ? @_iconbitmap.bitmap : nil
self.src_rect = oldrc
else
@_iconbitmap = nil
end
end
def getBitmap
return @_iconbitmap
end
def clearBitmaps
@_iconbitmap.dispose if @_iconbitmap
@_iconbitmap = nil
self.bitmap = nil if !self.disposed?
end
def update
super
return if !@_iconbitmap
@_iconbitmap.update
if self.bitmap != @_iconbitmap.bitmap
oldrc = self.src_rect
self.bitmap = @_iconbitmap.bitmap
self.src_rect = oldrc
end
end
end
#===============================================================================
# Old GifSprite class, retained for compatibility
#===============================================================================
class GifSprite < IconSprite
def initialize(path)
super(0, 0)
setBitmap(path)
end
end
#===============================================================================
# SpriteWrapper that stores multiple bitmaps, and displays only one at once.
#===============================================================================
class ChangelingSprite < SpriteWrapper
def initialize(x = 0, y = 0, viewport = nil)
super(viewport)
self.x = x
self.y = y
@bitmaps = {}
@currentBitmap = nil
end
def addBitmap(key, path)
@bitmaps[key].dispose if @bitmaps[key]
@bitmaps[key] = AnimatedBitmap.new(path)
end
def changeBitmap(key)
@currentBitmap = @bitmaps[key]
self.bitmap = (@currentBitmap) ? @currentBitmap.bitmap : nil
end
def dispose
return if disposed?
for bm in @bitmaps.values;
bm.dispose;
end
@bitmaps.clear
super
end
def update
return if disposed?
for bm in @bitmaps.values;
bm.update;
end
self.bitmap = (@currentBitmap) ? @currentBitmap.bitmap : nil
end
end

View File

@@ -0,0 +1,387 @@
#===============================================================================
#
#===============================================================================
class AnimatedBitmap
attr_reader :path
attr_reader :filename
def initialize(file, hue = 0)
raise "Filename is nil (missing graphic)." if file.nil?
path = file
filename = ""
if file.last != '/' # Isn't just a directory
split_file = file.split(/[\\\/]/)
filename = split_file.pop
path = split_file.join('/') + '/'
end
@filename = filename
@path = path
if filename[/^\[\d+(?:,\d+)?\]/] # Starts with 1 or 2 numbers in square brackets
@bitmap = PngAnimatedBitmap.new(path, filename, hue)
else
@bitmap = GifBitmap.new(path, filename, hue)
end
end
def setup_from_bitmap(bitmap,hue=0)
@path = ""
@filename = ""
@bitmap = GifBitmap.new("", '', hue)
@bitmap.bitmap = bitmap;
end
def self.from_bitmap(bitmap, hue=0)
obj = allocate
obj.send(:setup_from_bitmap, bitmap, hue)
obj
end
def pbSetColor(r = 0, g = 0, b = 0, a = 255)
color = Color.new(r, g, b, a)
pbSetColorValue(color)
end
def pbSetColorValue(color)
for i in 0..@bitmap.bitmap.width
for j in 0..@bitmap.bitmap.height
if @bitmap.bitmap.get_pixel(i, j).alpha != 0
@bitmap.bitmap.set_pixel(i, j, color)
end
end
end
end
def shiftColors(offset = 0)
@bitmap.bitmap.hue_change(offset)
end
def [](index)
; @bitmap[index];
end
def width
@bitmap.bitmap.width;
end
def height
@bitmap.bitmap.height;
end
def length
@bitmap.length;
end
def each
@bitmap.each { |item| yield item };
end
def bitmap
@bitmap.bitmap;
end
def bitmap=(bitmap)
@bitmap.bitmap = bitmap;
end
def currentIndex
@bitmap.currentIndex;
end
def totalFrames
@bitmap.totalFrames;
end
def disposed?
@bitmap.disposed?;
end
def update
@bitmap.update;
end
def dispose
@bitmap.dispose;
end
def deanimate
@bitmap.deanimate;
end
def copy
@bitmap.copy;
end
def scale_bitmap(scale)
return if scale == 1
new_width = @bitmap.bitmap.width * scale
new_height = @bitmap.bitmap.height * scale
destination_rect = Rect.new(0, 0, new_width, new_height)
source_rect = Rect.new(0, 0, @bitmap.bitmap.width, @bitmap.bitmap.height)
new_bitmap = Bitmap.new(new_width, new_height)
new_bitmap.stretch_blt(
destination_rect,
@bitmap.bitmap,
source_rect
)
@bitmap.bitmap = new_bitmap
end
def mirror
mirror_horizontally
end
def mirror_horizontally
bmp = @bitmap.bitmap
half_width = bmp.width / 2
height = bmp.height
(0...half_width).each do |x|
(0...height).each do |y|
left_pixel = bmp.get_pixel(x, y)
right_pixel = bmp.get_pixel(bmp.width - 1 - x, y)
bmp.set_pixel(x, y, right_pixel)
bmp.set_pixel(bmp.width - 1 - x, y, left_pixel)
end
end
end
def mirror_vertically
bmp = @bitmap.bitmap
width = bmp.width
half_height = bmp.height / 2
(0...half_height).each do |y|
(0...width).each do |x|
top_pixel = bmp.get_pixel(x, y)
bottom_pixel = bmp.get_pixel(x, bmp.height - 1 - y)
bmp.set_pixel(x, y, bottom_pixel)
bmp.set_pixel(x, bmp.height - 1 - y, top_pixel)
end
end
end
# def mirror
# @bitmap.bitmap
# end
end
#===============================================================================
#
#===============================================================================
class PngAnimatedBitmap
attr_accessor :frames
# Creates an animated bitmap from a PNG file.
def initialize(dir, filename, hue = 0)
@frames = []
@currentFrame = 0
@framecount = 0
panorama = RPG::Cache.load_bitmap(dir, filename, hue)
if filename[/^\[(\d+)(?:,(\d+))?\]/] # Starts with 1 or 2 numbers in brackets
# File has a frame count
numFrames = $1.to_i
delay = $2.to_i
delay = 10 if delay == 0
raise "Invalid frame count in #{filename}" if numFrames <= 0
raise "Invalid frame delay in #{filename}" if delay <= 0
if panorama.width % numFrames != 0
raise "Bitmap's width (#{panorama.width}) is not divisible by frame count: #{filename}"
end
@frameDelay = delay
subWidth = panorama.width / numFrames
for i in 0...numFrames
subBitmap = BitmapWrapper.new(subWidth, panorama.height)
subBitmap.blt(0, 0, panorama, Rect.new(subWidth * i, 0, subWidth, panorama.height))
@frames.push(subBitmap)
end
panorama.dispose
else
@frames = [panorama]
end
end
def [](index)
return @frames[index]
end
def width
self.bitmap.width;
end
def height
self.bitmap.height;
end
def deanimate
for i in 1...@frames.length
@frames[i].dispose
end
@frames = [@frames[0]]
@currentFrame = 0
return @frames[0]
end
def bitmap
return @frames[@currentFrame]
end
def currentIndex
return @currentFrame
end
def frameDelay(_index)
return @frameDelay
end
def length
return @frames.length
end
def each
@frames.each { |item| yield item }
end
def totalFrames
return @frameDelay * @frames.length
end
def disposed?
return @disposed
end
def update
return if disposed?
if @frames.length > 1
@framecount += 1
if @framecount >= @frameDelay
@framecount = 0
@currentFrame += 1
@currentFrame %= @frames.length
end
end
end
def dispose
if !@disposed
@frames.each { |f| f.dispose }
end
@disposed = true
end
def copy
x = self.clone
x.frames = x.frames.clone
for i in 0...x.frames.length
x.frames[i] = x.frames[i].copy
end
return x
end
end
#===============================================================================
#
#===============================================================================
class GifBitmap
attr_accessor :bitmap
attr_reader :loaded_from_cache
# Creates a bitmap from a GIF file. Can also load non-animated bitmaps.
def initialize(dir, filename, hue = 0)
@bitmap = nil
@disposed = false
@loaded_from_cache = false
filename = "" if !filename
begin
@bitmap = RPG::Cache.load_bitmap(dir, filename, hue)
@loaded_from_cache = true
rescue
@bitmap = nil
end
@bitmap = BitmapWrapper.new(32, 32) if @bitmap.nil?
@bitmap.play if @bitmap&.animated?
end
def [](_index)
return @bitmap
end
def deanimate
@bitmap&.goto_and_stop(0) if @bitmap&.animated?
return @bitmap
end
def currentIndex
return @bitmap&.current_frame || 0
end
def length
return @bitmap&.frame_count || 1
end
def each
yield @bitmap
end
def totalFrames
f_rate = @bitmap.frame_rate
f_rate = 1 if f_rate.nil? || f_rate == 0
return (@bitmap) ? (@bitmap.frame_count / f_rate).floor : 1
end
def disposed?
return @disposed
end
def width
return @bitmap&.width || 0
end
def height
return @bitmap&.height || 0
end
# Gifs are animated automatically by mkxp-z. This function does nothing.
def update; end
def dispose
return if @disposed
@bitmap.dispose
@disposed = true
end
def copy
x = self.clone
x.bitmap = @bitmap.copy if @bitmap
return x
end
end
#===============================================================================
#
#===============================================================================
def pbGetTileBitmap(filename, tile_id, hue, width = 1, height = 1)
return RPG::Cache.tileEx(filename, tile_id, hue, width, height) { |f|
AnimatedBitmap.new("Graphics/Tilesets/" + filename).deanimate
}
end
def pbGetTileset(name, hue = 0)
return AnimatedBitmap.new("Graphics/Tilesets/" + name, hue).deanimate
end
def pbGetAutotile(name, hue = 0)
return AnimatedBitmap.new("Graphics/Autotiles/" + name, hue).deanimate
end
def pbGetAnimation(name, hue = 0)
return AnimatedBitmap.new("Graphics/Animations/" + name, hue).deanimate
end

View File

@@ -0,0 +1,230 @@
#===============================================================================
#
#===============================================================================
class Plane
def update; end
def refresh; end
end
#===============================================================================
# This class works around a limitation that planes are always
# 640 by 480 pixels in size regardless of the window's size.
#===============================================================================
class LargePlane < Plane
attr_accessor :borderX
attr_accessor :borderY
def initialize(viewport=nil)
@__sprite=Sprite.new(viewport)
@__disposed=false
@__ox=0
@__oy=0
@__bitmap=nil
@__visible=true
@__sprite.visible=false
@borderX=0
@borderY=0
end
def disposed?
return @__disposed
end
def dispose
if !@__disposed
@__sprite.bitmap.dispose if @__sprite.bitmap
@__sprite.dispose
@__sprite=nil
@__bitmap=nil
@__disposed=true
end
#super
end
def ox; @__ox; end
def oy; @__oy; end
def ox=(value);
return if @__ox==value
@__ox = value
refresh
end
def oy=(value);
return if @__oy==value
@__oy = value
refresh
end
def bitmap
return @__bitmap
end
def bitmap=(value)
if value==nil
if @__bitmap!=nil
@__bitmap=nil
@__sprite.visible=(@__visible && !@__bitmap.nil?)
end
elsif @__bitmap!=value && !value.disposed?
@__bitmap=value
refresh
elsif value.disposed?
if @__bitmap!=nil
@__bitmap=nil
@__sprite.visible=(@__visible && !@__bitmap.nil?)
end
end
end
def viewport; @__sprite.viewport; end
def zoom_x; @__sprite.zoom_x; end
def zoom_y; @__sprite.zoom_y; end
def opacity; @__sprite.opacity; end
def blend_type; @__sprite.blend_type; end
def visible; @__visible; end
def z; @__sprite.z; end
def color; @__sprite.color; end
def tone; @__sprite.tone; end
def zoom_x=(v);
return if @__sprite.zoom_x==v
@__sprite.zoom_x = v
refresh
end
def zoom_y=(v);
return if @__sprite.zoom_y==v
@__sprite.zoom_y = v
refresh
end
def opacity=(v); @__sprite.opacity=(v); end
def blend_type=(v); @__sprite.blend_type=(v); end
def visible=(v); @__visible=v; @__sprite.visible=(@__visible && !@__bitmap.nil?); end
def z=(v); @__sprite.z=(v); end
def color=(v); @__sprite.color=(v); end
def tone=(v); @__sprite.tone=(v); end
def update; ;end
def refresh
@__sprite.visible = (@__visible && !@__bitmap.nil?)
if @__bitmap
if !@__bitmap.disposed?
@__ox += @__bitmap.width*@__sprite.zoom_x if @__ox<0
@__oy += @__bitmap.height*@__sprite.zoom_y if @__oy<0
@__ox -= @__bitmap.width*@__sprite.zoom_x if @__ox>@__bitmap.width
@__oy -= @__bitmap.height*@__sprite.zoom_y if @__oy>@__bitmap.height
dwidth = (Graphics.width/@__sprite.zoom_x+@borderX).to_i # +2
dheight = (Graphics.height/@__sprite.zoom_y+@borderY).to_i # +2
@__sprite.bitmap = ensureBitmap(@__sprite.bitmap,dwidth,dheight)
@__sprite.bitmap.clear
tileBitmap(@__sprite.bitmap,@__bitmap,@__bitmap.rect)
else
@__sprite.visible = false
end
end
end
private
def ensureBitmap(bitmap,dwidth,dheight)
if !bitmap || bitmap.disposed? || bitmap.width<dwidth || bitmap.height<dheight
bitmap.dispose if bitmap
bitmap = Bitmap.new([1,dwidth].max,[1,dheight].max)
end
return bitmap
end
def tileBitmap(dstbitmap,srcbitmap,srcrect)
return if !srcbitmap || srcbitmap.disposed?
dstrect = dstbitmap.rect
left = (dstrect.x-@__ox/@__sprite.zoom_x).to_i
top = (dstrect.y-@__oy/@__sprite.zoom_y).to_i
while left>0; left -= srcbitmap.width; end
while top>0; top -= srcbitmap.height; end
y = top
while y<dstrect.height
x = left
while x<dstrect.width
dstbitmap.blt(x+@borderX,y+@borderY,srcbitmap,srcrect)
x += srcrect.width
end
y += srcrect.height
end
end
end
#===============================================================================
# A plane class that displays a single color.
#===============================================================================
class ColoredPlane < LargePlane
def initialize(color,viewport=nil)
super(viewport)
self.bitmap=Bitmap.new(32,32)
setPlaneColor(color)
end
def dispose
self.bitmap.dispose if self.bitmap
super
end
def setPlaneColor(value)
self.bitmap.fill_rect(0,0,self.bitmap.width,self.bitmap.height,value)
self.refresh
end
end
#===============================================================================
# A plane class that supports animated images.
#===============================================================================
class AnimatedPlane < LargePlane
def initialize(viewport)
super(viewport)
@bitmap=nil
end
def dispose
clearBitmaps()
super
end
def update
super
if @bitmap
@bitmap.update
self.bitmap=@bitmap.bitmap
end
end
def clearBitmaps
@bitmap.dispose if @bitmap
@bitmap=nil
self.bitmap=nil if !self.disposed?
end
def setPanorama(file, hue=0)
clearBitmaps()
return if file==nil
@bitmap=AnimatedBitmap.new("Graphics/Panoramas/"+file,hue)
end
def setFog(file, hue=0)
clearBitmaps()
return if file==nil
@bitmap=AnimatedBitmap.new("Graphics/Fogs/"+file,hue)
end
def setBitmap(file, hue=0)
clearBitmaps()
return if file==nil
@bitmap=AnimatedBitmap.new(file,hue)
end
end

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,564 @@
#===============================================================================
#
#===============================================================================
class CharacterEntryHelper
attr_reader :text
attr_accessor :maxlength
attr_reader :passwordChar
attr_accessor :cursor
def initialize(text)
@maxlength=-1
@text=text
@passwordChar=""
@cursor=text.scan(/./m).length
end
def text=(value)
@text=value
end
def textChars
chars=text.scan(/./m)
if @passwordChar!=""
chars.length.times { |i| chars[i] = @passwordChar }
end
return chars
end
def passwordChar=(value)
@passwordChar=value ? value : ""
end
def length
return self.text.scan(/./m).length
end
def canInsert?
chars=self.text.scan(/./m)
return false if @maxlength>=0 && chars.length>=@maxlength
return true
end
def insert(ch)
chars=self.text.scan(/./m)
return false if @maxlength>=0 && chars.length>=@maxlength
chars.insert(@cursor,ch)
@text=""
for ch in chars
@text+=ch if ch
end
@cursor+=1
return true
end
def canDelete?
chars=self.text.scan(/./m)
return false if chars.length<=0 || @cursor<=0
return true
end
def delete
chars=self.text.scan(/./m)
return false if chars.length<=0 || @cursor<=0
chars.delete_at(@cursor-1)
@text=""
for ch in chars
@text+=ch if ch
end
@cursor-=1
return true
end
private
def ensure
return if @maxlength<0
chars=self.text.scan(/./m)
if chars.length>@maxlength && @maxlength>=0
chars=chars[0,@maxlength]
end
@text=""
for ch in chars
@text+=ch if ch
end
end
end
#===============================================================================
#
#===============================================================================
class Window_TextEntry < SpriteWindow_Base
def initialize(text,x,y,width,height,heading=nil,usedarkercolor=false)
super(x,y,width,height)
colors=getDefaultTextColors(self.windowskin)
@baseColor=colors[0]
@shadowColor=colors[1]
if usedarkercolor
@baseColor=Color.new(16,24,32)
@shadowColor=Color.new(168,184,184)
end
@helper=CharacterEntryHelper.new(text)
@heading=heading
self.active=true
@frame=0
refresh
end
def text
@helper.text
end
def maxlength
@helper.maxlength
end
def passwordChar
@helper.passwordChar
end
def text=(value)
@helper.text=value
self.refresh
end
def passwordChar=(value)
@helper.passwordChar=value
refresh
end
def maxlength=(value)
@helper.maxlength=value
self.refresh
end
def insert(ch)
if @helper.insert(ch)
@frame=0
self.refresh
return true
end
return false
end
def delete
if @helper.delete
@frame=0
self.refresh
return true
end
return false
end
def update
@frame += 1
@frame %= 20
self.refresh if (@frame%10)==0
return if !self.active
# Moving cursor
if Input.repeat?(Input::LEFT) && Input.press?(Input::ACTION)
if @helper.cursor > 0
@helper.cursor -= 1
@frame = 0
self.refresh
end
elsif Input.repeat?(Input::RIGHT) && Input.press?(Input::ACTION)
if @helper.cursor < self.text.scan(/./m).length
@helper.cursor += 1
@frame = 0
self.refresh
end
elsif Input.repeat?(Input::BACK) # Backspace
self.delete if @helper.cursor > 0
end
end
def refresh
self.contents=pbDoEnsureBitmap(self.contents,self.width-self.borderX,
self.height-self.borderY)
bitmap=self.contents
bitmap.clear
x=0
y=0
if @heading
textwidth=bitmap.text_size(@heading).width
pbDrawShadowText(bitmap,x,y, textwidth+4, 32, @heading,@baseColor,@shadowColor)
y+=32
end
x+=4
width=self.width-self.borderX
cursorcolor=Color.new(16,24,32)
textscan=self.text.scan(/./m)
scanlength=textscan.length
@helper.cursor=scanlength if @helper.cursor>scanlength
@helper.cursor=0 if @helper.cursor<0
startpos=@helper.cursor
fromcursor=0
while (startpos>0)
c=(@helper.passwordChar!="") ? @helper.passwordChar : textscan[startpos-1]
fromcursor+=bitmap.text_size(c).width
break if fromcursor>width-4
startpos-=1
end
for i in startpos...scanlength
c=(@helper.passwordChar!="") ? @helper.passwordChar : textscan[i]
textwidth=bitmap.text_size(c).width
next if c=="\n"
# Draw text
pbDrawShadowText(bitmap,x,y, textwidth+4, 32, c,@baseColor,@shadowColor)
# Draw cursor if necessary
if ((@frame/10)&1) == 0 && i==@helper.cursor
bitmap.fill_rect(x,y+4,2,24,cursorcolor)
end
# Add x to drawn text width
x += textwidth
end
if ((@frame/10)&1) == 0 && textscan.length==@helper.cursor
bitmap.fill_rect(x,y+4,2,24,cursorcolor)
end
end
end
#===============================================================================
#
#===============================================================================
class Window_TextEntry_Keyboard < Window_TextEntry
def update
@frame+=1
@frame%=20
self.refresh if ((@frame%10)==0)
return if !self.active
# Moving cursor
if Input.triggerex?(:LEFT) || Input.repeatex?(:LEFT)
if @helper.cursor > 0
@helper.cursor-=1
@frame=0
self.refresh
end
return
elsif Input.triggerex?(:RIGHT) || Input.repeatex?(:RIGHT)
if @helper.cursor < self.text.scan(/./m).length
@helper.cursor+=1
@frame=0
self.refresh
end
return
elsif Input.triggerex?(:BACKSPACE) || Input.repeatex?(:BACKSPACE)
self.delete if @helper.cursor>0
return
elsif Input.triggerex?(:RETURN) || Input.triggerex?(:ESCAPE)
return
end
Input.gets.each_char { |c| insert(c) }
end
end
#===============================================================================
#
#===============================================================================
class Window_MultilineTextEntry < SpriteWindow_Base
def initialize(text,x,y,width,height)
super(x,y,width,height)
colors=getDefaultTextColors(self.windowskin)
@baseColor=colors[0]
@shadowColor=colors[1]
@helper=CharacterEntryHelper.new(text)
@firstline=0
@cursorLine=0
@cursorColumn=0
@frame=0
self.active=true
refresh
end
attr_reader :baseColor
attr_reader :shadowColor
def baseColor=(value)
@baseColor=value
refresh
end
def shadowColor=(value)
@shadowColor=value
refresh
end
def text
@helper.text
end
def maxlength
@helper.maxlength
end
def text=(value)
@helper.text=value
@textchars=nil
self.refresh
end
def maxlength=(value)
@helper.maxlength=value
@textchars=nil
self.refresh
end
def insert(ch)
@helper.cursor=getPosFromLineAndColumn(@cursorLine,@cursorColumn)
if @helper.insert(ch)
@frame=0
@textchars=nil
moveCursor(0,1)
self.refresh
return true
end
return false
end
def delete
@helper.cursor=getPosFromLineAndColumn(@cursorLine,@cursorColumn)
if @helper.delete
@frame=0
moveCursor(0,-1) # use old textchars
@textchars=nil
self.refresh
return true
end
return false
end
def getTextChars
if !@textchars
@textchars=getLineBrokenText(self.contents,@helper.text,
self.contents.width,nil)
end
return @textchars
end
def getTotalLines
textchars=getTextChars
return 1 if textchars.length==0
tchar=textchars[textchars.length-1]
return tchar[5]+1
end
def getLineY(line)
textchars=getTextChars
return 0 if textchars.length==0
totallines=getTotalLines()
line=0 if line<0
line=totallines-1 if line>=totallines
maximumY=0
for i in 0...textchars.length
thisline=textchars[i][5]
y=textchars[i][2]
return y if thisline==line
maximumY=y if maximumY<y
end
return maximumY
end
def getColumnsInLine(line)
textchars=getTextChars
return 0 if textchars.length==0
totallines=getTotalLines()
line=0 if line<0
line=totallines-1 if line>=totallines
endpos=0
for i in 0...textchars.length
thisline=textchars[i][5]
thislength=textchars[i][8]
endpos+=thislength if thisline==line
end
return endpos
end
def getPosFromLineAndColumn(line,column)
textchars=getTextChars
return 0 if textchars.length==0
totallines=getTotalLines()
line=0 if line<0
line=totallines-1 if line>=totallines
endpos=0
for i in 0...textchars.length
thisline=textchars[i][5]
thispos=textchars[i][6]
thiscolumn=textchars[i][7]
thislength=textchars[i][8]
if thisline==line
endpos=thispos+thislength
# echoln [endpos,thispos+(column-thiscolumn),textchars[i]]
if column>=thiscolumn && column<=thiscolumn+thislength && thislength>0
return thispos+(column-thiscolumn)
end
end
end
# if endpos==0
# echoln [totallines,line,column]
# echoln textchars
# end
# echoln "endpos=#{endpos}"
return endpos
end
def getLastVisibleLine
getTextChars()
textheight=[1,self.contents.text_size("X").height].max
lastVisible=@firstline+((self.height-self.borderY)/textheight)-1
return lastVisible
end
def updateCursorPos(doRefresh)
# Calculate new cursor position
@helper.cursor=getPosFromLineAndColumn(@cursorLine,@cursorColumn)
if doRefresh
@frame=0
self.refresh
end
@firstline=@cursorLine if @cursorLine<@firstline
lastVisible=getLastVisibleLine()
@firstline+=(@cursorLine-lastVisible) if @cursorLine>lastVisible
end
def moveCursor(lineOffset, columnOffset)
# Move column offset first, then lines (since column offset
# can affect line offset)
# echoln ["beforemoving",@cursorLine,@cursorColumn]
totalColumns=getColumnsInLine(@cursorLine) # check current line
totalLines=getTotalLines()
oldCursorLine=@cursorLine
oldCursorColumn=@cursorColumn
@cursorColumn+=columnOffset
if @cursorColumn<0 && @cursorLine>0
# Will happen if cursor is moved left from the beginning of a line
@cursorLine-=1
@cursorColumn=getColumnsInLine(@cursorLine)
elsif @cursorColumn>totalColumns && @cursorLine<totalLines-1
# Will happen if cursor is moved right from the end of a line
@cursorLine+=1
@cursorColumn=0
end
# Ensure column bounds
totalColumns=getColumnsInLine(@cursorLine)
@cursorColumn=totalColumns if @cursorColumn>totalColumns
@cursorColumn=0 if @cursorColumn<0 # totalColumns can be 0
# Move line offset
@cursorLine+=lineOffset
@cursorLine=0 if @cursorLine<0
@cursorLine=totalLines-1 if @cursorLine>=totalLines
# Ensure column bounds again
totalColumns=getColumnsInLine(@cursorLine)
@cursorColumn=totalColumns if @cursorColumn>totalColumns
@cursorColumn=0 if @cursorColumn<0 # totalColumns can be 0
updateCursorPos(
oldCursorLine!=@cursorLine ||
oldCursorColumn!=@cursorColumn
)
# echoln ["aftermoving",@cursorLine,@cursorColumn]
end
def update
@frame+=1
@frame%=20
self.refresh if ((@frame%10)==0)
return if !self.active
# Moving cursor
if Input.triggerex?(:UP) || Input.repeatex?(:UP)
moveCursor(-1,0)
return
elsif Input.triggerex?(:DOWN) || Input.repeatex?(:DOWN)
moveCursor(1,0)
return
elsif Input.triggerex?(:LEFT) || Input.repeatex?(:LEFT)
moveCursor(0,-1)
return
elsif Input.triggerex?(:RIGHT) || Input.repeatex?(:RIGHT)
moveCursor(0,1)
return
end
if Input.press?(Input::CTRL) && Input.triggerex?(:HOME)
# Move cursor to beginning
@cursorLine=0
@cursorColumn=0
updateCursorPos(true)
return
elsif Input.press?(Input::CTRL) && Input.triggerex?(:END)
# Move cursor to end
@cursorLine=getTotalLines()-1
@cursorColumn=getColumnsInLine(@cursorLine)
updateCursorPos(true)
return
elsif Input.triggerex?(:RETURN) || Input.repeatex?(:RETURN)
self.insert("\n")
return
elsif Input.triggerex?(:BACKSPACE) || Input.repeatex?(:BACKSPACE) # Backspace
self.delete
return
end
Input.gets.each_char{|c|insert(c)}
end
def refresh
newContents=pbDoEnsureBitmap(self.contents,self.width-self.borderX,
self.height-self.borderY)
@textchars=nil if self.contents!=newContents
self.contents=newContents
bitmap=self.contents
bitmap.clear
getTextChars
height=self.height-self.borderY
cursorcolor=Color.new(0,0,0)
textchars=getTextChars()
startY=getLineY(@firstline)
for i in 0...textchars.length
thisline=textchars[i][5]
thiscolumn=textchars[i][7]
thislength=textchars[i][8]
textY=textchars[i][2]-startY
# Don't draw lines before the first or zero-length segments
next if thisline<@firstline || thislength==0
# Don't draw lines beyond the window's height
break if textY >= height
c=textchars[i][0]
# Don't draw spaces
next if c==" "
textwidth=textchars[i][3]+4 # add 4 to prevent draw_text from stretching text
textheight=textchars[i][4]
# Draw text
pbDrawShadowText(bitmap, textchars[i][1], textY, textwidth, textheight, c, @baseColor, @shadowColor)
end
# Draw cursor
if ((@frame/10)&1) == 0
textheight=bitmap.text_size("X").height
cursorY=(textheight*@cursorLine)-startY
cursorX=0
for i in 0...textchars.length
thisline=textchars[i][5]
thiscolumn=textchars[i][7]
thislength=textchars[i][8]
if thisline==@cursorLine && @cursorColumn>=thiscolumn &&
@cursorColumn<=thiscolumn+thislength
cursorY=textchars[i][2]-startY
cursorX=textchars[i][1]
textheight=textchars[i][4]
posToCursor=@cursorColumn-thiscolumn
if posToCursor>=0
partialString=textchars[i][0].scan(/./m)[0,posToCursor].join("")
cursorX+=bitmap.text_size(partialString).width
end
break
end
end
cursorY+=4
cursorHeight=[4,textheight-4,bitmap.text_size("X").height-4].max
bitmap.fill_rect(cursorX,cursorY,2,cursorHeight,cursorcolor)
end
end
end

View File

@@ -0,0 +1,171 @@
#####################################
# Needed because RGSS doesn't call at_exit procs on exit
# Exit is not called when game is reset (using F12)
$AtExitProcs=[] if !$AtExitProcs
def exit(code=0)
for p in $AtExitProcs
p.call
end
raise SystemExit.new(code)
end
def at_exit(&block)
$AtExitProcs.push(Proc.new(&block))
end
#===============================================================================
# Methods that determine the duration of an audio file.
#===============================================================================
def getOggPage(file)
fgetdw = proc { |file|
(file.eof? ? 0 : (file.read(4).unpack("V")[0] || 0))
}
dw = fgetdw.call(file)
return nil if dw != 0x5367674F
header = file.read(22)
bodysize = 0
hdrbodysize = (file.read(1)[0].ord rescue 0)
hdrbodysize.times do
bodysize += (file.read(1)[0].ord rescue 0)
end
ret = [header, file.pos, bodysize, file.pos + bodysize]
return ret
end
# internal function
def oggfiletime(file)
fgetdw = proc { |file|
(file.eof? ? 0 : (file.read(4).unpack("V")[0] || 0))
}
pages = []
page = nil
loop do
page = getOggPage(file)
break if !page
pages.push(page)
file.pos = page[3]
end
return -1 if pages.length == 0
curserial = nil
i = -1
pcmlengths = []
rates = []
for page in pages
header = page[0]
serial = header[10, 4].unpack("V")
frame = header[2, 8].unpack("C*")
frameno = frame[7]
frameno = (frameno << 8) | frame[6]
frameno = (frameno << 8) | frame[5]
frameno = (frameno << 8) | frame[4]
frameno = (frameno << 8) | frame[3]
frameno = (frameno << 8) | frame[2]
frameno = (frameno << 8) | frame[1]
frameno = (frameno << 8) | frame[0]
if serial != curserial
curserial = serial
file.pos = page[1]
packtype = (file.read(1)[0].ord rescue 0)
string = file.read(6)
return -1 if string != "vorbis"
return -1 if packtype != 1
i += 1
version = fgetdw.call(file)
return -1 if version != 0
rates[i] = fgetdw.call(file)
end
pcmlengths[i] = frameno
end
ret = 0.0
for i in 0...pcmlengths.length
ret += pcmlengths[i].to_f / rates[i].to_f
end
return ret * 256.0
end
# Gets the length of an audio file in seconds. Supports WAV, MP3, and OGG files.
def getPlayTime(filename)
if safeExists?(filename)
return [getPlayTime2(filename), 0].max
elsif safeExists?(filename + ".wav")
return [getPlayTime2(filename + ".wav"), 0].max
elsif safeExists?(filename + ".mp3")
return [getPlayTime2(filename + ".mp3"), 0].max
elsif safeExists?(filename + ".ogg")
return [getPlayTime2(filename + ".ogg"), 0].max
end
return 0
end
def getPlayTime2(filename)
return -1 if !safeExists?(filename)
time = -1
fgetdw = proc { |file|
(file.eof? ? 0 : (file.read(4).unpack("V")[0] || 0))
}
fgetw = proc { |file|
(file.eof? ? 0 : (file.read(2).unpack("v")[0] || 0))
}
File.open(filename, "rb") { |file|
file.pos = 0
fdw = fgetdw.call(file)
if fdw == 0x46464952 # "RIFF"
filesize = fgetdw.call(file)
wave = fgetdw.call(file)
return -1 if wave != 0x45564157 # "WAVE"
fmt = fgetdw.call(file)
return -1 if fmt != 0x20746d66 # "fmt "
fmtsize = fgetdw.call(file)
format = fgetw.call(file)
channels = fgetw.call(file)
rate = fgetdw.call(file)
bytessec = fgetdw.call(file)
return -1 if bytessec == 0
bytessample = fgetw.call(file)
bitssample = fgetw.call(file)
data = fgetdw.call(file)
return -1 if data != 0x61746164 # "data"
datasize = fgetdw.call(file)
time = (datasize*1.0)/bytessec
return time
elsif fdw == 0x5367674F # "OggS"
file.pos = 0
time = oggfiletime(file)
return time
end
file.pos = 0
# Find the length of an MP3 file
while true
rstr = ""
ateof = false
while !file.eof?
if (file.read(1)[0] rescue 0) == 0xFF
begin
rstr = file.read(3)
rescue
ateof = true
end
break
end
end
break if ateof || !rstr || rstr.length != 3
if rstr[0] == 0xFB
t = rstr[1] >> 4
next if t == 0 || t == 15
freqs = [44100, 22050, 11025, 48000]
bitrates = [32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320]
bitrate = bitrates[t]
t = (rstr[1] >> 2) & 3
freq = freqs[t]
t = (rstr[1] >> 1) & 1
filesize = FileTest.size(filename)
frameLength = ((144000 * bitrate) / freq) + t
numFrames = filesize / (frameLength + 4)
time = (numFrames * 1152.0 / freq)
break
end
end
}
return time
end

View File

@@ -0,0 +1,293 @@
def pbStringToAudioFile(str)
if str[/^(.*)\:\s*(\d+)\s*\:\s*(\d+)\s*$/] # Of the format "XXX: ###: ###"
file = $1
volume = $2.to_i
pitch = $3.to_i
return RPG::AudioFile.new(file,volume,pitch)
elsif str[/^(.*)\:\s*(\d+)\s*$/] # Of the format "XXX: ###"
file = $1
volume = $2.to_i
return RPG::AudioFile.new(file,volume,100)
else
return RPG::AudioFile.new(str,100,100)
end
end
# Converts an object to an audio file.
# str -- Either a string showing the filename or an RPG::AudioFile object.
# Possible formats for _str_:
# filename volume and pitch 100
# filename:volume pitch 100
# filename:volume:pitch
# volume -- Volume of the file, up to 100
# pitch -- Pitch of the file, normally 100
def pbResolveAudioFile(str,volume=nil,pitch=nil)
if str.is_a?(String)
str = pbStringToAudioFile(str)
str.volume = volume || 100
str.pitch = pitch || 100
end
if str.is_a?(RPG::AudioFile)
if volume || pitch
return RPG::AudioFile.new(str.name,volume || str.volume || 100 ,
pitch || str.pitch || 100)
else
return str
end
end
return str
end
################################################################################
# Plays a BGM file.
# param -- Either a string showing the filename
# (relative to Audio/BGM/) or an RPG::AudioFile object.
# Possible formats for _param_:
# filename volume and pitch 100
# filename:volume pitch 100
# filename:volume:pitch
# volume -- Volume of the file, up to 100
# pitch -- Pitch of the file, normally 100
def pbBGMPlay(param,volume=nil,pitch=nil)
return if !param
param=pbResolveAudioFile(param,volume,pitch)
param = pbResolveAudioFile("ultra_metropolis", volume, pitch) if darknessEffectOnCurrentMap() && !$PokemonTemp.during_battle
if param.name && param.name!=""
if $game_system && $game_system.respond_to?("bgm_play")
$game_system.bgm_play(param)
return
elsif (RPG.const_defined?(:BGM) rescue false)
b=RPG::BGM.new(param.name,param.volume,param.pitch)
if b && b.respond_to?("play")
b.play
return
end
end
Audio.bgm_play(canonicalize("Audio/BGM/"+param.name),param.volume,param.pitch)
end
end
# Fades out or stops BGM playback. 'x' is the time in seconds to fade out.
def pbBGMFade(x=0.0); pbBGMStop(x);end
# Fades out or stops BGM playback. 'x' is the time in seconds to fade out.
def pbBGMStop(timeInSeconds=0.0)
if $game_system && timeInSeconds>0.0 && $game_system.respond_to?("bgm_fade")
$game_system.bgm_fade(timeInSeconds)
return
elsif $game_system && $game_system.respond_to?("bgm_stop")
$game_system.bgm_stop
return
elsif (RPG.const_defined?(:BGM) rescue false)
begin
(timeInSeconds>0.0) ? RPG::BGM.fade((timeInSeconds*1000).floor) : RPG::BGM.stop
return
rescue
end
end
(timeInSeconds>0.0) ? Audio.bgm_fade((timeInSeconds*1000).floor) : Audio.bgm_stop
end
################################################################################
# Plays an ME file.
# param -- Either a string showing the filename
# (relative to Audio/ME/) or an RPG::AudioFile object.
# Possible formats for _param_:
# filename volume and pitch 100
# filename:volume pitch 100
# filename:volume:pitch
# volume -- Volume of the file, up to 100
# pitch -- Pitch of the file, normally 100
def pbMEPlay(param,volume=nil,pitch=nil)
return if !param
param=pbResolveAudioFile(param,volume,pitch)
if param.name && param.name!=""
if $game_system && $game_system.respond_to?("me_play")
$game_system.me_play(param)
return
elsif (RPG.const_defined?(:ME) rescue false)
b=RPG::ME.new(param.name,param.volume,param.pitch)
if b && b.respond_to?("play")
b.play; return
end
end
Audio.me_play(canonicalize("Audio/ME/"+param.name),param.volume,param.pitch)
end
end
# Fades out or stops ME playback. 'x' is the time in seconds to fade out.
def pbMEFade(x=0.0); pbMEStop(x);end
# Fades out or stops ME playback. 'x' is the time in seconds to fade out.
def pbMEStop(timeInSeconds=0.0)
if $game_system && timeInSeconds>0.0 && $game_system.respond_to?("me_fade")
$game_system.me_fade(timeInSeconds)
return
elsif $game_system && $game_system.respond_to?("me_stop")
$game_system.me_stop(nil)
return
elsif (RPG.const_defined?(:ME) rescue false)
begin
(timeInSeconds>0.0) ? RPG::ME.fade((timeInSeconds*1000).floor) : RPG::ME.stop
return
rescue
end
end
(timeInSeconds>0.0) ? Audio.me_fade((timeInSeconds*1000).floor) : Audio.me_stop
end
################################################################################
# Plays a BGS file.
# param -- Either a string showing the filename
# (relative to Audio/BGS/) or an RPG::AudioFile object.
# Possible formats for _param_:
# filename volume and pitch 100
# filename:volume pitch 100
# filename:volume:pitch
# volume -- Volume of the file, up to 100
# pitch -- Pitch of the file, normally 100
def pbBGSPlay(param,volume=nil,pitch=nil)
return if !param
param=pbResolveAudioFile(param,volume,pitch)
if param.name && param.name!=""
if $game_system && $game_system.respond_to?("bgs_play")
$game_system.bgs_play(param)
return
elsif (RPG.const_defined?(:BGS) rescue false)
b=RPG::BGS.new(param.name,param.volume,param.pitch)
if b && b.respond_to?("play")
b.play; return
end
end
Audio.bgs_play(canonicalize("Audio/BGS/"+param.name),param.volume,param.pitch)
end
end
# Fades out or stops BGS playback. 'x' is the time in seconds to fade out.
def pbBGSFade(x=0.0); pbBGSStop(x);end
# Fades out or stops BGS playback. 'x' is the time in seconds to fade out.
def pbBGSStop(timeInSeconds=0.0)
if $game_system && timeInSeconds>0.0 && $game_system.respond_to?("bgs_fade")
$game_system.bgs_fade(timeInSeconds)
return
elsif $game_system && $game_system.respond_to?("bgs_play")
$game_system.bgs_play(nil)
return
elsif (RPG.const_defined?(:BGS) rescue false)
begin
(timeInSeconds>0.0) ? RPG::BGS.fade((timeInSeconds*1000).floor) : RPG::BGS.stop
return
rescue
end
end
(timeInSeconds>0.0) ? Audio.bgs_fade((timeInSeconds*1000).floor) : Audio.bgs_stop
end
################################################################################
# Plays an SE file.
# param -- Either a string showing the filename
# (relative to Audio/SE/) or an RPG::AudioFile object.
# Possible formats for _param_:
# filename volume and pitch 100
# filename:volume pitch 100
# filename:volume:pitch
# volume -- Volume of the file, up to 100
# pitch -- Pitch of the file, normally 100
def pbSEPlay(param,volume=nil,pitch=nil)
return if !param
param = pbResolveAudioFile(param,volume,pitch)
if param.name && param.name!=""
if $game_system && $game_system.respond_to?("se_play")
$game_system.se_play(param)
return
end
if (RPG.const_defined?(:SE) rescue false)
b = RPG::SE.new(param.name,param.volume,param.pitch)
if b && b.respond_to?("play")
b.play
return
end
end
Audio.se_play(canonicalize("Audio/SE/"+param.name),param.volume,param.pitch)
end
end
# Stops SE playback.
def pbSEFade(x=0.0); pbSEStop(x);end
# Stops SE playback.
def pbSEStop(_timeInSeconds=0.0)
if $game_system
$game_system.se_stop
elsif (RPG.const_defined?(:SE) rescue false)
RPG::SE.stop rescue nil
else
Audio.se_stop
end
end
################################################################################
# Plays a sound effect that plays when the player moves the cursor.
def pbPlayCursorSE
if $data_system && $data_system.respond_to?("cursor_se") &&
$data_system.cursor_se && $data_system.cursor_se.name!=""
pbSEPlay($data_system.cursor_se)
elsif $data_system && $data_system.respond_to?("sounds") &&
$data_system.sounds && $data_system.sounds[0] && $data_system.sounds[0].name!=""
pbSEPlay($data_system.sounds[0])
elsif FileTest.audio_exist?("Audio/SE/GUI sel cursor")
pbSEPlay("GUI sel cursor",80)
end
end
# Plays a sound effect that plays when a decision is confirmed or a choice is made.
def pbPlayDecisionSE
if $data_system && $data_system.respond_to?("decision_se") &&
$data_system.decision_se && $data_system.decision_se.name!=""
pbSEPlay($data_system.decision_se)
elsif $data_system && $data_system.respond_to?("sounds") &&
$data_system.sounds && $data_system.sounds[1] && $data_system.sounds[1].name!=""
pbSEPlay($data_system.sounds[1])
elsif FileTest.audio_exist?("Audio/SE/GUI sel decision")
pbSEPlay("GUI sel decision",80)
end
end
# Plays a sound effect that plays when a choice is canceled.
def pbPlayCancelSE
if $data_system && $data_system.respond_to?("cancel_se") &&
$data_system.cancel_se && $data_system.cancel_se.name!=""
pbSEPlay($data_system.cancel_se)
elsif $data_system && $data_system.respond_to?("sounds") &&
$data_system.sounds && $data_system.sounds[2] && $data_system.sounds[2].name!=""
pbSEPlay($data_system.sounds[2])
elsif FileTest.audio_exist?("Audio/SE/GUI sel cancel")
pbSEPlay("GUI sel cancel",80)
end
end
# Plays a buzzer sound effect.
def pbPlayBuzzerSE
if $data_system && $data_system.respond_to?("buzzer_se") &&
$data_system.buzzer_se && $data_system.buzzer_se.name!=""
pbSEPlay($data_system.buzzer_se)
elsif $data_system && $data_system.respond_to?("sounds") &&
$data_system.sounds && $data_system.sounds[3] && $data_system.sounds[3].name!=""
pbSEPlay($data_system.sounds[3])
elsif FileTest.audio_exist?("Audio/SE/GUI sel buzzer")
pbSEPlay("GUI sel buzzer",80)
end
end
# Plays a sound effect that plays when the player moves the cursor.
def pbPlayCloseMenuSE
if FileTest.audio_exist?("Audio/SE/GUI menu close")
pbSEPlay("GUI menu close",80)
end
end

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,198 @@
class PictureSprite < SpriteWrapper
def initialize(viewport, picture)
super(viewport)
@picture = picture
@pictureBitmap = nil
@customBitmap = nil
@customBitmapIsBitmap = true
@hue = 0
update
end
def dispose
@pictureBitmap.dispose if @pictureBitmap
super
end
# Doesn't free the bitmap
def setCustomBitmap(bitmap)
@customBitmap = bitmap
@customBitmapIsBitmap = @customBitmap.is_a?(Bitmap)
end
def update
super
@pictureBitmap.update if @pictureBitmap
# If picture file name is different from current one
if @customBitmap && @picture.name==""
self.bitmap = (@customBitmapIsBitmap) ? @customBitmap : @customBitmap.bitmap
elsif @picture_name != @picture.name || @picture.hue.to_i != @hue.to_i
# Remember file name to instance variables
@picture_name = @picture.name
@hue = @picture.hue.to_i
# If file name is not empty
if @picture_name == ""
@pictureBitmap.dispose if @pictureBitmap
@pictureBitmap = nil
self.visible = false
return
end
# Get picture graphic
@pictureBitmap.dispose if @pictureBitmap
@pictureBitmap = AnimatedBitmap.new(@picture_name, @hue)
self.bitmap = (@pictureBitmap) ? @pictureBitmap.bitmap : nil
elsif @picture_name == ""
# Set sprite to invisible
self.visible = false
return
end
setPictureSprite(self,@picture)
end
end
def pbTextBitmap(text, maxwidth=Graphics.width)
tmp = Bitmap.new(maxwidth,Graphics.height)
pbSetSystemFont(tmp)
drawFormattedTextEx(tmp,0,0,maxwidth,text,Color.new(248,248,248),Color.new(168,184,184))
return tmp
end
#===============================================================================
# EventScene
#===============================================================================
class EventScene
attr_accessor :onCTrigger,:onBTrigger,:onUpdate
def initialize(viewport=nil)
@viewport = viewport
@onCTrigger = Event.new
@onBTrigger = Event.new
@onUpdate = Event.new
@pictures = []
@picturesprites = []
@usersprites = []
@disposed = false
end
def dispose
return if disposed?
for sprite in @picturesprites
sprite.dispose
end
for sprite in @usersprites
sprite.dispose
end
@onCTrigger.clear
@onBTrigger.clear
@onUpdate.clear
@pictures.clear
@picturesprites.clear
@usersprites.clear
@disposed = true
end
def disposed?
return @disposed
end
def addBitmap(x, y, bitmap)
# _bitmap_ can be a Bitmap or an AnimatedBitmap
# (update method isn't called if it's animated)
# EventScene doesn't take ownership of the passed-in bitmap
num = @pictures.length
picture = PictureEx.new(num)
picture.setXY(0,x,y)
picture.setVisible(0,true)
@pictures[num] = picture
@picturesprites[num] = PictureSprite.new(@viewport,picture)
@picturesprites[num].setCustomBitmap(bitmap)
return picture
end
def addLabel(x, y, width, text)
addBitmap(x,y,pbTextBitmap(text,width))
end
def addImage(x, y, name)
num = @pictures.length
picture = PictureEx.new(num)
picture.name = name
picture.setXY(0,x,y)
picture.setVisible(0,true)
@pictures[num] = picture
@picturesprites[num] = PictureSprite.new(@viewport,picture)
return picture
end
def addUserSprite(sprite)
@usersprites.push(sprite)
end
def getPicture(num)
return @pictures[num]
end
def wait(frames)
frames.times { update }
end
def pictureWait(extraframes=0)
loop do
hasRunning = false
for pic in @pictures
hasRunning = true if pic.running?
end
break if !hasRunning
update
end
extraframes.times { update }
end
def update
return if disposed?
Graphics.update
Input.update
for picture in @pictures
picture.update
end
for sprite in @picturesprites
sprite.update
end
for sprite in @usersprites
next if !sprite || sprite.disposed? || !sprite.is_a?(Sprite)
sprite.update
end
@onUpdate.trigger(self)
if Input.trigger?(Input::BACK)
@onBTrigger.trigger(self)
elsif Input.trigger?(Input::USE)
@onCTrigger.trigger(self)
end
end
def main
while !disposed?
update
end
end
end
#===============================================================================
#
#===============================================================================
def pbEventScreen(cls)
pbFadeOutIn {
viewport = Viewport.new(0,0,Graphics.width,Graphics.height)
viewport.z = 99999
PBDebug.logonerr {
cls.new(viewport).main
}
viewport.dispose
}
end

View File

@@ -0,0 +1,283 @@
module GameData
#=============================================================================
# A mixin module for data classes which provides common class methods (called
# by GameData::Thing.method) that provide access to data held within.
# Assumes the data class's data is stored in a class constant hash called DATA.
# For data that is known by a symbol or an ID number.
#=============================================================================
module ClassMethods
def register(hash)
self::DATA[hash[:id]] = self::DATA[hash[:id_number]] = self.new(hash)
end
# @param other [Symbol, self, String, Integer]
# @return [Boolean] whether the given other is defined as a self
def exists?(other)
return false if other.nil?
validate other => [Symbol, self, String, Integer]
other = other.id if other.is_a?(self)
other = other.to_sym if other.is_a?(String)
if self == GameData::Species
return !get(other).nil?
end
return !self::DATA[other].nil?
end
# @param other [Symbol, self, String, Integer]
# @return [self]
def get(other)
validate other => [Symbol, self, String, Integer]
return other if other.is_a?(self)
other = other.to_sym if other.is_a?(String)
#B1H1 - old format (still supported)
if other.to_s.match?(/\AB\d+H\d+\z/)
species = GameData::FusedSpecies.new(other)
return species
end
if other.to_s.include?("/")
species = GameData::FusedSpecies.new(other)
return species
end
if other.is_a?(Integer) && self == GameData::Species
if other > NB_POKEMON
body_id = getBodyID(other)
head_id = getHeadID(other, body_id)
pokemon_id = getFusedPokemonIdFromDexNum(body_id, head_id)
return GameData::FusedSpecies.new(pokemon_id)
end
end
if !self::DATA.has_key?(other)
if self == GameData::Item
return nil
else
return self::get(:PIKACHU)
end
end
return self::DATA[other]
end
# @param other [Symbol, self, String, Integer]
# @return [self, nil]
def try_get(other)
return nil if other.nil?
validate other => [Symbol, self, String, Integer]
return other if other.is_a?(self)
other = other.to_sym if other.is_a?(String)
if other.to_s.match?(/\AB\d+H\d+\z/) #old format (still supported)
species = GameData::FusedSpecies.new(other)
return species
end
if other.to_s.include?("_x_") #new format
species = GameData::FusedSpecies.new(other)
return species
end
if other.is_a?(Integer) && self == GameData::Species
if other > NB_POKEMON
body_id = getBodyID(other)
head_id = getHeadID(other, body_id)
pokemon_id = getFusedPokemonIdFromDexNum(body_id, head_id)
return GameData::FusedSpecies.new(pokemon_id)
end
end
# if other.is_a?(Integer)
# p "Please switch to symbols, thanks."
# end
return (self::DATA.has_key?(other)) ? self::DATA[other] : nil
end
# Returns the array of keys for the data.
# @return [Array]
def keys
return self::DATA.keys
end
# Yields all data in order of their id_number.
def each
keys = self::DATA.keys.sort { |a, b| self::DATA[a].id_number <=> self::DATA[b].id_number }
keys.each { |key| yield self::DATA[key] if !key.is_a?(Integer) }
end
def load
const_set(:DATA, load_data("Data/#{self::DATA_FILENAME}"))
end
def save
save_data(self::DATA, "Data/#{self::DATA_FILENAME}")
end
end
#=============================================================================
# A mixin module for data classes which provides common class methods (called
# by GameData::Thing.method) that provide access to data held within.
# Assumes the data class's data is stored in a class constant hash called DATA.
# For data that is only known by a symbol.
#=============================================================================
module ClassMethodsSymbols
def register(hash)
self::DATA[hash[:id]] = self.new(hash)
end
# @param other [Symbol, self, String]
# @return [Boolean] whether the given other is defined as a self
def exists?(other)
return false if other.nil?
validate other => [Symbol, self, String]
other = other.id if other.is_a?(self)
other = other.to_sym if other.is_a?(String)
return !self::DATA[other].nil?
end
# @param other [Symbol, self, String]
# @return [self]
def get(other)
validate other => [Symbol, self, String]
return other if other.is_a?(self)
other = other.to_sym if other.is_a?(String)
raise "Unknown ID #{other}." unless self::DATA.has_key?(other)
return self::DATA[other]
end
# @param other [Symbol, self, String]
# @return [self, nil]
def try_get(other)
return nil if other.nil?
validate other => [Symbol, self, String]
return other if other.is_a?(self)
other = other.to_sym if other.is_a?(String)
return (self::DATA.has_key?(other)) ? self::DATA[other] : nil
end
# Returns the array of keys for the data.
# @return [Array]
def keys
return self::DATA.keys
end
# Yields all data in alphabetical order.
def each
keys = self::DATA.keys.sort { |a, b| self::DATA[a].real_name <=> self::DATA[b].real_name }
keys.each { |key| yield self::DATA[key] }
end
def load
const_set(:DATA, load_data("Data/#{self::DATA_FILENAME}"))
end
def save
save_data(self::DATA, "Data/#{self::DATA_FILENAME}")
end
end
#=============================================================================
# A mixin module for data classes which provides common class methods (called
# by GameData::Thing.method) that provide access to data held within.
# Assumes the data class's data is stored in a class constant hash called DATA.
# For data that is only known by an ID number.
#=============================================================================
module ClassMethodsIDNumbers
def register(hash)
self::DATA[hash[:id]] = self.new(hash)
end
# @param other [self, Integer]
# @return [Boolean] whether the given other is defined as a self
def exists?(other)
return false if other.nil?
validate other => [self, Integer]
other = other.id if other.is_a?(self)
return !self::DATA[other].nil?
end
# @param other [self, Integer]
# @return [self]
def get(other)
validate other => [self, Integer]
return other if other.is_a?(self)
raise "Unknown ID #{other}." unless self::DATA.has_key?(other)
return self::DATA[other]
end
def try_get(other)
return nil if other.nil?
validate other => [self, Integer]
return other if other.is_a?(self)
return (self::DATA.has_key?(other)) ? self::DATA[other] : nil
end
# Returns the array of keys for the data.
# @return [Array]
def keys
return self::DATA.keys
end
# Yields all data in numberical order.
def each
keys = self::DATA.keys.sort
keys.each { |key| yield self::DATA[key] }
end
def load
const_set(:DATA, load_data("Data/#{self::DATA_FILENAME}"))
end
def save
save_data(self::DATA, "Data/#{self::DATA_FILENAME}")
end
end
#=============================================================================
# A mixin module for data classes which provides common instance methods
# (called by thing.method) that analyse the data of a particular thing which
# the instance represents.
#=============================================================================
module InstanceMethods
# @param other [Symbol, self.class, String, Integer]
# @return [Boolean] whether other represents the same thing as this thing
def ==(other)
return false if other.nil?
if other.is_a?(Symbol)
return @id == other
elsif other.is_a?(self.class)
return @id == other.id
elsif other.is_a?(String)
return @id_number == other.to_sym
elsif other.is_a?(Integer)
return @id_number == other
end
return false
end
end
#=============================================================================
# A bulk loader method for all data stored in .dat files in the Data folder.
#=============================================================================
def self.load_all
Type.load
Ability.load
Move.load
Item.load
BerryPlant.load
Species.load
Ribbon.load
Encounter.load
EncounterModern.load
EncounterRandom.load
TrainerType.load
Trainer.load
TrainerModern.load
TrainerExpert.load
Metadata.load
MapMetadata.load
end
end

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